diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 1f0c87ff6f..357c27fd72 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -463,7 +463,7 @@ void EditorViewportWidget::Update() if (m_updateCameraPositionNextTick) { - auto cameraState = m_renderViewport->GetCameraState(); + auto cameraState = GetCameraState(); AZ::Matrix3x4 matrix; matrix.SetBasisAndTranslation(cameraState.m_side, cameraState.m_forward, cameraState.m_up, cameraState.m_position); auto m = AZMatrix3x4ToLYMatrix3x4(matrix); @@ -1138,6 +1138,17 @@ void EditorViewportWidget::OnMenuSelectCurrentCamera() AzFramework::CameraState EditorViewportWidget::GetCameraState() { + if (m_viewEntityId.IsValid()) + { + bool cameraStateAcquired = false; + AzFramework::CameraState cameraState; + Camera::EditorCameraViewRequestBus::BroadcastResult(cameraStateAcquired, + &Camera::EditorCameraViewRequestBus::Events::GetCameraState, cameraState); + if (cameraStateAcquired) + { + return cameraState; + } + } return m_renderViewport->GetCameraState(); } diff --git a/Code/Editor/Util/UndoUtil.h b/Code/Editor/Util/UndoUtil.h index f352eb317b..e825647f6b 100644 --- a/Code/Editor/Util/UndoUtil.h +++ b/Code/Editor/Util/UndoUtil.h @@ -31,7 +31,7 @@ public: static void Record(IUndoObject* undo); private: - static const uint32 scDescSize = 256; + static const AZ::u32 scDescSize = 256; char m_description[scDescSize]; bool m_bCancelled; bool m_bStartedRecord; diff --git a/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp b/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp index 4e9d7a698b..debbea5235 100644 --- a/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp @@ -10,28 +10,74 @@ #include #include #include +#include namespace AZ { namespace Debug { + //! Trace Message Event Handler for Automation. + //! Since TraceMessageBus will be called from multiple threads and + //! python interpreter is single threaded, all the bus calls are + //! queued into a list and called at the end of the frame in the main thread. + //! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER + //! macro as the signature needs to be changed to connect to Tick bus. class TraceMessageBusHandler : public AZ::Debug::TraceMessageBus::Handler , public AZ::BehaviorEBusHandler + , public AZ::TickBus::Handler { public: + AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0); + AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler); - AZ_EBUS_BEHAVIOR_BINDER(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::SystemAllocator - , OnPreAssert - , OnPreError - , OnPreWarning - , OnAssert - , OnError - , OnWarning - , OnException - , OnPrintf - , OnOutput - ); + TraceMessageBusHandler(); + + using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence< + decltype(&TraceMessageBusHandler::OnPreAssert), + decltype(&TraceMessageBusHandler::OnPreError), + decltype(&TraceMessageBusHandler::OnPreWarning), + decltype(&TraceMessageBusHandler::OnAssert), + decltype(&TraceMessageBusHandler::OnError), + decltype(&TraceMessageBusHandler::OnWarning), + decltype(&TraceMessageBusHandler::OnException), + decltype(&TraceMessageBusHandler::OnPrintf), + decltype(&TraceMessageBusHandler::OnOutput) + >; + + enum + { + FN_OnPreAssert = 0, + FN_OnPreError, + FN_OnPreWarning, + FN_OnAssert, + FN_OnError, + FN_OnWarning, + FN_OnException, + FN_OnPrintf, + FN_OnOutput, + FN_MAX + }; + + static inline constexpr const char* m_functionNames[FN_MAX] = + { + "OnPreAssert", + "OnPreError", + "OnPreWarning", + "OnAssert", + "OnError", + "OnWarning", + "OnException", + "OnPrintf", + "OnOutput" + }; + + // AZ::BehaviorEBusHandler overrides... + int GetFunctionIndex(const char* functionName) const override; + void Disconnect() override; + bool Connect(AZ::BehaviorValueParameter* id = nullptr) override; + bool IsConnected() override; + bool IsConnectedId(AZ::BehaviorValueParameter* id) override; // TraceMessageBus /* @@ -48,63 +94,190 @@ namespace AZ bool OnPrintf(const char* window, const char* message) override; bool OnOutput(const char* window, const char* message) override; + // AZ::TickBus::Handler overrides ... + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + int GetTickOrder() override; + private: - template - R CallResultReturn(const R& defaultReturnValue, int index, Args&&... args) const - { - R returnVal = defaultReturnValue; - CallResult(returnVal, index, AZStd::forward(args)...); - return returnVal; - } + void QueueMessageCall(AZStd::function messageCall); + void FlushMessageCalls(); + + AZStd::list> m_messageCalls; + AZStd::mutex m_messageCallsLock; }; + TraceMessageBusHandler::TraceMessageBusHandler() + { + m_events.resize(FN_MAX); + + SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]); + SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]); + SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]); + SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]); + SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]); + SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]); + SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]); + SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]); + SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]); + } + + int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const + { + for (int i = 0; i < FN_MAX; ++i) + { + if (azstricmp(functionName, m_functionNames[i]) == 0) + { + return i; + } + } + return -1; + } + + void TraceMessageBusHandler::Disconnect() + { + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + AZ::TickBus::Handler::BusDisconnect(); + } + + bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id) + { + AZ::TickBus::Handler::BusConnect(); + return AZ::Internal::EBusConnector::Connect(this, id); + } + + bool TraceMessageBusHandler::IsConnected() + { + return AZ::Internal::EBusConnector::IsConnected(this); + } + + bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id) + { + return AZ::Internal::EBusConnector::IsConnectedId(this, id); + } + ////////////////////////////////////////////////////////////////////////// // TraceMessageBusHandler Implementation inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message) { - return CallResultReturn(false, FN_OnPreAssert, fileName, line, func, message); + QueueMessageCall( + [this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() + { + Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) { - return CallResultReturn(false, FN_OnPreError, window, fileName, line, func, message); + QueueMessageCall( + [this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() + { + Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) { - return CallResultReturn(false, FN_OnPreWarning, window, fileName, line, func, message); + QueueMessageCall( + [this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() + { + return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnAssert(const char* message) { - return CallResultReturn(false, FN_OnAssert, message); + QueueMessageCall( + [this, messageString = AZStd::string(message)]() + { + return Call(FN_OnAssert, messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnError(const char* window, const char* message) { - return CallResultReturn(false, FN_OnError, window, message); + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() + { + return Call(FN_OnError, windowString.c_str(), messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message) { - return CallResultReturn(false, FN_OnWarning, window, message); + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() + { + return Call(FN_OnWarning, windowString.c_str(), messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnException(const char* message) { - return CallResultReturn(false, FN_OnException, message); + QueueMessageCall( + [this, messageString = AZStd::string(message)]() + { + return Call(FN_OnException, messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message) { - return CallResultReturn(false, FN_OnPrintf, window, message); + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() + { + return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str()); + }); + return false; } inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message) { - return CallResultReturn(false, FN_OnOutput, window, message); + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() + { + return Call(FN_OnOutput, windowString.c_str(), messageString.c_str()); + }); + return false; } + void TraceMessageBusHandler::OnTick( + [[maybe_unused]] float deltaTime, + [[maybe_unused]] AZ::ScriptTimePoint time) + { + FlushMessageCalls(); + } + + int TraceMessageBusHandler::GetTickOrder() + { + return AZ::TICK_LAST; + } + + void TraceMessageBusHandler::QueueMessageCall(AZStd::function messageCall) + { + AZStd::lock_guard lock(m_messageCallsLock); + m_messageCalls.push_back(messageCall); + } + + void TraceMessageBusHandler::FlushMessageCalls() + { + AZStd::list> messageCalls; + { + AZStd::lock_guard lock(m_messageCallsLock); + m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible + } + + for (auto& messageCall : messageCalls) + { + messageCall(); + } + } void TraceReflect(ReflectContext* context) { diff --git a/Code/Framework/AzCore/AzCore/Math/MatrixUtils.cpp b/Code/Framework/AzCore/AzCore/Math/MatrixUtils.cpp index ccdabc9198..a578640d0d 100644 --- a/Code/Framework/AzCore/AzCore/Math/MatrixUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Math/MatrixUtils.cpp @@ -71,7 +71,7 @@ namespace AZ return &out; } - Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist) + Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist, bool reverseDepth) { AZ_Assert(right > left, "right should be greater than left"); // valid to have matrix invert top/bottom and far/near @@ -83,6 +83,11 @@ namespace AZ return nullptr; } + if (reverseDepth) + { + AZStd::swap(nearDist, farDist); + } + out.SetRow(0, 2.f/(right - left), 0.f, 0.f, - (right + left) / (right - left) ); out.SetRow(1, 0.f, 2.f / (top - bottom), 0.f, - (top + bottom) / (top - bottom) ); out.SetRow(2, 0.f, 0.f, 1 / (nearDist - farDist), nearDist / (nearDist - farDist) ); diff --git a/Code/Framework/AzCore/AzCore/Math/MatrixUtils.h b/Code/Framework/AzCore/AzCore/Math/MatrixUtils.h index fc85b7fccc..72a7b29887 100644 --- a/Code/Framework/AzCore/AzCore/Math/MatrixUtils.h +++ b/Code/Framework/AzCore/AzCore/Math/MatrixUtils.h @@ -57,8 +57,9 @@ namespace AZ //! @param top The y coordinate of top view-plane //! @param near Distance to the near view-plane. Must be no less than zero. //! @param far Distance to the far view-plane. Must be greater than zero. + //! @param reverseDepth Set to true to reverse depth which means near distance maps to 1 and far distance maps to 0. //! @return Pointer of the output matrix - Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist); + Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist, bool reverseDepth = false); //! Transforms a position by a matrix. This function can be used with any generic cases which include projection matrices. Vector3 MatrixTransformPosition(const Matrix4x4& matrix, const Vector3& inPosition); diff --git a/Code/Framework/AzCore/AzCore/std/containers/fixed_unordered_map.h b/Code/Framework/AzCore/AzCore/std/containers/fixed_unordered_map.h index 8da0d84a23..19ad46dc62 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/fixed_unordered_map.h +++ b/Code/Framework/AzCore/AzCore/std/containers/fixed_unordered_map.h @@ -120,6 +120,11 @@ namespace AZStd base_type::insert(*first); } } + fixed_unordered_map(const AZStd::initializer_list& list, const hasher& hash = hasher(), + const key_eq& keyEqual = key_eq()) + : fixed_unordered_map(list.begin(), list.end(), hash, keyEqual) + { + } AZ_FORCE_INLINE pair_iter_bool insert(const value_type& value) { @@ -241,6 +246,12 @@ namespace AZStd base_type::insert(*first); } } + fixed_unordered_multimap(const AZStd::initializer_list& list, const hasher& hash = hasher(), + const key_eq& keyEqual = key_eq()) + : fixed_unordered_multimap(list.begin(), list.end(), hash, keyEqual) + { + } + AZ_FORCE_INLINE iterator insert(const value_type& value) { return base_type::insert_impl(value).first; diff --git a/Code/Framework/AzCore/CMakeLists.txt b/Code/Framework/AzCore/CMakeLists.txt index ff8a825aab..ea7cc27af5 100644 --- a/Code/Framework/AzCore/CMakeLists.txt +++ b/Code/Framework/AzCore/CMakeLists.txt @@ -12,7 +12,7 @@ ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common) -if(LY_ENABLE_RAD_TELEMETRY) +if(LY_RAD_TELEMETRY_ENABLED) set(AZ_CORE_RADTELEMETRY_FILES ${common_dir}/azcore_profile_telemetry_files.cmake) set(AZ_CORE_RADTELEMETRY_PLATFORM_INCLUDES ${pal_dir}/profile_telemetry_platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) set(AZ_CORE_RADTELEMETRY_INCLUDE_DIRECTORIES ${common_dir}) diff --git a/Code/Framework/AzCore/Platform/Android/profile_telemetry_platform_android.cmake b/Code/Framework/AzCore/Platform/Android/profile_telemetry_platform_android.cmake index 5b74429383..df12777586 100644 --- a/Code/Framework/AzCore/Platform/Android/profile_telemetry_platform_android.cmake +++ b/Code/Framework/AzCore/Platform/Android/profile_telemetry_platform_android.cmake @@ -12,6 +12,6 @@ # is being avoided to prevent overriding functions declared in other targets platfrom # specific cmake files -if(LY_ENABLE_RAD_TELEMETRY) +if(LY_RAD_TELEMETRY_ENABLED) set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY) endif() diff --git a/Code/Framework/AzCore/Platform/Mac/profile_telemetry_platform_mac.cmake b/Code/Framework/AzCore/Platform/Mac/profile_telemetry_platform_mac.cmake index 5b74429383..df12777586 100644 --- a/Code/Framework/AzCore/Platform/Mac/profile_telemetry_platform_mac.cmake +++ b/Code/Framework/AzCore/Platform/Mac/profile_telemetry_platform_mac.cmake @@ -12,6 +12,6 @@ # is being avoided to prevent overriding functions declared in other targets platfrom # specific cmake files -if(LY_ENABLE_RAD_TELEMETRY) +if(LY_RAD_TELEMETRY_ENABLED) set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY) endif() diff --git a/Code/Framework/AzCore/Platform/Windows/profile_telemetry_platform_windows.cmake b/Code/Framework/AzCore/Platform/Windows/profile_telemetry_platform_windows.cmake index 5b74429383..df12777586 100644 --- a/Code/Framework/AzCore/Platform/Windows/profile_telemetry_platform_windows.cmake +++ b/Code/Framework/AzCore/Platform/Windows/profile_telemetry_platform_windows.cmake @@ -12,6 +12,6 @@ # is being avoided to prevent overriding functions declared in other targets platfrom # specific cmake files -if(LY_ENABLE_RAD_TELEMETRY) +if(LY_RAD_TELEMETRY_ENABLED) set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY) endif() diff --git a/Code/Framework/AzCore/Platform/iOS/profile_telemetry_platform_ios.cmake b/Code/Framework/AzCore/Platform/iOS/profile_telemetry_platform_ios.cmake index 1a12c4b4e0..aeb91ebce6 100644 --- a/Code/Framework/AzCore/Platform/iOS/profile_telemetry_platform_ios.cmake +++ b/Code/Framework/AzCore/Platform/iOS/profile_telemetry_platform_ios.cmake @@ -6,6 +6,6 @@ # # -if(LY_ENABLE_RAD_TELEMETRY) +if(LY_RAD_TELEMETRY_ENABLED) set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY) endif() diff --git a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp index fd3e4c8f14..156719b8a7 100644 --- a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp +++ b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -698,7 +699,7 @@ namespace UnitTest auto& assetManager = AssetManager::Instance(); AssetBusCallbacks callbacks{}; - callbacks.SetOnAssetReadyCallback([&](const Asset&, AssetBusCallbacks&) + callbacks.SetOnAssetReadyCallback([&, AssetNoRefB](const Asset&, AssetBusCallbacks&) { // This callback should run inside the "main thread" dispatch events loop auto loadAsset = assetManager.GetAsset(AZ::Uuid(AssetNoRefB), AssetLoadBehavior::Default); diff --git a/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h b/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h index 618aa76401..0b2a0cbb78 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h +++ b/Code/Framework/AzFramework/AzFramework/Components/CameraBus.h @@ -63,6 +63,13 @@ namespace Camera //! @return The camera frustum's height virtual float GetFrustumHeight() = 0; + //! Gets whether or not the camera is using an orthographic projection. + //! @return True if the camera is using an orthographic projection, or false if the camera is using a perspective projection. + virtual bool IsOrthographic() = 0; + + //! @return The half width of the orthographic projection, @see SetOrthographicHalfWidth. + virtual float GetOrthographicHalfWidth() = 0; + //! Sets the camera's field of view in degrees between 0 < fov < 180 degrees //! @param fov The camera frustum's new field of view in degrees virtual void SetFov(float fov) @@ -95,6 +102,15 @@ namespace Camera //! @param height The camera frustum's new height virtual void SetFrustumHeight(float height) = 0; + //! Sets whether or not the camera should use an orthographic projection in place of a perspective projection. + //! @param orthographic If true, the camera will use an orthographic projection + virtual void SetOrthographic(bool orthographic) = 0; + + //! Sets the half-width of the orthographic projection. + //! @params halfWidth Used to calculate the bounds of the projection while in orthographic mode. + //! The height is calculated automatically based on the aspect ratio. + virtual void SetOrthographicHalfWidth(float halfWidth) = 0; + //! Makes the camera the active view virtual void MakeActiveView() = 0; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 1e324a4919..c5b6a2ff96 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -147,7 +147,9 @@ namespace AzFramework m_scrollDelta = scroll->m_delta; } - return m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta); + m_handlingEvents = m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta); + + return m_handlingEvents; } Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime) diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 7c97bc7f89..bb0df4853a 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -262,12 +262,14 @@ namespace AzFramework public: bool HandleEvents(const InputEvent& event); Camera StepCamera(const Camera& targetCamera, float deltaTime); + bool HandlingEvents() const { return m_handlingEvents; } Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller. private: ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional. float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. + bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated). }; //! A camera input to handle motion deltas that can rotate or orbit the camera. diff --git a/Code/Framework/AzFramework/CMakeLists.txt b/Code/Framework/AzFramework/CMakeLists.txt index 8e80234263..8a68aac887 100644 --- a/Code/Framework/AzFramework/CMakeLists.txt +++ b/Code/Framework/AzFramework/CMakeLists.txt @@ -10,7 +10,7 @@ ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common) -set(LY_ENABLE_STATISTICAL_PROFILING OFF CACHE BOOL "Enables statistical profiling when using AZ_PROFILE_SCOPE. If True, it takes effect only if RAD Telemetry is disabled.") +set(LY_STATISTICAL_PROFILING_ENABLED OFF CACHE BOOL "Enables statistical profiling when using AZ_PROFILE_SCOPE. If True, it takes effect only if RAD Telemetry is disabled.") set(LY_TOUCHBENDING_LAYER_BIT 63 CACHE STRING "Use TouchBending as the collision layer. The TouchBending layer can be a number from 1 to 63 (Default=63).") ly_add_target( @@ -38,7 +38,7 @@ ly_add_target( 3rdParty::lz4 ) -if(LY_ENABLE_STATISTICAL_PROFILING) +if(LY_STATISTICAL_PROFILING_ENABLED) ly_add_source_properties( SOURCES AzFramework/Debug/StatisticalProfilerProxy.h PROPERTY COMPILE_DEFINITIONS diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h b/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h index 2c7d6e100c..03c65ce0c3 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h @@ -9,8 +9,13 @@ #pragma once +#include #include +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB +#include +#endif // LY_COMPILE_DEFINITIONS + namespace AzFramework { class LinuxLifecycleEvents @@ -25,4 +30,31 @@ namespace AzFramework using Bus = AZ::EBus; }; + +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + class LinuxXcbConnectionManager + { + public: + AZ_RTTI(LinuxXcbConnectionManager, "{649951316-3626-4C9D-9DCA-2E7ABF84C0A9}"); + + virtual ~LinuxXcbConnectionManager() = default; + + virtual xcb_connection_t* GetXcbConnection() const = 0; + }; + + class LinuxXcbConnectionManagerBusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using LinuxXcbConnectionManagerBus = AZ::EBus; + using LinuxXcbConnectionManagerInterface = AZ::Interface; + +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp index 71779444b1..eb4165453e 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp @@ -12,6 +12,32 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + class LinuxXcbConnectionManagerImpl + : public LinuxXcbConnectionManagerBus::Handler + { + public: + LinuxXcbConnectionManagerImpl() + { + m_xcbConnection = xcb_connect(nullptr, nullptr); + AZ_Error("ApplicationLinux", m_xcbConnection != nullptr, "Unable to connect to X11 Server."); + LinuxXcbConnectionManagerBus::Handler::BusConnect(); + } + + ~LinuxXcbConnectionManagerImpl() + { + LinuxXcbConnectionManagerBus::Handler::BusDisconnect(); + xcb_disconnect(m_xcbConnection); + } + xcb_connection_t* GetXcbConnection() const override + { + return m_xcbConnection; + } + private: + xcb_connection_t* m_xcbConnection = nullptr; + }; +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + //////////////////////////////////////////////////////////////////////////////////////////////// class ApplicationLinux : public Application::Implementation @@ -27,6 +53,12 @@ namespace AzFramework // Application::Implementation void PumpSystemEventLoopOnce() override; void PumpSystemEventLoopUntilEmpty() override; + private: + +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + AZStd::unique_ptr m_xcbConnectionManager; +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + }; //////////////////////////////////////////////////////////////////////////////////////////////// @@ -39,11 +71,26 @@ namespace AzFramework ApplicationLinux::ApplicationLinux() { LinuxLifecycleEvents::Bus::Handler::BusConnect(); + +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + m_xcbConnectionManager = AZStd::make_unique(); + if (LinuxXcbConnectionManagerInterface::Get() == nullptr) + { + LinuxXcbConnectionManagerInterface::Register(m_xcbConnectionManager.get()); + } +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB } //////////////////////////////////////////////////////////////////////////////////////////////// ApplicationLinux::~ApplicationLinux() { +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + if (LinuxXcbConnectionManagerInterface::Get() == m_xcbConnectionManager.get()) + { + LinuxXcbConnectionManagerInterface::Unregister(m_xcbConnectionManager.get()); + } + m_xcbConnectionManager.reset(); +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB LinuxLifecycleEvents::Bus::Handler::BusDisconnect(); } diff --git a/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake b/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake index 7a325ca97e..c79c5f1dff 100644 --- a/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake +++ b/Code/Framework/AzFramework/Platform/Linux/platform_linux.cmake @@ -5,3 +5,30 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # + +# Based on the linux window manager trait, perform the appropriate additional build configurations +# Only 'xcb', 'wayland', and 'xlib' are recognized +if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb") + + find_library(XCB_LIBRARY xcb) + + set(LY_BUILD_DEPENDENCIES + PRIVATE + ${XCB_LIBRARY} + ) + + set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB) + +elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland") + + set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND) + +elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "xlib") + + set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XLIB) + +else() + + message(FATAL_ERROR, "Linux Window Manager ${PAL_TRAIT_LINUX_WINDOW_MANAGER} is not recognized") + +endif() diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Utilities/Conversions.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Utilities/Conversions.cpp index 4764b9f911..bef41bb487 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Utilities/Conversions.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Utilities/Conversions.cpp @@ -43,19 +43,20 @@ namespace AzQtComponents { const QChar decimalPoint = locale.decimalPoint(); const QChar zeroDigit = locale.zeroDigit(); + const int numToStringDecimals = AZStd::max(numDecimals, 20); - // We want to truncate, not round. toString will round, so we add an extra decimal place to the formatting - // so we can remove the last value - QString retValue = locale.toString(value, 'f', (numDecimals > 0) ? numDecimals + 1 : 0); + // We want to truncate, not round. toString will round, so we add extra decimal places to the formatting + // so we can remove the last values + QString retValue = locale.toString(value, 'f', (numDecimals > 0) ? numToStringDecimals : 0); // Handle special cases when we have decimals in our value if (numDecimals > 0) { - // Truncate the extra digit now, if it's still there + // Truncate the extra digits now, if they're still there int decimalPointIndex = retValue.lastIndexOf(decimalPoint); - if ((decimalPointIndex > 0) && (retValue.size() - (decimalPointIndex + 1)) == (numDecimals + 1)) + if ((decimalPointIndex > 0) && (retValue.size() - (decimalPointIndex + 1)) == numToStringDecimals) { - retValue.resize(retValue.size() - 1); + retValue.resize(retValue.size() - (numToStringDecimals - numDecimals)); } // Remove trailing zeros, since the locale conversion won't do diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorCameraBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorCameraBus.h index cb4a003fb4..60d19dfd85 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorCameraBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorCameraBus.h @@ -102,7 +102,7 @@ namespace Camera using EditorCameraNotificationBus = AZ::EBus; /** - * This bus is for requesting any camera-view-related changes + * This bus is for requesting any camera-view-related changes or information */ class EditorCameraViewRequests : public AZ::ComponentBus { @@ -115,6 +115,11 @@ namespace Camera * Sets this camera as the active view in the scene, otherwise restores the default editor camera if it was already active */ virtual void ToggleCameraAsActiveView() = 0; + + /** + * Gets the camera state associated with this view. + */ + virtual bool GetCameraState(AzFramework::CameraState& cameraState) = 0; }; using EditorCameraViewRequestBus = AZ::EBus; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp index 319307009c..17155076ec 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp @@ -1951,12 +1951,13 @@ namespace AzToolsFramework return; } + // If prefabs are enabled, there will be no root slice so bail out here since we don't need + // to show any slice options in the menu AZ::SliceComponent* rootSlice = nullptr; AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(rootSlice, contextId, &AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice); if (!rootSlice) { - AZ_Error("PropertyEditor", false, "Entity context has no root slice"); return; } @@ -2105,10 +2106,6 @@ namespace AzToolsFramework { QMenu* revertMenu = nullptr; - revertMenu = menu.addMenu(tr("Revert overrides")); - revertMenu->setToolTipsVisible(true); - revertMenu->setEnabled(false); - //check for changes on selected property if (componentClassData) { @@ -2128,6 +2125,11 @@ namespace AzToolsFramework return; } + // Only add the "Revert overrides" menu option if it belongs to a slice + revertMenu = menu.addMenu(tr("Revert overrides")); + revertMenu->setToolTipsVisible(true); + revertMenu->setEnabled(false); + if (fieldNode) { bool hasChanges = fieldNode->HasChangesVersusComparison(false); diff --git a/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp b/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp index d1c3ee5b3c..eb09c68cee 100644 --- a/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp @@ -77,6 +77,19 @@ namespace UnitTest m_intSpinBox.reset(); } + QString setupTruncationTest(QString textValue) + { + QString retval; + m_doubleSpinBoxWithLineEdit->setDecimals(7); + m_doubleSpinBoxWithLineEdit->setDisplayDecimals(3); + m_doubleSpinBoxWithLineEdit->setFocus(); + m_doubleSpinBoxWithLineEdit->GetLineEdit()->setText(textValue); + m_doubleSpinBoxWithLineEdit->clearFocus(); + + return m_doubleSpinBoxWithLineEdit->textFromValue(m_doubleSpinBoxWithLineEdit->value()); + } + + AZStd::unique_ptr m_dummyWidget; AZStd::unique_ptr m_intSpinBox; AZStd::unique_ptr m_doubleSpinBox; @@ -277,4 +290,34 @@ namespace UnitTest // test would result in a crash EXPECT_TRUE(m_intSpinBox.get() == nullptr); } + + TEST_F(SpinBoxFixture, SpinBoxCheckHighValueTruncatesCorrectly) + { + QString value = setupTruncationTest("0.9999999"); + + EXPECT_TRUE(value == "0.999"); + } + + TEST_F(SpinBoxFixture, SpinBoxCheckLowValueTruncatesCorrectly) + { + QString value = setupTruncationTest("0.0000001"); + + EXPECT_TRUE(value == "0.0"); + } + + TEST_F(SpinBoxFixture, SpinBoxCheckBugValuesTruncatesCorrectly) + { + QString value = setupTruncationTest("0.12395"); + + EXPECT_TRUE(value == "0.123"); + + value = setupTruncationTest("0.94496"); + + EXPECT_TRUE(value == "0.944"); + + value = setupTruncationTest("0.0009999"); + + EXPECT_TRUE(value == "0.0"); + } + } // namespace UnitTest diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp index c0d977a5f5..7ca51fb9c5 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp @@ -32,7 +32,7 @@ namespace O3DE::ProjectManager vsWherePath, QStringList{ "-version", - "16.0", + "16.9.2", "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", @@ -50,10 +50,11 @@ namespace O3DE::ProjectManager } } - return AZ::Failure(QObject::tr("Visual Studio 2019 not found.\n\n" + return AZ::Failure(QObject::tr("Visual Studio 2019 version 16.9.2 or higher not found.\n\n" "Visual Studio 2019 is required to build this project." " Install any edition of Visual Studio 2019" - " before proceeding to the next step.")); + " or update to a newer version before proceeding to the next step." + " While installing configure Visual Studio with these workloads.")); } } // namespace ProjectUtils diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp index 8379f4be7d..51e0147599 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp @@ -87,7 +87,7 @@ namespace AZ // AssImp only has one bitangentStream per mesh. bitangentStream->SetBitangentSetIndex(0); - bitangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene); + bitangentStream->SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene); bitangentStream->ReserveContainerSpace(vertexCount); for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp index 92f84ad8fc..eeddf4a0b3 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp @@ -41,45 +41,6 @@ namespace AZ } } - void MakeBoneMap(const aiScene* scene, AZStd::unordered_map& boneLookup) - { - AZStd::queue queue; - AZStd::unordered_set nodesWithNoMesh; - - queue.push(scene->mRootNode); - - while (!queue.empty()) - { - const aiNode* currentNode = queue.front(); - queue.pop(); - - if (currentNode->mNumMeshes == 0) - { - nodesWithNoMesh.emplace(currentNode->mName.C_Str()); - } - - for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) - { - queue.push(currentNode->mChildren[childIndex]); - } - } - - for (unsigned int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) - { - const aiMesh* mesh = scene->mMeshes[meshIndex]; - - for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) - { - const aiBone* bone = mesh->mBones[boneIndex]; - - if (nodesWithNoMesh.contains(bone->mName.C_Str())) - { - boneLookup.emplace(bone->mName.C_Str(), bone); - } - } - } - } - aiMatrix4x4 CalculateWorldTransform(const aiNode* currentNode) { aiMatrix4x4 transform = {}; @@ -106,37 +67,39 @@ namespace AZ return Events::ProcessingResult::Ignored; } - bool isBone = false; - + AZStd::unordered_multimap boneByNameMap; + FindAllBones(scene, boneByNameMap); + + bool isBone = FindFirstBoneByNodeName(currentNode, boneByNameMap); + if (!isBone) { - AZStd::unordered_map boneLookup; - MakeBoneMap(scene, boneLookup); - - isBone = boneLookup.contains(currentNode->mName.C_Str()); - - // If we have an animation, the bones will be listed in there - if (!isBone) + for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex) { - for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex) + aiAnimation* animation = scene->mAnimations[animIndex]; + + for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex) { - aiAnimation* animation = scene->mAnimations[animIndex]; + aiNodeAnim* nodeAnim = animation->mChannels[channelIndex]; - for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex) - { - aiNodeAnim* nodeAnim = animation->mChannels[channelIndex]; - - if (nodeAnim->mNodeName == currentNode->mName) - { - isBone = true; - break; - } - } - - if (isBone) + if (nodeAnim->mNodeName == currentNode->mName) { + isBone = true; break; } } + + if (isBone) + { + break; + } + } + + // In case any of the children, or children of children is a bone, make sure to not skip this node. + // Don't do this for the scene root itself, else wise all mesh nodes will be exported as bones and pollute the skeleton. + if (currentNode != scene->mRootNode && + RecursiveHasChildBone(currentNode, boneByNameMap)) + { + isBone = true; } } diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp index 15bb65399c..81feff7d69 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp @@ -6,12 +6,13 @@ * */ -#include - -#include -#include - #include +#include +#include +#include +#include +#include +#include namespace AZ { @@ -85,6 +86,107 @@ namespace AZ return combinedTransform; } + + void FindAllBones(const aiScene* scene, AZStd::unordered_multimap& outBoneByNameMap) + { + outBoneByNameMap.clear(); + AZStd::queue queue; + AZStd::unordered_set nodesWithNoMesh; + + queue.push(scene->mRootNode); + + while (!queue.empty()) + { + const aiNode* currentNode = queue.front(); + queue.pop(); + + if (currentNode->mNumMeshes == 0) + { + nodesWithNoMesh.emplace(currentNode->mName.C_Str()); + } + + for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) + { + queue.push(currentNode->mChildren[childIndex]); + } + } + + for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) + { + const aiMesh* mesh = scene->mMeshes[meshIndex]; + + for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) + { + const aiBone* bone = mesh->mBones[boneIndex]; + + if (nodesWithNoMesh.contains(bone->mName.C_Str())) + { + outBoneByNameMap.emplace(bone->mName.C_Str(), bone); + } + } + } + } + + DataTypes::MatrixType GetLocalSpaceBindPoseTransform(const aiScene* scene, const aiNode* node) + { + AZStd::unordered_multimap boneByNameMap; + FindAllBones(scene, boneByNameMap); + + const aiBone* bone = FindFirstBoneByNodeName(node, boneByNameMap); + if (bone) + { + const DataTypes::MatrixType inverseOffsetMatrix = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(bone->mOffsetMatrix).GetInverseFull(); + + const aiBone* parentBone = FindFirstBoneByNodeName(node->mParent, boneByNameMap); + if (parentBone) + { + const DataTypes::MatrixType parentBoneOffsetMatrix = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(parentBone->mOffsetMatrix); + return parentBoneOffsetMatrix * inverseOffsetMatrix; + } + else + { + return inverseOffsetMatrix; + } + } + + return AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(node)); + } + + const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap& boneByNameMap) + { + if (!node) + { + return nullptr; + } + + auto boneIterator = boneByNameMap.find(node->mName.C_Str()); + if (boneIterator != boneByNameMap.end()) + { + return boneIterator->second; + } + + return nullptr; + } + + bool RecursiveHasChildBone(const aiNode* node, const AZStd::unordered_multimap& boneByNameMap) + { + const bool isBone = boneByNameMap.contains(node->mName.C_Str()); + if (isBone) + { + return true; + } + + for (int childIndex = 0; childIndex < node->mNumChildren; ++childIndex) + { + const aiNode* childNode = node->mChildren[childIndex]; + if (RecursiveHasChildBone(childNode, boneByNameMap)) + { + return true; + } + } + + return false; + } } // namespace SceneBuilder } // namespace SceneAPI } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h index 5a943f339c..a629fe52d8 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h @@ -9,13 +9,15 @@ #pragma once #include +#include #include +#include +struct aiBone; struct aiNode; struct aiScene; struct aiString; - namespace AZ::SceneAPI::SceneBuilder { inline constexpr char PivotNodeMarker[] = "_$AssimpFbx$_"; @@ -30,5 +32,16 @@ namespace AZ::SceneAPI::SceneBuilder // Gets the entire, combined local transform for a node taking pivot nodes into account. When pivot nodes are not used, this just returns the node's transform aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode); + + DataTypes::MatrixType GetLocalSpaceBindPoseTransform(const aiScene* scene, const aiNode* node); + + // Gather all bones from the scene. (Bone in AssImp corresponds to nodes that influence any of the vertices). + void FindAllBones(const aiScene* scene, AZStd::unordered_multimap& outBoneByNameMap); + + // Find the first bone with the name of the given node. + const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap& boneByNameMap); + + // Check if the given node or any of its children, or children of children, is a bone by checking if the node name is part of the given map. + bool RecursiveHasChildBone(const aiNode* node, const AZStd::unordered_multimap& boneByNameMap); } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp index 8a1079b0d2..6f1c364399 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp @@ -89,7 +89,7 @@ namespace AZ // AssImp only has one tangentStream per mesh. tangentStream->SetTangentSetIndex(0); - tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene); + tangentStream->SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene); tangentStream->ReserveContainerSpace(vertexCount); for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp index eba1063a1e..134c408bae 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp @@ -42,45 +42,6 @@ namespace AZ serializeContext->Class()->Version(1); } } - - void GetAllBones(const aiScene* scene, AZStd::unordered_multimap& boneLookup) - { - AZStd::queue queue; - AZStd::unordered_set nodesWithNoMesh; - - queue.push(scene->mRootNode); - - while (!queue.empty()) - { - const aiNode* currentNode = queue.front(); - queue.pop(); - - if (currentNode->mNumMeshes == 0) - { - nodesWithNoMesh.emplace(currentNode->mName.C_Str()); - } - - for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) - { - queue.push(currentNode->mChildren[childIndex]); - } - } - - for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) - { - const aiMesh* mesh = scene->mMeshes[meshIndex]; - - for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) - { - const aiBone* bone = mesh->mBones[boneIndex]; - - if (nodesWithNoMesh.contains(bone->mName.C_Str())) - { - boneLookup.emplace(bone->mName.C_Str(), bone); - } - } - } - } Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context) { @@ -93,54 +54,7 @@ namespace AZ return Events::ProcessingResult::Ignored; } - AZStd::unordered_multimap boneLookup; - GetAllBones(scene, boneLookup); - - auto boneIterator = boneLookup.find(currentNode->mName.C_Str()); - const bool isBone = boneIterator != boneLookup.end(); - - DataTypes::MatrixType localTransform; - - if (isBone) - { - AZStd::vector offsets, inverseOffsets; - auto iteratingNode = currentNode; - - while (iteratingNode && boneLookup.count(iteratingNode->mName.C_Str())) - { - AZStd::string name = iteratingNode->mName.C_Str(); - - auto range = boneLookup.equal_range(name); - - if (range.first != range.second) - { - // There can be multiple offsetMatrices for a given bone, we're only interested in grabbing the first one - auto boneFirstOffsetMatrix = range.first->second->mOffsetMatrix; - auto azMat = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(boneFirstOffsetMatrix); - offsets.push_back(azMat); - inverseOffsets.push_back(azMat.GetInverseFull()); - } - - iteratingNode = iteratingNode->mParent; - } - - if (inverseOffsets.size() == 1) - { - // If this is the root bone, just use the inverseOffset, otherwise the equation below just results in the identity matrix - localTransform = inverseOffsets[0]; - } - else - { - localTransform = offsets.at(1) // parent bone offset - * inverseOffsets.at(inverseOffsets.size() - 1) // Inverse of root bone offset - * offsets.at(offsets.size() - 1) // Root bone offset - * inverseOffsets.at(0); // Inverse of current node offset - } - } - else - { - localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(currentNode)); - } + DataTypes::MatrixType localTransform = GetLocalSpaceBindPoseTransform(scene, currentNode); // Don't bother adding a node with the identity matrix if (localTransform == DataTypes::MatrixType::Identity()) diff --git a/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h b/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h index d05042c534..027459460c 100644 --- a/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h +++ b/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexBitangentData.h @@ -17,32 +17,24 @@ namespace AZ class Vector3; } -namespace AZ +namespace AZ::SceneAPI::DataTypes { - namespace SceneAPI + class IMeshVertexBitangentData + : public IGraphObject { - namespace DataTypes - { + public: + AZ_RTTI(IMeshVertexBitangentData, "{6C8F6109-B0BD-49D1-A998-4A4946557DF9}", IGraphObject); - class IMeshVertexBitangentData - : public IGraphObject - { - public: - AZ_RTTI(IMeshVertexBitangentData, "{6C8F6109-B0BD-49D1-A998-4A4946557DF9}", IGraphObject); + virtual ~IMeshVertexBitangentData() override = default; - virtual ~IMeshVertexBitangentData() override = default; + void CloneAttributesFrom([[maybe_unused]] const IGraphObject* sourceObject) override {} - void CloneAttributesFrom([[maybe_unused]] const IGraphObject* sourceObject) override {} - - virtual size_t GetCount() const = 0; - virtual const AZ::Vector3& GetBitangent(size_t index) const = 0; - virtual void SetBitangent(size_t vertexIndex, const AZ::Vector3& bitangent) = 0; - virtual void SetBitangentSetIndex(size_t setIndex) = 0; - virtual size_t GetBitangentSetIndex() const = 0; - virtual TangentSpace GetTangentSpace() const = 0; - virtual void SetTangentSpace(TangentSpace space) = 0; - }; - - } // DataTypes - } // SceneAPI -} // AZ + virtual size_t GetCount() const = 0; + virtual const AZ::Vector3& GetBitangent(size_t index) const = 0; + virtual void SetBitangent(size_t vertexIndex, const AZ::Vector3& bitangent) = 0; + virtual void SetBitangentSetIndex(size_t setIndex) = 0; + virtual size_t GetBitangentSetIndex() const = 0; + virtual TangentGenerationMethod GetGenerationMethod() const = 0; + virtual void SetGenerationMethod(TangentGenerationMethod method) = 0; + }; +} // AZ::SceneAPI::DataTypes diff --git a/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h b/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h index ffeeedf9fe..a51999c4b1 100644 --- a/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h +++ b/Code/Tools/SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexTangentData.h @@ -16,42 +16,36 @@ namespace AZ class Vector4; } -namespace AZ +namespace AZ::SceneAPI::DataTypes { - namespace SceneAPI + enum class TangentGenerationMethod { - namespace DataTypes - { - enum class TangentSpace - { - FromSourceScene = 0, - MikkT = 1 - }; + FromSourceScene = 0, + MikkT = 1 + }; - enum class BitangentMethod - { - UseFromTangentSpace = 0, - Orthogonal = 1 - }; + enum class MikkTSpaceMethod + { + TSpace = 0, + TSpaceBasic = 1 + }; - class IMeshVertexTangentData - : public IGraphObject - { - public: - AZ_RTTI(IMeshVertexTangentData, "{B24084FF-09B1-4EE5-BA5B-2D392E92ECC1}", IGraphObject); + class IMeshVertexTangentData + : public IGraphObject + { + public: + AZ_RTTI(IMeshVertexTangentData, "{B24084FF-09B1-4EE5-BA5B-2D392E92ECC1}", IGraphObject); - virtual ~IMeshVertexTangentData() override = default; + virtual ~IMeshVertexTangentData() override = default; - void CloneAttributesFrom([[maybe_unused]] const IGraphObject* sourceObject) override {} + void CloneAttributesFrom([[maybe_unused]] const IGraphObject* sourceObject) override {} - virtual size_t GetCount() const = 0; - virtual const AZ::Vector4& GetTangent(size_t index) const = 0; - virtual void SetTangent(size_t vertexIndex, const AZ::Vector4& tangent) = 0; - virtual void SetTangentSetIndex(size_t setIndex) = 0; - virtual size_t GetTangentSetIndex() const = 0; - virtual TangentSpace GetTangentSpace() const = 0; - virtual void SetTangentSpace(TangentSpace space) = 0; - }; - } // DataTypes - } // SceneAPI -} // AZ + virtual size_t GetCount() const = 0; + virtual const AZ::Vector4& GetTangent(size_t index) const = 0; + virtual void SetTangent(size_t vertexIndex, const AZ::Vector4& tangent) = 0; + virtual void SetTangentSetIndex(size_t setIndex) = 0; + virtual size_t GetTangentSetIndex() const = 0; + virtual TangentGenerationMethod GetGenerationMethod() const = 0; + virtual void SetGenerationMethod(TangentGenerationMethod method) = 0; + }; +} // AZ::SceneAPI::DataTypes diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.cpp b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.cpp index faa9bf457e..efd020f80a 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.cpp +++ b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.cpp @@ -10,110 +10,95 @@ #include #include -namespace AZ +namespace AZ::SceneData::GraphData { - namespace SceneData + void MeshVertexBitangentData::Reflect(ReflectContext* context) { - namespace GraphData + SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) { - void MeshVertexBitangentData::Reflect(ReflectContext* context) - { - SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class()->Version(2); - } + serializeContext->Class()->Version(2); + } - BehaviorContext* behaviorContext = azrtti_cast(context); - if (behaviorContext) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Method("GetCount", &MeshVertexBitangentData::GetCount) - ->Method("GetBitangent", &MeshVertexBitangentData::GetBitangent) - ->Method("GetBitangentSetIndex", &MeshVertexBitangentData::GetBitangentSetIndex) - ->Method("GetTangentSpace", &MeshVertexBitangentData::GetTangentSpace) - ->Enum<(int)SceneAPI::DataTypes::TangentSpace::FromSourceScene>("FromSourceScene") - ->Enum<(int)SceneAPI::DataTypes::TangentSpace::MikkT>("MikkT"); - } - } + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("GetCount", &MeshVertexBitangentData::GetCount) + ->Method("GetBitangent", &MeshVertexBitangentData::GetBitangent) + ->Method("GetBitangentSetIndex", &MeshVertexBitangentData::GetBitangentSetIndex) + ->Method("GetGenerationMethod", &MeshVertexBitangentData::GetGenerationMethod) + ->Enum<(int)SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene>("FromSourceScene") + ->Enum<(int)SceneAPI::DataTypes::TangentGenerationMethod::MikkT>("MikkT"); + } + } - void MeshVertexBitangentData::CloneAttributesFrom(const IGraphObject* sourceObject) - { - IMeshVertexBitangentData::CloneAttributesFrom(sourceObject); - if (const auto* typedSource = azrtti_cast(sourceObject)) - { - SetTangentSpace(typedSource->GetTangentSpace()); - SetBitangentSetIndex(typedSource->GetBitangentSetIndex()); - } - } + void MeshVertexBitangentData::CloneAttributesFrom(const IGraphObject* sourceObject) + { + IMeshVertexBitangentData::CloneAttributesFrom(sourceObject); + if (const auto* typedSource = azrtti_cast(sourceObject)) + { + SetGenerationMethod(typedSource->GetGenerationMethod()); + SetBitangentSetIndex(typedSource->GetBitangentSetIndex()); + } + } - size_t MeshVertexBitangentData::GetCount() const - { - return m_bitangents.size(); - } + size_t MeshVertexBitangentData::GetCount() const + { + return m_bitangents.size(); + } + const AZ::Vector3& MeshVertexBitangentData::GetBitangent(size_t index) const + { + AZ_Assert(index < m_bitangents.size(), "Invalid index %i for mesh bitangents.", index); + return m_bitangents[index]; + } - const AZ::Vector3& MeshVertexBitangentData::GetBitangent(size_t index) const - { - AZ_Assert(index < m_bitangents.size(), "Invalid index %i for mesh bitangents.", index); - return m_bitangents[index]; - } + void MeshVertexBitangentData::ReserveContainerSpace(size_t numVerts) + { + m_bitangents.reserve(numVerts); + } + void MeshVertexBitangentData::Resize(size_t numVerts) + { + m_bitangents.resize(numVerts); + } - void MeshVertexBitangentData::ReserveContainerSpace(size_t numVerts) - { - m_bitangents.reserve(numVerts); - } + void MeshVertexBitangentData::AppendBitangent(const AZ::Vector3& bitangent) + { + m_bitangents.push_back(bitangent); + } + void MeshVertexBitangentData::SetBitangent(size_t vertexIndex, const AZ::Vector3& bitangent) + { + m_bitangents[vertexIndex] = bitangent; + } - void MeshVertexBitangentData::Resize(size_t numVerts) - { - m_bitangents.resize(numVerts); - } + void MeshVertexBitangentData::SetBitangentSetIndex(size_t setIndex) + { + m_setIndex = setIndex; + } + size_t MeshVertexBitangentData::GetBitangentSetIndex() const + { + return m_setIndex; + } - void MeshVertexBitangentData::AppendBitangent(const AZ::Vector3& bitangent) - { - m_bitangents.push_back(bitangent); - } + AZ::SceneAPI::DataTypes::TangentGenerationMethod MeshVertexBitangentData::GetGenerationMethod() const + { + return m_generationMethod; + } + void MeshVertexBitangentData::SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod method) + { + m_generationMethod = method; + } - void MeshVertexBitangentData::SetBitangent(size_t vertexIndex, const AZ::Vector3& bitangent) - { - m_bitangents[vertexIndex] = bitangent; - } - - - void MeshVertexBitangentData::SetBitangentSetIndex(size_t setIndex) - { - m_setIndex = setIndex; - } - - - size_t MeshVertexBitangentData::GetBitangentSetIndex() const - { - return m_setIndex; - } - - - AZ::SceneAPI::DataTypes::TangentSpace MeshVertexBitangentData::GetTangentSpace() const - { - return m_tangentSpace; - } - - - void MeshVertexBitangentData::SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace space) - { - m_tangentSpace = space; - } - - void MeshVertexBitangentData::GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const - { - output.Write("Bitangents", m_bitangents); - output.Write("TangentSpace", aznumeric_cast(m_tangentSpace)); - } - } // GraphData - } // SceneData -} // AZ + void MeshVertexBitangentData::GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const + { + output.Write("Bitangents", m_bitangents); + output.Write("GenerationMethod", aznumeric_cast(m_generationMethod)); + } +} // AZ::SceneData::GraphData diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h index 9afb174f17..151f4c963b 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h +++ b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexBitangentData.h @@ -10,51 +10,41 @@ #include #include - #include #include - -namespace AZ +namespace AZ::SceneData::GraphData { - namespace SceneData + class SCENE_DATA_CLASS MeshVertexBitangentData + : public AZ::SceneAPI::DataTypes::IMeshVertexBitangentData { - namespace GraphData - { + public: + AZ_RTTI(MeshVertexBitangentData, "{F56FB088-4C92-4453-AFE9-4E820F03FA90}", AZ::SceneAPI::DataTypes::IMeshVertexBitangentData); - class SCENE_DATA_CLASS MeshVertexBitangentData - : public AZ::SceneAPI::DataTypes::IMeshVertexBitangentData - { - public: - AZ_RTTI(MeshVertexBitangentData, "{F56FB088-4C92-4453-AFE9-4E820F03FA90}", AZ::SceneAPI::DataTypes::IMeshVertexBitangentData); + static void Reflect(ReflectContext* context); - static void Reflect(ReflectContext* context); + SCENE_DATA_API ~MeshVertexBitangentData() override = default; - SCENE_DATA_API ~MeshVertexBitangentData() override = default; + SCENE_DATA_API void CloneAttributesFrom(const IGraphObject* sourceObject) override; - SCENE_DATA_API void CloneAttributesFrom(const IGraphObject* sourceObject) override; + SCENE_DATA_API size_t GetCount() const override; + SCENE_DATA_API const AZ::Vector3& GetBitangent(size_t index) const override; + SCENE_DATA_API void SetBitangent(size_t vertexIndex, const AZ::Vector3& bitangent) override; - SCENE_DATA_API size_t GetCount() const override; - SCENE_DATA_API const AZ::Vector3& GetBitangent(size_t index) const override; - SCENE_DATA_API void SetBitangent(size_t vertexIndex, const AZ::Vector3& bitangent) override; + SCENE_DATA_API void SetBitangentSetIndex(size_t setIndex) override; + SCENE_DATA_API size_t GetBitangentSetIndex() const override; - SCENE_DATA_API void SetBitangentSetIndex(size_t setIndex) override; - SCENE_DATA_API size_t GetBitangentSetIndex() const override; + SCENE_DATA_API void Resize(size_t numVerts); + SCENE_DATA_API void ReserveContainerSpace(size_t numVerts); + SCENE_DATA_API void AppendBitangent(const AZ::Vector3& bitangent); - SCENE_DATA_API void Resize(size_t numVerts); - SCENE_DATA_API void ReserveContainerSpace(size_t numVerts); - SCENE_DATA_API void AppendBitangent(const AZ::Vector3& bitangent); + SCENE_DATA_API AZ::SceneAPI::DataTypes::TangentGenerationMethod GetGenerationMethod() const override; + SCENE_DATA_API void SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod method) override; - SCENE_DATA_API AZ::SceneAPI::DataTypes::TangentSpace GetTangentSpace() const override; - SCENE_DATA_API void SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace space) override; - - SCENE_DATA_API void GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const override; - protected: - AZStd::vector m_bitangents; - AZ::SceneAPI::DataTypes::TangentSpace m_tangentSpace = AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene; - size_t m_setIndex = 0; - }; - - } // GraphData - } // SceneData -} // AZ + SCENE_DATA_API void GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const override; + protected: + AZStd::vector m_bitangents; + AZ::SceneAPI::DataTypes::TangentGenerationMethod m_generationMethod = AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene; + size_t m_setIndex = 0; + }; +} // AZ::SceneData::GraphData diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.cpp b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.cpp index 9f27f4eb44..31ae04b19e 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.cpp +++ b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.cpp @@ -10,112 +10,96 @@ #include #include -namespace AZ +namespace AZ::SceneData::GraphData { - namespace SceneData + void MeshVertexTangentData::Reflect(ReflectContext* context) { - namespace GraphData + SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) { - void MeshVertexTangentData::Reflect(ReflectContext* context) - { - SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class()->Version(2); - } + serializeContext->Class()->Version(2); + } - BehaviorContext* behaviorContext = azrtti_cast(context); - if (behaviorContext) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Method("GetCount", &MeshVertexTangentData::GetCount) - ->Method("GetTangent", &MeshVertexTangentData::GetTangent) - ->Method("GetTangentSetIndex", &MeshVertexTangentData::GetTangentSetIndex) - ->Method("GetTangentSpace", &MeshVertexTangentData::GetTangentSpace) - ->Enum<(int)SceneAPI::DataTypes::TangentSpace::FromSourceScene>("FromSourceScene") - ->Enum<(int)SceneAPI::DataTypes::TangentSpace::MikkT>("MikkT"); - } - } + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("GetCount", &MeshVertexTangentData::GetCount) + ->Method("GetTangent", &MeshVertexTangentData::GetTangent) + ->Method("GetTangentSetIndex", &MeshVertexTangentData::GetTangentSetIndex) + ->Method("GetGenerationMethod", &MeshVertexTangentData::GetGenerationMethod) + ->Enum<(int)SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene>("FromSourceScene") + ->Enum<(int)SceneAPI::DataTypes::TangentGenerationMethod::MikkT>("MikkT"); + } + } - void MeshVertexTangentData::CloneAttributesFrom(const IGraphObject* sourceObject) - { - IMeshVertexTangentData::CloneAttributesFrom(sourceObject); - if (const auto* typedSource = azrtti_cast(sourceObject)) - { - SetTangentSpace(typedSource->GetTangentSpace()); - SetTangentSetIndex(typedSource->GetTangentSetIndex()); - } - } + void MeshVertexTangentData::CloneAttributesFrom(const IGraphObject* sourceObject) + { + IMeshVertexTangentData::CloneAttributesFrom(sourceObject); + if (const auto* typedSource = azrtti_cast(sourceObject)) + { + SetGenerationMethod(typedSource->GetGenerationMethod()); + SetTangentSetIndex(typedSource->GetTangentSetIndex()); + } + } - size_t MeshVertexTangentData::GetCount() const - { - return m_tangents.size(); - } + size_t MeshVertexTangentData::GetCount() const + { + return m_tangents.size(); + } + const AZ::Vector4& MeshVertexTangentData::GetTangent(size_t index) const + { + AZ_Assert(index < m_tangents.size(), "Invalid index %i for mesh tangents.", index); + return m_tangents[index]; + } - const AZ::Vector4& MeshVertexTangentData::GetTangent(size_t index) const - { - AZ_Assert(index < m_tangents.size(), "Invalid index %i for mesh tangents.", index); - return m_tangents[index]; - } + void MeshVertexTangentData::ReserveContainerSpace(size_t numVerts) + { + m_tangents.reserve(numVerts); + } + void MeshVertexTangentData::Resize(size_t numVerts) + { + m_tangents.resize(numVerts); + } - void MeshVertexTangentData::ReserveContainerSpace(size_t numVerts) - { - m_tangents.reserve(numVerts); - } + void MeshVertexTangentData::AppendTangent(const AZ::Vector4& tangent) + { + m_tangents.push_back(tangent); + } + void MeshVertexTangentData::GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const + { + output.Write("Tangents", m_tangents); + output.Write("GenerationMethod", aznumeric_cast(m_generationMethod)); + output.Write("SetIndex", aznumeric_cast(m_setIndex)); + } - void MeshVertexTangentData::Resize(size_t numVerts) - { - m_tangents.resize(numVerts); - } + void MeshVertexTangentData::SetTangent(size_t vertexIndex, const AZ::Vector4& tangent) + { + m_tangents[vertexIndex] = tangent; + } + void MeshVertexTangentData::SetTangentSetIndex(size_t setIndex) + { + m_setIndex = setIndex; + } - void MeshVertexTangentData::AppendTangent(const AZ::Vector4& tangent) - { - m_tangents.push_back(tangent); - } + size_t MeshVertexTangentData::GetTangentSetIndex() const + { + return m_setIndex; + } - void MeshVertexTangentData::GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const - { - output.Write("Tangents", m_tangents); - output.Write("TangentSpace", aznumeric_cast(m_tangentSpace)); - output.Write("SetIndex", aznumeric_cast(m_setIndex)); - } + AZ::SceneAPI::DataTypes::TangentGenerationMethod MeshVertexTangentData::GetGenerationMethod() const + { + return m_generationMethod; + } - - void MeshVertexTangentData::SetTangent(size_t vertexIndex, const AZ::Vector4& tangent) - { - m_tangents[vertexIndex] = tangent; - } - - - void MeshVertexTangentData::SetTangentSetIndex(size_t setIndex) - { - m_setIndex = setIndex; - } - - - size_t MeshVertexTangentData::GetTangentSetIndex() const - { - return m_setIndex; - } - - - AZ::SceneAPI::DataTypes::TangentSpace MeshVertexTangentData::GetTangentSpace() const - { - return m_tangentSpace; - } - - - void MeshVertexTangentData::SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace space) - { - m_tangentSpace = space; - } - - } // GraphData - } // SceneData -} // AZ + void MeshVertexTangentData::SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod method) + { + m_generationMethod = method; + } +} // AZ::SceneData::GraphData diff --git a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.h b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.h index a9d6023b70..47993c2281 100644 --- a/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.h +++ b/Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexTangentData.h @@ -14,46 +14,39 @@ #include #include -namespace AZ +namespace AZ::SceneData::GraphData { - namespace SceneData + class SCENE_DATA_CLASS MeshVertexTangentData + : public AZ::SceneAPI::DataTypes::IMeshVertexTangentData { - namespace GraphData - { + public: + AZ_RTTI(MeshVertexTangentData, "{C16F0F38-8F8F-45A2-A33B-F2758922A7C4}", AZ::SceneAPI::DataTypes::IMeshVertexTangentData); - class SCENE_DATA_CLASS MeshVertexTangentData - : public AZ::SceneAPI::DataTypes::IMeshVertexTangentData - { - public: - AZ_RTTI(MeshVertexTangentData, "{C16F0F38-8F8F-45A2-A33B-F2758922A7C4}", AZ::SceneAPI::DataTypes::IMeshVertexTangentData); + static void Reflect(ReflectContext* context); - static void Reflect(ReflectContext* context); + SCENE_DATA_API ~MeshVertexTangentData() override = default; - SCENE_DATA_API ~MeshVertexTangentData() override = default; + SCENE_DATA_API void CloneAttributesFrom(const IGraphObject* sourceObject) override; - SCENE_DATA_API void CloneAttributesFrom(const IGraphObject* sourceObject) override; + SCENE_DATA_API size_t GetCount() const override; + SCENE_DATA_API const AZ::Vector4& GetTangent(size_t index) const override; + SCENE_DATA_API void SetTangent(size_t vertexIndex, const AZ::Vector4& tangent) override; - SCENE_DATA_API size_t GetCount() const override; - SCENE_DATA_API const AZ::Vector4& GetTangent(size_t index) const override; - SCENE_DATA_API void SetTangent(size_t vertexIndex, const AZ::Vector4& tangent) override; + SCENE_DATA_API void SetTangentSetIndex(size_t setIndex) override; + SCENE_DATA_API size_t GetTangentSetIndex() const override; - SCENE_DATA_API void SetTangentSetIndex(size_t setIndex) override; - SCENE_DATA_API size_t GetTangentSetIndex() const override; + SCENE_DATA_API AZ::SceneAPI::DataTypes::TangentGenerationMethod GetGenerationMethod() const override; + SCENE_DATA_API void SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod method) override; - SCENE_DATA_API AZ::SceneAPI::DataTypes::TangentSpace GetTangentSpace() const override; - SCENE_DATA_API void SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace space) override; + SCENE_DATA_API void Resize(size_t numVerts); + SCENE_DATA_API void ReserveContainerSpace(size_t numVerts); + SCENE_DATA_API void AppendTangent(const AZ::Vector4& tangent); - SCENE_DATA_API void Resize(size_t numVerts); - SCENE_DATA_API void ReserveContainerSpace(size_t numVerts); - SCENE_DATA_API void AppendTangent(const AZ::Vector4& tangent); + SCENE_DATA_API void GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const override; - SCENE_DATA_API void GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const override; - protected: - AZStd::vector m_tangents; - AZ::SceneAPI::DataTypes::TangentSpace m_tangentSpace = AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene; - size_t m_setIndex = 0; - }; - - } // GraphData - } // SceneData -} // AZ + protected: + AZStd::vector m_tangents; + AZ::SceneAPI::DataTypes::TangentGenerationMethod m_generationMethod = AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene; + size_t m_setIndex = 0; + }; +} // AZ::SceneData::GraphData diff --git a/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.cpp b/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.cpp index 7b4be1dfdf..5dcc0b99f8 100644 --- a/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.cpp +++ b/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.cpp @@ -26,13 +26,22 @@ namespace AZ { TangentsRule::TangentsRule() : DataTypes::IRule() - , m_tangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::MikkT) { } - AZ::SceneAPI::DataTypes::TangentSpace TangentsRule::GetTangentSpace() const + AZ::SceneAPI::DataTypes::TangentGenerationMethod TangentsRule::GetGenerationMethod() const { - return m_tangentSpace; + return m_generationMethod; + } + + AZ::SceneAPI::DataTypes::MikkTSpaceMethod TangentsRule::GetMikkTSpaceMethod() const + { + return m_tSpaceMethod; + } + + AZ::Crc32 TangentsRule::GetSpaceMethodVisibility() const + { + return (m_generationMethod == AZ::SceneAPI::DataTypes::TangentGenerationMethod::MikkT) ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide; } void TangentsRule::Reflect(AZ::ReflectContext* context) @@ -43,20 +52,29 @@ namespace AZ return; } - serializeContext->Class()->Version(3) - ->Field("tangentSpace", &TangentsRule::m_tangentSpace); + serializeContext->Class()->Version(4) + ->Field("tangentSpace", &TangentsRule::m_generationMethod) + ->Field("tSpaceMethod", &TangentsRule::m_tSpaceMethod); AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { editContext->Class("Tangents", "Specify how tangents are imported or generated.") ->ClassElement(Edit::ClassElements::EditorData, "") - ->Attribute("AutoExpand", true) - ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "") - ->DataElement(AZ::Edit::UIHandlers::ComboBox, &AZ::SceneAPI::SceneData::TangentsRule::m_tangentSpace, "Tangent space", "Specify the tangent space used for normal map baking. Choose 'From Fbx' to extract the tangents and bitangents directly from the Fbx file. When there is no tangents rule or the Fbx has no tangents stored inside it, the 'MikkT' option will be used with orthogonal tangents of unit length, so with the normalize option enabled, using the first UV set.") - ->EnumAttribute(AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene, "From Source Scene") - ->EnumAttribute(AZ::SceneAPI::DataTypes::TangentSpace::MikkT, "MikkT") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) + ->Attribute("AutoExpand", true) + ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "") + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &AZ::SceneAPI::SceneData::TangentsRule::m_generationMethod, "Generation Method", "Specify the tangent generation method. Choose 'From Source Scene' to extract the tangents and bitangents directly from the source scene file. When there is no tangents rule or the source scene has no tangents stored inside it, the 'MikkT' option will be used.") + ->EnumAttribute(AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene, "From Source Scene") + ->EnumAttribute(AZ::SceneAPI::DataTypes::TangentGenerationMethod::MikkT, "MikkT") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &AZ::SceneAPI::SceneData::TangentsRule::m_tSpaceMethod, "TSpace Method", + "TSpace generates the tangents and bitangents with their true magnitudes which can be used for relief mapping effects. " + " It calculates the 'real' bitangent which may not be perpendicular to the tangent. " + "However, both, the tangent and bitangent are perpendicular to the vertex normal. " + "TSpaceBasic calculates unit vector tangents and bitangents at pixel/vertex level which are sufficient for basic normal mapping.") + ->EnumAttribute(AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpace, "TSpace") + ->EnumAttribute(AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpaceBasic, "TSpaceBasic") + ->Attribute(AZ::Edit::Attributes::Visibility, &TangentsRule::GetSpaceMethodVisibility); ; } } diff --git a/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.h b/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.h index b368fce88a..450b1331ed 100644 --- a/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.h +++ b/Code/Tools/SceneAPI/SceneData/Rules/TangentsRule.h @@ -45,12 +45,17 @@ namespace AZ SCENE_DATA_API TangentsRule(); SCENE_DATA_API ~TangentsRule() override = default; - SCENE_DATA_API AZ::SceneAPI::DataTypes::TangentSpace GetTangentSpace() const; + SCENE_DATA_API AZ::SceneAPI::DataTypes::TangentGenerationMethod GetGenerationMethod() const; + SCENE_DATA_API AZ::SceneAPI::DataTypes::MikkTSpaceMethod GetMikkTSpaceMethod() const; static void Reflect(ReflectContext* context); protected: - AZ::SceneAPI::DataTypes::TangentSpace m_tangentSpace; /**< Specifies how to handle tangents. Either generate them, or import them. */ + AZ::SceneAPI::DataTypes::TangentGenerationMethod m_generationMethod = AZ::SceneAPI::DataTypes::TangentGenerationMethod::MikkT; /**< Specifies how to handle tangents. Either generate them, or import them. */ + + // MikkT specific settings + AZ::Crc32 GetSpaceMethodVisibility() const; + AZ::SceneAPI::DataTypes::MikkTSpaceMethod m_tSpaceMethod = AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpace; }; } // SceneData } // SceneAPI diff --git a/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp index bdcf690844..ab0fa79e62 100644 --- a/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneData/Tests/GraphData/GraphDataBehaviorTests.cpp @@ -84,7 +84,7 @@ namespace AZ auto* bitangentData = AZStd::any_cast(&data); bitangentData->AppendBitangent(AZ::Vector3{0.12f, 0.34f, 0.56f}); bitangentData->AppendBitangent(AZ::Vector3{0.77f, 0.88f, 0.99f}); - bitangentData->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene); + bitangentData->SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene); bitangentData->SetBitangentSetIndex(1); return true; } @@ -94,7 +94,7 @@ namespace AZ tangentData->AppendTangent(AZ::Vector4{0.12f, 0.34f, 0.56f, 0.78f}); tangentData->AppendTangent(AZ::Vector4{0.18f, 0.28f, 0.19f, 0.29f}); tangentData->AppendTangent(AZ::Vector4{0.21f, 0.43f, 0.65f, 0.87f}); - tangentData->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::MikkT); + tangentData->SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod::MikkT); tangentData->SetTangentSetIndex(2); return true; } @@ -318,7 +318,7 @@ namespace AZ ExpectExecute("TestExpectFloatEquals(bitangentData.y, 0.88)"); ExpectExecute("TestExpectFloatEquals(bitangentData.z, 0.99)"); ExpectExecute("TestExpectIntegerEquals(meshVertexBitangentData:GetBitangentSetIndex(), 1)"); - ExpectExecute("TestExpectTrue(meshVertexBitangentData:GetTangentSpace(), MeshVertexBitangentData.FromSourceScene)"); + ExpectExecute("TestExpectTrue(meshVertexBitangentData:GetGenerationMethod(), MeshVertexBitangentData.FromSourceScene)"); } TEST_F(GrapDatahBehaviorScriptTest, SceneGraph_MeshVertexTangentData_AccessWorks) @@ -337,7 +337,7 @@ namespace AZ ExpectExecute("TestExpectFloatEquals(tangentData.z, 0.19)"); ExpectExecute("TestExpectFloatEquals(tangentData.w, 0.29)"); ExpectExecute("TestExpectIntegerEquals(meshVertexTangentData:GetTangentSetIndex(), 2)"); - ExpectExecute("TestExpectTrue(meshVertexTangentData:GetTangentSpace(), MeshVertexTangentData.EMotionFX)"); + ExpectExecute("TestExpectTrue(meshVertexTangentData:GetGenerationMethod(), MeshVertexTangentData.EMotionFX)"); } TEST_F(GrapDatahBehaviorScriptTest, SceneGraph_AnimationData_AccessWorks) diff --git a/Code/Tools/TestImpactFramework/CMakeLists.txt b/Code/Tools/TestImpactFramework/CMakeLists.txt index 1fc59d1711..04cbee98dc 100644 --- a/Code/Tools/TestImpactFramework/CMakeLists.txt +++ b/Code/Tools/TestImpactFramework/CMakeLists.txt @@ -10,7 +10,9 @@ ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Platf include(${pal_source_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) -if(${LY_TEST_IMPACT_ACTIVE} AND PAL_TRAIT_TEST_IMPACT_FRAMEWORK_SUPPORTED) - add_subdirectory(Runtime) - add_subdirectory(Frontend) +if(PAL_TRAIT_TEST_IMPACT_FRAMEWORK_SUPPORTED) + if(LY_TEST_IMPACT_INSTRUMENTATION_BIN) + add_subdirectory(Runtime) + add_subdirectory(Frontend) + endif() endif() diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp index d9e7dea8c7..f54d7f71af 100644 --- a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #pragma warning(disable : 4996) diff --git a/Gems/AWSCore/cdk/app.py b/Gems/AWSCore/cdk/app.py index 17b7241227..ea038f8a51 100755 --- a/Gems/AWSCore/cdk/app.py +++ b/Gems/AWSCore/cdk/app.py @@ -37,7 +37,7 @@ env = core.Environment(account=ACCOUNT, region=REGION) app = core.App() -core = AWSCore( +core_construct = AWSCore( app, id_=f'{PROJECT_FEATURE_NAME}-Construct', project_name=PROJECT_NAME, @@ -46,20 +46,19 @@ core = AWSCore( ) # Below is the Core example stack which is provided for working with AWSCore ScriptCanvas examples. -# It also provided as an example how to reference properties across stacks in the same CDK applications -# Note: This will make the consuming stack a dependent stack on core -# CDK will deploy the dependent stack first and then the core stack +# It also provided as an example how to reference resources across stacks via stack outputs. # See https://docs.aws.amazon.com/cdk/latest/guide/resources.html#resource_stack -core_properties = core.properties -example = ExampleResources( +example_stack = ExampleResources( app, id_=f'{PROJECT_FEATURE_NAME}-Example-{env.region}', - props_=core_properties, project_name=f'{PROJECT_NAME}', feature_name=FEATURE_NAME, tags={Constants.O3DE_PROJECT_TAG_NAME: PROJECT_NAME, Constants.O3DE_FEATURE_TAG_NAME: FEATURE_NAME}, env=env ) +# +# Add the common stack as a dependency of the feature stack +example_stack.add_dependency(core_construct.common_stack) app.synth() diff --git a/Gems/AWSCore/cdk/core/aws_core.py b/Gems/AWSCore/cdk/core/aws_core.py index 49b5449443..53f1d0693b 100755 --- a/Gems/AWSCore/cdk/core/aws_core.py +++ b/Gems/AWSCore/cdk/core/aws_core.py @@ -42,3 +42,7 @@ class AWSCore(core.Construct): @property def properties(self): return self._feature_stack.properties + + @property + def common_stack(self): + return self._feature_stack diff --git a/Gems/AWSCore/cdk/core/core_stack.py b/Gems/AWSCore/cdk/core/core_stack.py index 24efca774f..fc1b4cf8d8 100755 --- a/Gems/AWSCore/cdk/core/core_stack.py +++ b/Gems/AWSCore/cdk/core/core_stack.py @@ -8,11 +8,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT from aws_cdk import ( core, aws_iam as iam, + aws_s3 as s3, aws_resourcegroups as resource_groups, ) from constants import Constants -from core_stack_properties import CoreStackProperties class CoreStack(core.Stack): @@ -60,6 +60,17 @@ class CoreStack(core.Stack): type='TAG_FILTERS_1_0') ) + # Create an S3 bucket for Amazon S3 server access logging + # See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html + self._server_access_logs_bucket = s3.Bucket( + self, + f'{self._project_name}-{self._feature_name}-Access-Log-Bucket', + block_public_access=s3.BlockPublicAccess.BLOCK_ALL, + encryption=s3.BucketEncryption.S3_MANAGED, + access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE + ) + self._server_access_logs_bucket.grant_read(self._admin_group) + # Define exports # Export resource group self._resource_group_output = core.CfnOutput( @@ -83,9 +94,10 @@ class CoreStack(core.Stack): export_name=f"{self._project_name}:AdminGroup", value=self._admin_group.group_arn) - @property - def properties(self) -> CoreStackProperties: - _props = CoreStackProperties() - _props.user_group = self._user_group - _props.admin_group = self._admin_group - return _props + # Export access log bucket name + self._server_access_logs_bucket_output = core.CfnOutput( + self, + id=f'ServerAccessLogsBucketOutput', + description='Name of the S3 bucket for storing server access logs generated by the sample CDK application(s)', + export_name=f"{self._project_name}:ServerAccessLogsBucket", + value=self._server_access_logs_bucket.bucket_name) diff --git a/Gems/AWSCore/cdk/core_stack_properties.py b/Gems/AWSCore/cdk/core_stack_properties.py deleted file mode 100755 index 60ec697d53..0000000000 --- a/Gems/AWSCore/cdk/core_stack_properties.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -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 -""" - -from aws_cdk import ( - core, - aws_iam as iam -) - - -class CoreStackProperties(core.StackProps): - """ - Support for cross stack references in the application. - - Define any properties from the CoreStack other stacks in this application - may need to consume. - """ - # Common IAM group for users - user_group: iam.Group - - # Common IAM group for Admin users - admin_group: iam.Group diff --git a/Gems/AWSCore/cdk/example/example_resources_stack.py b/Gems/AWSCore/cdk/example/example_resources_stack.py index 7a63e1a727..23bc78d8fc 100755 --- a/Gems/AWSCore/cdk/example/example_resources_stack.py +++ b/Gems/AWSCore/cdk/example/example_resources_stack.py @@ -8,13 +8,13 @@ import os from aws_cdk import ( aws_lambda as lambda_, + aws_iam as iam, aws_s3 as s3, aws_s3_deployment as s3_deployment, aws_dynamodb as dynamo, core ) -from core_stack_properties import CoreStackProperties from .auth import AuthPolicy @@ -25,8 +25,7 @@ class ExampleResources(core.Stack): * A python 'echo' lambda * A small dynamodb table with the a primary 'id': str key """ - def __init__(self, scope: core.Construct, id_: str, project_name: str, feature_name: str, - props_: CoreStackProperties, **kwargs) -> None: + def __init__(self, scope: core.Construct, id_: str, project_name: str, feature_name: str, **kwargs) -> None: super().__init__(scope, id_, **kwargs, description=f'Contains resources for the AWSCore examples as part of the ' f'{project_name} project') @@ -42,17 +41,74 @@ class ExampleResources(core.Stack): self.__create_outputs() # Finally grant cross stack references - self.__grant_access(props=props_) + self.__grant_access() - def __grant_access(self, props: CoreStackProperties): - self._s3_bucket.grant_read(props.user_group) - self._s3_bucket.grant_read(props.admin_group) + def __grant_access(self): + user_group = iam.Group.from_group_arn( + self, + f'{self._project_name}-{self._feature_name}-ImportedUserGroup', + core.Fn.import_value(f'{self._project_name}:UserGroup') + ) + admin_group = iam.Group.from_group_arn( + self, + f'{self._project_name}-{self._feature_name}-ImportedAdminGroup', + core.Fn.import_value(f'{self._project_name}:AdminGroup') + ) - self._lambda.grant_invoke(props.user_group) - self._lambda.grant_invoke(props.admin_group) + # Provide the admin and user groups permissions to read the example S3 bucket. + # Cannot use the grant_read method defined by the Bucket structure since the method tries to add to + # the resource-based policy but the imported IAM groups (which are tokens from Fn.ImportValue) are + # not valid principals in S3 bucket policies. + # Check https://aws.amazon.com/premiumsupport/knowledge-center/s3-invalid-principal-in-policy-error/ + user_group.add_to_principal_policy( + iam.PolicyStatement( + actions=[ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + effect=iam.Effect.ALLOW, + resources=[self._s3_bucket.bucket_arn, f'{self._s3_bucket.bucket_arn}/*'] + ) + ) + admin_group.add_to_principal_policy( + iam.PolicyStatement( + actions=[ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + effect=iam.Effect.ALLOW, + resources=[self._s3_bucket.bucket_arn, f'{self._s3_bucket.bucket_arn}/*'] + ) + ) - self._table.grant_read_data(props.user_group) - self._table.grant_read_data(props.admin_group) + # Provide the admin and user groups permissions to invoke the example Lambda function. + # Cannot use the grant_invoke method defined by the Function structure since the method tries to add to + # the resource-based policy but the imported IAM groups (which are tokens from Fn.ImportValue) are + # not valid principals in Lambda function policies. + user_group.add_to_principal_policy( + iam.PolicyStatement( + actions=[ + "lambda:InvokeFunction" + ], + effect=iam.Effect.ALLOW, + resources=[self._lambda.function_arn] + ) + ) + admin_group.add_to_principal_policy( + iam.PolicyStatement( + actions=[ + "lambda:InvokeFunction" + ], + effect=iam.Effect.ALLOW, + resources=[self._lambda.function_arn] + ) + ) + + # Provide the admin and user groups permissions to read from the DynamoDB table. + self._table.grant_read_data(user_group) + self._table.grant_read_data(admin_group) def __create_s3_bucket(self) -> s3.Bucket: # Create a sample S3 bucket following S3 best practices @@ -60,11 +116,21 @@ class ExampleResources(core.Stack): # 1. Block all public access to the bucket # 2. Use SSE-S3 encryption. Explore encryption at rest options via # https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html + # 3. Enable Amazon S3 server access logging + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html + server_access_logs_bucket = s3.Bucket.from_bucket_name( + self, + f'{self._project_name}-{self._feature_name}-ImportedAccessLogsBucket', + core.Fn.import_value(f"{self._project_name}:ServerAccessLogsBucket") + ) + example_bucket = s3.Bucket( self, f'{self._project_name}-{self._feature_name}-Example-S3bucket', block_public_access=s3.BlockPublicAccess.BLOCK_ALL, - encryption=s3.BucketEncryption.S3_MANAGED + encryption=s3.BucketEncryption.S3_MANAGED, + server_access_logs_bucket=server_access_logs_bucket, + server_access_logs_prefix=f'{self._project_name}-{self._feature_name}-{self.region}-AccessLogs' ) s3_deployment.BucketDeployment( diff --git a/Gems/AWSMetrics/cdk/aws_metrics/auth.py b/Gems/AWSMetrics/cdk/aws_metrics/auth.py index 4b445c0c6d..95fbecd915 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/auth.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/auth.py @@ -13,6 +13,7 @@ from aws_cdk import ( from .aws_metrics_stack import AWSMetricsStack from aws_metrics.policy_statements_builder.user_policy_statements_builder import UserPolicyStatementsBuilder from aws_metrics.policy_statements_builder.admin_policy_statements_builder import AdminPolicyStatementsBuilder +from .aws_utils import resource_name_sanitizer class AuthPolicy: @@ -58,12 +59,13 @@ class AuthPolicy: policy = iam.ManagedPolicy( self._stack, policy_id, - managed_policy_name=f'{self._stack.stack_name}-{role_name}Policy', + managed_policy_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-{role_name}Policy', 'iam_managed_policy'), statements=policy_statements) policy_output = core.CfnOutput( self._stack, id=f'{policy_id}Output', description=f'{role_name} policy arn to call service', - export_name=f"{self._application_name}:{policy_id}", + export_name=f'{self._application_name}:{policy_id}', value=policy.managed_policy_arn) diff --git a/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_construct.py b/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_construct.py index c78fe06a82..70d85d1586 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_construct.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_construct.py @@ -8,6 +8,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT from aws_cdk import core from .aws_metrics_stack import AWSMetricsStack from .auth import AuthPolicy +from .aws_utils import resource_name_sanitizer class AWSMetrics(core.Construct): @@ -23,19 +24,20 @@ class AWSMetrics(core.Construct): env: core.Environment) -> None: super().__init__(scope, id_) # Set-up any stack name(s) to be unique in account - stack_name = f'{project_name}-{feature_name}-{env.region}' + stack_name = resource_name_sanitizer.sanitize_resource_name( + f'{project_name}-{feature_name}-{env.region}', 'cloudformation_stack') application_name = f'{project_name}-{feature_name}' # Check context variables to get enabled optional features optional_features = { - 'batch_processing': self.node.try_get_context("batch_processing") == 'true' + 'batch_processing': self.node.try_get_context("batch_processing") == 'true', + 'server_access_logs_bucket': self.node.try_get_context("server_access_logs_bucket") } # Deploy AWS Metrics Stack self._feature_stack = AWSMetricsStack( scope, stack_name, - stack_name=stack_name, application_name=application_name, description=f'Contains resources for the AWS Metrics Gem Feature stack as part of the {project_name} project', optional_features=optional_features, diff --git a/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_stack.py b/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_stack.py index aaea9b27a6..337a14a301 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_stack.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/aws_metrics_stack.py @@ -43,9 +43,11 @@ class AWSMetricsStack(core.Stack): ) batch_processing_enabled = optional_features.get('batch_processing', False) + server_access_logs_bucket = optional_features.get('server_access_logs_bucket') self._data_lake_integration = DataLakeIntegration( self, - application_name=application_name + application_name=application_name, + server_access_logs_bucket=server_access_logs_bucket ) if batch_processing_enabled else None self._batch_processing = BatchProcessing( diff --git a/Gems/AWSMetrics/cdk/aws_metrics/aws_utils/__init__.py b/Gems/AWSMetrics/cdk/aws_metrics/aws_utils/__init__.py new file mode 100644 index 0000000000..50cbb262dd --- /dev/null +++ b/Gems/AWSMetrics/cdk/aws_metrics/aws_utils/__init__.py @@ -0,0 +1,6 @@ +""" +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 +""" diff --git a/Gems/AWSMetrics/cdk/aws_metrics/aws_utils/resource_name_sanitizer.py b/Gems/AWSMetrics/cdk/aws_metrics/aws_utils/resource_name_sanitizer.py new file mode 100644 index 0000000000..45d7bb34cd --- /dev/null +++ b/Gems/AWSMetrics/cdk/aws_metrics/aws_utils/resource_name_sanitizer.py @@ -0,0 +1,45 @@ +""" +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 +""" + +import hashlib + +MAX_RESOURCE_NAME_LENGTH_MAPPING = { + 'athena_work_group': 128, + 'athena_named_query': 128, + 'cloudformation_stack': 128, + 'cloudwatch_dashboard': 255, + 'cloudwatch_log_group': 512, + 'firehose_delivery_stream': 64, + 'iam_managed_policy': 144, + 'iam_role': 64, + 'kinesis_application': 128, + 'kinesis_stream': 128, + 'lambda_function': 64, + 's3_bucket': 63 +} + + +def sanitize_resource_name(resource_name: str, resource_type: str) -> str: + """ + Truncate the resource name if its length exceeds the limit. + This is the best effort for sanitizing resource names based on the AWS documents since each AWS service + has its unique restrictions. Customers can extend this function for validation or sanitization. + + :param resource_name: Original name of the resource. + :param resource_type: Type of the resource. + :return Sanitized resource name that can be deployed with AWS. + """ + result = resource_name + if not MAX_RESOURCE_NAME_LENGTH_MAPPING.get(resource_type): + return result + + if len(resource_name) > MAX_RESOURCE_NAME_LENGTH_MAPPING[resource_type]: + # PYTHONHASHSEED is set to "random" by default in Python 3.3 and up. Cannot use + # the built-in hash function here since it will give a different return value in each session + digest = "-%x" % (int(hashlib.md5(resource_name.encode('ascii', 'ignore')).hexdigest(), 16) & 0xffffffff) + result = resource_name[:MAX_RESOURCE_NAME_LENGTH_MAPPING[resource_type] - len(digest)] + digest + return result diff --git a/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py b/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py index 1b6bda7345..5be6562982 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/batch_analytics.py @@ -11,6 +11,7 @@ from aws_cdk import ( ) from . import aws_metrics_constants +from .aws_utils import resource_name_sanitizer class BatchAnalytics: @@ -37,7 +38,8 @@ class BatchAnalytics: self._athena_work_group = athena.CfnWorkGroup( self._stack, id='AthenaWorkGroup', - name=f'{self._stack.stack_name}-AthenaWorkGroup', + name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-AthenaWorkGroup', 'athena_work_group'), recursive_delete_option=True, state='ENABLED', work_group_configuration=athena.CfnWorkGroup.WorkGroupConfigurationProperty( @@ -65,7 +67,8 @@ class BatchAnalytics: athena.CfnNamedQuery( self._stack, id='NamedQuery-CreatePartitionedEventsJson', - name=f'{self._stack.stack_name}-NamedQuery-CreatePartitionedEventsJson', + name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-NamedQuery-CreatePartitionedEventsJson', 'athena_named_query'), database=self._events_database_name, query_string="CREATE TABLE events_json " "WITH (format='JSON',partitioned_by=ARRAY['application_id']) " @@ -78,7 +81,8 @@ class BatchAnalytics: athena.CfnNamedQuery( self._stack, id='NamedQuery-TotalEventsLastMonth', - name=f'{self._stack.stack_name}-NamedQuery-TotalEventsLastMonth', + name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-NamedQuery-TotalEventsLastMonth', 'athena_named_query'), database=self._events_database_name, query_string="WITH detail AS " "(SELECT date_trunc('month', date(date_parse(CONCAT(year, '-', month, '-', day), '%Y-%m-%d'))) as event_month, * " @@ -93,7 +97,8 @@ class BatchAnalytics: athena.CfnNamedQuery( self._stack, id='NamedQuery-NewUsersLastMonth', - name=f'{self._stack.stack_name}-NamedQuery-NewUsersLastMonth', + name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-NamedQuery-NewUsersLastMonth', 'athena_named_query'), database=self._events_database_name, query_string="WITH detail AS (" "SELECT date_trunc('month', date(date_parse(CONCAT(year, '-', month, '-', day), '%Y-%m-%d'))) as event_month, * " diff --git a/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py b/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py index 4b40d4393c..4dbb3b2120 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py @@ -16,6 +16,7 @@ from aws_cdk import ( import os from . import aws_metrics_constants +from .aws_utils import resource_name_sanitizer class BatchProcessing: @@ -42,7 +43,8 @@ class BatchProcessing: """ Generate the events processing lambda to filter the invalid metrics events. """ - events_processing_lambda_name = f'{self._stack.stack_name}-EventsProcessingLambda' + events_processing_lambda_name = resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-EventsProcessingLambda', 'lambda_function') self._create_events_processing_lambda_role(events_processing_lambda_name) self._events_processing_lambda = lambda_.Function( @@ -89,7 +91,8 @@ class BatchProcessing: self._events_processing_lambda_role = iam.Role( self._stack, id='EventsProcessingLambdaRole', - role_name=f'{self._stack.stack_name}-EventsProcessingLambdaRole', + role_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-EventsProcessingLambdaRole', 'iam_role'), assumed_by=iam.ServicePrincipal( service='lambda.amazonaws.com' ), @@ -107,8 +110,10 @@ class BatchProcessing: self._events_firehose_delivery_stream = kinesisfirehose.CfnDeliveryStream( self._stack, - id=f'{self._stack.stack_name}-EventsFirehoseDeliveryStream', + id=f'EventsFirehoseDeliveryStream', delivery_stream_type='KinesisStreamAsSource', + delivery_stream_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-EventsFirehoseDeliveryStream', 'firehose_delivery_stream'), kinesis_stream_source_configuration=kinesisfirehose.CfnDeliveryStream.KinesisStreamSourceConfigurationProperty( kinesis_stream_arn=self._input_stream_arn, role_arn=self._firehose_delivery_stream_role.role_arn @@ -192,7 +197,8 @@ class BatchProcessing: self._firehose_delivery_stream_log_group = logs.LogGroup( self._stack, id='FirehoseLogGroup', - log_group_name=f'{self._stack.stack_name}-FirehoseLogGroup', + log_group_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-FirehoseLogGroup', 'cloudwatch_log_group'), removal_policy=core.RemovalPolicy.DESTROY, retention=logs.RetentionDays.ONE_MONTH ) @@ -299,7 +305,8 @@ class BatchProcessing: self._firehose_delivery_stream_role = iam.Role( self._stack, id='GameEventsFirehoseRole', - role_name=f'{self._stack.stack_name}-GameEventsFirehoseRole', + role_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-GameEventsFirehoseRole', 'iam_role'), assumed_by=iam.ServicePrincipal( service='firehose.amazonaws.com' ), diff --git a/Gems/AWSMetrics/cdk/aws_metrics/dashboard.py b/Gems/AWSMetrics/cdk/aws_metrics/dashboard.py index f86b374abf..32ff0d9c84 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/dashboard.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/dashboard.py @@ -12,6 +12,7 @@ from aws_cdk import ( from . import aws_metrics_constants from .layout_widget_construct import LayoutWidget +from .aws_utils import resource_name_sanitizer class Dashboard: @@ -28,7 +29,8 @@ class Dashboard: events_processing_lambda_name: str = '', ) -> None: - self._dashboard_name = f"{stack.stack_name}-Dashboard" + self._dashboard_name = resource_name_sanitizer.sanitize_resource_name( + f'{stack.stack_name}-Dashboard', 'cloudwatch_dashboard') self._dashboard = cloudwatch.Dashboard( stack, id="DashBoard", diff --git a/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py b/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py index 6f31818bf4..a21e629c38 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py @@ -12,10 +12,11 @@ from aws_cdk import ( aws_kinesis as kinesis ) -from . import aws_metrics_constants - import json +from . import aws_metrics_constants +from .aws_utils import resource_name_sanitizer + class DataIngestion: """ @@ -29,7 +30,8 @@ class DataIngestion: self._input_stream = kinesis.Stream( self._stack, id='InputStream', - stream_name=f'{self._stack.stack_name}-InputStream', + stream_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-InputStream', 'kinesis_stream'), shard_count=1 ) diff --git a/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py b/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py index 3e0527b8e3..aaaf03b1fa 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/data_lake_integration.py @@ -13,15 +13,18 @@ from aws_cdk import ( ) from . import aws_metrics_constants +from .aws_utils import resource_name_sanitizer class DataLakeIntegration: """ Create the AWS resources including the S3 bucket, Glue database, table and crawler for data lake integration """ - def __init__(self, stack: core.Construct, application_name: str) -> None: + def __init__(self, stack: core.Construct, application_name: str, + server_access_logs_bucket: str = None) -> None: self._stack = stack self._application_name = application_name + self._server_access_logs_bucket = server_access_logs_bucket self._create_analytics_bucket() self._create_events_database() @@ -34,19 +37,31 @@ class DataLakeIntegration: The bucket uses server-side encryption with a CMK managed by S3: https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html """ + # Enable server access logging if the server access logs bucket is provided following S3 best practices. + # See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html + server_access_logs_bucket = s3.Bucket.from_bucket_name( + self._stack, + f'{self._stack.stack_name}-ImportedAccessLogsBucket', + self._server_access_logs_bucket, + ) if self._server_access_logs_bucket else None + # Bucket name cannot contain uppercase characters # Do not specify the bucket name here since bucket name is required to be unique globally. If we set # a specific name here, only one customer can deploy the bucket successfully. self._analytics_bucket = s3.Bucket( self._stack, - id=f'{self._stack.stack_name}-AnalyticsBucket'.lower(), + id=f'AnalyticsBucket'.lower(), + bucket_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-AnalyticsBucket'.lower(), 's3_bucket'), encryption=s3.BucketEncryption.S3_MANAGED, block_public_access=s3.BlockPublicAccess( block_public_acls=True, block_public_policy=True, ignore_public_acls=True, restrict_public_buckets=True - ) + ), + server_access_logs_bucket=server_access_logs_bucket, + server_access_logs_prefix=f'{self._stack.stack_name}-AccessLogs' if server_access_logs_bucket else None ) # For Amazon S3 buckets, you must delete all objects in the bucket for deletion to succeed. @@ -285,7 +300,8 @@ class DataLakeIntegration: self._events_crawler_role = iam.Role( self._stack, id='EventsCrawlerRole', - role_name=f'{self._stack.stack_name}-EventsCrawlerRole', + role_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-EventsCrawlerRole', 'iam_role'), assumed_by=iam.ServicePrincipal( service='glue.amazonaws.com' ), diff --git a/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py b/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py index 9809bf0c9c..52fdcdc122 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/real_time_data_processing.py @@ -16,6 +16,7 @@ from aws_cdk import ( import os from . import aws_metrics_constants +from .aws_utils import resource_name_sanitizer class RealTimeDataProcessing: @@ -44,7 +45,8 @@ class RealTimeDataProcessing: self._analytics_application = analytics.CfnApplication( self._stack, 'AnalyticsApplication', - application_name=f'{self._stack.stack_name}-AnalyticsApplication', + application_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-AnalyticsApplication', 'kinesis_application'), inputs=[ analytics.CfnApplication.InputProperty( input_schema=analytics.CfnApplication.InputSchemaProperty( @@ -162,7 +164,8 @@ class RealTimeDataProcessing: kinesis_analytics_role = iam.Role( self._stack, id='AnalyticsApplicationRole', - role_name=f'{self._stack.stack_name}-AnalyticsApplicationRole', + role_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-AnalyticsApplicationRole', 'iam_role'), assumed_by=iam.ServicePrincipal( service='kinesisanalytics.amazonaws.com' ), @@ -178,7 +181,8 @@ class RealTimeDataProcessing: """ Generate the analytics processing lambda to send processed data to CloudWatch for visualization. """ - analytics_processing_function_name = f'{self._stack.stack_name}-AnalyticsProcessingLambdaName' + analytics_processing_function_name = resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-AnalyticsProcessingLambdaName', 'lambda_function') self._analytics_processing_lambda_role = self._create_analytics_processing_lambda_role( analytics_processing_function_name ) @@ -246,7 +250,8 @@ class RealTimeDataProcessing: analytics_processing_lambda_role = iam.Role( self._stack, id='AnalyticsLambdaRole', - role_name=f'{self._stack.stack_name}-AnalyticsLambdaRole', + role_name=resource_name_sanitizer.sanitize_resource_name( + f'{self._stack.stack_name}-AnalyticsLambdaRole', 'iam_role'), assumed_by=iam.ServicePrincipal( service='lambda.amazonaws.com' ), diff --git a/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzerSystemComponent.cpp b/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzerSystemComponent.cpp index 987444dd3c..bc9763496c 100644 --- a/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzerSystemComponent.cpp +++ b/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzerSystemComponent.cpp @@ -6,6 +6,8 @@ * */ +#include + #include #include // For AZ_MAX_PATH_LEN #include diff --git a/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h b/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h index 6f8484de9f..d5c84c7441 100644 --- a/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h +++ b/Gems/Atom/Component/DebugCamera/Code/Include/Atom/Component/DebugCamera/CameraComponent.h @@ -92,12 +92,16 @@ namespace AZ float GetFarClipDistance() override; float GetFrustumWidth() override; float GetFrustumHeight() override; + bool IsOrthographic() override; + float GetOrthographicHalfWidth() override; void SetFovDegrees(float fov) override; void SetFovRadians(float fov) override; void SetNearClipDistance(float nearClipDistance) override; void SetFarClipDistance(float farClipDistance) override; void SetFrustumWidth(float width) override; void SetFrustumHeight(float height) override; + void SetOrthographic(bool orthographic) override; + void SetOrthographicHalfWidth(float halfWidth) override; void MakeActiveView() override; // RPI::WindowContextNotificationBus overrides... diff --git a/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp b/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp index 27359ebdce..9a6f7c83ab 100644 --- a/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp +++ b/Gems/Atom/Component/DebugCamera/Code/Source/CameraComponent.cpp @@ -185,6 +185,15 @@ namespace AZ return m_componentConfig.m_depthFar * tanf(m_componentConfig.m_fovY / 2) * 2; } + bool CameraComponent::IsOrthographic() + { + return false; + } + + float CameraComponent::GetOrthographicHalfWidth() + { + return 0.0f; + } void CameraComponent::SetFovDegrees(float fov) { @@ -226,6 +235,16 @@ namespace AZ UpdateViewToClipMatrix(); } + void CameraComponent::SetOrthographic(bool orthographic) + { + AZ_Assert(!orthographic, "DebugCamera does not support orthographic projection"); + } + + void CameraComponent::SetOrthographicHalfWidth([[maybe_unused]] float halfWidth) + { + AZ_Assert(false, "DebugCamera does not support orthographic projection"); + } + void CameraComponent::MakeActiveView() { // do nothing diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli index 64d384b2a4..b88b6574a4 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/PointLight.azsli @@ -116,7 +116,7 @@ void ApplyPointLight(ViewSrg::PointLight light, Surface surface, inout LightingD float sphereIntensityNormalization = GetIntensityAdjustedByRadiusAndRoughness(surface.roughnessA, light.m_bulbRadius, d2); // Specular contribution - lightingData.specularLighting += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight)); + lightingData.specularLighting += sphereIntensityNormalization * GetSpecularLighting(surface, lightingData, lightIntensity, normalize(posToLight)) * litRatio; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp index fefd889551..0c4c4d9689 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp @@ -453,8 +453,8 @@ namespace AZ JsonSerializerSettings serializationSettings; serializationSettings.m_keepDefaults = true; - TimestampSerializer timestapSerializer(CollectPassesRecursively(root)); - const auto saveResult = JsonSerializationUtils::SaveObjectToFile(×tapSerializer, + TimestampSerializer timestampSerializer(CollectPassesRecursively(root)); + const auto saveResult = JsonSerializationUtils::SaveObjectToFile(×tampSerializer, outputFilePath, (TimestampSerializer*)nullptr, &serializationSettings); AZStd::string captureInfo = outputFilePath; diff --git a/Gems/Atom/RHI/Vulkan/3rdParty/Platform/Linux/glad_vulkan_linux.cmake b/Gems/Atom/RHI/Vulkan/3rdParty/Platform/Linux/glad_vulkan_linux.cmake index 41de383023..1936c5b911 100644 --- a/Gems/Atom/RHI/Vulkan/3rdParty/Platform/Linux/glad_vulkan_linux.cmake +++ b/Gems/Atom/RHI/Vulkan/3rdParty/Platform/Linux/glad_vulkan_linux.cmake @@ -6,6 +6,18 @@ # # -set(GLAD_VULKAN_COMPILE_DEFINITIONS - VK_USE_PLATFORM_XCB_KHR -) +if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb") + set(GLAD_VULKAN_COMPILE_DEFINITIONS + VK_USE_PLATFORM_XCB_KHR + ) +elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland") + set(GLAD_VULKAN_COMPILE_DEFINITIONS + VK_USE_PLATFORM_WAYLAND_KHR + ) +elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "xlib") + set(GLAD_VULKAN_COMPILE_DEFINITIONS + VK_USE_PLATFORM_XLIB_KHR + ) +else() + message(FATAL_ERROR, "Linux Window Manager ${PAL_TRAIT_LINUX_WINDOW_MANAGER} is not recognized") +endif() diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp index 87b75d8609..98e91519ea 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/Platform/Linux/RHI/WSISurface_Linux.cpp @@ -5,6 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ +#include #include #include #include @@ -17,15 +18,36 @@ namespace AZ { Instance& instance = Instance::GetInstance(); +#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + + xcb_connection_t* xcb_connection = nullptr; + if (auto xcbConnectionManager = AzFramework::LinuxXcbConnectionManagerInterface::Get(); + xcbConnectionManager != nullptr) + { + xcb_connection = xcbConnectionManager->GetXcbConnection(); + } + AZ_Error("AtomVulkan_RHI", xcb_connection!=nullptr, "Unable to get XCB Connection"); + VkXcbSurfaceCreateInfoKHR createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; createInfo.pNext = nullptr; createInfo.flags = 0; + createInfo.connection = xcb_connection; createInfo.window = static_cast(m_descriptor.m_windowHandle.GetIndex()); const VkResult result = vkCreateXcbSurfaceKHR(instance.GetNativeInstance(), &createInfo, nullptr, &m_nativeSurface); AssertSuccess(result); return ConvertResult(result); +#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND + #error "Linux Window Manager Wayland not supported." + return RHI::ResultCode::Unimplemented; +#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_XLIB + #error "Linux Window Manager XLIB not supported." + return RHI::ResultCode::Unimplemented; +#else + #error "Linux Window Manager not recognized." + return RHI::ResultCode::Unimplemented; +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB } } } diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.cpp index 76a5486953..c00c1804b9 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/PhysicalDevice.cpp @@ -261,7 +261,7 @@ namespace AZ VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME } }; - [[maybe_unused]] uint32_t optionalExtensionCount = sizeof(optionalExtensions) / sizeof(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME); + [[maybe_unused]] uint32_t optionalExtensionCount = aznumeric_cast(optionalExtensions.size()); AZ_Assert(optionalExtensionCount == static_cast(OptionalDeviceExtension::Count), "The order and size must match the enum OptionalDeviceExtensions."); diff --git a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material index c359fea3b5..bafb047be9 100644 --- a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material +++ b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/001_lucy_regression_test.material @@ -30,7 +30,7 @@ }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.png" + "textureMap": "Objects/Lucy/Lucy_Normal.png" }, "subsurfaceScattering": { "enableSubsurfaceScattering": true, diff --git a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material index ce42f32b67..400044d29f 100644 --- a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material +++ b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material @@ -29,7 +29,7 @@ }, "normal": { "flipY": true, - "textureMap": "Objects/Lucy/Lucy_normal.png" + "textureMap": "Objects/Lucy/Lucy_Normal.png" }, "subsurfaceScattering": { "enableSubsurfaceScattering": true, diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 9e2945fe31..10cfa059aa 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -117,19 +117,24 @@ namespace AtomToolsFramework AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect(); } - // should the camera system respond to this particular event - static bool ShouldHandle(const AzFramework::ViewportControllerPriority priority, const bool exclusive) + // what priority should the camera system respond to + static AzFramework::ViewportControllerPriority GetPriority(const AzFramework::CameraSystem& cameraSystem) { - // ModernViewportCameraControllerInstance receives events at all priorities, it should only respond - // to normal priority events if it is not in 'exclusive' mode and when in 'exclusive' mode it should - // only respond to the highest priority events - return !exclusive && priority == AzFramework::ViewportControllerPriority::Normal || - exclusive && priority == AzFramework::ViewportControllerPriority::Highest; + // ModernViewportCameraControllerInstance receives events at all priorities, when it is in 'exclusive' mode + // or it is actively handling events (essentially when the camera system is 'active' and responding to inputs) + // it should only respond to the highest priority + if (cameraSystem.m_cameras.Exclusive() || cameraSystem.HandlingEvents()) + { + return AzFramework::ViewportControllerPriority::Highest; + } + + // otherwise it should only respond to normal priority events + return AzFramework::ViewportControllerPriority::Normal; } bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) { - if (ShouldHandle(event.m_priority, m_cameraSystem.m_cameras.Exclusive())) + if (event.m_priority == GetPriority(m_cameraSystem)) { return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel)); } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index 0ea59fcf9e..5167e3d3f6 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -88,7 +88,7 @@ namespace AtomToolsFramework [this](const AzFramework::InputChannel* inputChannel, QEvent* event) { AzFramework::NativeWindowHandle windowId = reinterpret_cast(winId()); - if (m_controllerList->HandleInputChannelEvent({GetId(), windowId, *inputChannel})) + if (m_controllerList->HandleInputChannelEvent(AzFramework::ViewportControllerInputEvent{GetId(), windowId, *inputChannel})) { // If the controller handled the input event, mark the event as accepted so it doesn't continue to propagate. if (event) diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/StableDynamicArray.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/StableDynamicArray.inl index 5a66dec7d8..25f3f528ab 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/StableDynamicArray.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/StableDynamicArray.inl @@ -7,6 +7,7 @@ */ #pragma once +#include #include namespace AZ diff --git a/Gems/AudioEngineWwise/Code/CMakeLists.txt b/Gems/AudioEngineWwise/Code/CMakeLists.txt index c6442df1ba..98a31fb91a 100644 --- a/Gems/AudioEngineWwise/Code/CMakeLists.txt +++ b/Gems/AudioEngineWwise/Code/CMakeLists.txt @@ -16,9 +16,6 @@ set(AUDIOENGINEWWISE_COMPILEDEFINITIONS ) find_package(Wwise MODULE) -if (NOT Wwise_FOUND) - message(STATUS "** Update the LY_WWISE_INSTALL_PATH cache variable if you intend to use Wwise.") -endif() ################################################################################ # Server / Unsupported diff --git a/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.cpp b/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.cpp index 4dfafa132a..5bad3ecf1c 100644 --- a/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.cpp +++ b/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.cpp @@ -19,10 +19,7 @@ #include #include -#include -#include -#include -#include +#include void InitWwiseResources() { @@ -217,28 +214,34 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - TConnectionPtr CAudioSystemEditor_wwise::CreateConnectionFromXMLNode(XmlNodeRef node, EACEControlType atlControlType) + TConnectionPtr CAudioSystemEditor_wwise::CreateConnectionFromXMLNode(AZ::rapidxml::xml_node* node, EACEControlType atlControlType) { if (node) { - const AZStd::string tag(node->getTag()); - TImplControlType type = TagToType(tag); + AZStd::string_view element(node->name()); + TImplControlType type = TagToType(element); if (type != AUDIO_IMPL_INVALID_TYPE) { - AZStd::string name(node->getAttr(Audio::WwiseXmlTags::WwiseNameAttribute)); - AZStd::string localized(node->getAttr(Audio::WwiseXmlTags::WwiseLocalizedAttribute)); + AZStd::string name; + AZStd::string_view localized; - // Legacy Preload support - if (localized.empty()) + if (auto nameAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute, 0, false); + nameAttr != nullptr) { - localized = node->getAttr(Audio::WwiseXmlTags::Legacy::WwiseLocalizedAttribute); + name = nameAttr->value(); } - bool isLocalized = AZ::StringFunc::Equal(localized.c_str(), "true"); + if (auto localizedAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseLocalizedAttribute, 0, false); + localizedAttr != nullptr) + { + localized = localizedAttr->value(); + } - // If control not found, create a placeholder. - // We want to keep that connection even if it's not in the middleware. - // The user could be using the engine without the wwise project + bool isLocalized = AZ::StringFunc::Equal(localized, "true"); + + // If the control wasn't found, create a placeholder. + // We want to see that connection even if it's not in the middleware. + // User could be viewing the editor without a middleware project. IAudioSystemControl* control = GetControlByName(name, isLocalized); if (!control) { @@ -250,27 +253,26 @@ namespace AudioControls } } - // If it's a switch we actually connect to one of the states within the switch + // If it's a switch we connect to one of the states within the switch if (type == eWCT_WWISE_SWITCH_GROUP || type == eWCT_WWISE_GAME_STATE_GROUP) { - if (node->getChildCount() == 1) + if (auto childNode = node->first_node(); + childNode != nullptr) { - node = node->getChild(0); - if (node) + AZStd::string childName; + if (auto childNameAttr = childNode->first_attribute(Audio::WwiseXmlTags::WwiseNameAttribute, 0, false); + childNameAttr != nullptr) { - AZStd::string childName(node->getAttr(Audio::WwiseXmlTags::WwiseNameAttribute)); - - IAudioSystemControl* childControl = GetControlByName(childName, false, control); - if (!childControl) - { - childControl = CreateControl(SControlDef(childName, type == eWCT_WWISE_SWITCH_GROUP ? eWCT_WWISE_SWITCH : eWCT_WWISE_GAME_STATE, false, control)); - } - control = childControl; + childName = childNameAttr->value(); } - } - else - { - CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_ERROR, "Audio Controls Editor (Wwise): Error reading connection to Wwise control %s", name.c_str()); + + IAudioSystemControl* childControl = GetControlByName(childName, false, control); + if (!childControl) + { + childControl = CreateControl(SControlDef( + childName, type == eWCT_WWISE_SWITCH_GROUP ? eWCT_WWISE_SWITCH : eWCT_WWISE_GAME_STATE, false, control)); + } + control = childControl; } } @@ -289,16 +291,19 @@ namespace AudioControls float mult = 1.0f; float shift = 0.0f; - if (node->haveAttr(Audio::WwiseXmlTags::WwiseMutiplierAttribute)) + + if (auto multAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseMutiplierAttribute, 0, false); + multAttr != nullptr) { - const AZStd::string multProperty(node->getAttr(Audio::WwiseXmlTags::WwiseMutiplierAttribute)); - mult = AZStd::stof(multProperty); + mult = AZStd::stof(AZStd::string(multAttr->value())); } - if (node->haveAttr(Audio::WwiseXmlTags::WwiseShiftAttribute)) + + if (auto shiftAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseShiftAttribute, 0, false); + shiftAttr != nullptr) { - const AZStd::string shiftProperty(node->getAttr(Audio::WwiseXmlTags::WwiseShiftAttribute)); - shift = AZStd::stof(shiftProperty); + shift = AZStd::stof(AZStd::string(shiftAttr->value())); } + connection->m_mult = mult; connection->m_shift = shift; return connection; @@ -308,11 +313,12 @@ namespace AudioControls TStateConnectionPtr connection = AZStd::make_shared(control->GetId()); float value = 0.0f; - if (node->haveAttr(Audio::WwiseXmlTags::WwiseValueAttribute)) + if (auto valueAttr = node->first_attribute(Audio::WwiseXmlTags::WwiseValueAttribute, 0, false); + valueAttr != nullptr) { - const AZStd::string valueProperty(node->getAttr(Audio::WwiseXmlTags::WwiseValueAttribute)); - value = AZStd::stof(valueProperty); + value = AZStd::stof(AZStd::string(valueAttr->value())); } + connection->m_value = value; return connection; } @@ -329,28 +335,50 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - XmlNodeRef CAudioSystemEditor_wwise::CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) + AZ::rapidxml::xml_node* CAudioSystemEditor_wwise::CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) { const IAudioSystemControl* control = GetControl(connection->GetID()); if (control) { + XmlAllocator& xmlAllocator(AudioControls::s_xmlAllocator); + switch (control->GetType()) { case AudioControls::eWCT_WWISE_SWITCH: + [[fallthrough]]; case AudioControls::eWCT_WWISE_SWITCH_GROUP: + [[fallthrough]]; case AudioControls::eWCT_WWISE_GAME_STATE: + [[fallthrough]]; case AudioControls::eWCT_WWISE_GAME_STATE_GROUP: { const IAudioSystemControl* parent = control->GetParent(); if (parent) { - XmlNodeRef switchNode = GetISystem()->CreateXmlNode(TypeToTag(parent->GetType()).data()); - switchNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, parent->GetName().c_str()); + AZStd::string_view parentType = TypeToTag(parent->GetType()); + auto switchNode = xmlAllocator.allocate_node( + AZ::rapidxml::node_element, + xmlAllocator.allocate_string(parentType.data()) + ); - XmlNodeRef stateNode = switchNode->createNode(Audio::WwiseXmlTags::WwiseValueTag); - stateNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str()); - switchNode->addChild(stateNode); + auto switchNameAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseNameAttribute, + xmlAllocator.allocate_string(parent->GetName().c_str()) + ); + auto stateNode = xmlAllocator.allocate_node( + AZ::rapidxml::node_element, + Audio::WwiseXmlTags::WwiseValueTag + ); + + auto stateNameAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseNameAttribute, + xmlAllocator.allocate_string(control->GetName().c_str()) + ); + + switchNode->append_attribute(switchNameAttr); + stateNode->append_attribute(stateNameAttr); + switchNode->append_node(stateNode); return switchNode; } break; @@ -358,51 +386,98 @@ namespace AudioControls case AudioControls::eWCT_WWISE_RTPC: { - XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data()); - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str()); + auto connectionNode = xmlAllocator.allocate_node( + AZ::rapidxml::node_element, + xmlAllocator.allocate_string(TypeToTag(control->GetType()).data()) + ); + + auto nameAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseNameAttribute, + xmlAllocator.allocate_string(control->GetName().c_str()) + ); + + connectionNode->append_attribute(nameAttr); if (atlControlType == eACET_RTPC) { AZStd::shared_ptr rtpcConnection = AZStd::static_pointer_cast(connection); - if (rtpcConnection->m_mult != 1.0f) + if (rtpcConnection->m_mult != 1.f) { - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseMutiplierAttribute, rtpcConnection->m_mult); + auto multAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseMutiplierAttribute, + xmlAllocator.allocate_string(AZStd::to_string(rtpcConnection->m_mult).c_str()) + ); + + connectionNode->append_attribute(multAttr); } - if (rtpcConnection->m_shift != 0.0f) + + if (rtpcConnection->m_shift != 0.f) { - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseShiftAttribute, rtpcConnection->m_shift); + auto shiftAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseShiftAttribute, + xmlAllocator.allocate_string(AZStd::to_string(rtpcConnection->m_shift).c_str()) + ); + + connectionNode->append_attribute(shiftAttr); } } else if (atlControlType == eACET_SWITCH_STATE) { AZStd::shared_ptr stateConnection = AZStd::static_pointer_cast(connection); - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseValueAttribute, stateConnection->m_value); + + auto valueAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseValueAttribute, + xmlAllocator.allocate_string(AZStd::to_string(stateConnection->m_value).c_str()) + ); + + connectionNode->append_attribute(valueAttr); } + return connectionNode; } case AudioControls::eWCT_WWISE_EVENT: - { - XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data()); - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str()); - return connectionNode; - } - + [[fallthrough]]; case AudioControls::eWCT_WWISE_AUX_BUS: { - XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data()); - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str()); + auto connectionNode = xmlAllocator.allocate_node( + AZ::rapidxml::node_element, + xmlAllocator.allocate_string(TypeToTag(control->GetType()).data()) + ); + + auto nameAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseNameAttribute, + xmlAllocator.allocate_string(control->GetName().c_str()) + ); + + connectionNode->append_attribute(nameAttr); return connectionNode; } case AudioControls::eWCT_WWISE_SOUND_BANK: { - XmlNodeRef connectionNode = GetISystem()->CreateXmlNode(TypeToTag(control->GetType()).data()); - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseNameAttribute, control->GetName().c_str()); + auto connectionNode = xmlAllocator.allocate_node( + AZ::rapidxml::node_element, + xmlAllocator.allocate_string(TypeToTag(control->GetType()).data()) + ); + + auto nameAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseNameAttribute, + xmlAllocator.allocate_string(control->GetName().c_str()) + ); + + connectionNode->append_attribute(nameAttr); + if (control->IsLocalized()) { - connectionNode->setAttr(Audio::WwiseXmlTags::WwiseLocalizedAttribute, "true"); + auto locAttr = xmlAllocator.allocate_attribute( + Audio::WwiseXmlTags::WwiseLocalizedAttribute, + xmlAllocator.allocate_string("true") + ); + + connectionNode->append_attribute(locAttr); } + return connectionNode; } } diff --git a/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.h b/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.h index 14708a5816..25c621de76 100644 --- a/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.h +++ b/Gems/AudioEngineWwise/Code/Source/Editor/AudioSystemEditor_wwise.h @@ -77,8 +77,8 @@ namespace AudioControls EACEControlType ImplTypeToATLType(TImplControlType type) const override; TImplControlTypeMask GetCompatibleTypes(EACEControlType atlControlType) const override; TConnectionPtr CreateConnectionToControl(EACEControlType atlControlType, IAudioSystemControl* middlewareControl) override; - TConnectionPtr CreateConnectionFromXMLNode(XmlNodeRef node, EACEControlType atlControlType) override; - XmlNodeRef CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) override; + TConnectionPtr CreateConnectionFromXMLNode(AZ::rapidxml::xml_node* node, EACEControlType atlControlType) override; + AZ::rapidxml::xml_node* CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) override; const AZStd::string_view GetTypeIcon(TImplControlType type) const override; const AZStd::string_view GetTypeIconSelected(TImplControlType type) const override; AZStd::string GetName() const override; diff --git a/Gems/AudioEngineWwise/Code/Source/Editor/AudioWwiseLoader.cpp b/Gems/AudioEngineWwise/Code/Source/Editor/AudioWwiseLoader.cpp index 30320a567f..616a30994c 100644 --- a/Gems/AudioEngineWwise/Code/Source/Editor/AudioWwiseLoader.cpp +++ b/Gems/AudioEngineWwise/Code/Source/Editor/AudioWwiseLoader.cpp @@ -9,20 +9,12 @@ #include -#include - #include #include #include #include #include -#include -#include -#include -#include - -using namespace PathUtil; namespace AudioControls { @@ -68,8 +60,7 @@ namespace AudioControls for (const auto& filePath : foundFiles) { AZ_Assert(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.c_str()), "FindFiles found file '%s' but FileIO says it doesn't exist!", filePath.c_str()); - AZStd::string fileName; - AZ::StringFunc::Path::GetFullFileName(filePath.c_str(), fileName); + AZ::IO::PathView fileName = filePath.Filename(); if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(filePath.c_str())) { @@ -79,15 +70,15 @@ namespace AudioControls // we load only one as all of them should have the // same content (in the future we want to have a // consistency report to highlight if this is not the case) - m_localizationFolder = fileName; + m_localizationFolder.assign(fileName.Native().data(), fileName.Native().size()); LoadSoundBanks(rootFolder, m_localizationFolder, true); isLocalizedLoaded = true; } } - else if (AZ::StringFunc::Find(fileName.c_str(), Audio::Wwise::BankExtension) != AZStd::string::npos - && !AZ::StringFunc::Equal(fileName.c_str(), Audio::Wwise::InitBank)) + else if (fileName.Extension() == Audio::Wwise::BankExtension && fileName != Audio::Wwise::InitBank) { - m_audioSystemImpl->CreateControl(SControlDef(fileName, eWCT_WWISE_SOUND_BANK, isLocalized, nullptr, subPath)); + m_audioSystemImpl->CreateControl( + SControlDef(AZStd::string{ fileName.Native() }, eWCT_WWISE_SOUND_BANK, isLocalized, nullptr, subPath)); } } } @@ -103,14 +94,14 @@ namespace AudioControls if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(filePath.c_str())) { - LoadControlsInFolder(filePath); + LoadControlsInFolder(filePath.Native()); } else { // Open the file, read into an xmlDoc, and call LoadControls with the root xml node... AZ_TracePrintf("AudioWwiseLoader", "Loading Xml from '%s'", filePath.c_str()); - Audio::ScopedXmlLoader xmlFileLoader(filePath); + Audio::ScopedXmlLoader xmlFileLoader(filePath.Native()); if (!xmlFileLoader.HasError()) { LoadControl(xmlFileLoader.GetRootNode()); diff --git a/Gems/AudioEngineWwise/Code/Source/Engine/FileIOHandler_wwise.cpp b/Gems/AudioEngineWwise/Code/Source/Engine/FileIOHandler_wwise.cpp index 388a539be1..9571dd86a9 100644 --- a/Gems/AudioEngineWwise/Code/Source/Engine/FileIOHandler_wwise.cpp +++ b/Gems/AudioEngineWwise/Code/Source/Engine/FileIOHandler_wwise.cpp @@ -7,18 +7,16 @@ */ -#include #include + #include +#include #include #include #include #include - -#include -#include -#include +#include #define MAX_NUMBER_STRING_SIZE (10) // 4G #define ID_TO_STRING_FORMAT_BANK AKTEXT("%u.bnk") @@ -90,34 +88,36 @@ namespace Audio bool CBlockingDevice_wwise::Open(const char* filename, AkOpenMode openMode, AkFileDesc& fileDesc) { - const char* openModeString = nullptr; + AZ::IO::OpenMode azOpenMode = AZ::IO::OpenMode::ModeBinary; switch (openMode) { case AK_OpenModeRead: - openModeString = "rbx"; + azOpenMode |= AZ::IO::OpenMode::ModeRead; break; case AK_OpenModeWrite: - openModeString = "wbx"; + azOpenMode |= AZ::IO::OpenMode::ModeWrite; break; case AK_OpenModeWriteOvrwr: - openModeString = "w+bx"; + azOpenMode |= (AZ::IO::OpenMode::ModeUpdate | AZ::IO::OpenMode::ModeWrite); break; case AK_OpenModeReadWrite: - openModeString = "abx"; + azOpenMode |= (AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeWrite); break; default: AZ_Assert(false, "Unknown Wwise file open mode."); return false; } - const size_t fileSize = gEnv->pCryPak->FGetSize(filename); - if (fileSize > 0) + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + if (AZ::u64 fileSize = 0; + fileIO->Size(filename, fileSize) && fileSize != 0) { - AZ::IO::HandleType fileHandle = gEnv->pCryPak->FOpen(filename, openModeString, AZ::IO::IArchive::FOPEN_HINT_DIRECT_OPERATION); + AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; + fileIO->Open(filename, azOpenMode, fileHandle); if (fileHandle != AZ::IO::InvalidHandle) { fileDesc.hFile = GetAkFileHandle(fileHandle); - fileDesc.iFileSize = static_cast(fileSize); + fileDesc.iFileSize = aznumeric_cast(fileSize); fileDesc.uSector = 0; fileDesc.deviceID = m_deviceID; fileDesc.pCustomParam = nullptr; @@ -132,50 +132,58 @@ namespace Audio AKRESULT CBlockingDevice_wwise::Read(AkFileDesc& fileDesc, const AkIoHeuristics&, void* buffer, AkIOTransferInfo& transferInfo) { - AZ_Assert(buffer, "Wwise didn't provide a valid buffer to write to."); + AZ_Assert(buffer, "Wwise didn't provide a valid desination buffer to Read into."); AZ::IO::HandleType fileHandle = GetRealFileHandle(fileDesc.hFile); - const uint64_t currentFileReadPos = gEnv->pCryPak->FTell(fileHandle); - const uint64_t wantedFileReadPos = static_cast(transferInfo.uFilePosition); + auto fileIO = AZ::IO::FileIOBase::GetInstance(); - if (currentFileReadPos != wantedFileReadPos) + AZ::u64 currentFileReadPos = 0; + fileIO->Tell(fileHandle, currentFileReadPos); + + if (currentFileReadPos != transferInfo.uFilePosition) { - gEnv->pCryPak->FSeek(fileHandle, wantedFileReadPos, SEEK_SET); + fileIO->Seek(fileHandle, aznumeric_cast(transferInfo.uFilePosition), AZ::IO::SeekType::SeekFromStart); } - const size_t bytesRead = gEnv->pCryPak->FReadRaw(buffer, 1, transferInfo.uRequestedSize, fileHandle); - AZ_Assert(bytesRead == static_cast(transferInfo.uRequestedSize), - "Number of bytes read (%zu) for Wwise request doesn't match the requested size (%u).", bytesRead, transferInfo.uRequestedSize); - return (bytesRead > 0) ? AK_Success : AK_Fail; + AZ::u64 bytesRead = 0; + fileIO->Read(fileHandle, buffer, aznumeric_cast(transferInfo.uRequestedSize), &bytesRead); + const bool readOk = (bytesRead == aznumeric_cast(transferInfo.uRequestedSize)); + + AZ_Assert(readOk, + "Number of bytes read (%" PRIu64 ") for read request doesn't match the requested size (%u).", + bytesRead, transferInfo.uRequestedSize); + return readOk ? AK_Success : AK_Fail; } AKRESULT CBlockingDevice_wwise::Write(AkFileDesc& fileDesc, const AkIoHeuristics&, void* data, AkIOTransferInfo& transferInfo) { - AZ_Assert(data, "Wwise didn't provide a valid buffer to read from."); + AZ_Assert(data, "Wwise didn't provide a valid source buffer to Write from."); AZ::IO::HandleType fileHandle = GetRealFileHandle(fileDesc.hFile); + auto fileIO = AZ::IO::FileIOBase::GetInstance(); - const uint64_t currentFileWritePos = gEnv->pCryPak->FTell(fileHandle); - const uint64_t wantedFileWritePos = static_cast(transferInfo.uFilePosition); + AZ::u64 currentFileWritePos = 0; + fileIO->Tell(fileHandle, currentFileWritePos); - if (currentFileWritePos != wantedFileWritePos) + if (currentFileWritePos != transferInfo.uFilePosition) { - gEnv->pCryPak->FSeek(fileHandle, wantedFileWritePos, SEEK_SET); + fileIO->Seek(fileHandle, aznumeric_cast(transferInfo.uFilePosition), AZ::IO::SeekType::SeekFromStart); } - const size_t bytesWritten = gEnv->pCryPak->FWrite(data, 1, static_cast(transferInfo.uRequestedSize), fileHandle); - if (bytesWritten != static_cast(transferInfo.uRequestedSize)) - { - AZ_Error("Wwise", false, "Number of bytes written (%zu) for Wwise request doesn't match the requested size (%u).", + AZ::u64 bytesWritten = 0; + fileIO->Write(fileHandle, data, aznumeric_cast(transferInfo.uRequestedSize), &bytesWritten); + const bool writeOk = (bytesWritten == aznumeric_cast(transferInfo.uRequestedSize)); + + AZ_Error("Wwise", writeOk, + "Number of bytes written (%" PRIu64 ") for write request doesn't match the requested size (%u).", bytesWritten, transferInfo.uRequestedSize); - return AK_Fail; - } - return AK_Success; + return writeOk ? AK_Success : AK_Fail; } AKRESULT CBlockingDevice_wwise::Close(AkFileDesc& fileDesc) { - return gEnv->pCryPak->FClose(GetRealFileHandle(fileDesc.hFile)) ? AK_Success : AK_Fail; + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + return fileIO->Close(GetRealFileHandle(fileDesc.hFile)) ? AK_Success : AK_Fail; } AkUInt32 CBlockingDevice_wwise::GetBlockSize([[maybe_unused]] AkFileDesc& fileDesc) @@ -189,7 +197,7 @@ namespace Audio deviceDesc.bCanRead = true; deviceDesc.bCanWrite = true; deviceDesc.deviceID = m_deviceID; - AK_CHAR_TO_UTF16(deviceDesc.szDeviceName, "CryPak", AZ_ARRAY_SIZE(deviceDesc.szDeviceName)); + AK_CHAR_TO_UTF16(deviceDesc.szDeviceName, "IO::IArchive", AZ_ARRAY_SIZE(deviceDesc.szDeviceName)); deviceDesc.uStringSize = AKPLATFORM::AkUtf16StrLen(deviceDesc.szDeviceName); } @@ -231,12 +239,13 @@ namespace Audio bool CStreamingDevice_wwise::Open(const char* filename, [[maybe_unused]] AkOpenMode openMode, AkFileDesc& fileDesc) { AZ_Assert(openMode == AK_OpenModeRead, "Wwise Async File IO - Only supports opening files for reading.\n"); - const size_t fileSize = gEnv->pCryPak->FGetSize(filename); - if (fileSize) + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + if (AZ::u64 fileSize = 0; + fileIO->Size(filename, fileSize) && fileSize != 0) { AZStd::string* filenameStore = azcreate(AZStd::string, (filename)); fileDesc.hFile = AkFileHandle(); - fileDesc.iFileSize = static_cast(fileSize); + fileDesc.iFileSize = aznumeric_cast(fileSize); fileDesc.uSector = 0; fileDesc.deviceID = m_deviceID; fileDesc.pCustomParam = filenameStore; @@ -326,7 +335,7 @@ namespace Audio deviceDesc.bCanRead = true; deviceDesc.bCanWrite = false; deviceDesc.deviceID = m_deviceID; - AK_CHAR_TO_UTF16(deviceDesc.szDeviceName, "Streamer", AZ_ARRAY_SIZE(deviceDesc.szDeviceName)); + AK_CHAR_TO_UTF16(deviceDesc.szDeviceName, "IO::IStreamer", AZ_ARRAY_SIZE(deviceDesc.szDeviceName)); deviceDesc.uStringSize = AKPLATFORM::AkUtf16StrLen(deviceDesc.szDeviceName); } diff --git a/Gems/AudioSystem/Code/Include/Editor/ACETypes.h b/Gems/AudioSystem/Code/Include/Editor/ACETypes.h index cc1e35028c..1ff6112056 100644 --- a/Gems/AudioSystem/Code/Include/Editor/ACETypes.h +++ b/Gems/AudioSystem/Code/Include/Editor/ACETypes.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace AudioControls { @@ -39,4 +40,7 @@ namespace AudioControls using FilepathSet = AZStd::set; + using XmlAllocator = AZ::rapidxml::memory_pool<>; + inline XmlAllocator s_xmlAllocator; + } // namespace AudioControls diff --git a/Gems/AudioSystem/Code/Include/Editor/IAudioSystemEditor.h b/Gems/AudioSystem/Code/Include/Editor/IAudioSystemEditor.h index 5196e2f0bc..5a9c773a32 100644 --- a/Gems/AudioSystem/Code/Include/Editor/IAudioSystemEditor.h +++ b/Gems/AudioSystem/Code/Include/Editor/IAudioSystemEditor.h @@ -12,12 +12,10 @@ #include #include #include +#include #include -#include -#include - namespace AudioControls { class IAudioSystemEditor; @@ -117,14 +115,14 @@ namespace AudioControls //! @param node XML node where the connection is defined. //! @param atlControlType The type of the ATL control you are connecting to. //! @return A pointer to the newly created connection. - virtual TConnectionPtr CreateConnectionFromXMLNode(XmlNodeRef node, EACEControlType atlControlType) = 0; + virtual TConnectionPtr CreateConnectionFromXMLNode(AZ::rapidxml::xml_node* node, EACEControlType atlControlType) = 0; //! When serializing connections between controls this function will be called once per connection to serialize its properties. //! This function should be in sync with CreateConnectionToControl as whatever it's written here will have to be read there. //! @param connection Connection to serialize. //! @param atlControlType Type of the ATL control that has this connection. //! @return XML node with the connection serialized. - virtual XmlNodeRef CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) = 0; + virtual AZ::rapidxml::xml_node* CreateXMLNodeFromConnection(const TConnectionPtr connection, const EACEControlType atlControlType) = 0; //! Whenever a connection is removed from an ATL control this function should be called. //! To keep the system informed of which controls have been connected and which ones haven't. diff --git a/Gems/AudioSystem/Code/Include/Engine/ATLCommon.h b/Gems/AudioSystem/Code/Include/Engine/ATLCommon.h index dae411fd00..45997b1262 100644 --- a/Gems/AudioSystem/Code/Include/Engine/ATLCommon.h +++ b/Gems/AudioSystem/Code/Include/Engine/ATLCommon.h @@ -47,6 +47,7 @@ namespace Audio static constexpr const char* ATLInternalNameAttribute = "atl_internal_name"; static constexpr const char* ATLTypeAttribute = "atl_type"; static constexpr const char* ATLConfigGroupAttribute = "atl_config_group_name"; + static constexpr const char* ATLPathAttribute = "path"; static constexpr const char* ATLDataLoadType = "AutoLoad"; diff --git a/Gems/AudioSystem/Code/Include/Engine/AudioFileUtils.h b/Gems/AudioSystem/Code/Include/Engine/AudioFileUtils.h index 5f071fef63..65122662b1 100644 --- a/Gems/AudioSystem/Code/Include/Engine/AudioFileUtils.h +++ b/Gems/AudioSystem/Code/Include/Engine/AudioFileUtils.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -20,22 +21,26 @@ namespace Audio /*! * FindFilesInPath */ - static AZStd::vector FindFilesInPath(const AZStd::string_view folderPath, const char* filter) + static AZStd::vector FindFilesInPath(const AZStd::string_view folderPath, const char* filter) { - AZStd::vector foundFiles; + AZStd::vector foundFiles; AZ::IO::FileIOBase::FindFilesCallbackType findFilesCallback = [&foundFiles](const char* file) -> bool { - foundFiles.emplace_back(file); + foundFiles.emplace_back(AZ::IO::PathView{ file }.LexicallyNormal()); return true; }; - auto fileIO = AZ::IO::FileIOBase::GetInstance(); - if (fileIO) + if (auto fileIO = AZ::IO::FileIOBase::GetInstance(); + fileIO != nullptr) { AZ::IO::Result result = fileIO->FindFiles(folderPath.data(), filter, findFilesCallback); + if (result == AZ::IO::ResultCode::Success) + { + return AZStd::move(foundFiles); + } } - return foundFiles; + return {}; } /*! diff --git a/Gems/AudioSystem/Code/Source/Editor/ATLControlsPanel.cpp b/Gems/AudioSystem/Code/Source/Editor/ATLControlsPanel.cpp index 6503e83545..48a16e54e6 100644 --- a/Gems/AudioSystem/Code/Source/Editor/ATLControlsPanel.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/ATLControlsPanel.cpp @@ -16,13 +16,8 @@ #include #include #include -#include -#include -#include -#include #include #include -#include #include #include diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControl.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioControl.cpp index 40c1cd3c79..ce268022a8 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControl.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControl.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include namespace AudioControls @@ -346,7 +345,7 @@ namespace AudioControls { for (auto& connectionNode : m_connectionNodes) { - if (TConnectionPtr connection = audioSystemImpl->CreateConnectionFromXMLNode(connectionNode.m_xmlNode, m_type)) + if (TConnectionPtr connection = audioSystemImpl->CreateConnectionFromXMLNode(connectionNode.m_xmlNode.get(), m_type)) { AddConnection(connection); connectionNode.m_isValid = true; diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControl.h b/Gems/AudioSystem/Code/Source/Editor/AudioControl.h index f7c856fb46..37e67c815c 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControl.h +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControl.h @@ -11,13 +11,11 @@ #include #include +#include #include #include -#include -#include - namespace AudioControls { class CATLControlsModel; @@ -25,15 +23,52 @@ namespace AudioControls //-------------------------------------------------------------------------------------------// struct SRawConnectionData { - SRawConnectionData(XmlNodeRef node, bool isValid) - : m_xmlNode(node) - , m_isValid(isValid) - {} + SRawConnectionData(AZ::rapidxml::xml_node* node, bool isValid) + { + m_xmlNode = AZStd::move(DeepCopyNode(node)); + m_isValid = isValid; + } - XmlNodeRef m_xmlNode; + AZStd::unique_ptr> m_xmlNode{}; // indicates if the connection is valid for the currently loaded middleware - bool m_isValid; + bool m_isValid{ false }; + + // Rapid XML provides a 'clone_node' utility that will copy an entire node tree, + // but it only copies pointers of any strings in the node names and values. + // This causes problems with storing raw xml nodes as this class does because strings + // will be pointing into the memory pool of an xml document that has gone out of scope. + // This function is a rewritten version of 'clone_node' that does the deep copy of strings + // into the new destination tree. + [[nodiscard]] static AZStd::unique_ptr> DeepCopyNode(AZ::rapidxml::xml_node* srcNode) + { + AZStd::unique_ptr> destNode; + if (srcNode) + { + XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator); + destNode.reset(xmlAlloc.allocate_node(srcNode->type())); + + destNode->name(xmlAlloc.allocate_string(srcNode->name(), srcNode->name_size()), srcNode->name_size()); + destNode->value(xmlAlloc.allocate_string(srcNode->value(), srcNode->value_size()), srcNode->value_size()); + + for (AZ::rapidxml::xml_node* child = srcNode->first_node(); child != nullptr; child = child->next_sibling()) + { + destNode->append_node(DeepCopyNode(child).release()); + } + + for (AZ::rapidxml::xml_attribute* attr = srcNode->first_attribute(); attr != nullptr; attr = attr->next_attribute()) + { + destNode->append_attribute(xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(attr->name(), attr->name_size()), + xmlAlloc.allocate_string(attr->value(), attr->value_size()), + attr->name_size(), + attr->value_size() + )); + } + } + + return destNode; + } }; using TXmlNodeList = AZStd::vector; diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp index b8761c4d62..973cb836a0 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp @@ -14,9 +14,6 @@ #include #include -#include -#include -#include #include #include @@ -28,7 +25,6 @@ using namespace AudioControls; -using namespace PathUtil; CATLControlsModel CAudioControlsEditorPlugin::ms_ATLModel; QATLTreeModel CAudioControlsEditorPlugin::ms_layoutModel; @@ -152,20 +148,18 @@ void CAudioControlsEditorPlugin::ExecuteTrigger(const AZStd::string_view sTrigge Audio::AudioSystemRequestBus::BroadcastResult(ms_nAudioTriggerID, &Audio::AudioSystemRequestBus::Events::GetAudioTriggerID, sTriggerName.data()); if (ms_nAudioTriggerID != INVALID_AUDIO_CONTROL_ID) { - const CCamera& camera = GetIEditor()->GetSystem()->GetViewCamera(); - Audio::SAudioRequest request; request.nFlags = Audio::eARF_PRIORITY_NORMAL; - const AZ::Matrix3x4 cameraMatrix = LYTransformToAZMatrix3x4(camera.GetMatrix()); + const AZ::Matrix3x4 listenerTxfm = AZ::Matrix3x4::CreateIdentity(); - Audio::SAudioListenerRequestData requestData(cameraMatrix); + Audio::SAudioListenerRequestData requestData(listenerTxfm); requestData.oNewPosition.NormalizeForwardVec(); requestData.oNewPosition.NormalizeUpVec(); request.pData = &requestData; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, request); - ms_pIAudioProxy->SetPosition(cameraMatrix); + ms_pIAudioProxy->SetPosition(listenerTxfm); ms_pIAudioProxy->ExecuteTrigger(ms_nAudioTriggerID); } } diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorWindow.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorWindow.cpp index b352534510..54a8ce616d 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorWindow.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorWindow.cpp @@ -9,21 +9,22 @@ #include -#include +#include + +#include + #include #include #include #include +#include #include -#include -#include -#include #include #include #include -#include #include -#include + +#include #include #include @@ -31,6 +32,7 @@ #include #include + void InitACEResources() { Q_INIT_RESOURCE(AudioControlsEditorUI); @@ -106,25 +108,16 @@ namespace AudioControls { m_fileSystemWatcher.addPath(folder.data()); - AZStd::string search; - AZ::StringFunc::Path::Join(folder.data(), "*", search, true, false); - auto pCryPak = gEnv->pCryPak; - AZ::IO::ArchiveFileIterator handle = pCryPak->FindFirst(search.c_str()); - if (handle) + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + auto foundFiles = Audio::FindFilesInPath(folder, "*"); + for (auto& file : foundFiles) { - do + if (fileIO->IsDirectory(file.c_str())) { - AZStd::string sName = static_cast(handle.m_filename); - if (!sName.empty() && sName[0] != '.') - { - if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory) - { - AZ::StringFunc::Path::Join(folder.data(), sName.c_str(), sName); - StartWatchingFolder(sName); - } - } - } while (handle = pCryPak->FindNext(handle)); - pCryPak->FindClose(handle); + AZ::IO::FixedMaxPath resolvedPath; + fileIO->ReplaceAlias(resolvedPath, file); + StartWatchingFolder(file.Native()); + } } } @@ -318,19 +311,24 @@ namespace AudioControls // once we can listen to delete messages from Asset system, this can be changed to an EBus handler. const char* controlsPath = nullptr; Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath); - AZStd::string sControlsPath(Path::GetEditingGameDataFolder()); - AZ::StringFunc::Path::Join(sControlsPath.c_str(), controlsPath, sControlsPath); - Audio::SAudioManagerRequestData oParseGlobalRequestData(sControlsPath.c_str(), Audio::eADS_GLOBAL); + + AZ::IO::FixedMaxPath controlsFolder{ controlsPath }; + + Audio::SAudioManagerRequestData oParseGlobalRequestData(controlsFolder.c_str(), Audio::eADS_GLOBAL); oConfigDataRequest.pData = &oParseGlobalRequestData; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oConfigDataRequest); // parse the AudioSystem level-specific config data - AZStd::string levelName{ GetIEditor()->GetLevelName().toUtf8().data() }; - AZ::StringFunc::Path::Join(sControlsPath.c_str(), "levels", sControlsPath); - AZ::StringFunc::Path::Join(sControlsPath.c_str(), levelName.c_str(), sControlsPath); - Audio::SAudioManagerRequestData oParseLevelRequestData(sControlsPath.c_str(), Audio::eADS_LEVEL_SPECIFIC); - oConfigDataRequest.pData = &oParseLevelRequestData; - Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oConfigDataRequest); + AZStd::string levelName; + AzToolsFramework::EditorRequestBus::BroadcastResult(levelName, &AzToolsFramework::EditorRequests::GetLevelName); + if (!levelName.empty() && levelName != "Untitled") + { + controlsFolder /= "levels"; + controlsFolder /= levelName; + Audio::SAudioManagerRequestData oParseLevelRequestData(controlsFolder.c_str(), Audio::eADS_LEVEL_SPECIFIC); + oConfigDataRequest.pData = &oParseLevelRequestData; + Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oConfigDataRequest); + } // inform the middleware specific plugin that the data has been saved // to disk (in case it needs to update something) diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.cpp index c247abf855..220cd32b5c 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.cpp @@ -10,27 +10,21 @@ #include #include -#include +#include #include #include #include +#include #include #include #include #include -#include -#include -#include -#include -#include -#include +#include #include -using namespace PathUtil; - namespace AudioControls { //-------------------------------------------------------------------------------------------// @@ -91,100 +85,81 @@ namespace AudioControls { const CUndoSuspend suspendUndo; - // Get the partial path (relative under asset root) where the controls live. + // Get the relative path (under asset root) where the controls live. const char* controlsPath = nullptr; Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath); // Get the full path up to asset root. - AZStd::string controlsFullPath(Path::GetEditingGameDataFolder()); - AZ::StringFunc::Path::Join(controlsFullPath.c_str(), controlsPath, controlsFullPath); + AZ::IO::FixedMaxPath controlsFullPath = AZ::Utils::GetProjectPath(); + controlsFullPath /= controlsPath; // load the global controls - LoadAllLibrariesInFolder(controlsFullPath, ""); + LoadAllLibrariesInFolder(controlsFullPath.Native(), ""); - // load the level specific controls - auto cryPak = gEnv->pCryPak; + AZ::IO::FixedMaxPath searchPath = controlsFullPath / LoaderStrings::LevelsSubFolder; - AZStd::string searchMask; - AZ::StringFunc::Path::Join(controlsFullPath.c_str(), LoaderStrings::LevelsSubFolder, searchMask); - AZ::StringFunc::Path::Join(searchMask.c_str(), "*", searchMask, true, false); - AZ::IO::ArchiveFileIterator handle = cryPak->FindFirst(searchMask.c_str()); - if (handle) + auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*"); + + for (const auto& file : foundFiles) { - do + if (AZ::IO::FileIOBase::GetInstance()->IsDirectory(file.c_str())) { - if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory) + AZStd::string levelName{ file.Filename().Native() }; + LoadAllLibrariesInFolder(controlsFullPath.Native(), levelName); + + if (!m_atlControlsModel->ScopeExists(levelName)) { - AZStd::string_view name = handle.m_filename; - if (name != "." && name != "..") - { - LoadAllLibrariesInFolder(controlsFullPath, name); - if (!m_atlControlsModel->ScopeExists(name)) - { - // if the control doesn't exist it - // means it is not a real level in the - // project so it is flagged as LocalOnly - m_atlControlsModel->AddScope(name, true); - } - } + // If the scope doesn't exist it means it is not a real + // level in the project so it's flagged as LocalOnly + m_atlControlsModel->AddScope(levelName, true); } } - while (handle = cryPak->FindNext(handle)); - cryPak->FindClose(handle); } + CreateDefaultControls(); } //-------------------------------------------------------------------------------------------// void CAudioControlsLoader::LoadAllLibrariesInFolder(const AZStd::string_view folderPath, const AZStd::string_view level) { - AZStd::string path(folderPath); - if (path.back() != AZ_CORRECT_FILESYSTEM_SEPARATOR) - { - path.append(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING); - } + AZ::IO::FixedMaxPath searchPath{ folderPath }; if (!level.empty()) { - path.append(LoaderStrings::LevelsSubFolder); - path.append(GetSlash()); - path.append(level); - path.append(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING); + searchPath /= LoaderStrings::LevelsSubFolder; + searchPath /= level; } - AZStd::string searchPath = path + "*.xml"; - auto cryPak = gEnv->pCryPak; - AZ::IO::ArchiveFileIterator handle = cryPak->FindFirst(searchPath.c_str()); - if (handle) + auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*.xml"); + + for (auto& file : foundFiles) { - do + Audio::ScopedXmlLoader xmlLoader(file.Native()); + if (xmlLoader.HasError()) { - AZStd::string filename = path + AZStd::string{ static_cast(handle.m_filename) }; - AZ::StringFunc::Path::Normalize(filename); - XmlNodeRef root = GetISystem()->LoadXmlFromFile(filename.c_str()); - if (root) + AZ_Warning("AudioControlsLoader", false, "Unable to load the xml file '%s'", file.c_str()); + continue; + } + + auto xmlRootNode = xmlLoader.GetRootNode(); + if (xmlRootNode && azstricmp(xmlRootNode->name(), Audio::ATLXmlTags::RootNodeTag) == 0) + { + AZ::IO::PathView fileName = file.Filename(); + + AZStd::to_lower(file.Native().begin(), file.Native().end()); + m_loadedFilenames.insert(file.c_str()); + + if (auto nameAttr = xmlRootNode->first_attribute(Audio::ATLXmlTags::ATLNameAttribute, 0, false); nameAttr != nullptr) { - AZStd::string tag = root->getTag(); - if (tag == Audio::ATLXmlTags::RootNodeTag) - { - AZStd::to_lower(filename.begin(), filename.end()); - m_loadedFilenames.insert(filename.c_str()); - AZStd::string file = static_cast(handle.m_filename); - if (root->haveAttr(Audio::ATLXmlTags::ATLNameAttribute)) - { - file = root->getAttr(Audio::ATLXmlTags::ATLNameAttribute); - } - AZ::StringFunc::Path::StripExtension(file); - LoadControlsLibrary(root, folderPath, level, file); - } + fileName = nameAttr->value(); } else { - CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_ERROR, "(Audio Controls Editor) Failed parsing ATL Library '%s'", filename.c_str()); + fileName = fileName.Stem(); } - } while (handle = cryPak->FindNext(handle)); - cryPak->FindClose(handle); + LoadControlsLibrary(xmlRootNode, folderPath, level, fileName.Native()); + } } } @@ -233,74 +208,93 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - void CAudioControlsLoader::LoadControlsLibrary(XmlNodeRef rootNode, [[maybe_unused]] const AZStd::string_view filePath, const AZStd::string_view level, const AZStd::string_view fileName) + void CAudioControlsLoader::LoadControlsLibrary( + const AZ::rapidxml::xml_node* rootNode, + [[maybe_unused]] const AZStd::string_view filePath, + const AZStd::string_view level, + const AZStd::string_view fileName) { QStandardItem* rootFolderItem = AddUniqueFolderPath(m_layoutModel->invisibleRootItem(), QString(fileName.data())); if (rootFolderItem && rootNode) { - const int numControlTypes = rootNode->getChildCount(); - for (int i = 0; i < numControlTypes; ++i) + auto controlTypeNode = rootNode->first_node(); // e.g. "AudioTriggers", "AudioRtpcs", etc + while (controlTypeNode) { - XmlNodeRef node = rootNode->getChild(i); - const int numControls = node->getChildCount(); - for (int j = 0; j < numControls; ++j) + auto controlNode = controlTypeNode->first_node(); // e.g. "ATLTrigger", "ATLRtpc", etc + while (controlNode) { - LoadControl(node->getChild(j), rootFolderItem, level); + LoadControl(controlNode, rootFolderItem, level); + controlNode = controlNode->next_sibling(); } + controlTypeNode = controlTypeNode->next_sibling(); } } } //-------------------------------------------------------------------------------------------// - CATLControl* CAudioControlsLoader::LoadControl(XmlNodeRef node, QStandardItem* folderItem, const AZStd::string_view scope) + CATLControl* CAudioControlsLoader::LoadControl(AZ::rapidxml::xml_node* node, QStandardItem* folderItem, const AZStd::string_view scope) { CATLControl* control = nullptr; - if (node) + + AZStd::string controlPath; + if (auto controlPathAttr = node->first_attribute("path", 0, false); + controlPathAttr != nullptr) { - QStandardItem* parentItem = AddUniqueFolderPath(folderItem, QString(node->getAttr(LoaderStrings::PathAttribute))); - if (parentItem) + controlPath = controlPathAttr->value(); + } + + QStandardItem* parentItem = AddUniqueFolderPath(folderItem, QString(controlPath.c_str())); + if (parentItem) + { + AZStd::string name; + if (auto nameAttr = node->first_attribute(Audio::ATLXmlTags::ATLNameAttribute, 0, false); + nameAttr != nullptr) { - const AZStd::string name = node->getAttr(Audio::ATLXmlTags::ATLNameAttribute); - const EACEControlType controlType = TagToType(node->getTag()); + name = nameAttr->value(); + } - control = m_atlControlsModel->CreateControl(name, controlType); - if (control) + const EACEControlType controlType = TagToType(node->name()); + + control = m_atlControlsModel->CreateControl(name, controlType); + if (control) + { + QStandardItem* item = new QAudioControlItem(QString(control->GetName().c_str()), control); + if (item) { - QStandardItem* item = new QAudioControlItem(QString(control->GetName().c_str()), control); - if (item) - { - parentItem->appendRow(item); - } - - switch (controlType) - { - case eACET_SWITCH: - { - const int numStates = node->getChildCount(); - for (int i = 0; i < numStates; ++i) - { - CATLControl* stateControl = LoadControl(node->getChild(i), item, scope); - if (stateControl) - { - stateControl->SetParent(control); - control->AddChild(stateControl); - } - } - break; - } - case eACET_PRELOAD: - { - LoadPreloadConnections(node, control); - break; - } - default: - { - LoadConnections(node, control); - break; - } - } - control->SetScope(scope); + parentItem->appendRow(item); } + + switch (controlType) + { + case eACET_SWITCH: + { + auto switchStateNode = node->first_node(); + while (switchStateNode) + { + CATLControl* stateControl = LoadControl(switchStateNode, item, scope); + if (stateControl) + { + stateControl->SetParent(control); + control->AddChild(stateControl); + } + + switchStateNode = switchStateNode->next_sibling(); + } + break; + } + case eACET_PRELOAD: + { + LoadPreloadConnections(node, control); + break; + } + default: + { + LoadConnections(node, control); + break; + } + } + + control->SetScope(scope); } } @@ -310,44 +304,37 @@ namespace AudioControls //-------------------------------------------------------------------------------------------// void CAudioControlsLoader::LoadScopes() { - AZStd::string levelsFolderPath; - AZ::StringFunc::Path::Join(Path::GetEditingGameDataFolder().c_str(), LoaderStrings::LevelsSubFolder, levelsFolderPath); - LoadScopesImpl(levelsFolderPath); + AZ::IO::FixedMaxPath levelsFolderPath = AZ::Utils::GetProjectPath(); + levelsFolderPath /= "Levels"; + LoadScopesImpl(levelsFolderPath.Native()); } //-------------------------------------------------------------------------------------------// void CAudioControlsLoader::LoadScopesImpl(const AZStd::string_view levelsFolder) { - AZStd::string search; - AZ::StringFunc::Path::Join(levelsFolder.data(), "*", search, true, false); - auto cryPak = gEnv->pCryPak; - AZ::IO::ArchiveFileIterator handle = cryPak->FindFirst(search.c_str()); - if (handle) + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ::IO::FixedMaxPath searchPath{ levelsFolder }; + + auto foundFiles = Audio::FindFilesInPath(searchPath.Native(), "*"); + for (auto& file : foundFiles) { - do + AZ::IO::PathView filePath{ file }; + AZ::IO::PathView fileName = filePath.Filename(); + if (fileIO->IsDirectory(filePath.Native().data())) { - AZStd::string name = static_cast(handle.m_filename); - if (name != "." && name != ".." && !name.empty()) + LoadScopesImpl((searchPath / fileName).Native()); + } + else + { + AZ::IO::PathView fileExt = filePath.Extension(); + if (fileExt == ".ly" || fileExt == ".cry" || fileExt == ".prefab") { - if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory) - { - AZ::StringFunc::Path::Join(levelsFolder.data(), name.c_str(), search); - LoadScopesImpl(search); - } - else - { - AZStd::string extension; - AZ::StringFunc::Path::GetExtension(name.c_str(), extension, false); - if (extension.compare("cry") == 0 || extension.compare("ly") == 0) - { - AZ::StringFunc::Path::StripExtension(name); - m_atlControlsModel->AddScope(name); - } - } + AZ::IO::PathView fileStem = filePath.Stem(); + // May need to verify that .prefabs are the actual "level" prefab + // i.e. that it matches levels//.prefab + m_atlControlsModel->AddScope(fileStem.Native()); } } - while (handle = cryPak->FindNext(handle)); - cryPak->FindClose(handle); } } @@ -475,100 +462,80 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - void CAudioControlsLoader::LoadConnections(XmlNodeRef rootNode, CATLControl* control) + void CAudioControlsLoader::LoadConnections(AZ::rapidxml::xml_node* rootNode, CATLControl* control) { - if (!rootNode || !control) + if (control && rootNode && m_audioSystemImpl) { - return; - } - - const int numChildren = rootNode->getChildCount(); - for (int i = 0; i < numChildren; ++i) - { - XmlNodeRef node = rootNode->getChild(i); - const AZStd::string tag = node->getTag(); - if (m_audioSystemImpl) + auto childNode = rootNode->first_node(); + while (childNode) { - TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(node, control->GetType()); + TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(childNode, control->GetType()); if (connection) { control->AddConnection(connection); } - control->m_connectionNodes.push_back(SRawConnectionData(node, connection != nullptr)); + + control->m_connectionNodes.push_back(SRawConnectionData(childNode, connection != nullptr)); + + childNode = childNode->next_sibling(); } } } //-------------------------------------------------------------------------------------------// - void CAudioControlsLoader::LoadPreloadConnections(XmlNodeRef node, CATLControl* control) + void CAudioControlsLoader::LoadPreloadConnections(AZ::rapidxml::xml_node* node, CATLControl* control) { - if (!node || !control) + if (!control || !node || !m_audioSystemImpl) { return; } - AZStd::string type = node->getAttr(Audio::ATLXmlTags::ATLTypeAttribute); - if (type.compare(Audio::ATLXmlTags::ATLDataLoadType) == 0) + AZStd::string type; + if (auto typeAttr = node->first_attribute(Audio::ATLXmlTags::ATLTypeAttribute, 0, false); + typeAttr != nullptr) { - control->SetAutoLoad(true); - } - else - { - control->SetAutoLoad(false); + type = typeAttr->value(); } - // Legacy Preload XML parsing... - // Read all the platform definitions for this control - XmlNodeRef platformsGroupNode = node->findChild(Audio::ATLXmlTags::ATLPlatformsTag); - if (platformsGroupNode) + control->SetAutoLoad(type == Audio::ATLXmlTags::ATLDataLoadType); + + auto platformGroupNode = node->first_node(Audio::ATLXmlTags::ATLPlatformsTag, 0, false); + if (platformGroupNode) { + // Legacy preload parsing... // Don't parse the platform groups xml chunk anymore. // Read the connection information for all connected preloads... - const int numChildren = node->getChildCount(); - for (int i = 0; i < numChildren; ++i) + auto configGroupNode = node->first_node(Audio::ATLXmlTags::ATLConfigGroupTag, 0, false); + while (configGroupNode) { - XmlNodeRef groupNode = node->getChild(i); - const AZStd::string tag = groupNode->getTag(); - if (tag.compare(Audio::ATLXmlTags::ATLConfigGroupTag) != 0) - { - continue; - } - - const AZStd::string groupName = groupNode->getAttr(Audio::ATLXmlTags::ATLNameAttribute); - const int numConnections = groupNode->getChildCount(); - for (int j = 0; j < numConnections; ++j) - { - XmlNodeRef connectionNode = groupNode->getChild(j); - if (connectionNode && m_audioSystemImpl) - { - TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType()); - if (connection) - { - control->AddConnection(connection); - } - control->m_connectionNodes.push_back(SRawConnectionData(connectionNode, connection != nullptr)); - } - } - } - } - else - { - // New Preload XML parsing... - const int numChildren = node->getChildCount(); - for (int i = 0; i < numChildren; ++i) - { - XmlNodeRef connectionNode = node->getChild(i); - if (connectionNode && m_audioSystemImpl) + auto connectionNode = configGroupNode->first_node(); + while (connectionNode) { TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType()); if (connection) { control->AddConnection(connection); } - control->m_connectionNodes.push_back(SRawConnectionData(connectionNode, connection != nullptr)); + connectionNode = connectionNode->next_sibling(); } + configGroupNode = configGroupNode->next_sibling(); + } + } + else + { + // New format preload parsing... + auto connectionNode = node->first_node(); + while (connectionNode) + { + TConnectionPtr connection = m_audioSystemImpl->CreateConnectionFromXMLNode(connectionNode, control->GetType()); + if (connection) + { + control->AddConnection(connection); + } + control->m_connectionNodes.push_back(SRawConnectionData(connectionNode, connection != nullptr)); + connectionNode = connectionNode->next_sibling(); } } } @@ -590,11 +557,24 @@ namespace AudioControls { CATLControl* childControl = m_atlControlsModel->CreateControl(stateName, eACET_SWITCH_STATE, parentControl); - XmlNodeRef requestNode = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::ATLSwitchRequestTag); - requestNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, switchName.c_str()); - XmlNodeRef valueNode = requestNode->createNode(Audio::ATLXmlTags::ATLValueTag); - valueNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, stateName.c_str()); - requestNode->addChild(valueNode); + XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator); + AZ::rapidxml::xml_node* requestNode = + xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLSwitchRequestTag)); + + AZ::rapidxml::xml_attribute* switchNameAttr = xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(switchName.c_str())); + + requestNode->append_attribute(switchNameAttr); + + AZ::rapidxml::xml_node* valueNode = + xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLValueTag)); + + AZ::rapidxml::xml_attribute* stateNameAttr = xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(stateName.c_str())); + + valueNode->append_attribute(stateNameAttr); + + requestNode->append_node(valueNode); childControl->m_connectionNodes.push_back(SRawConnectionData(requestNode, false)); return childControl; diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.h b/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.h index bee55289e3..290327b946 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.h +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsLoader.h @@ -10,12 +10,11 @@ #pragma once #include +#include #include #include -#include - #include class QStandardItemModel; @@ -38,11 +37,11 @@ namespace AudioControls private: void LoadAllLibrariesInFolder(const AZStd::string_view folderPath, const AZStd::string_view level); - void LoadControlsLibrary(XmlNodeRef rootNode, const AZStd::string_view filePath, const AZStd::string_view level, const AZStd::string_view fileName); - CATLControl* LoadControl(XmlNodeRef node, QStandardItem* folderItem, const AZStd::string_view scope); + void LoadControlsLibrary(const AZ::rapidxml::xml_node* rootNode, const AZStd::string_view filePath, const AZStd::string_view level, const AZStd::string_view fileName); + CATLControl* LoadControl(AZ::rapidxml::xml_node* node, QStandardItem* folderItem, const AZStd::string_view scope); - void LoadPreloadConnections(XmlNodeRef node, CATLControl* control); - void LoadConnections(XmlNodeRef rootNode, CATLControl* control); + void LoadPreloadConnections(AZ::rapidxml::xml_node* node, CATLControl* control); + void LoadConnections(AZ::rapidxml::xml_node* rootNode, CATLControl* control); void CreateDefaultControls(); QStandardItem* AddControl(CATLControl* control, QStandardItem* folderItem); diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.cpp index bb7409e179..91f9aa5138 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.cpp @@ -9,29 +9,28 @@ #include +#include +#include #include #include +#include +#include #include #include -#include #include #include #include + #include #include #include -#include -#include -#include #include #include #include -using namespace PathUtil; - namespace AudioControls { namespace WriterStrings @@ -80,6 +79,18 @@ namespace AudioControls index = index.sibling(++i, 0); } + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + AZStd::for_each( + m_foundLibraryPaths.begin(), m_foundLibraryPaths.end(), + [fileIO](AZStd::string& libraryPath) -> void + { + if (auto newPathOpt = fileIO->ConvertToAlias(AZ::IO::PathView{ libraryPath }); + newPathOpt.has_value()) + { + libraryPath = newPathOpt.value().Native(); + } + AZStd::to_lower(libraryPath.begin(), libraryPath.end()); + }); // Delete libraries that don't exist anymore from disk FilepathSet librariesToDelete; @@ -103,7 +114,10 @@ namespace AudioControls //-------------------------------------------------------------------------------------------// void CAudioControlsWriter::WriteLibrary(const AZStd::string_view libraryName, QModelIndex root) { - if (root.isValid()) + const char* controlsPath = nullptr; + Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath); + + if (root.isValid() && controlsPath) { TLibraryStorage library; int i = 0; @@ -114,68 +128,63 @@ namespace AudioControls child = root.model()->index(++i, 0, root); } - const char* controlsPath = nullptr; - Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath); - for (auto& libraryPair : library) { - AZStd::string libraryPath; + AZ::IO::FixedMaxPath libraryPath{ controlsPath }; const AZStd::string& scope = libraryPair.first; if (scope.empty()) { // no scope, file at the root level - libraryPath.append(controlsPath); - AZ::StringFunc::Path::Join(libraryPath.c_str(), libraryName.data(), libraryPath); - libraryPath.append(WriterStrings::LibraryExtension); + libraryPath /= libraryName; + libraryPath.ReplaceExtension(WriterStrings::LibraryExtension); } else { // with scope, inside level folder - libraryPath.append(controlsPath); - libraryPath.append(WriterStrings::LevelsSubFolder); - AZ::StringFunc::Path::Join(libraryPath.c_str(), scope.c_str(), libraryPath); - AZ::StringFunc::Path::Join(libraryPath.c_str(), libraryName.data(), libraryPath); - libraryPath.append(WriterStrings::LibraryExtension); + libraryPath /= AZ::IO::FixedMaxPath{ WriterStrings::LevelsSubFolder } / scope / libraryName; + libraryPath.ReplaceExtension(WriterStrings::LibraryExtension); } - // should be able to change this back to GamePathToFullPath once a path normalization bug has been fixed: - AZStd::string fullFilePath; - AZ::StringFunc::Path::Join(Path::GetEditingGameDataFolder().c_str(), libraryPath.c_str(), fullFilePath); - AZStd::to_lower(fullFilePath.begin(), fullFilePath.end()); + AZ::IO::FixedMaxPath fullFilePath = AZ::Utils::GetProjectPath(); + fullFilePath /= libraryPath; m_foundLibraryPaths.insert(fullFilePath.c_str()); const SLibraryScope& libScope = libraryPair.second; if (libScope.m_isDirty) { - XmlNodeRef fileNode = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::RootNodeTag); - fileNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, libraryName.data()); + XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator); + AZ::rapidxml::xml_node* fileNode = + xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(Audio::ATLXmlTags::RootNodeTag)); + + AZ::rapidxml::xml_attribute* nameAttr = xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(libraryName.data())); + + fileNode->append_attribute(nameAttr); for (int ii = 0; ii < eACET_NUM_TYPES; ++ii) { - if (ii != eACET_SWITCH_STATE) // switch_states are written inside the switches + if (libScope.m_nodes[ii] && libScope.m_nodes[ii]->first_node() != nullptr) { - if (libScope.m_nodes[ii]->getChildCount() > 0) - { - fileNode->addChild(libScope.m_nodes[ii]); - } + fileNode->append_node(libScope.m_nodes[ii]); } } - if (QFileInfo::exists(fullFilePath.c_str())) + if (auto fileInfo = QFileInfo(fullFilePath.c_str()); + fileInfo.exists()) { - const DWORD fileAttributes = GetFileAttributes(fullFilePath.c_str()); - if (fileAttributes & FILE_ATTRIBUTE_READONLY) + if (!fileInfo.isWritable()) { - // file is read-only - CheckOutFile(fullFilePath); + // file exists and is read-only + CheckOutFile(fullFilePath.Native()); } - fileNode->saveToFile(fullFilePath.c_str()); + + [[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode); } else { - // save the file, CheckOutFile will add it, since it's new - fileNode->saveToFile(fullFilePath.c_str()); - CheckOutFile(fullFilePath); + // since it's a new file, save the file first, CheckOutFile will add it + [[maybe_unused]] bool writeOk = WriteXmlToFile(fullFilePath.Native(), fileNode); + CheckOutFile(fullFilePath.Native()); } } } @@ -249,14 +258,63 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - void CAudioControlsWriter::WriteControlToXml(XmlNodeRef node, CATLControl* control, const AZStd::string_view path) + bool CAudioControlsWriter::WriteXmlToFile(const AZStd::string_view filepath, AZ::rapidxml::xml_node* rootNode) { + if (!rootNode) + { + return false; + } + + using namespace AZ::IO; + AZStd::string docString; + ByteContainerStream stringStream(&docString); + + AZ::rapidxml::xml_document xmlDoc; + xmlDoc.append_node(rootNode); + + RapidXMLStreamWriter streamWriter(&stringStream); + AZ::rapidxml::print(streamWriter.Iterator(), xmlDoc); + streamWriter.FlushCache(); + + constexpr int openMode = + (SystemFile::SF_OPEN_WRITE_ONLY | SystemFile::SF_OPEN_CREATE | SystemFile::SF_OPEN_CREATE_PATH); + + if (SystemFile fileOut; + fileOut.Open(filepath.data(), openMode)) + { + auto bytesWritten = fileOut.Write(docString.data(), docString.size()); + return (bytesWritten == docString.size()); + } + return false; + } + + //-------------------------------------------------------------------------------------------// + void CAudioControlsWriter::WriteControlToXml(AZ::rapidxml::xml_node* node, CATLControl* control, const AZStd::string_view path) + { + if (!node || !control) + { + return; + } + + XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator); + const EACEControlType type = control->GetType(); - XmlNodeRef childNode = node->createNode(TypeToTag(type).data()); - childNode->setAttr(Audio::ATLXmlTags::ATLNameAttribute, control->GetName().c_str()); + AZStd::string_view typeName = TypeToTag(type); + + AZ::rapidxml::xml_node* childNode = + xmlAlloc.allocate_node(AZ::rapidxml::node_element, xmlAlloc.allocate_string(typeName.data())); + + AZ::rapidxml::xml_attribute* nameAttr = xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLNameAttribute), xmlAlloc.allocate_string(control->GetName().c_str())); + + childNode->append_attribute(nameAttr); + if (!path.empty()) { - childNode->setAttr("path", path.data()); + AZ::rapidxml::xml_attribute* pathAttr = xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLPathAttribute), xmlAlloc.allocate_string(path.data())); + + childNode->append_attribute(pathAttr); } if (type == eACET_SWITCH) @@ -271,7 +329,11 @@ namespace AudioControls { if (control->IsAutoLoad()) { - childNode->setAttr(Audio::ATLXmlTags::ATLTypeAttribute, Audio::ATLXmlTags::ATLDataLoadType); + AZ::rapidxml::xml_attribute* loadAttr = xmlAlloc.allocate_attribute( + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLTypeAttribute), + xmlAlloc.allocate_string(Audio::ATLXmlTags::ATLDataLoadType)); + + childNode->append_attribute(loadAttr); } // New Preloads XML... @@ -282,38 +344,33 @@ namespace AudioControls WriteConnectionsToXml(childNode, control); } - node->addChild(childNode); + node->append_node(childNode); } //-------------------------------------------------------------------------------------------// - void CAudioControlsWriter::WriteConnectionsToXml(XmlNodeRef node, CATLControl* control) + void CAudioControlsWriter::WriteConnectionsToXml(AZ::rapidxml::xml_node* node, CATLControl* control) { - if (control && m_audioSystemImpl) + if (node && control && m_audioSystemImpl) { - TXmlNodeList otherNodes = control->m_connectionNodes; - auto end = AZStd::remove_if(otherNodes.begin(), otherNodes.end(), - [](const SRawConnectionData& node) - { - return node.m_isValid; - } - ); - otherNodes.erase(end, otherNodes.end()); - - for (auto& connectionNode : otherNodes) + for (auto& connectionNode : control->m_connectionNodes) { - node->addChild(connectionNode.m_xmlNode); + if (!connectionNode.m_isValid) + { + auto nodeCopy = SRawConnectionData::DeepCopyNode(connectionNode.m_xmlNode.get()); + node->append_node(nodeCopy.release()); + } } const size_t size = control->ConnectionCount(); for (size_t i = 0; i < size; ++i) { - TConnectionPtr connection = control->GetConnectionAt(i); - if (connection) + if (TConnectionPtr connection = control->GetConnectionAt(i); + connection != nullptr) { - XmlNodeRef childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType()); - if (childNode) + if (auto childNode = m_audioSystemImpl->CreateXMLNodeFromConnection(connection, control->GetType()); + childNode != nullptr) { - node->addChild(childNode); + node->append_node(childNode); control->m_connectionNodes.push_back(SRawConnectionData(childNode, true)); } } @@ -322,24 +379,24 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - void CAudioControlsWriter::CheckOutFile(const AZStd::string& filepath) + void CAudioControlsWriter::CheckOutFile(const AZStd::string_view filepath) { IEditor* editor = GetIEditor(); IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr; if (fileUtil) { - fileUtil->CheckoutFile(filepath.c_str(), nullptr); + fileUtil->CheckoutFile(AZ::IO::FixedMaxPath{ filepath }.c_str(), nullptr); } } //-------------------------------------------------------------------------------------------// - void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string& filepath) + void CAudioControlsWriter::DeleteLibraryFile(const AZStd::string_view filepath) { IEditor* editor = GetIEditor(); IFileUtil* fileUtil = editor ? editor->GetFileUtil() : nullptr; if (fileUtil) { - fileUtil->DeleteFromSourceControl(filepath.c_str(), nullptr); + fileUtil->DeleteFromSourceControl(AZ::IO::FixedMaxPath{ filepath }.c_str(), nullptr); } } diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.h b/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.h index 9e3978b94b..1414f6725d 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.h +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsWriter.h @@ -16,10 +16,9 @@ #include #include #include -#include + #include -#include class QStandardItemModel; @@ -33,15 +32,16 @@ namespace AudioControls { SLibraryScope() { - m_nodes[eACET_TRIGGER] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::TriggersNodeTag); - m_nodes[eACET_RTPC] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::RtpcsNodeTag); - m_nodes[eACET_SWITCH] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::SwitchesNodeTag); + XmlAllocator& xmlAlloc(AudioControls::s_xmlAllocator); + m_nodes[eACET_TRIGGER] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::TriggersNodeTag); + m_nodes[eACET_RTPC] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::RtpcsNodeTag); + m_nodes[eACET_SWITCH] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::SwitchesNodeTag); m_nodes[eACET_SWITCH_STATE] = nullptr; - m_nodes[eACET_ENVIRONMENT] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::EnvironmentsNodeTag); - m_nodes[eACET_PRELOAD] = GetISystem()->CreateXmlNode(Audio::ATLXmlTags::PreloadsNodeTag); + m_nodes[eACET_ENVIRONMENT] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::EnvironmentsNodeTag); + m_nodes[eACET_PRELOAD] = xmlAlloc.allocate_node(AZ::rapidxml::node_element, Audio::ATLXmlTags::PreloadsNodeTag); } - XmlNodeRef m_nodes[eACET_NUM_TYPES]; + AZ::rapidxml::xml_node* m_nodes[eACET_NUM_TYPES]; bool m_isDirty = false; }; @@ -56,12 +56,13 @@ namespace AudioControls private: void WriteLibrary(const AZStd::string_view libraryName, QModelIndex root); void WriteItem(QModelIndex index, const AZStd::string& path, TLibraryStorage& library, bool isParentModified); - void WriteControlToXml(XmlNodeRef node, CATLControl* control, const AZStd::string_view path); - void WriteConnectionsToXml(XmlNodeRef node, CATLControl* control); + void WriteControlToXml(AZ::rapidxml::xml_node* node, CATLControl* control, const AZStd::string_view path); + void WriteConnectionsToXml(AZ::rapidxml::xml_node* node, CATLControl* control); bool IsItemModified(QModelIndex index); - void CheckOutFile(const AZStd::string& filepath); - void DeleteLibraryFile(const AZStd::string& filepath); + bool WriteXmlToFile(const AZStd::string_view filepath, AZ::rapidxml::xml_node* rootNode); + void CheckOutFile(const AZStd::string_view filepath); + void DeleteLibraryFile(const AZStd::string_view filepath); CATLControlsModel* m_atlModel; QStandardItemModel* m_layoutModel; diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp index 90c50ac741..f0c9ebe3d1 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioSystemPanel.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioSystemPanel.cpp index 99f615d1fe..deccfc1364 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioSystemPanel.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioSystemPanel.cpp @@ -12,10 +12,7 @@ #include #include #include -#include -#include #include -#include #include #include diff --git a/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp b/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp index fc146a109d..bee5c1dd51 100644 --- a/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp @@ -14,8 +14,6 @@ #include #include #include -#include -#include //-----------------------------------------------------------------------------------------------// diff --git a/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.h b/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.h index dd6d61d310..70a99fcc0e 100644 --- a/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.h +++ b/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.h @@ -10,9 +10,6 @@ #pragma once #if !defined(Q_MOC_RUN) -#include -#include - #include #endif diff --git a/Gems/AudioSystem/Code/Source/Editor/InspectorPanel.cpp b/Gems/AudioSystem/Code/Source/Editor/InspectorPanel.cpp index a9e2046348..318aea211c 100644 --- a/Gems/AudioSystem/Code/Source/Editor/InspectorPanel.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/InspectorPanel.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include diff --git a/Gems/AudioSystem/Code/Source/Editor/QAudioControlEditorIcons.h b/Gems/AudioSystem/Code/Source/Editor/QAudioControlEditorIcons.h index ce77125443..4d322a49d9 100644 --- a/Gems/AudioSystem/Code/Source/Editor/QAudioControlEditorIcons.h +++ b/Gems/AudioSystem/Code/Source/Editor/QAudioControlEditorIcons.h @@ -31,7 +31,7 @@ namespace AudioControls iconFile = ":/Icons/Switch_Icon.svg"; break; case AudioControls::eACET_SWITCH_STATE: - iconFile = ":/Icons/Property_Icon.svg"; + iconFile = ":/Icons/Property_Icon.png"; break; case AudioControls::eACET_ENVIRONMENT: iconFile = ":/Icons/Environment_Icon.svg"; @@ -41,7 +41,7 @@ namespace AudioControls break; default: // should make a "default"/empty icon... - iconFile = ":/Icons/RTPC_Icon.svg"; + iconFile = ":/Icons/Unassigned.svg"; } QIcon icon(iconFile); diff --git a/Gems/AudioSystem/Code/Source/Editor/QConnectionsWidget.cpp b/Gems/AudioSystem/Code/Source/Editor/QConnectionsWidget.cpp index 8a6fece85c..b65e690f1b 100644 --- a/Gems/AudioSystem/Code/Source/Editor/QConnectionsWidget.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/QConnectionsWidget.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/Gems/AudioSystem/Code/Source/Engine/ATLComponents.cpp b/Gems/AudioSystem/Code/Source/Engine/ATLComponents.cpp index ca9cb214e2..7010012e87 100644 --- a/Gems/AudioSystem/Code/Source/Engine/ATLComponents.cpp +++ b/Gems/AudioSystem/Code/Source/Engine/ATLComponents.cpp @@ -1005,14 +1005,14 @@ namespace Audio AZStd::string searchPath; AZ::StringFunc::Path::Join(m_rootPath.c_str(), folderPath, searchPath); - AZStd::vector foundFiles = Audio::FindFilesInPath(searchPath, "*.xml"); + auto foundFiles = Audio::FindFilesInPath(searchPath, "*.xml"); for (const auto& file : foundFiles) { AZ_Assert(AZ::IO::FileIOBase::GetInstance()->Exists(file.c_str()), "FindFiles found file '%s' but FileIO says it doesn't exist!", file.c_str()); g_audioLogger.Log(eALT_ALWAYS, "Loading Audio Controls Library: '%s'", file.c_str()); - Audio::ScopedXmlLoader xmlFileLoader(file); + Audio::ScopedXmlLoader xmlFileLoader(file.Native()); if (xmlFileLoader.HasError()) { continue; @@ -1053,14 +1053,14 @@ namespace Audio AZStd::string searchPath; AZ::StringFunc::Path::Join(m_rootPath.c_str(), folderPath, searchPath); - AZStd::vector foundFiles = Audio::FindFilesInPath(searchPath, "*.xml"); + auto foundFiles = Audio::FindFilesInPath(searchPath, "*.xml"); for (const auto& file : foundFiles) { AZ_Assert(AZ::IO::FileIOBase::GetInstance()->Exists(file.c_str()), "FindFiles found file '%s' but FileIO says it doesn't exist!", file.c_str()); g_audioLogger.Log(eALT_ALWAYS, "Loading Audio Preloads Library: '%s'", file.c_str()); - Audio::ScopedXmlLoader xmlFileLoader(file); + Audio::ScopedXmlLoader xmlFileLoader(file.Native()); if (xmlFileLoader.HasError()) { continue; diff --git a/Gems/AudioSystem/Code/Source/Engine/AudioSystem.cpp b/Gems/AudioSystem/Code/Source/Engine/AudioSystem.cpp index 0353a8b39d..30d815cbec 100644 --- a/Gems/AudioSystem/Code/Source/Engine/AudioSystem.cpp +++ b/Gems/AudioSystem/Code/Source/Engine/AudioSystem.cpp @@ -23,6 +23,9 @@ namespace Audio extern CAudioLogger g_audioLogger; static constexpr const char AudioControlsBasePath[]{ "libs/gameaudio/" }; + // Save off the threadId of the "Main Thread" that was used to connect EBuses. + AZStd::thread_id g_mainThreadId; + /////////////////////////////////////////////////////////////////////////////////////////////////// // CAudioThread /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -77,6 +80,8 @@ namespace Audio CAudioSystem::CAudioSystem() : m_bSystemInitialized(false) { + g_mainThreadId = AZStd::this_thread::get_id(); + m_apAudioProxies.reserve(Audio::CVars::s_AudioObjectPoolSize); m_apAudioProxiesToBeFreed.reserve(16); m_controlsPath.assign(Audio::AudioControlsBasePath); @@ -99,7 +104,7 @@ namespace Audio { CAudioRequestInternal request(audioRequestData); - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::PushRequest - called from non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::PushRequest - called from non-Main thread!"); AZ_Assert(0 == (request.nFlags & eARF_THREAD_SAFE_PUSH), "AudioSystem::PushRequest - called with flag THREAD_SAFE_PUSH!"); AZ_Assert(0 == (request.nFlags & eARF_EXECUTE_BLOCKING), "AudioSystem::PushRequest - called with flag EXECUTE_BLOCKING!"); @@ -114,7 +119,7 @@ namespace Audio CAudioRequestInternal request(audioRequestData); - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::PushRequestBlocking - called from non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::PushRequestBlocking - called from non-Main thread!"); AZ_Assert(0 != (request.nFlags & eARF_EXECUTE_BLOCKING), "AudioSystem::PushRequestBlocking - called without EXECUTE_BLOCKING flag!"); AZ_Assert(0 == (request.nFlags & eARF_THREAD_SAFE_PUSH), "AudioSystem::PushRequestBlocking - called with THREAD_SAFE_PUSH flag!"); @@ -139,7 +144,7 @@ namespace Audio const EAudioRequestType requestType, const TATLEnumFlagsType specificRequestMask) { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::AddRequestListener - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::AddRequestListener - called from a non-Main thread!"); if (func) { @@ -155,7 +160,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// void CAudioSystem::RemoveRequestListener(AudioRequestCallbackType func, void* const callbackOwner) { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::RemoveRequestListener - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::RemoveRequestListener - called from a non-Main thread!"); SAudioEventListener listener; listener.m_callbackOwner = callbackOwner; @@ -167,7 +172,7 @@ namespace Audio void CAudioSystem::ExternalUpdate() { // Main Thread! - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::ExternalUpdate - called from non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::ExternalUpdate - called from non-Main thread!"); // Notify callbacks on the pending callbacks queue... // These are requests that were completed then queued for callback processing to happen here. @@ -242,7 +247,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// bool CAudioSystem::Initialize() { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::Initialize - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::Initialize - called from a non-Main thread!"); if (!m_bSystemInitialized) { @@ -265,7 +270,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// void CAudioSystem::Release() { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::Release - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::Release - called from a non-Main thread!"); for (auto audioProxy : m_apAudioProxies) { @@ -331,14 +336,14 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// bool CAudioSystem::ReserveAudioListenerID(TAudioObjectID& rAudioObjectID) { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::ReserveAudioListenerID - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::ReserveAudioListenerID - called from a non-Main thread!"); return m_oATL.ReserveAudioListenerID(rAudioObjectID); } /////////////////////////////////////////////////////////////////////////////////////////////////// bool CAudioSystem::ReleaseAudioListenerID(TAudioObjectID const nAudioObjectID) { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::ReleaseAudioListenerID - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::ReleaseAudioListenerID - called from a non-Main thread!"); return m_oATL.ReleaseAudioListenerID(nAudioObjectID); } @@ -385,7 +390,7 @@ namespace Audio void CAudioSystem::RefreshAudioSystem([[maybe_unused]] const char* const levelName) { #if !defined(AUDIO_RELEASE) - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::RefreshAudioSystem - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::RefreshAudioSystem - called from a non-Main thread!"); // Get the controls path and a level-specific preload Id first. // This will be passed with the request so that it doesn't have to lookup this data @@ -409,7 +414,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// IAudioProxy* CAudioSystem::GetFreeAudioProxy() { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::GetFreeAudioProxy - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::GetFreeAudioProxy - called from a non-Main thread!"); CAudioProxy* audioProxy = nullptr; if (!m_apAudioProxies.empty()) @@ -435,7 +440,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// void CAudioSystem::FreeAudioProxy(IAudioProxy* const audioProxyI) { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::FreeAudioProxy - called from a non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::FreeAudioProxy - called from a non-Main thread!"); auto const audioProxy = static_cast(audioProxyI); if (AZStd::find(m_apAudioProxiesToBeFreed.begin(), m_apAudioProxiesToBeFreed.end(), audioProxy) != m_apAudioProxiesToBeFreed.end() || AZStd::find(m_apAudioProxies.begin(), m_apAudioProxies.end(), audioProxy) != m_apAudioProxies.end()) @@ -469,7 +474,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// const char* CAudioSystem::GetAudioControlName([[maybe_unused]] const EAudioControlType controlType, [[maybe_unused]] const TATLIDType atlID) const { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::GetAudioControlName - called from non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::GetAudioControlName - called from non-Main thread!"); const char* sResult = nullptr; #if !defined(AUDIO_RELEASE) @@ -524,7 +529,7 @@ namespace Audio /////////////////////////////////////////////////////////////////////////////////////////////////// const char* CAudioSystem::GetAudioSwitchStateName([[maybe_unused]] const TAudioControlID switchID, [[maybe_unused]] const TAudioSwitchStateID stateID) const { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::GetAudioSwitchStateName - called from non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::GetAudioSwitchStateName - called from non-Main thread!"); const char* sResult = nullptr; #if !defined(AUDIO_RELEASE) @@ -638,7 +643,7 @@ namespace Audio AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::Audio, "Normal Request: %s", request.ToString().c_str()); - AZ_Assert(gEnv->mMainThreadId != CryGetCurrentThreadId(), "AudioSystem::ProcessRequestByPriority - called from Main thread!"); + AZ_Assert(g_mainThreadId != AZStd::this_thread::get_id(), "AudioSystem::ProcessRequestByPriority - called from Main thread!"); if (m_oATL.CanProcessRequests()) { @@ -698,7 +703,7 @@ namespace Audio #if !defined(AUDIO_RELEASE) void CAudioSystem::DrawAudioDebugData() { - AZ_Assert(gEnv->mMainThreadId == CryGetCurrentThreadId(), "AudioSystem::DrawAudioDebugData - called from non-Main thread!"); + AZ_Assert(g_mainThreadId == AZStd::this_thread::get_id(), "AudioSystem::DrawAudioDebugData - called from non-Main thread!"); if (CVars::s_debugDrawOptions.GetRawFlags() != 0) { diff --git a/Gems/AudioSystem/Code/Source/Engine/FileCacheManager.cpp b/Gems/AudioSystem/Code/Source/Engine/FileCacheManager.cpp index d6fe618998..f1a360fd7d 100644 --- a/Gems/AudioSystem/Code/Source/Engine/FileCacheManager.cpp +++ b/Gems/AudioSystem/Code/Source/Engine/FileCacheManager.cpp @@ -9,12 +9,12 @@ #include +#include #include #include #include #include #include -#include #include #include @@ -115,9 +115,9 @@ namespace Audio newAudioFileEntry->m_dataScope = dataScope; AZStd::to_lower(newAudioFileEntry->m_filePath.begin(), newAudioFileEntry->m_filePath.end()); - const size_t fileSize = gEnv->pCryPak->FGetSize(newAudioFileEntry->m_filePath.c_str()); - - if (fileSize > 0) + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + if (AZ::u64 fileSize = 0; + fileIO->Size(newAudioFileEntry->m_filePath.c_str(), fileSize) && fileSize != 0) { newAudioFileEntry->m_fileSize = fileSize; newAudioFileEntry->m_flags.ClearFlags(eAFF_NOTFOUND); @@ -770,9 +770,12 @@ namespace Audio } AZStd::to_lower(audioFileEntry->m_filePath.begin(), audioFileEntry->m_filePath.end()); - audioFileEntry->m_fileSize = gEnv->pCryPak->FGetSize(audioFileEntry->m_filePath.c_str()); + AZ::u64 fileSize = 0; + auto fileIO = AZ::IO::FileIOBase::GetInstance(); + fileIO->Size(audioFileEntry->m_filePath.c_str(), fileSize); + audioFileEntry->m_fileSize = fileSize; - AZ_Assert(audioFileEntry->m_fileSize > 0, "FileCacheManager - UpdateLocalizedFileEntryData expected file size to be greater than zero!"); + AZ_Assert(audioFileEntry->m_fileSize != 0, "FileCacheManager - UpdateLocalizedFileEntryData expected file size to be greater than zero!"); } /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Gems/AudioSystem/Code/Tests/AudioSystemEditorTest.cpp b/Gems/AudioSystem/Code/Tests/AudioSystemEditorTest.cpp index a486e24326..0debe128e2 100644 --- a/Gems/AudioSystem/Code/Tests/AudioSystemEditorTest.cpp +++ b/Gems/AudioSystem/Code/Tests/AudioSystemEditorTest.cpp @@ -10,54 +10,45 @@ #include #include +#include + #include #include -#include -#include -#include - using ::testing::NiceMock; using namespace AudioControls; namespace CustomMocks { - class AudioControlsEditorTest_CryPakMock - : public CryPakMock + class AudioControlsEditorTest_FileIOMock + : public AZ::IO::MockFileIOBase { public: - AZ_TEST_CLASS_ALLOCATOR(AudioControlsEditorTest_CryPakMock) + AZ_TEST_CLASS_ALLOCATOR(AudioControlsEditorTest_FileIOMock); - AudioControlsEditorTest_CryPakMock(const char* levelName) - : m_levelName(levelName) - {} - - AZ::IO::ArchiveFileIterator FindFirst([[maybe_unused]] AZStd::string_view dir, AZ::IO::IArchive::EFileSearchType) override + AudioControlsEditorTest_FileIOMock() { - AZ::IO::FileDesc fileDesc; - fileDesc.nSize = sizeof(AZ::IO::FileDesc); - // Add a filename and file description reference to the TestFindData map to make sure the file iterator is valid - m_findData = new TestFindData(); - m_findData->m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ static_cast(m_findData.get()), m_levelName, fileDesc }); - return m_findData->Fetch(); } - AZ::IO::ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator iter) override + bool IsDirectory([[maybe_unused]] const char* path) override { - return ++iter; + return false; + } + + AZ::IO::Result FindFiles( + [[maybe_unused]] const char* path, + [[maybe_unused]] const char* filter, + AZ::IO::FileIOBase::FindFilesCallbackType callback) override + { + if (callback) + { + callback(m_levelName.c_str()); + return AZ::IO::ResultCode::Success; + } + return AZ::IO::ResultCode::Error; } - // public: for easy resetting... AZStd::string m_levelName; - - // Add an inherited FindData class to control the adding of a mapfile which indicates that a FileIterator is valid - struct TestFindData - : AZ::IO::FindData - { - using AZ::IO::FindData::m_fileSet; - }; - - AZStd::intrusive_ptr m_findData; }; } // namespace CustomMocks @@ -75,10 +66,6 @@ protected: void SetupEnvironment() override { m_allocatorScope.ActivateAllocators(); - - m_stubEnv.pCryPak = nullptr; - m_stubEnv.pFileIO = nullptr; - gEnv = &m_stubEnv; } void TeardownEnvironment() override @@ -87,30 +74,68 @@ protected: } private: - AZ::AllocatorScope m_allocatorScope; - SSystemGlobalEnvironment m_stubEnv; + AZ::AllocatorScope m_allocatorScope; }; AZ_UNIT_TEST_HOOK(new AudioControlsEditorTestEnvironment); -TEST(AudioControlsEditorTest, AudioControlsLoader_LoadScopes_ScopesAreAdded) +class AudioControlsEditorTest + : public ::testing::Test { - ASSERT_TRUE(gEnv != nullptr); - ASSERT_TRUE(gEnv->pCryPak == nullptr); +public: + void SetUp() override + { + // Store and remove the existing fileIO... + m_prevFileIO = AZ::IO::FileIOBase::GetInstance(); + if (m_prevFileIO) + { + AZ::IO::FileIOBase::SetInstance(nullptr); + } - NiceMock m_cryPakMock("ly_extension.ly"); - gEnv->pCryPak = &m_cryPakMock; + // Replace with a new FileIO Mock... + m_fileIO = AZStd::make_unique(); + AZ::IO::FileIOBase::SetInstance(m_fileIO.get()); + } + void TearDown() override + { + // Destroy our LocalFileIO... + m_fileIO.reset(); + + // Replace the old fileIO (set instance to null first)... + AZ::IO::FileIOBase::SetInstance(nullptr); + if (m_prevFileIO) + { + AZ::IO::FileIOBase::SetInstance(m_prevFileIO); + m_prevFileIO = nullptr; + } + } + +protected: + AZ::IO::FileIOBase* m_prevFileIO = nullptr; + AZStd::unique_ptr m_fileIO; +}; + +TEST_F(AudioControlsEditorTest, AudioControlsLoader_LoadScopes_ScopesAreAdded) +{ CATLControlsModel atlModel; CAudioControlsLoader loader(&atlModel, nullptr, nullptr); + m_fileIO->m_levelName = "ly_extension.ly"; loader.LoadScopes(); EXPECT_TRUE(atlModel.ScopeExists("ly_extension")); - m_cryPakMock.m_levelName = "cry_extension.cry"; + m_fileIO->m_levelName = "cry_extension.cry"; loader.LoadScopes(); EXPECT_TRUE(atlModel.ScopeExists("cry_extension")); + m_fileIO->m_levelName = "prefab_extension.prefab"; + loader.LoadScopes(); + EXPECT_TRUE(atlModel.ScopeExists("prefab_extension")); + + m_fileIO->m_levelName = "spawnable_extension.spawnable"; + loader.LoadScopes(); + EXPECT_FALSE(atlModel.ScopeExists("spawnable_extension")); + atlModel.ClearScopes(); - gEnv->pCryPak = nullptr; } diff --git a/Gems/BarrierInput/CMakeLists.txt b/Gems/BarrierInput/CMakeLists.txt new file mode 100644 index 0000000000..de2b439e68 --- /dev/null +++ b/Gems/BarrierInput/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# 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 +# +# + +add_subdirectory(Code) diff --git a/Gems/BarrierInput/Code/CMakeLists.txt b/Gems/BarrierInput/Code/CMakeLists.txt new file mode 100644 index 0000000000..96231cfdbf --- /dev/null +++ b/Gems/BarrierInput/Code/CMakeLists.txt @@ -0,0 +1,45 @@ +# +# 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 +# +# + +ly_add_target( + NAME BarrierInput.Static STATIC + NAMESPACE Gem + FILES_CMAKE + barrierinput_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + AZ::AzCore + AZ::AzFramework + AZ::AtomCore + Gem::Atom_RPI.Public + RUNTIME_DEPENDENCIES + Gem::Atom_RPI.Private +) + +ly_add_target( + NAME BarrierInput ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} + NAMESPACE Gem + FILES_CMAKE + barrierinput_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PRIVATE + Gem::BarrierInput.Static +) + +# Barrier Input is only needed for the client: +ly_create_alias(NAME BarrierInput.Clients NAMESPACE Gem TARGETS Gem::BarrierInput) diff --git a/Gems/BarrierInput/Code/Include/BarrierInput/RawInputNotificationBus_Barrier.h b/Gems/BarrierInput/Code/Include/BarrierInput/RawInputNotificationBus_Barrier.h new file mode 100644 index 0000000000..17a326e806 --- /dev/null +++ b/Gems/BarrierInput/Code/Include/BarrierInput/RawInputNotificationBus_Barrier.h @@ -0,0 +1,102 @@ +/* + * 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 + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + //////////////////////////////////////////////////////////////////////////////////////////////// + //! Barrier keyboard modifier bit mask + enum ModifierMask + { + ModifierMask_None = 0x0000, + ModifierMask_Shift = 0x0001, + ModifierMask_Ctrl = 0x0002, + ModifierMask_AltL = 0x0004, + ModifierMask_Windows = 0x0010, + ModifierMask_AltR = 0x0020, + ModifierMask_CapsLock = 0x1000, + ModifierMask_NumLock = 0x2000, + ModifierMask_ScrollLock = 0x4000, + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //! EBus interface used to listen for raw Barrier input as broadcast by the BarrierClient. + //! + //! It's possible to receive multiple events per button/key per frame, and it's very likely that + //! Barrier input events will not be dispatched from the main thread, so care should be taken to + //! ensure thread safety when implementing event handlers that connect to this Barrier event bus. + //! + //! This EBus is intended primarily for the BarrierClient to send raw input to Barrier devices. + //! Most systems that need to process input should use the generic AzFramework input interfaces, + //! but if necessary it is perfectly valid to connect directly to this EBus for Barrier events. + class RawInputNotificationsBarrier : public AZ::EBusTraits + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + //! EBus Trait: raw input notifications are addressed to a single address + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! EBus Trait: raw input notifications can be handled by multiple listeners + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Default destructor + virtual ~RawInputNotificationsBarrier() = default; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw mouse button down events (assumed to be dispatched from any thread) + //! \param[in] buttonIndex The index of the button that was pressed down + virtual void OnRawMouseButtonDownEvent([[maybe_unused]]uint32_t buttonIndex) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw mouse button up events (assumed to be dispatched from any thread) + //! \param[in] buttonIndex The index of the button that was released up + virtual void OnRawMouseButtonUpEvent([[maybe_unused]]uint32_t buttonIndex) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw mouse movement events (assumed to be dispatched from any thread) + //! \param[in] movementX The x movement of the mouse + //! \param[in] movementY The y movement of the mouse + virtual void OnRawMouseMovementEvent([[maybe_unused]]float movementX, [[maybe_unused]]float movementY) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw mouse position events (assumed to be dispatched from any thread) + //! \param[in] positionX The x position of the mouse + //! \param[in] positionY The y position of the mouse + virtual void OnRawMousePositionEvent([[maybe_unused]]float positionX, [[maybe_unused]]float positionY) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw keyboard key down events (assumed to be dispatched from any thread) + //! \param[in] scanCode The scan code of the key that was pressed down + //! \param[in] activeModifiers The bit mask of currently active modifier keys + virtual void OnRawKeyboardKeyDownEvent([[maybe_unused]]uint32_t scanCode, [[maybe_unused]]ModifierMask activeModifiers) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw keyboard key up events (assumed to be dispatched from any thread) + //! \param[in] scanCode The scan code of the key that was released up + //! \param[in] activeModifiers The bit mask of currently active modifier keys + virtual void OnRawKeyboardKeyUpEvent([[maybe_unused]]uint32_t scanCode, [[maybe_unused]]ModifierMask activeModifiers) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw keyboard key repeat events (assumed to be dispatched from any thread) + //! \param[in] scanCode The scan code of the key that was repeatedly held down + //! \param[in] activeModifiers The bit mask of currently active modifier keys + virtual void OnRawKeyboardKeyRepeatEvent([[maybe_unused]]uint32_t scanCode, [[maybe_unused]]ModifierMask activeModifiers) {} + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Process raw clipboard events (assumed to be dispatched from any thread) + //! \param[in] clipboardContents The contents of the clipboard + virtual void OnRawClipboardEvent([[maybe_unused]]const char* clipboardContents) {} + }; + using RawInputNotificationBusBarrier = AZ::EBus; +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputClient.cpp b/Gems/BarrierInput/Code/Source/BarrierInputClient.cpp new file mode 100644 index 0000000000..b1ca6cb7d7 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputClient.cpp @@ -0,0 +1,398 @@ +/* + * 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 +#include + +#include +#include + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// The majority of this file was resurrected from legacy code, and could use some love, but it works. +namespace BarrierInput +{ + struct Stream + { + explicit Stream(int size) + { + buffer = (AZ::u8*)malloc(size); + end = data = buffer; + bufferSize = size; + packet = nullptr; + } + + ~Stream() + { + free(buffer); + } + + AZ::u8* data; + AZ::u8* end; + AZ::u8* buffer; + AZ::u8* packet; + int bufferSize; + + void Rewind() { data = buffer; } + int GetBufferSize() { return bufferSize; } + + char* GetBuffer() { return (char*)buffer; } + char* GetData() { return (char*)data; } + + void SetLength(int len) { end = data + len; } + int GetLength() { return (int)(end - data); } + + int ReadU32() { int ret = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; data += 4; return ret; } + int ReadU16() { int ret = (data[0] << 8) | data[1]; data += 2; return ret; } + int ReadU8() { int ret = data[0]; data += 1; return ret; } + void Eat(int len) { data += len; } + + void InsertString(const char* str) { int len = strlen(str); memcpy(end, str, len); end += len; } + void InsertU32(int a) { end[0] = a >> 24; end[1] = a >> 16; end[2] = a >> 8; end[3] = a; end += 4; } + void InsertU16(int a) { end[0] = a >> 8; end[1] = a; end += 2; } + void InsertU8(int a) { end[0] = a; end += 1; } + void OpenPacket() { packet = end; end += 4; } + void ClosePacket() { int len = GetLength() - sizeof(AZ::u32); packet[0] = len >> 24; packet[1] = len >> 16; packet[2] = len >> 8; packet[3] = len; packet = NULL; } + }; + + enum ArgType + { + ARG_END = 0, + ARG_UINT8, + ARG_UINT16, + ARG_UINT32 + }; + constexpr int MAX_ARGS = 16; + + typedef bool (*packetCallback)(BarrierClient* pContext, int* pArgs, Stream* pStream, int streamLeft); + + struct Packet + { + const char* pattern; + ArgType args[MAX_ARGS + 1]; + packetCallback callback; + }; + + static bool barrierSendFunc(BarrierClient* pContext, const char* buffer, int length) + { + int ret = AZ::AzSock::Send(pContext->GetSocket(), buffer, length, 0); + return (ret == length) ? true : false; + } + + static bool barrierPacket(BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + Stream stream(256); + stream.OpenPacket(); + stream.InsertString("Barrier"); + stream.InsertU16(1); + stream.InsertU16(4); + stream.InsertU32(pContext->GetClientScreenName().length()); + stream.InsertString(pContext->GetClientScreenName().c_str()); + stream.ClosePacket(); + return barrierSendFunc(pContext, stream.GetBuffer(), stream.GetLength()); + } + + static bool barrierQueryInfo(BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + Stream stream(256); + stream.OpenPacket(); + stream.InsertString("DINF"); + stream.InsertU16(0); + stream.InsertU16(0); + + auto atomViewportRequests = AZ::Interface::Get(); + AZ::RPI::ViewportContextPtr viewportContext = atomViewportRequests->GetDefaultViewportContext(); + if (viewportContext) + { + const AzFramework::WindowSize windowSize = viewportContext->GetViewportSize(); + stream.InsertU16(windowSize.m_width); + stream.InsertU16(windowSize.m_height); + } + else + { + stream.InsertU16(1920); + stream.InsertU16(1080); + } + stream.InsertU16(0); + stream.InsertU16(0); + stream.InsertU16(0); + stream.ClosePacket(); + return barrierSendFunc(pContext, stream.GetBuffer(), stream.GetLength()); + } + + static bool barrierKeepAlive(BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + Stream stream(256); + stream.OpenPacket(); + stream.InsertString("CALV"); + stream.ClosePacket(); + return barrierSendFunc(pContext, stream.GetBuffer(), stream.GetLength()); + } + + static bool barrierEnterScreen([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const float positionX = static_cast(pArgs[0]); + const float positionY = static_cast(pArgs[1]); + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMousePositionEvent, + positionX, + positionY); + return true; + } + + static bool barrierExitScreen([[maybe_unused]]BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + return true; + } + + static bool barrierMouseMove([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const float positionX = static_cast(pArgs[0]); + const float positionY = static_cast(pArgs[1]); + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMousePositionEvent, + positionX, + positionY); + return true; + } + + static bool barrierMouseMoveRelative([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const float movementX = static_cast(pArgs[0]); + const float movementY = static_cast(pArgs[1]); + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMouseMovementEvent, + movementX, + movementY); + return true; + } + + static bool barrierMouseButtonDown([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const uint32_t buttonIndex = pArgs[0]; + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMouseButtonDownEvent, buttonIndex); + return true; + } + + static bool barrierMouseButtonUp([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const uint32_t buttonIndex = pArgs[0]; + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawMouseButtonUpEvent, buttonIndex); + return true; + } + + static bool barrierKeyboardDown([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const uint32_t scanCode = pArgs[2]; + const ModifierMask activeModifiers = static_cast(pArgs[1]); + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawKeyboardKeyDownEvent, scanCode, activeModifiers); + return true; + } + + static bool barrierKeyboardUp([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const uint32_t scanCode = pArgs[2]; + const ModifierMask activeModifiers = static_cast(pArgs[1]); + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawKeyboardKeyUpEvent, scanCode, activeModifiers); + return true; + } + + static bool barrierKeyboardRepeat([[maybe_unused]]BarrierClient* pContext, int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + const uint32_t scanCode = pArgs[2]; + const ModifierMask activeModifiers = static_cast(pArgs[1]); + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawKeyboardKeyRepeatEvent, scanCode, activeModifiers); + return true; + } + + static bool barrierClipboard([[maybe_unused]]BarrierClient* pContext, int* pArgs, Stream* pStream, [[maybe_unused]]int streamLeft) + { + for (int i = 0; i < pArgs[3]; i++) + { + int format = pStream->ReadU32(); + int size = pStream->ReadU32(); + if (format == 0) // Is text + { + char* clipboardContents = new char[size]; + memcpy(clipboardContents, pStream->GetData(), size); + clipboardContents[size] = '\0'; + RawInputNotificationBusBarrier::Broadcast(&RawInputNotificationsBarrier::OnRawClipboardEvent, clipboardContents); + delete[] clipboardContents; + } + pStream->Eat(size); + } + return true; + } + + static bool barrierBye([[maybe_unused]]BarrierClient* pContext, [[maybe_unused]]int* pArgs, [[maybe_unused]]Stream* pStream, [[maybe_unused]]int streamLeft) + { + AZLOG_INFO("BarrierClient: Server said bye. Disconnecting\n"); + return false; + } + + static Packet s_packets[] = { + { "Barrier", { ARG_UINT16, ARG_UINT16 }, barrierPacket }, + { "QINF", {}, barrierQueryInfo }, + { "CALV", {}, barrierKeepAlive }, + { "CINN", { ARG_UINT16, ARG_UINT16, ARG_UINT32, ARG_UINT16 }, barrierEnterScreen }, + { "COUT", { }, barrierExitScreen }, + { "CBYE", { }, barrierBye }, + { "DMMV", { ARG_UINT16, ARG_UINT16 }, barrierMouseMove }, + { "DMRM", { ARG_UINT16, ARG_UINT16 }, barrierMouseMoveRelative }, + { "DMDN", { ARG_UINT8 }, barrierMouseButtonDown }, + { "DMUP", { ARG_UINT8 }, barrierMouseButtonUp }, + { "DKDN", { ARG_UINT16, ARG_UINT16, ARG_UINT16 }, barrierKeyboardDown }, + { "DKUP", { ARG_UINT16, ARG_UINT16, ARG_UINT16 }, barrierKeyboardUp }, + { "DKRP", { ARG_UINT16, ARG_UINT16, ARG_UINT16, ARG_UINT16 }, barrierKeyboardRepeat }, + { "DCLP", { ARG_UINT8, ARG_UINT32, ARG_UINT32, ARG_UINT32 }, barrierClipboard } + }; + + static bool ProcessPackets(BarrierClient* pContext, Stream& stream) + { + while (stream.data < stream.end) + { + const int packetLength = stream.ReadU32(); + const int streamLength = stream.GetLength(); + const char* packetStart = stream.GetData(); + if (packetLength > streamLength) + { + AZLOG_INFO("BarrierClient: Packet overruns buffer (Packet Length: %d Buffer Length: %d), probably lots of data on clipboard?\n", packetLength, streamLength); + return false; + } + + const int numPackets = sizeof(s_packets) / sizeof(s_packets[0]); + int i; + for (i = 0; i < numPackets; ++i) + { + const int len = strlen(s_packets[i].pattern); + if (packetLength >= len && memcmp(stream.GetData(), s_packets[i].pattern, len) == 0) + { + bool bDone = false; + int numArgs = 0; + int args[MAX_ARGS]; + stream.Eat(len); + while (!bDone) + { + switch (s_packets[i].args[numArgs]) + { + case ARG_UINT8: + args[numArgs++] = stream.ReadU8(); + break; + case ARG_UINT16: + args[numArgs++] = stream.ReadU16(); + break; + case ARG_UINT32: + args[numArgs++] = stream.ReadU32(); + break; + case ARG_END: + bDone = true; + break; + } + } + if (s_packets[i].callback) + { + if (!s_packets[i].callback(pContext, args, &stream, packetLength - (int)(stream.GetData() - packetStart))) + { + return false; + } + } + stream.Eat(packetLength - (int)(stream.GetData() - packetStart)); + break; + } + } + if (i == numPackets) + { + stream.Eat(packetLength); + } + } + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + BarrierClient::BarrierClient(const char* clientScreenName, const char* serverHostName, AZ::u32 connectionPort) + : m_clientScreenName(clientScreenName) + , m_serverHostName(serverHostName) + , m_connectionPort(connectionPort) + , m_socket(AZ_SOCKET_INVALID) + , m_threadHandle() + , m_threadQuit(false) + { + AZStd::thread_desc threadDesc; + threadDesc.m_name = "BarrierInputClientThread"; + m_threadHandle = AZStd::thread(AZStd::bind(&BarrierClient::Run, this), &threadDesc); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + BarrierClient::~BarrierClient() + { + if (AZ::AzSock::IsAzSocketValid(m_socket)) + { + AZ::AzSock::CloseSocket(m_socket); + } + m_threadQuit = true; + m_threadHandle.join(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierClient::Run() + { + Stream stream(4 * 1024); + bool connected = false; + while (!m_threadQuit) + { + if (!connected) + { + connected = ConnectToServer(); + continue; + } + + const int lengthReceived = AZ::AzSock::Recv(m_socket, stream.GetBuffer(), stream.GetBufferSize(), 0); + if (lengthReceived <= 0) + { + AZLOG_INFO("BarrierClient: Receive failed, reconnecting.\n"); + connected = false; + continue; + } + + stream.Rewind(); + stream.SetLength(lengthReceived); + if (!ProcessPackets(this, stream)) + { + AZLOG_INFO("BarrierClient: Packet processing failed, reconnecting.\n"); + connected = false; + continue; + } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + bool BarrierClient::ConnectToServer() + { + if (AZ::AzSock::IsAzSocketValid(m_socket)) + { + AZ::AzSock::CloseSocket(m_socket); + } + + m_socket = AZ::AzSock::Socket(); + if (AZ::AzSock::IsAzSocketValid(m_socket)) + { + AZ::AzSock::AzSocketAddress socketAddress; + if (socketAddress.SetAddress(m_serverHostName.c_str(), m_connectionPort)) + { + const int result = AZ::AzSock::Connect(m_socket, socketAddress); + if (!AZ::AzSock::SocketErrorOccured(result)) + { + return true; + } + } + AZ::AzSock::CloseSocket(m_socket); + m_socket = AZ_SOCKET_INVALID; + } + + return false; + } +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputClient.h b/Gems/BarrierInput/Code/Source/BarrierInputClient.h new file mode 100644 index 0000000000..ba37cb1008 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputClient.h @@ -0,0 +1,80 @@ +/* + * 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 +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + //////////////////////////////////////////////////////////////////////////////////////////////// + //! Barrier client that manages a connection with a Barrier server. + class BarrierClient + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + static constexpr AZ::u32 DEFAULT_BARRIER_CONNECTION_PORT_NUMBER = 24800; + + //////////////////////////////////////////////////////////////////////////////////////////// + // Allocator + AZ_CLASS_ALLOCATOR(BarrierClient, AZ::SystemAllocator, 0); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Constructor + //! \param[in] clientScreenName Name of the Barrier client screen this class implements + //! \param[in] serverHostName Name of the Barrier server host this client connects to + //! \param[in] connectionPort Port number over which to connect to the Barrier server + BarrierClient(const char* clientScreenName, + const char* serverHostName, + AZ::u32 connectionPort = DEFAULT_BARRIER_CONNECTION_PORT_NUMBER); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Destructor + ~BarrierClient(); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Access to the Barrier client screen this class implements + //! \return Name of the Barrier client screen this class implements + const AZStd::string& GetClientScreenName() const { return m_clientScreenName; } + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Access to the Barrier server host this client connects to + //! \return Name of the Barrier server host this client connects to + const AZStd::string& GetServerHostName() const { return m_serverHostName; } + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Access to the socket the Barrier client is communicating over + //! \return The socket the Barrier client is communicating over + const AZSOCKET& GetSocket() const { return m_socket; } + + protected: + //////////////////////////////////////////////////////////////////////////////////////////// + //! The client connection loop that runs in it's own thread + void Run(); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Try to connect to the Barrier server + //! \return True if we're connected to the Barrier server, false otherwise + bool ConnectToServer(); + + private: + //////////////////////////////////////////////////////////////////////////////////////////// + // Variables + AZStd::string m_clientScreenName; + AZStd::string m_serverHostName; + AZ::u32 m_connectionPort; + AZStd::thread m_threadHandle; + AZStd::atomic_bool m_threadQuit; + AZSOCKET m_socket; + }; +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.cpp b/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.cpp new file mode 100644 index 0000000000..5e486df680 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.cpp @@ -0,0 +1,260 @@ +/* + * 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 + +#include + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + using namespace AzFramework; + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceKeyboard::Implementation* InputDeviceKeyboardBarrier::Create(InputDeviceKeyboard& inputDevice) + { + return aznew InputDeviceKeyboardBarrier(inputDevice); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceKeyboardBarrier::InputDeviceKeyboardBarrier(InputDeviceKeyboard& inputDevice) + : InputDeviceKeyboard::Implementation(inputDevice) + , m_threadAwareRawKeyEventQueuesById() + , m_threadAwareRawKeyEventQueuesByIdMutex() + , m_threadAwareRawTextEventQueue() + , m_threadAwareRawTextEventQueueMutex() + , m_hasTextEntryStarted(false) + { + RawInputNotificationBusBarrier::Handler::BusConnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceKeyboardBarrier::~InputDeviceKeyboardBarrier() + { + RawInputNotificationBusBarrier::Handler::BusDisconnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + bool InputDeviceKeyboardBarrier::IsConnected() const + { + // We could check the validity of the socket connection to the Barrier server + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + bool InputDeviceKeyboardBarrier::HasTextEntryStarted() const + { + return m_hasTextEntryStarted; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::TextEntryStart(const InputTextEntryRequests::VirtualKeyboardOptions&) + { + m_hasTextEntryStarted = true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::TextEntryStop() + { + m_hasTextEntryStarted = false; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::TickInputDevice() + { + { + // Queue all key events that were received in the other thread + AZStd::scoped_lock lock(m_threadAwareRawKeyEventQueuesByIdMutex); + for (const auto& keyEventQueuesById : m_threadAwareRawKeyEventQueuesById) + { + const InputChannelId& inputChannelId = keyEventQueuesById.first; + for (bool rawKeyState : keyEventQueuesById.second) + { + QueueRawKeyEvent(inputChannelId, rawKeyState); + } + } + m_threadAwareRawKeyEventQueuesById.clear(); + } + + { + // Queue all text events that were received in the other thread + AZStd::scoped_lock lock(m_threadAwareRawTextEventQueueMutex); + for (const AZStd::string& rawTextEvent : m_threadAwareRawTextEventQueue) + { + #if !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT) + if (!m_hasTextEntryStarted) + { + continue; + } + #endif // !defined(ALWAYS_DISPATCH_KEYBOARD_TEXT_INPUT) + QueueRawTextEvent(rawTextEvent); + } + m_threadAwareRawTextEventQueue.clear(); + } + + // Process raw event queues once each frame + ProcessRawEventQueues(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::OnRawKeyboardKeyDownEvent(uint32_t scanCode, + ModifierMask activeModifiers) + { + // Queue key events and text events + ThreadSafeQueueRawKeyEvent(scanCode, true); + if (char asciiChar = TranslateRawKeyEventToASCIIChar(scanCode, activeModifiers)) + { + const AZStd::string text(1, asciiChar); + ThreadSafeQueueRawTextEvent(text); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::OnRawKeyboardKeyUpEvent(uint32_t scanCode, + [[maybe_unused]]ModifierMask activeModifiers) + { + // Queue key events, not text events + ThreadSafeQueueRawKeyEvent(scanCode, false); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::OnRawKeyboardKeyRepeatEvent(uint32_t scanCode, + ModifierMask activeModifiers) + { + // Don't queue key events, only text events + if (char asciiChar = TranslateRawKeyEventToASCIIChar(scanCode, activeModifiers)) + { + const AZStd::string text(1, asciiChar); + ThreadSafeQueueRawTextEvent(text); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::ThreadSafeQueueRawKeyEvent(uint32_t scanCode, bool rawKeyState) + { + // From observation, Barrier scan codes in the: + // - Range 0x0-0x7F (0-127) correspond to windows scan codes without the extended bit set + // - Range 0x100-0x17F (256-383) correspond to windows scan codes with the extended bit set + const InputChannelId* inputChannelId = nullptr; + if (scanCode < InputChannelIdByScanCodeTable.size()) + { + inputChannelId = InputChannelIdByScanCodeTable[scanCode]; + } + else if (0 <= (scanCode - 0x100) && scanCode < InputChannelIdByScanCodeWithExtendedPrefixTable.size()) + { + inputChannelId = InputChannelIdByScanCodeWithExtendedPrefixTable[scanCode - 0x100]; + } + + if (inputChannelId) + { + AZStd::scoped_lock lock(m_threadAwareRawKeyEventQueuesByIdMutex); + m_threadAwareRawKeyEventQueuesById[*inputChannelId].push_back(rawKeyState); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceKeyboardBarrier::ThreadSafeQueueRawTextEvent(const AZStd::string& textUTF8) + { + AZStd::scoped_lock lock(m_threadAwareRawTextEventQueueMutex); + m_threadAwareRawTextEventQueue.push_back(textUTF8); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + char InputDeviceKeyboardBarrier::TranslateRawKeyEventToASCIIChar(uint32_t scanCode, + ModifierMask activeModifiers) + { + // Map ASCII character pairs keyed by their keyboard scan code, assuming an ANSI mechanical + // keyboard layout with a standard QWERTY key mapping. The first element of the pair is the + // character that should be produced if the key is pressed while no shift or caps modifiers + // are active, while the second element is the character that should be produced if the key + // is pressed while a shift or caps modifier is active. Required because Barrier only sends + // raw key events, not translated text input. While we would ideally support the full range + // of UTF-8 text input, that is beyond the scope of this debug/development only class. Note + // that this function assumes an ANSI mechanical keyboard layout with a standard QWERTY key + // mapping, and will not produce correct results if used with other key layouts or mappings. + static const AZStd::fixed_unordered_map, 16, 64> ScanCodeToASCIICharMap = + { + { 2, { '1', '!' } }, + { 3, { '2', '@' } }, + { 4, { '3', '#' } }, + { 5, { '4', '$' } }, + { 6, { '5', '%' } }, + { 7, { '6', '^' } }, + { 8, { '7', '&' } }, + { 9, { '8', '*' } }, + { 10, { '9', '(' } }, + { 11, { '0', ')' } }, + { 12, { '-', '_' } }, + { 13, { '=', '+' } }, + { 15, { '\t', '\t' } }, + { 16, { 'q', 'Q' } }, + { 17, { 'w', 'W' } }, + { 18, { 'e', 'E' } }, + { 19, { 'r', 'R' } }, + { 20, { 't', 'T' } }, + { 21, { 'y', 'Y' } }, + { 22, { 'u', 'U' } }, + { 23, { 'i', 'I' } }, + { 24, { 'o', 'O' } }, + { 25, { 'p', 'P' } }, + { 26, { '[', '{' } }, + { 27, { ']', '}' } }, + { 30, { 'a', 'A' } }, + { 31, { 's', 'S' } }, + { 32, { 'd', 'D' } }, + { 33, { 'f', 'F' } }, + { 34, { 'g', 'G' } }, + { 35, { 'h', 'H' } }, + { 36, { 'j', 'J' } }, + { 37, { 'k', 'K' } }, + { 38, { 'l', 'L' } }, + { 39, { ';', ':' } }, + { 40, { '\'', '"' } }, + { 41, { '`', '~' } }, + { 43, { '\\', '|' } }, + { 44, { 'z', 'Z' } }, + { 45, { 'x', 'X' } }, + { 46, { 'c', 'C' } }, + { 47, { 'v', 'V' } }, + { 48, { 'b', 'B' } }, + { 49, { 'n', 'N' } }, + { 50, { 'm', 'M' } }, + { 51, { ',', '<' } }, + { 52, { '.', '>' } }, + { 53, { '/', '?' } }, + { 55, { '*', '*' } }, + { 57, { ' ', ' ' } }, + { 71, { '7', '7' } }, + { 72, { '8', '8' } }, + { 73, { '9', '9' } }, + { 74, { '-', '-' } }, + { 75, { '4', '4' } }, + { 76, { '5', '5' } }, + { 77, { '6', '6' } }, + { 78, { '+', '+' } }, + { 79, { '1', '1' } }, + { 80, { '2', '2' } }, + { 81, { '3', '3' } }, + { 82, { '0', '0' } }, + { 83, { '.', '.' } }, + { 309, { '/', '/' } } + }; + + const auto& it = ScanCodeToASCIICharMap.find(scanCode); + if (it == ScanCodeToASCIICharMap.end()) + { + return '\0'; + } + + const bool shiftOrCapsLockActive = (activeModifiers & ModifierMask_Shift) || + (activeModifiers & ModifierMask_CapsLock); + return shiftOrCapsLockActive ? it->second.second : it->second.first; + } +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.h b/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.h new file mode 100644 index 0000000000..d7321faaec --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputKeyboard.h @@ -0,0 +1,109 @@ +/* + * 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 + +#include + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + //////////////////////////////////////////////////////////////////////////////////////////////// + //! Barrier specific implementation for keyboard input devices. + class InputDeviceKeyboardBarrier : public AzFramework::InputDeviceKeyboard::Implementation + , public RawInputNotificationBusBarrier::Handler + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + // Allocator + AZ_CLASS_ALLOCATOR(InputDeviceKeyboardBarrier, AZ::SystemAllocator, 0); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Custom factory create function + //! \param[in] inputDevice Reference to the input device being implemented + static Implementation* Create(AzFramework::InputDeviceKeyboard& inputDevice); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Constructor + //! \param[in] inputDevice Reference to the input device being implemented + InputDeviceKeyboardBarrier(AzFramework::InputDeviceKeyboard& inputDevice); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Destructor + ~InputDeviceKeyboardBarrier() override; + + private: + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceKeyboard::Implementation::IsConnected + bool IsConnected() const override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceKeyboard::Implementation::HasTextEntryStarted + bool HasTextEntryStarted() const override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStart + void TextEntryStart(const AzFramework::InputTextEntryRequests::VirtualKeyboardOptions& options) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceKeyboard::Implementation::TextEntryStop + void TextEntryStop() override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceKeyboard::Implementation::TickInputDevice + void TickInputDevice() override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawKeyboardKeyDownEvent + virtual void OnRawKeyboardKeyDownEvent(uint32_t scanCode, ModifierMask activeModifiers); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawKeyboardKeyUpEvent + virtual void OnRawKeyboardKeyUpEvent(uint32_t scanCode, ModifierMask activeModifiers); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawKeyboardKeyRepeatEvent + virtual void OnRawKeyboardKeyRepeatEvent(uint32_t scanCode, ModifierMask activeModifiers); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Thread safe method to queue raw key events to be processed in the main thread update + //! \param[in] scanCode The scan code of the key + //! \param[in] rawKeyState The raw key state + void ThreadSafeQueueRawKeyEvent(uint32_t scanCode, bool rawKeyState); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Thread safe method to queue raw text events to be processed in the main thread update + //! \param[in] textUTF8 The text to queue (encoded using UTF-8) + void ThreadSafeQueueRawTextEvent(const AZStd::string& textUTF8); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Translate a key event to an ASCII character. This is required because Barrier only sends + //! raw key events, not translated text input. While we would ideally support the full range + //! of UTF-8 text input, that is beyond the scope of this debug/development only class. Note + //! that this function assumes an ANSI mechanical keyboard layout with a standard QWERTY key + //! mapping, and will not produce correct results if used with other key layouts or mappings. + //! \param[in] scanCode The scan code of the key + //! \param[in] activeModifiers The bit mask of currently active modifier keys + //! \return If the scan code and active modifiers produce a valid ASCII character + char TranslateRawKeyEventToASCIIChar(uint32_t scanCode, ModifierMask activeModifiers); + + //////////////////////////////////////////////////////////////////////////////////////////// + // Variables + RawKeyEventQueueByIdMap m_threadAwareRawKeyEventQueuesById; + AZStd::mutex m_threadAwareRawKeyEventQueuesByIdMutex; + + AZStd::vector m_threadAwareRawTextEventQueue; + AZStd::mutex m_threadAwareRawTextEventQueueMutex; + + bool m_hasTextEntryStarted; + }; +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputModule.cpp b/Gems/BarrierInput/Code/Source/BarrierInputModule.cpp new file mode 100644 index 0000000000..716dfb4704 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputModule.cpp @@ -0,0 +1,47 @@ +/* + * 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 +#include + +#include + +namespace BarrierInput +{ + class BarrierInputModule + : public AZ::Module + { + public: + AZ_RTTI(BarrierInputModule, "{C338BB3B-EA09-4FC8-AD49-840F8A22837F}", AZ::Module); + AZ_CLASS_ALLOCATOR(BarrierInputModule, AZ::SystemAllocator, 0); + + BarrierInputModule() + : AZ::Module() + { + // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. + m_descriptors.insert(m_descriptors.end(), { + BarrierInputSystemComponent::CreateDescriptor(), + }); + } + + /** + * Add required SystemComponents to the SystemEntity. + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList{ + azrtti_typeid(), + }; + } + }; +} + +// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM +// The first parameter should be GemName_GemIdLower +// The second should be the fully qualified name of the class above +AZ_DECLARE_MODULE_CLASS(Gem_BarrierInput, BarrierInput::BarrierInputModule) diff --git a/Gems/BarrierInput/Code/Source/BarrierInputMouse.cpp b/Gems/BarrierInput/Code/Source/BarrierInputMouse.cpp new file mode 100644 index 0000000000..b0053f5ba5 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputMouse.cpp @@ -0,0 +1,195 @@ +/* + * 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 + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + using namespace AzFramework; + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceMouse::Implementation* InputDeviceMouseBarrier::Create(InputDeviceMouse& inputDevice) + { + return aznew InputDeviceMouseBarrier(inputDevice); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceMouseBarrier::InputDeviceMouseBarrier(InputDeviceMouse& inputDevice) + : InputDeviceMouse::Implementation(inputDevice) + , m_systemCursorState(SystemCursorState::Unknown) + , m_systemCursorPositionNormalized(0.5f, 0.5f) + , m_threadAwareRawButtonEventQueuesById() + , m_threadAwareRawButtonEventQueuesByIdMutex() + , m_threadAwareRawMovementEventQueuesById() + , m_threadAwareRawMovementEventQueuesByIdMutex() + , m_threadAwareSystemCursorPosition(0.0f, 0.0f) + , m_threadAwareSystemCursorPositionMutex() + { + RawInputNotificationBusBarrier::Handler::BusConnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceMouseBarrier::~InputDeviceMouseBarrier() + { + RawInputNotificationBusBarrier::Handler::BusDisconnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + bool InputDeviceMouseBarrier::IsConnected() const + { + // We could check the validity of the socket connection to the Barrier server + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::SetSystemCursorState(SystemCursorState systemCursorState) + { + // This doesn't apply when using Barrier, but we'll store it so it can be queried + m_systemCursorState = systemCursorState; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + SystemCursorState InputDeviceMouseBarrier::GetSystemCursorState() const + { + return m_systemCursorState; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) + { + // This will simply get overridden by the next call to OnRawMousePositionEvent, but there's + // not much we can do about it, and Barrier mouse input is only for debug purposes anyway. + m_systemCursorPositionNormalized = positionNormalized; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + AZ::Vector2 InputDeviceMouseBarrier::GetSystemCursorPositionNormalized() const + { + return m_systemCursorPositionNormalized; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::TickInputDevice() + { + { + // Queue all mouse button events that were received in the other thread + AZStd::scoped_lock lock(m_threadAwareRawButtonEventQueuesByIdMutex); + for (const auto& buttonEventQueuesById : m_threadAwareRawButtonEventQueuesById) + { + const InputChannelId& inputChannelId = buttonEventQueuesById.first; + for (bool rawButtonState : buttonEventQueuesById.second) + { + QueueRawButtonEvent(inputChannelId, rawButtonState); + } + } + m_threadAwareRawButtonEventQueuesById.clear(); + } + + bool receivedRawMovementEvents = false; + { + // Queue all mouse movement events that were received in the other thread + AZStd::scoped_lock lock(m_threadAwareRawMovementEventQueuesByIdMutex); + for (const auto& movementEventQueuesById : m_threadAwareRawMovementEventQueuesById) + { + const InputChannelId& inputChannelId = movementEventQueuesById.first; + for (float rawMovementDelta : movementEventQueuesById.second) + { + QueueRawMovementEvent(inputChannelId, rawMovementDelta); + receivedRawMovementEvents = true; + } + } + m_threadAwareRawMovementEventQueuesById.clear(); + } + + // Update the system cursor position + auto atomViewportRequests = AZ::Interface::Get(); + AZ::RPI::ViewportContextPtr viewportContext = atomViewportRequests->GetDefaultViewportContext(); + if (viewportContext) + { + const AzFramework::WindowSize windowSize = viewportContext->GetViewportSize(); + const float windowWidth = static_cast(windowSize.m_width); + const float windowHeight = static_cast(windowSize.m_height); + const AZ::Vector2 oldSystemCursorPositionNormalized = m_systemCursorPositionNormalized; + + AZStd::scoped_lock lock(m_threadAwareSystemCursorPositionMutex); + { + const AZ::Vector2 normalizedPosition(m_threadAwareSystemCursorPosition.GetX() / windowWidth, + m_threadAwareSystemCursorPosition.GetY() / windowHeight); + m_systemCursorPositionNormalized = normalizedPosition; + } + + // In theory Barrier should send relative mouse movement events as 'DMRM' messages, which are + // forwarded to InputDeviceMouseBarrier::OnRawMouseMovementEvent, but this does not appear to + // be happening, so if we didn't receive any relative mouse movement events this frame we can + // just approximate the movement ourselves. Unlike other mouse implementations where movement + // events are sent 'raw' before any operating system ballistics/smoothing is applied, Barrier + // seems to calculate relative mouse movement events by taking the delta between the previous + // system cursor position and the current one, so we should obtain the same result regardless. + if (!receivedRawMovementEvents) + { + const AZ::Vector2 mouseMovementDelta = m_systemCursorPositionNormalized - oldSystemCursorPositionNormalized; + QueueRawMovementEvent(InputDeviceMouse::Movement::X, mouseMovementDelta.GetX() * windowWidth); + QueueRawMovementEvent(InputDeviceMouse::Movement::Y, mouseMovementDelta.GetY() * windowHeight); + } + } + + // Process raw event queues once each frame + ProcessRawEventQueues(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::OnRawMouseButtonDownEvent(uint32_t buttonIndex) + { + ThreadSafeQueueRawButtonEvent(buttonIndex, true); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::OnRawMouseButtonUpEvent(uint32_t buttonIndex) + { + ThreadSafeQueueRawButtonEvent(buttonIndex, false); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::OnRawMouseMovementEvent(float movementX, float movementY) + { + AZStd::scoped_lock lock(m_threadAwareRawMovementEventQueuesByIdMutex); + m_threadAwareRawMovementEventQueuesById[InputDeviceMouse::Movement::X].push_back(movementX); + m_threadAwareRawMovementEventQueuesById[InputDeviceMouse::Movement::Y].push_back(movementY); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::OnRawMousePositionEvent(float positionX, + float positionY) + { + AZStd::scoped_lock lock(m_threadAwareSystemCursorPositionMutex); + m_threadAwareSystemCursorPosition = AZ::Vector2(positionX, positionY); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void InputDeviceMouseBarrier::ThreadSafeQueueRawButtonEvent(uint32_t buttonIndex, + bool rawButtonState) + { + const InputChannelId* inputChannelId = nullptr; + switch (buttonIndex) + { + case 1: { inputChannelId = &InputDeviceMouse::Button::Left; } break; + case 2: { inputChannelId = &InputDeviceMouse::Button::Middle; } break; + case 3: { inputChannelId = &InputDeviceMouse::Button::Right; } break; + } + + if (inputChannelId) + { + AZStd::scoped_lock lock(m_threadAwareRawButtonEventQueuesByIdMutex); + m_threadAwareRawButtonEventQueuesById[*inputChannelId].push_back(rawButtonState); + } + } +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputMouse.h b/Gems/BarrierInput/Code/Source/BarrierInputMouse.h new file mode 100644 index 0000000000..0d160c8769 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputMouse.h @@ -0,0 +1,105 @@ +/* + * 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 + +#include + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + //////////////////////////////////////////////////////////////////////////////////////////////// + //! Barrier specific implementation for mouse input devices. + class InputDeviceMouseBarrier : public AzFramework::InputDeviceMouse::Implementation + , public RawInputNotificationBusBarrier::Handler + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + // Allocator + AZ_CLASS_ALLOCATOR(InputDeviceMouseBarrier, AZ::SystemAllocator, 0); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Custom factory create function + //! \param[in] inputDevice Reference to the input device being implemented + static Implementation* Create(AzFramework::InputDeviceMouse& inputDevice); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Constructor + //! \param[in] inputDevice Reference to the input device being implemented + InputDeviceMouseBarrier(AzFramework::InputDeviceMouse& inputDevice); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Destructor + ~InputDeviceMouseBarrier() override; + + private: + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected + bool IsConnected() const override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState + void SetSystemCursorState(AzFramework::SystemCursorState systemCursorState) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState + AzFramework::SystemCursorState GetSystemCursorState() const override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized + void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized + AZ::Vector2 GetSystemCursorPositionNormalized() const override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice + void TickInputDevice() override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawMouseButtonDownEvent + void OnRawMouseButtonDownEvent(uint32_t buttonIndex) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawMouseButtonUpEvent + void OnRawMouseButtonUpEvent(uint32_t buttonIndex) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawMouseMovementEvent + void OnRawMouseMovementEvent(float movementX, float movementY) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref RawInputNotificationsBarrier::OnRawMousePositionEvent + void OnRawMousePositionEvent(float positionX, float positionY) override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Thread safe method to queue raw button events to be processed in the main thread update + //! \param[in] buttonIndex The index of the button + //! \param[in] rawButtonState The raw button state + void ThreadSafeQueueRawButtonEvent(uint32_t buttonIndex, bool rawButtonState); + + //////////////////////////////////////////////////////////////////////////////////////////// + // Variables + AzFramework::SystemCursorState m_systemCursorState; + AZ::Vector2 m_systemCursorPositionNormalized; + + RawButtonEventQueueByIdMap m_threadAwareRawButtonEventQueuesById; + AZStd::mutex m_threadAwareRawButtonEventQueuesByIdMutex; + + RawMovementEventQueueByIdMap m_threadAwareRawMovementEventQueuesById; + AZStd::mutex m_threadAwareRawMovementEventQueuesByIdMutex; + + AZ::Vector2 m_threadAwareSystemCursorPosition; + AZStd::mutex m_threadAwareSystemCursorPositionMutex; + }; +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.cpp b/Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.cpp new file mode 100644 index 0000000000..a3c9fb3cc7 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.cpp @@ -0,0 +1,149 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + //////////////////////////////////////////////////////////////////////////////////////////////// + template + void OnBarrierConnectionCVarChanged(const T&) + { + BarrierInputConnectionNotificationBus::Broadcast(&BarrierInputConnectionNotifications::OnBarrierConnectionCVarChanged); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + AZ_CVAR(AZ::CVarFixedString, + barrier_clientScreenName, + "", + OnBarrierConnectionCVarChanged, + AZ::ConsoleFunctorFlags::DontReplicate, + "The Barrier screen name assigned to this client."); + + //////////////////////////////////////////////////////////////////////////////////////////////// + AZ_CVAR(AZ::CVarFixedString, + barrier_serverHostName, + "", + OnBarrierConnectionCVarChanged, + AZ::ConsoleFunctorFlags::DontReplicate, + "The IP or hostname of the Barrier server to connect to."); + + //////////////////////////////////////////////////////////////////////////////////////////////// + AZ_CVAR(AZ::u32, + barrier_connectionPort, + BarrierClient::DEFAULT_BARRIER_CONNECTION_PORT_NUMBER, + OnBarrierConnectionCVarChanged, + AZ::ConsoleFunctorFlags::DontReplicate, + "The port number over which to connect to the Barrier server."); + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0); + + if (AZ::EditContext* ec = serialize->GetEditContext()) + { + ec->Class("BarrierInput", "Provides functionality related to Barrier input.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ; + } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC("BarrierInputService")); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC("BarrierInputService")); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::Activate() + { + TryCreateBarrierClientAndInputDeviceImplementations(); + BarrierInputConnectionNotificationBus::Handler::BusConnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::Deactivate() + { + BarrierInputConnectionNotificationBus::Handler::BusDisconnect(); + DestroyBarrierClientAndInputDeviceImplementations(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::OnBarrierConnectionCVarChanged() + { + TryCreateBarrierClientAndInputDeviceImplementations(); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::TryCreateBarrierClientAndInputDeviceImplementations() + { + // Destroy any existing Barrier client and input device implementations. + DestroyBarrierClientAndInputDeviceImplementations(); + + const AZ::CVarFixedString barrierClientScreenNameCVar = static_cast(barrier_clientScreenName); + const AZ::CVarFixedString barrierServerHostNameCVar = static_cast(barrier_serverHostName); + const AZ::u32 barrierConnectionPort = static_cast(barrier_connectionPort); + if (!barrierClientScreenNameCVar.empty() && !barrierServerHostNameCVar.empty() && barrierConnectionPort) + { + // Enable the Barrier keyboard/mouse input device implementations. + AzFramework::InputDeviceImplementationRequest::Bus::Event( + AzFramework::InputDeviceKeyboard::Id, + &AzFramework::InputDeviceImplementationRequest::SetCustomImplementation, + BarrierInput::InputDeviceKeyboardBarrier::Create); + AzFramework::InputDeviceImplementationRequest::Bus::Event( + AzFramework::InputDeviceMouse::Id, + &AzFramework::InputDeviceImplementationRequest::SetCustomImplementation, + BarrierInput::InputDeviceMouseBarrier::Create); + + // Create the Barrier client instance. + m_barrierClient = AZStd::make_unique(barrierClientScreenNameCVar.c_str(), barrierServerHostNameCVar.c_str(), barrierConnectionPort); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + void BarrierInputSystemComponent::DestroyBarrierClientAndInputDeviceImplementations() + { + if (m_barrierClient) + { + // Destroy the Barrier client instance. + m_barrierClient.reset(); + + // Reset to the default keyboard/mouse input device implementations. + AzFramework::InputDeviceImplementationRequest::Bus::Event( + AzFramework::InputDeviceKeyboard::Id, + &AzFramework::InputDeviceImplementationRequest::SetCustomImplementation, + AzFramework::InputDeviceKeyboard::Implementation::Create); + AzFramework::InputDeviceImplementationRequest::Bus::Event( + AzFramework::InputDeviceMouse::Id, + &AzFramework::InputDeviceImplementationRequest::SetCustomImplementation, + AzFramework::InputDeviceMouse::Implementation::Create); + } + } +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.h b/Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.h new file mode 100644 index 0000000000..d602d66711 --- /dev/null +++ b/Gems/BarrierInput/Code/Source/BarrierInputSystemComponent.h @@ -0,0 +1,86 @@ +/* + * 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 + +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////// +namespace BarrierInput +{ + //////////////////////////////////////////////////////////////////////////////////////////////// + //! EBus interface used to listen for changes to Barrier connection related CVars. + class BarrierInputConnectionNotifications : public AZ::EBusTraits + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + //! Called when a CVar relating to the Barrier input connection changes. + virtual void OnBarrierConnectionCVarChanged() {} + }; + using BarrierInputConnectionNotificationBus = AZ::EBus; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //! A system component providing functionality related to Barrier input. + class BarrierInputSystemComponent : public AZ::Component + , public BarrierInputConnectionNotificationBus::Handler + { + public: + //////////////////////////////////////////////////////////////////////////////////////////// + // AZ::Component Setup + AZ_COMPONENT(BarrierInputSystemComponent, "{720B6420-8A76-46F9-80C7-0DBF0CD467C2}"); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AZ::ComponentDescriptor::Reflect + static void Reflect(AZ::ReflectContext* context); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AZ::ComponentDescriptor::GetProvidedServices + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AZ::ComponentDescriptor::GetIncompatibleServices + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Default constructor + BarrierInputSystemComponent() = default; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Default destructor + ~BarrierInputSystemComponent() override = default; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AZ::Component::Activate + void Activate() override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref AZ::Component::Deactivate + void Deactivate() override; + + protected: + //////////////////////////////////////////////////////////////////////////////////////////// + //! \ref BarrierInput::BarrierInputConnectionNotifications::OnBarrierConnectionCVarChanged + void OnBarrierConnectionCVarChanged() override; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Try to create the Barrier client and input device implementations. + void TryCreateBarrierClientAndInputDeviceImplementations(); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Destroy the Barrier client and input device implementations (if they've been created). + void DestroyBarrierClientAndInputDeviceImplementations(); + + private: + //////////////////////////////////////////////////////////////////////////////////////////// + //! The Barrier client instance. + AZStd::unique_ptr m_barrierClient; + }; +} // namespace BarrierInput diff --git a/Gems/BarrierInput/Code/barrierinput_files.cmake b/Gems/BarrierInput/Code/barrierinput_files.cmake new file mode 100644 index 0000000000..e82944c288 --- /dev/null +++ b/Gems/BarrierInput/Code/barrierinput_files.cmake @@ -0,0 +1,19 @@ +# +# 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 +# +# + +set(FILES + Include/BarrierInput/RawInputNotificationBus_Barrier.h + Source/BarrierInputClient.cpp + Source/BarrierInputClient.h + Source/BarrierInputKeyboard.cpp + Source/BarrierInputKeyboard.h + Source/BarrierInputMouse.cpp + Source/BarrierInputMouse.h + Source/BarrierInputSystemComponent.cpp + Source/BarrierInputSystemComponent.h +) diff --git a/Gems/BarrierInput/Code/barrierinput_shared_files.cmake b/Gems/BarrierInput/Code/barrierinput_shared_files.cmake new file mode 100644 index 0000000000..e8b7ed7a24 --- /dev/null +++ b/Gems/BarrierInput/Code/barrierinput_shared_files.cmake @@ -0,0 +1,11 @@ +# +# 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 +# +# + +set(FILES + Source/BarrierInputModule.cpp +) diff --git a/Gems/BarrierInput/gem.json b/Gems/BarrierInput/gem.json new file mode 100644 index 0000000000..738d644d84 --- /dev/null +++ b/Gems/BarrierInput/gem.json @@ -0,0 +1,12 @@ +{ + "gem_name": "BarrierInput", + "display_name": "Barrier Input", + "license": "Apache-2.0 Or MIT", + "origin": "Open 3D Engine - o3de.org", + "type": "Code", + "summary": "The Barrier Input Gem allows the Open 3D Engine to function as a Barrier client so that it can receive input from a remote Barrier server.", + "canonical_tags": ["Gem"], + "user_tags": ["Input", "Barrier", "Synergy"], + "icon_path": "preview.png", + "requirements": "" +} diff --git a/Gems/BarrierInput/preview.png b/Gems/BarrierInput/preview.png new file mode 100644 index 0000000000..a8457c7f6e --- /dev/null +++ b/Gems/BarrierInput/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7917fbf6e4e3a89e3432b8f48822b660bb245d2b84bb8efdf9f715593c0973df +size 38792 diff --git a/Gems/Camera/Code/Source/CameraComponent.cpp b/Gems/Camera/Code/Source/CameraComponent.cpp index 814fd1c4e7..04c245bc95 100644 --- a/Gems/Camera/Code/Source/CameraComponent.cpp +++ b/Gems/Camera/Code/Source/CameraComponent.cpp @@ -103,9 +103,15 @@ namespace Camera ->Event("SetNearClipDistance", &CameraRequestBus::Events::SetNearClipDistance) ->Event("SetFarClipDistance", &CameraRequestBus::Events::SetFarClipDistance) ->Event("MakeActiveView", &CameraRequestBus::Events::MakeActiveView) + ->Event("IsOrthographic", &CameraRequestBus::Events::IsOrthographic) + ->Event("SetOrthographic", &CameraRequestBus::Events::SetOrthographic) + ->Event("GetOrthographicHalfWidth", &CameraRequestBus::Events::GetOrthographicHalfWidth) + ->Event("SetOrthographicHalfWidth", &CameraRequestBus::Events::SetOrthographicHalfWidth) ->VirtualProperty("FieldOfView","GetFovDegrees","SetFovDegrees") ->VirtualProperty("NearClipDistance", "GetNearClipDistance", "SetNearClipDistance") ->VirtualProperty("FarClipDistance", "GetFarClipDistance", "SetFarClipDistance") + ->VirtualProperty("Orthographic", "IsOrthographic", "SetOrthographic") + ->VirtualProperty("OrthographicHalfWidth", "GetOrthographicHalfWidth", "SetOrthographicHalfWidth") ; behaviorContext->Class()->RequestBus("CameraRequestBus"); diff --git a/Gems/Camera/Code/Source/CameraComponentController.cpp b/Gems/Camera/Code/Source/CameraComponentController.cpp index dbcc7fe6f1..bbe8235449 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.cpp +++ b/Gems/Camera/Code/Source/CameraComponentController.cpp @@ -24,7 +24,9 @@ namespace Camera if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(2) + ->Version(3) + ->Field("Orthographic", &CameraComponentConfig::m_orthographic) + ->Field("Orthographic Half Width", &CameraComponentConfig::m_orthographicHalfWidth) ->Field("Field of View", &CameraComponentConfig::m_fov) ->Field("Near Clip Plane Distance", &CameraComponentConfig::m_nearClipDistance) ->Field("Far Clip Plane Distance", &CameraComponentConfig::m_farClipDistance) @@ -42,25 +44,33 @@ namespace Camera ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_makeActiveViewOnActivation, "Make active camera on activation?", "If true, this camera will become the active render camera when it activates") + ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_orthographic, "Orthographic", + "If set, this camera will use an orthographic projection instead of a perspective one. Objects will appear as the same size, regardless of distance from the camera.") + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree) + ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_orthographicHalfWidth, "Orthographic Half-width", "The half-width used to calculate the orthographic projection. The height will be determined by the aspect ratio.") + ->Attribute(AZ::Edit::Attributes::Visibility, &CameraComponentConfig::GetOrthographicParameterVisibility) + ->Attribute(AZ::Edit::Attributes::Min, 0.001f) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_fov, "Field of view", "Vertical field of view in degrees") ->Attribute(AZ::Edit::Attributes::Min, MIN_FOV) ->Attribute(AZ::Edit::Attributes::Suffix, " degrees") ->Attribute(AZ::Edit::Attributes::Step, 1.f) ->Attribute(AZ::Edit::Attributes::Max, AZ::RadToDeg(AZ::Constants::Pi) - 0.0001f) //We assert at fovs >= Pi so set the max for this field to be just under that - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshValues", 0x28e720d4)) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::ValuesOnly) + ->Attribute(AZ::Edit::Attributes::Visibility, &CameraComponentConfig::GetPerspectiveParameterVisibility) ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_nearClipDistance, "Near clip distance", "Distance to the near clip plane of the view Frustum") ->Attribute(AZ::Edit::Attributes::Min, CAMERA_MIN_NEAR) ->Attribute(AZ::Edit::Attributes::Suffix, " m") ->Attribute(AZ::Edit::Attributes::Step, 0.1f) ->Attribute(AZ::Edit::Attributes::Max, &CameraComponentConfig::GetFarClipDistance) - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c)) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) ->DataElement(AZ::Edit::UIHandlers::Default, &CameraComponentConfig::m_farClipDistance, "Far clip distance", "Distance to the far clip plane of the view Frustum") ->Attribute(AZ::Edit::Attributes::Min, &CameraComponentConfig::GetNearClipDistance) ->Attribute(AZ::Edit::Attributes::Suffix, " m") ->Attribute(AZ::Edit::Attributes::Step, 10.f) - ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c)) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) ; } } @@ -81,6 +91,16 @@ namespace Camera return AZ::EntityId(m_editorEntityId); } + AZ::u32 CameraComponentConfig::GetPerspectiveParameterVisibility() const + { + return m_orthographic ? AZ::Edit::PropertyVisibility::Hide : AZ::Edit::PropertyVisibility::Show; + } + + AZ::u32 CameraComponentConfig::GetOrthographicParameterVisibility() const + { + return m_orthographic ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide; + } + CameraComponentController::CameraComponentController(const CameraComponentConfig& config) { SetConfiguration(config); @@ -289,6 +309,16 @@ namespace Camera return m_config; } + AZ::RPI::ViewportContextPtr CameraComponentController::GetViewportContext() + { + auto atomViewportRequests = AZ::Interface::Get(); + if (m_atomCamera && atomViewportRequests) + { + return atomViewportRequests->GetDefaultViewportContext(); + } + return nullptr; + } + AZ::EntityId CameraComponentController::GetCameras() { return m_entityId; @@ -324,6 +354,16 @@ namespace Camera return m_config.m_frustumHeight; } + bool CameraComponentController::IsOrthographic() + { + return m_config.m_orthographic; + } + + float CameraComponentController::GetOrthographicHalfWidth() + { + return m_config.m_orthographicHalfWidth; + } + void CameraComponentController::SetFovDegrees(float fov) { m_config.m_fov = AZ::GetClamp(fov, MinFoV, MaxFoV); @@ -359,6 +399,18 @@ namespace Camera UpdateCamera(); } + void CameraComponentController::SetOrthographic(bool orthographic) + { + m_config.m_orthographic = orthographic; + UpdateCamera(); + } + + void CameraComponentController::SetOrthographicHalfWidth(float halfWidth) + { + m_config.m_orthographicHalfWidth = halfWidth; + UpdateCamera(); + } + void CameraComponentController::MakeActiveView() { // Set Legacy Cry view, if it exists @@ -423,30 +475,38 @@ namespace Camera m_view->SetCurrentParams(viewParams); } - auto atomViewportRequests = AZ::Interface::Get(); - if (m_atomCamera && atomViewportRequests) + if (auto viewportContext = GetViewportContext()) { AZ::Matrix4x4 viewToClipMatrix; float aspectRatio = m_view ? m_view->GetCamera().GetPixelAspectRatio() : 1.f; - auto viewportContext = atomViewportRequests->GetViewportContextByName( - atomViewportRequests->GetDefaultViewportContextName()); - if (viewportContext) + if (!m_atomAuxGeom) { - if (!m_atomAuxGeom) - { - SetupAtomAuxGeom(viewportContext); - } - auto windowSize = viewportContext->GetViewportSize(); - aspectRatio = aznumeric_cast(windowSize.m_width) / aznumeric_cast(windowSize.m_height); + SetupAtomAuxGeom(viewportContext); } + auto windowSize = viewportContext->GetViewportSize(); + aspectRatio = aznumeric_cast(windowSize.m_width) / aznumeric_cast(windowSize.m_height); // This assumes a reversed depth buffer, in line with other LY Atom integration - AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, - AZ::DegToRad(m_config.m_fov), - aspectRatio, - m_config.m_nearClipDistance, - m_config.m_farClipDistance, - true); + if (m_config.m_orthographic) + { + AZ::MakeOrthographicMatrixRH(viewToClipMatrix, + -m_config.m_orthographicHalfWidth, + m_config.m_orthographicHalfWidth, + -m_config.m_orthographicHalfWidth / aspectRatio, + m_config.m_orthographicHalfWidth / aspectRatio, + m_config.m_nearClipDistance, + m_config.m_farClipDistance, + true); + } + else + { + AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, + AZ::DegToRad(m_config.m_fov), + aspectRatio, + m_config.m_nearClipDistance, + m_config.m_farClipDistance, + true); + } m_updatingTransformFromEntity = true; m_atomCamera->SetViewToClipMatrix(viewToClipMatrix); m_updatingTransformFromEntity = false; diff --git a/Gems/Camera/Code/Source/CameraComponentController.h b/Gems/Camera/Code/Source/CameraComponentController.h index 7f4e419176..c004dbe6ec 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.h +++ b/Gems/Camera/Code/Source/CameraComponentController.h @@ -40,6 +40,9 @@ namespace Camera float GetNearClipDistance() const; AZ::EntityId GetEditorEntityId() const; + AZ::u32 GetPerspectiveParameterVisibility() const; + AZ::u32 GetOrthographicParameterVisibility() const; + // Reflected members float m_fov = DefaultFoV; float m_nearClipDistance = DefaultNearPlaneDistance; @@ -49,6 +52,8 @@ namespace Camera bool m_specifyFrustumDimensions = false; AZ::u64 m_editorEntityId = AZ::EntityId::InvalidEntityId; bool m_makeActiveViewOnActivation = true; + bool m_orthographic = false; + float m_orthographicHalfWidth = 5.f; }; class CameraComponentController @@ -78,6 +83,7 @@ namespace Camera void Deactivate(); void SetConfiguration(const CameraComponentConfig& config); const CameraComponentConfig& GetConfiguration() const; + AZ::RPI::ViewportContextPtr GetViewportContext(); // CameraBus::Handler interface AZ::EntityId GetCameras() override; @@ -89,12 +95,17 @@ namespace Camera float GetFarClipDistance() override; float GetFrustumWidth() override; float GetFrustumHeight() override; + bool IsOrthographic() override; + float GetOrthographicHalfWidth() override; void SetFovDegrees(float fov) override; void SetFovRadians(float fov) override; void SetNearClipDistance(float nearClipDistance) override; void SetFarClipDistance(float farClipDistance) override; void SetFrustumWidth(float width) override; void SetFrustumHeight(float height) override; + void SetOrthographic(bool orthographic) override; + void SetOrthographicHalfWidth(float halfWidth) override; + void MakeActiveView() override; // AZ::TransformNotificationBus::Handler interface diff --git a/Gems/Camera/Code/Source/EditorCameraComponent.cpp b/Gems/Camera/Code/Source/EditorCameraComponent.cpp index beed4d7097..14e8e46e72 100644 --- a/Gems/Camera/Code/Source/EditorCameraComponent.cpp +++ b/Gems/Camera/Code/Source/EditorCameraComponent.cpp @@ -17,6 +17,9 @@ #include #include +#include +#include + namespace Camera { namespace ClassConverters @@ -155,6 +158,42 @@ namespace Camera } } + bool EditorCameraComponent::GetCameraState(AzFramework::CameraState& cameraState) + { + const CameraComponentConfig& config = m_controller.GetConfiguration(); + AZ::RPI::ViewportContextPtr viewportContext = m_controller.GetViewportContext(); + AZ::RPI::ViewPtr view = m_controller.GetView(); + + if (viewportContext == nullptr || view == nullptr) + { + return false; + } + + AzFramework::SetCameraTransform(cameraState, view->GetCameraTransform()); + + { + const AzFramework::WindowSize viewportSize = viewportContext->GetViewportSize(); + cameraState.m_viewportSize = + AZ::Vector2{aznumeric_cast(viewportSize.m_width), aznumeric_cast(viewportSize.m_height)}; + } + + if (config.m_orthographic) + { + cameraState.m_fovOrZoom = cameraState.m_viewportSize.GetX() / (config.m_orthographicHalfWidth * 2.0f); + cameraState.m_orthographic = true; + } + else + { + cameraState.m_fovOrZoom = config.m_fov; + cameraState.m_orthographic = false; + } + + cameraState.m_nearClip = config.m_nearClipDistance; + cameraState.m_farClip = config.m_farClipDistance; + + return true; + } + AZ::Crc32 EditorCameraComponent::OnPossessCameraButtonClicked() { AZ::EntityId currentViewEntity; @@ -201,9 +240,20 @@ namespace Camera const CameraComponentConfig& config = m_controller.GetConfiguration(); const float distance = config.m_farClipDistance * m_frustumViewPercentLength * 0.01f; - float tangent = static_cast(tan(0.5f * AZ::DegToRad(config.m_fov))); - float height = distance * tangent; - float width = height * debugDisplay.GetAspectRatio(); + float width; + float height; + + if (config.m_orthographic) + { + width = config.m_orthographicHalfWidth; + height = width / debugDisplay.GetAspectRatio(); + } + else + { + const float tangent = static_cast(tan(0.5f * AZ::DegToRad(config.m_fov))); + height = distance * tangent; + width = height * debugDisplay.GetAspectRatio(); + } AZ::Vector3 farPoints[4]; farPoints[0] = AZ::Vector3( width, distance, height); @@ -211,12 +261,21 @@ namespace Camera farPoints[2] = AZ::Vector3(-width, distance, -height); farPoints[3] = AZ::Vector3( width, distance, -height); - AZ::Vector3 start(0, 0, 0); AZ::Vector3 nearPoints[4]; - nearPoints[0] = farPoints[0].GetNormalizedSafe() * config.m_nearClipDistance; - nearPoints[1] = farPoints[1].GetNormalizedSafe() * config.m_nearClipDistance; - nearPoints[2] = farPoints[2].GetNormalizedSafe() * config.m_nearClipDistance; - nearPoints[3] = farPoints[3].GetNormalizedSafe() * config.m_nearClipDistance; + if (config.m_orthographic) + { + nearPoints[0] = AZ::Vector3( width, config.m_nearClipDistance, height); + nearPoints[1] = AZ::Vector3(-width, config.m_nearClipDistance, height); + nearPoints[2] = AZ::Vector3(-width, config.m_nearClipDistance, -height); + nearPoints[3] = AZ::Vector3( width, config.m_nearClipDistance, -height); + } + else + { + nearPoints[0] = farPoints[0].GetNormalizedSafe() * config.m_nearClipDistance; + nearPoints[1] = farPoints[1].GetNormalizedSafe() * config.m_nearClipDistance; + nearPoints[2] = farPoints[2].GetNormalizedSafe() * config.m_nearClipDistance; + nearPoints[3] = farPoints[3].GetNormalizedSafe() * config.m_nearClipDistance; + } debugDisplay.PushMatrix(world); debugDisplay.SetColor(m_frustumDrawColor.GetAsVector4()); diff --git a/Gems/Camera/Code/Source/EditorCameraComponent.h b/Gems/Camera/Code/Source/EditorCameraComponent.h index bf788256a9..095427a4a7 100644 --- a/Gems/Camera/Code/Source/EditorCameraComponent.h +++ b/Gems/Camera/Code/Source/EditorCameraComponent.h @@ -58,7 +58,9 @@ namespace Camera /// EditorCameraNotificationBus::Handler interface void OnViewportViewEntityChanged(const AZ::EntityId& newViewId) override; + /// EditorCameraViewRequestBus::Handler interface void ToggleCameraAsActiveView() override { OnPossessCameraButtonClicked(); } + bool GetCameraState(AzFramework::CameraState& cameraState) override; protected: void EditorDisplay(AzFramework::DebugDisplayRequests& displayInterface, const AZ::Transform& world); diff --git a/Gems/EditorPythonBindings/Code/Tests/PythonThreadingTests.cpp b/Gems/EditorPythonBindings/Code/Tests/PythonThreadingTests.cpp index b1cb2ef162..b2e18265cb 100644 --- a/Gems/EditorPythonBindings/Code/Tests/PythonThreadingTests.cpp +++ b/Gems/EditorPythonBindings/Code/Tests/PythonThreadingTests.cpp @@ -217,4 +217,79 @@ namespace UnitTest e.Deactivate(); } + TEST_F(PythonThreadingTest, PythonInterface_DebugTrace_CallsOnTick) + { + enum class LogTypes + { + Skip = 0, + OnPrewarning + }; + + m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int + { + if (AzFramework::StringFunc::Equal(window, "python")) + { + if (AzFramework::StringFunc::StartsWith(message, "OnPrewarning")) + { + return aznumeric_cast(LogTypes::OnPrewarning); + } + } + return aznumeric_cast(LogTypes::Skip); + }; + + AZ::Entity e; + Activate(e); + SimulateEditorBecomingInitialized(); + + try + { + // prepare handler on this thread + pybind11::exec(R"( + import azlmbr.debug + + def on_prewarning(args): + print ('OnPrewarning: ' + args[0]) + + handler = azlmbr.debug.TraceMessageBusHandler() + handler.connect() + handler.add_callback('OnPreWarning', on_prewarning) + )"); + + const size_t numWarnings = 64; + auto doWarning = []() + { + AZ_Warning("PythonThreadingTest", false, "This is a warning message"); + }; + + // start threads. In thread issue a warning. + AZStd::vector threads; + threads.reserve(numWarnings); + for (size_t i = 0; i < numWarnings; ++i) + { + threads.emplace_back(doWarning); + } + for (AZStd::thread& thread : threads) + { + thread.join(); + } + + // No prewarning calls should have happened because all of them were queued + EXPECT_EQ(0, m_testSink.m_evaluationMap[aznumeric_cast(LogTypes::OnPrewarning)]); + + // Do one tick + const float timeOneFrameSeconds = 0.016f; //approx 60 fps + AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick, + timeOneFrameSeconds, + AZ::ScriptTimePoint(AZStd::chrono::system_clock::now())); + + // After one tick all the queued calls should have been processed + EXPECT_EQ(numWarnings, m_testSink.m_evaluationMap[aznumeric_cast(LogTypes::OnPrewarning)]); + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Error("UnitTest", false, "Failed during thread test with %s", e.what()); + } + + e.Deactivate(); + } } diff --git a/Gems/InAppPurchases/Code/Source/InAppPurchasesSystemComponent.cpp b/Gems/InAppPurchases/Code/Source/InAppPurchasesSystemComponent.cpp index 8b91b72a60..fd1f4146dc 100644 --- a/Gems/InAppPurchases/Code/Source/InAppPurchasesSystemComponent.cpp +++ b/Gems/InAppPurchases/Code/Source/InAppPurchasesSystemComponent.cpp @@ -6,6 +6,8 @@ * */ +#include + #include #include #include diff --git a/Gems/Microphone/Code/Source/Platform/iOS/MicrophoneSystemComponent_iOS.mm b/Gems/Microphone/Code/Source/Platform/iOS/MicrophoneSystemComponent_iOS.mm index a603aa0ab4..fe8580723a 100644 --- a/Gems/Microphone/Code/Source/Platform/iOS/MicrophoneSystemComponent_iOS.mm +++ b/Gems/Microphone/Code/Source/Platform/iOS/MicrophoneSystemComponent_iOS.mm @@ -238,7 +238,7 @@ public: void ProcessAudio(AudioBufferList* bufferList) { AudioBuffer sourceBuffer = bufferList->mBuffers[0]; - m_captureData->AddData((int16*)sourceBuffer.mData, sourceBuffer.mDataByteSize / 2, m_config.m_numChannels); + m_captureData->AddData((AZ::s16*)sourceBuffer.mData, sourceBuffer.mDataByteSize / 2, m_config.m_numChannels); } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 2eb57498aa..84fdc9ae54 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -176,7 +176,7 @@ RewindableFixedVector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}> m_{{ LowerFirst(Property.attrib['Name']) }}; {% endif %} {% elif Property.attrib['IsRewindable']|booleanTrue %} -Multiplayer::RewindableObject<{{ Property.attrib['Type'] }}, Multiplayer::RewindHistorySize> m_{{ LowerFirst(Property.attrib['Name']) }} = {{ Property.attrib['Init'] }}; +Multiplayer::RewindableObject<{{ Property.attrib['Type'] }}, Multiplayer::RewindHistorySize> m_{{ LowerFirst(Property.attrib['Name']) }} { {{ Property.attrib['Init'] }} }; {% else %} {{ Property.attrib['Type'] }} m_{{ LowerFirst(Property.attrib['Name']) }} = {{ Property.attrib['Init'] }}; {% endif %} diff --git a/Gems/MultiplayerCompression/Code/CMakeLists.txt b/Gems/MultiplayerCompression/Code/CMakeLists.txt index 405d92e683..f7494be3fd 100644 --- a/Gems/MultiplayerCompression/Code/CMakeLists.txt +++ b/Gems/MultiplayerCompression/Code/CMakeLists.txt @@ -6,8 +6,6 @@ # # -set(LY_ENABLE_MULTIPLAYER_COMPRESSION OFF CACHE BOOL "Enables usage of Multiplayer Compressor.") - ly_add_target( NAME MultiplayerCompression.Static STATIC NAMESPACE Gem diff --git a/Gems/RADTelemetry/Code/CMakeLists.txt b/Gems/RADTelemetry/Code/CMakeLists.txt index 3b65d6d47a..544540f273 100644 --- a/Gems/RADTelemetry/Code/CMakeLists.txt +++ b/Gems/RADTelemetry/Code/CMakeLists.txt @@ -6,8 +6,9 @@ # # -set(LY_ENABLE_RAD_TELEMETRY OFF CACHE BOOL "Enables RAD Telemetry in Debug/Profile mode.") -set(LY_RAD_TELEMETRY_INSTALL_ROOT "${LY_3RDPARTY_PATH}/RadTelemetry" CACHE PATH "Install path to RAD Telemetry.") +set(LY_RAD_TELEMETRY_ENABLED OFF CACHE BOOL "Enables RAD Telemetry in Debug/Profile mode.") +set(LY_RAD_TELEMETRY_INSTALL_ROOT "@LY_3RDPARTY_PATH@/RadTelemetry" CACHE PATH "Install path to RAD Telemetry.") +string(CONFIGURE ${LY_RAD_TELEMETRY_INSTALL_ROOT} LY_RAD_TELEMETRY_INSTALL_ROOT @ONLY) ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME}) diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h index f5b716876e..dd3b715a21 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshBuilderSkinningInfo.h @@ -35,7 +35,7 @@ namespace AZ::MeshBuilder MeshBuilderSkinningInfo(size_t numOrgVertices); - void AddInfluence(size_t orgVtxNr, const Influence& influence) { mInfluences.resize(AZStd::max(mInfluences.size(), orgVtxNr)); mInfluences.at(orgVtxNr).emplace_back(influence); } + void AddInfluence(size_t orgVtxNr, const Influence& influence) { mInfluences.resize(AZStd::max(mInfluences.size(), orgVtxNr + 1)); mInfluences.at(orgVtxNr).emplace_back(influence); } void RemoveInfluence(size_t orgVtxNr, size_t influenceNr) { mInfluences.at(orgVtxNr).erase(mInfluences.at(orgVtxNr).begin() + influenceNr); } const Influence& GetInfluence(size_t orgVtxNr, size_t influenceNr) const { return mInfluences.at(orgVtxNr).at(influenceNr); } size_t GetNumInfluences(size_t orgVtxNr) const { return mInfluences.at(orgVtxNr).size(); } diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp index 35d6db6d03..6bc47476f8 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/MeshOptimizer/MeshOptimizerComponent.cpp @@ -105,7 +105,6 @@ namespace AZ::SceneGenerationComponents // Vector3 as a key into a unordered_map. template class Vector3Map - : private AZStd::unordered_map { public: Vector3Map(const MeshDataType* meshData, bool hasBlendShapes, float positionTolerance) @@ -116,9 +115,6 @@ namespace AZ::SceneGenerationComponents { } - using AZStd::unordered_map::reserve; - using AZStd::unordered_map::size; - AZ::u32 operator[](const AZ::u32 vertexIndex) { if (m_hasBlendShapes) @@ -130,7 +126,7 @@ namespace AZ::SceneGenerationComponents return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex)); } - const auto& [iter, didInsert] = try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex); + const auto& [iter, didInsert] = m_map.try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex); if (didInsert) { ++m_currentOriginalVertexIndex; @@ -149,11 +145,32 @@ namespace AZ::SceneGenerationComponents return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex)); } - auto iter = find(GetPositionForIndex(vertexIndex)); - AZSTD_CONTAINER_ASSERT(iter != end(), "Element with key is not present"); + auto iter = m_map.find(GetPositionForIndex(vertexIndex)); + AZSTD_CONTAINER_ASSERT(iter != m_map.end(), "Element with key is not present"); return iter->second; } + [[nodiscard]] size_t size() const + { + if (m_hasBlendShapes) + { + // Since blend shapes are present, the vertex welding is disabled, and the map will always be empty. + // Use the underlying mesh's vertex count instead. + return m_meshData->GetUsedControlPointCount(); + } + return m_map.size(); + } + + void reserve(size_t count) + { + if (m_hasBlendShapes) + { + // Since blend shapes are present, the vertex welding is disabled, and the map will always be empty. + return; + } + m_map.reserve(count); + } + private: AZ::Vector3 GetPositionForIndex(const AZ::u32 vertexIndex) const @@ -167,6 +184,7 @@ namespace AZ::SceneGenerationComponents ) * m_positionTolerance; } + AZStd::unordered_map m_map; const MeshDataType* m_meshData; bool m_hasBlendShapes; float m_positionTolerance; diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.cpp index c1c47e1955..fdf58f2502 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.cpp @@ -15,8 +15,6 @@ #include #include -#include - #include #include #include @@ -52,7 +50,7 @@ namespace AZ::SceneGenerationComponents } } - AZ::SceneAPI::DataTypes::TangentSpace TangentGenerateComponent::GetTangentSpaceFromRule(const AZ::SceneAPI::Containers::Scene& scene) const + const AZ::SceneAPI::SceneData::TangentsRule* TangentGenerateComponent::GetTangentRule(const AZ::SceneAPI::Containers::Scene& scene) const { for (const auto& object : scene.GetManifest().GetValueStorage()) { @@ -62,12 +60,12 @@ namespace AZ::SceneGenerationComponents const AZ::SceneAPI::SceneData::TangentsRule* rule = group->GetRuleContainerConst().FindFirstByType().get(); if (rule) { - return rule->GetTangentSpace(); + return rule; } } } - return AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene; + return nullptr; } AZ::SceneAPI::Events::ProcessingResult TangentGenerateComponent::GenerateTangentData(TangentGenerateContext& context) @@ -189,8 +187,8 @@ namespace AZ::SceneGenerationComponents return true; // No fatal error } - // Check what tangent spaces we need. - const AZ::SceneAPI::DataTypes::TangentSpace ruleTangentSpace = GetTangentSpaceFromRule(scene); + const AZ::SceneAPI::SceneData::TangentsRule* tangentsRule = GetTangentRule(scene); + const AZ::SceneAPI::DataTypes::TangentGenerationMethod ruleGenerationMethod = tangentsRule ? tangentsRule->GetGenerationMethod() : AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene; // Find all blend shape data under the mesh. We need to generate the tangent and bitangent for blend shape as well. AZStd::vector blendShapes; @@ -208,12 +206,12 @@ namespace AZ::SceneGenerationComponents } // Check if we had tangents inside the source scene file. - AZ::SceneAPI::DataTypes::TangentSpace tangentSpace = ruleTangentSpace; + AZ::SceneAPI::DataTypes::TangentGenerationMethod generationMethod = ruleGenerationMethod; AZ::SceneAPI::DataTypes::IMeshVertexTangentData* tangentData = FindTangentData(graph, nodeIndex, uvSetIndex); AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* bitangentData = FindBitangentData(graph, nodeIndex, uvSetIndex); // If all we need is import from the source scene, and we have tangent data from the source scene already, then skip generating. - if ((tangentSpace == AZ::SceneAPI::DataTypes::TangentSpace::FromSourceScene)) + if ((generationMethod == AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene)) { if (tangentData && bitangentData) { @@ -226,50 +224,56 @@ namespace AZ::SceneGenerationComponents // In case there are no tangents/bitangents while the user selected to use the source ones, default to MikkT. AZ_Warning(AZ::SceneAPI::Utilities::WarningWindow, false, "Cannot use source scene tangents as there are none in the asset for mesh '%s' for uv set %zu. Defaulting to generating tangents using MikkT.\n", scene.GetGraph().GetNodeName(nodeIndex).GetName(), uvSetIndex); - tangentSpace = AZ::SceneAPI::DataTypes::TangentSpace::MikkT; + generationMethod = AZ::SceneAPI::DataTypes::TangentGenerationMethod::MikkT; } } if (!tangentData) { if (!AZ::SceneGenerationComponents::TangentGenerateComponent::CreateTangentLayer(scene.GetManifest(), nodeIndex, meshData->GetVertexCount(), uvSetIndex, - tangentSpace, graph, &tangentData)) + generationMethod, graph, &tangentData)) { AZ_Error(AZ::SceneAPI::Utilities::ErrorWindow, false, "Failed to create tangents data set for mesh %s for uv set %zu.\n", scene.GetGraph().GetNodeName(nodeIndex).GetName(), uvSetIndex); continue; } } + AZ_Assert(tangentData == FindTangentData(graph, nodeIndex, uvSetIndex), "Used tangent data is not the same as the graph returns."); + if (!bitangentData) { if (!AZ::SceneGenerationComponents::TangentGenerateComponent::CreateBitangentLayer(scene.GetManifest(), nodeIndex, meshData->GetVertexCount(), uvSetIndex, - tangentSpace, graph, &bitangentData)) + generationMethod, graph, &bitangentData)) { AZ_Error(AZ::SceneAPI::Utilities::ErrorWindow, false, "Failed to create bitangents data set for mesh %s for uv set %zu.\n", scene.GetGraph().GetNodeName(nodeIndex).GetName(), uvSetIndex); continue; } } - tangentData->SetTangentSpace(tangentSpace); - bitangentData->SetTangentSpace(tangentSpace); + AZ_Assert(bitangentData == FindBitangentData(graph, nodeIndex, uvSetIndex), "Used bitangent data is not the same as the graph returns."); - switch (tangentSpace) + tangentData->SetGenerationMethod(generationMethod); + bitangentData->SetGenerationMethod(generationMethod); + + switch (generationMethod) { // Generate using MikkT space. - case AZ::SceneAPI::DataTypes::TangentSpace::MikkT: + case AZ::SceneAPI::DataTypes::TangentGenerationMethod::MikkT: { - allSuccess &= AZ::TangentGeneration::Mesh::MikkT::GenerateTangents(meshData, uvData, tangentData, bitangentData); + const AZ::SceneAPI::DataTypes::MikkTSpaceMethod tSpaceMethod = tangentsRule ? tangentsRule->GetMikkTSpaceMethod() : AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpace; + + allSuccess &= AZ::TangentGeneration::Mesh::MikkT::GenerateTangents(meshData, uvData, tangentData, bitangentData, tSpaceMethod); for (AZ::SceneData::GraphData::BlendShapeData* blendShape : blendShapes) { - allSuccess &= AZ::TangentGeneration::BlendShape::MikkT::GenerateTangents(blendShape, uvSetIndex); + allSuccess &= AZ::TangentGeneration::BlendShape::MikkT::GenerateTangents(blendShape, uvSetIndex, tSpaceMethod); } } break; default: { - AZ_Assert(false, "Unknown tangent space selected (spaceID=%d) for UV set %d, cannot generate tangents!\n", static_cast(tangentSpace), uvSetIndex); + AZ_Assert(false, "Unknown tangent generation method selected (%d) for UV set %d, cannot generate tangents.\n", static_cast(generationMethod), uvSetIndex); allSuccess = false; } } @@ -339,7 +343,7 @@ namespace AZ::SceneGenerationComponents const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, size_t numVerts, size_t uvSetIndex, - AZ::SceneAPI::DataTypes::TangentSpace tangentSpace, + AZ::SceneAPI::DataTypes::TangentGenerationMethod generationMethod, AZ::SceneAPI::Containers::SceneGraph& graph, AZ::SceneAPI::DataTypes::IMeshVertexTangentData** outTangentData) { @@ -356,7 +360,7 @@ namespace AZ::SceneGenerationComponents } tangentData->SetTangentSetIndex(uvSetIndex); - tangentData->SetTangentSpace(tangentSpace); + tangentData->SetGenerationMethod(generationMethod); const AZStd::string tangentGeneratedName = AZStd::string::format("TangentSet_%zu", uvSetIndex); const AZStd::string tangentSetName = AZ::SceneAPI::DataTypes::Utilities::CreateUniqueName(tangentGeneratedName, manifest); @@ -394,7 +398,7 @@ namespace AZ::SceneGenerationComponents const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, size_t numVerts, size_t uvSetIndex, - AZ::SceneAPI::DataTypes::TangentSpace tangentSpace, + AZ::SceneAPI::DataTypes::TangentGenerationMethod generationMethod, AZ::SceneAPI::Containers::SceneGraph& graph, AZ::SceneAPI::DataTypes::IMeshVertexBitangentData** outBitangentData) { @@ -411,7 +415,7 @@ namespace AZ::SceneGenerationComponents } bitangentData->SetBitangentSetIndex(uvSetIndex); - bitangentData->SetTangentSpace(tangentSpace); + bitangentData->SetGenerationMethod(generationMethod); const AZStd::string bitangentGeneratedName = AZStd::string::format("BitangentSet_%zu", uvSetIndex); const AZStd::string bitangentSetName = AZ::SceneAPI::DataTypes::Utilities::CreateUniqueName(bitangentGeneratedName, manifest); diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.h b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.h index 06d9aded11..d2aa59b549 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.h +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerateComponent.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace AZ::SceneAPI::DataTypes { class IMeshData; } @@ -59,7 +60,7 @@ namespace AZ::SceneGenerationComponents AZStd::vector& outBlendShapes) const; bool GenerateTangentsForMesh(AZ::SceneAPI::Containers::Scene& scene, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::SceneAPI::DataTypes::IMeshData* meshData); void UpdateFbxTangentWValues(AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, const AZ::SceneAPI::DataTypes::IMeshData* meshData); - AZ::SceneAPI::DataTypes::TangentSpace GetTangentSpaceFromRule(const AZ::SceneAPI::Containers::Scene& scene) const; + const AZ::SceneAPI::SceneData::TangentsRule* GetTangentRule(const AZ::SceneAPI::Containers::Scene& scene) const; size_t CalcUvSetCount(AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex) const; AZ::SceneAPI::DataTypes::IMeshVertexUVData* FindUvData(AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::u64 uvSet) const; @@ -69,7 +70,7 @@ namespace AZ::SceneGenerationComponents const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, size_t numVerts, size_t uvSetIndex, - AZ::SceneAPI::DataTypes::TangentSpace tangentSpace, + AZ::SceneAPI::DataTypes::TangentGenerationMethod generationMethod, AZ::SceneAPI::Containers::SceneGraph& graph, AZ::SceneAPI::DataTypes::IMeshVertexTangentData** outTangentData); @@ -78,7 +79,7 @@ namespace AZ::SceneGenerationComponents const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, size_t numVerts, size_t uvSetIndex, - AZ::SceneAPI::DataTypes::TangentSpace tangentSpace, + AZ::SceneAPI::DataTypes::TangentGenerationMethod generationMethod, AZ::SceneAPI::Containers::SceneGraph& graph, AZ::SceneAPI::DataTypes::IMeshVertexBitangentData** outBitangentData); }; diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.cpp index 4e60fd3d15..b472aa4e09 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.cpp @@ -76,33 +76,47 @@ namespace AZ::TangentGeneration::BlendShape::MikkT const AZ::Vector4 tangentVec(tangent[0]*magS, tangent[1]*magS, tangent[2]*magS, flipSign); const AZ::Vector3 bitangentVec(bitangent[0]*magT, bitangent[1]*magT, bitangent[2]*magT); - // Set the tangent and bitangent back to the blendshape + // Set the tangent and bitangent back to the blend shape AZStd::vector& tangents = customData->m_blendShapeData->GetTangents(); AZStd::vector& bitangents = customData->m_blendShapeData->GetBitangents(); tangents[vertexIndex] = tangentVec; bitangents[vertexIndex] = bitangentVec; } - bool GenerateTangents(AZ::SceneData::GraphData::BlendShapeData* blendShapeData, size_t uvSetIndex) + void SetTSpaceBasic(const SMikkTSpaceContext* context, const float tangent[], const float signValue, const int face, const int vert) + { + MikktCustomData* customData = static_cast(context->m_pUserData); + const AZ::u32 vertexIndex = customData->m_blendShapeData->GetFaceVertexIndex(face, vert); + AZ::Vector3 tangentVec3(tangent[0], tangent[1], tangent[2]); + tangentVec3.NormalizeSafe(); + AZ::Vector3 normal = customData->m_blendShapeData->GetNormal(vertexIndex); + normal.NormalizeSafe(); + const AZ::Vector3 bitangent = normal.Cross(tangentVec3) * signValue; + + // Set the tangent and bitangent back to the blend shape + AZStd::vector& tangents = customData->m_blendShapeData->GetTangents(); + AZStd::vector& bitangents = customData->m_blendShapeData->GetBitangents(); + tangents[vertexIndex] = AZ::Vector4(tangentVec3.GetX(), tangentVec3.GetY(), tangentVec3.GetZ(), signValue); + bitangents[vertexIndex] = bitangent; + } + + bool GenerateTangents(AZ::SceneData::GraphData::BlendShapeData* blendShapeData, + size_t uvSetIndex, + AZ::SceneAPI::DataTypes::MikkTSpaceMethod tSpaceMethod) { // Create tangent and bitangent data sets and relate them to the given UV set. const AZStd::vector& uvSet = blendShapeData->GetUVs(uvSetIndex); if (uvSet.empty()) { - AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Cannot find UV data (set index=%d) to generate tangents and bitangents from in MikkT generator!\n", uvSetIndex); - return false; - } - - AZStd::vector& tangents = blendShapeData->GetTangents(); - AZStd::vector& bitangents = blendShapeData->GetBitangents(); - if (!tangents.empty() || !bitangents.empty()) - { - AZ_TracePrintf( - AZ::SceneAPI::Utilities::WarningWindow, "Cannot generate tangents and bitangents because existing tangent or bitangent data has been found.\n"); + AZ_Error(AZ::SceneAPI::Utilities::ErrorWindow, false, + "Cannot find UV data (set index=%d) to generate tangents and bitangents from in MikkT generator.\n", + uvSetIndex); return false; } // Pre-allocate the tangent and bitangent data. + AZStd::vector& tangents = blendShapeData->GetTangents(); + AZStd::vector& bitangents = blendShapeData->GetBitangents(); tangents.resize(blendShapeData->GetVertexCount()); bitangents.resize(blendShapeData->GetVertexCount()); @@ -114,10 +128,24 @@ namespace AZ::TangentGeneration::BlendShape::MikkT mikkInterface.m_getNormal = GetNormal; mikkInterface.m_getPosition = GetPosition; mikkInterface.m_getTexCoord = GetTexCoord; - mikkInterface.m_setTSpace = SetTSpace; - mikkInterface.m_setTSpaceBasic = nullptr; mikkInterface.m_getNumVerticesOfFace= GetNumVerticesOfFace; + switch (tSpaceMethod) + { + case AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpaceBasic: + { + mikkInterface.m_setTSpace = nullptr; + mikkInterface.m_setTSpaceBasic = SetTSpaceBasic; + break; + } + default: + { + mikkInterface.m_setTSpace = SetTSpace; + mikkInterface.m_setTSpaceBasic = nullptr; + break; + } + } + // Set the MikkT custom data. MikktCustomData customData; customData.m_blendShapeData = blendShapeData; diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.h b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.h index cbb170375a..68d4835817 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.h +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace AZ::SceneData::GraphData { @@ -24,5 +25,7 @@ namespace AZ::TangentGeneration::BlendShape::MikkT }; // The main generation method. - bool GenerateTangents(AZ::SceneData::GraphData::BlendShapeData* blendShapeData, size_t uvSetIndex); + bool GenerateTangents(AZ::SceneData::GraphData::BlendShapeData* blendShapeData, + size_t uvSetIndex, + AZ::SceneAPI::DataTypes::MikkTSpaceMethod tSpaceMethod = AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpace); } // namespace AZ::TangentGeneration::MikkT diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.cpp b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.cpp index 3f736815e5..d3b694e852 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.cpp +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.cpp @@ -108,7 +108,8 @@ namespace AZ::TangentGeneration::Mesh::MikkT bool GenerateTangents(const AZ::SceneAPI::DataTypes::IMeshData* meshData, const AZ::SceneAPI::DataTypes::IMeshVertexUVData* uvData, AZ::SceneAPI::DataTypes::IMeshVertexTangentData* outTangentData, - AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* outBitangentData) + AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* outBitangentData, + AZ::SceneAPI::DataTypes::MikkTSpaceMethod tSpaceMethod) { // Provide the MikkT interface. SMikkTSpaceInterface mikkInterface; @@ -116,10 +117,24 @@ namespace AZ::TangentGeneration::Mesh::MikkT mikkInterface.m_getNormal = GetNormal; mikkInterface.m_getPosition = GetPosition; mikkInterface.m_getTexCoord = GetTexCoord; - mikkInterface.m_setTSpace = SetTSpace; - mikkInterface.m_setTSpaceBasic = nullptr;//SetTSpaceBasic; mikkInterface.m_getNumVerticesOfFace= GetNumVerticesOfFace; + switch (tSpaceMethod) + { + case AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpaceBasic: + { + mikkInterface.m_setTSpace = nullptr; + mikkInterface.m_setTSpaceBasic = SetTSpaceBasic; + break; + } + default: + { + mikkInterface.m_setTSpace = SetTSpace; + mikkInterface.m_setTSpaceBasic = nullptr; + break; + } + } + // Set the MikkT custom data. MikktCustomData customData; customData.m_meshData = meshData; diff --git a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.h b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.h index 4b1d8ebd72..4604a6e4c5 100644 --- a/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.h +++ b/Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.h @@ -8,6 +8,7 @@ #pragma once +#include #include namespace AZ::SceneAPI::DataTypes { class IMeshData; } @@ -28,5 +29,6 @@ namespace AZ::TangentGeneration::Mesh::MikkT bool GenerateTangents(const AZ::SceneAPI::DataTypes::IMeshData* meshData, const AZ::SceneAPI::DataTypes::IMeshVertexUVData* uvData, AZ::SceneAPI::DataTypes::IMeshVertexTangentData* outTangentData, - AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* outBitangentData); + AZ::SceneAPI::DataTypes::IMeshVertexBitangentData* outBitangentData, + AZ::SceneAPI::DataTypes::MikkTSpaceMethod tSpaceMethod = AZ::SceneAPI::DataTypes::MikkTSpaceMethod::TSpace); } // namespace AZ::TangentGeneration::MikkT diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp index 1771d9b4a6..6a5344ed9b 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp @@ -137,16 +137,9 @@ namespace ScriptCanvasBuilder if (!ScriptCanvas::Grammar::IsParserGeneratedId(entityId.first)) { - auto graphEntityId = variables.FindVariable(entityId.first); - if (!graphEntityId) - { - AZ_Error("ScriptCanvasBuilder", false, "Missing EntityId from graph data that was just parsed"); - continue; - } - - // copy to override list for editor display - if (graphEntityId->IsComponentProperty()) + if (auto graphEntityId = variables.FindVariable(entityId.first); graphEntityId && graphEntityId->IsComponentProperty()) { + // copy to override list for editor display m_overrides.push_back(*graphEntityId); auto& overrideValue = m_overrides.back(); overrideValue.SetScriptInputControlVisibility(AZ::Edit::PropertyVisibility::Hide); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp index f957071bc1..9a1ef73126 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp @@ -1943,7 +1943,8 @@ namespace ScriptCanvas { const auto requirement = ParseConstructionRequirement(variable); - if (requirement == Grammar::VariableConstructionRequirement::None || (requirement != Grammar::VariableConstructionRequirement::Static && !execution->IsStartCall())) + if (requirement == Grammar::VariableConstructionRequirement::None + || requirement != Grammar::VariableConstructionRequirement::Static && execution != m_model.GetStart()) { m_dotLua.WriteLineIndented("local %s = %s", variable->m_name.data(), ToValueString(variable->m_datum, m_configuration).data()); } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp index 31bd5cbe41..cc9e60e765 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp @@ -186,20 +186,6 @@ namespace ScriptCanvas if (auto editContext = serializeContext->GetEditContext()) { - auto propertyChoices = [] { - AZStd::vector< AZStd::pair> choices; - choices.emplace_back(AZStd::make_pair(VariableFlags::InitialValueSource::Graph, s_InitialValueSourceNames[0])); - choices.emplace_back(AZStd::make_pair(VariableFlags::InitialValueSource::Component, s_InitialValueSourceNames[1])); - return choices; - }; - - auto scopeChoices = [] { - AZStd::vector< AZStd::pair> choices; - choices.emplace_back(AZStd::make_pair(VariableFlags::Scope::Graph, s_ScopeNames[0])); - choices.emplace_back(AZStd::make_pair(VariableFlags::Scope::Function, s_ScopeNames[1])); - return choices; - }; - editContext->Class("Variable", "Represents a Variable field within a Script Canvas Graph") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Visibility, &GraphVariable::GetVisibility) @@ -208,7 +194,7 @@ namespace ScriptCanvas ->Attribute(AZ::Edit::Attributes::DescriptionTextOverride, &GraphVariable::GetDescriptionOverride) ->DataElement(AZ::Edit::UIHandlers::ComboBox, &GraphVariable::m_InitialValueSource, "Initial Value Source", "Variables can get their values from within the graph or through component properties.") - ->Attribute(AZ::Edit::Attributes::GenericValueList, propertyChoices) + ->Attribute(AZ::Edit::Attributes::GenericValueList, &GraphVariable::GetPropertyChoices) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GraphVariable::OnInitialValueSourceChanged) ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) ->Attribute(AZ::Edit::Attributes::Visibility, &GraphVariable::GetInputControlVisibility) @@ -219,7 +205,7 @@ namespace ScriptCanvas ->DataElement(AZ::Edit::UIHandlers::ComboBox, &GraphVariable::m_scope, "Scope", "Controls the scope of this variable. i.e. If this is exposed as input to this script, or output from this script, or if the variable is just locally scoped.") ->Attribute(AZ::Edit::Attributes::Visibility, &GraphVariable::GetScopeControlVisibility) - ->Attribute(AZ::Edit::Attributes::GenericValueList, scopeChoices) + ->Attribute(AZ::Edit::Attributes::GenericValueList, &GraphVariable::GetScopeChoices) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GraphVariable::OnScopeTypedChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &GraphVariable::m_networkProperties, "Network Properties", "Enables whether or not this value should be network synchronized") diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h index 9ccc00ce14..fd15ac95ee 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h @@ -182,6 +182,22 @@ namespace ScriptCanvas private: + AZStd::vector> GetPropertyChoices() const + { + AZStd::vector< AZStd::pair> choices; + choices.emplace_back(AZStd::make_pair(static_cast(VariableFlags::InitialValueSource::Graph), s_InitialValueSourceNames[0])); + choices.emplace_back(AZStd::make_pair(static_cast(VariableFlags::InitialValueSource::Component), s_InitialValueSourceNames[1])); + return choices; + } + + AZStd::vector> GetScopeChoices() const + { + AZStd::vector< AZStd::pair> choices; + choices.emplace_back(AZStd::make_pair(static_cast(VariableFlags::Scope::Graph), s_ScopeNames[0])); + choices.emplace_back(AZStd::make_pair(static_cast(VariableFlags::Scope::Function), s_ScopeNames[1])); + return choices; + } + bool IsInFunction() const; void OnScopeTypedChanged(); diff --git a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_EntityIdInputForOnGraphStart.scriptcanvas b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_EntityIdInputForOnGraphStart.scriptcanvas new file mode 100644 index 0000000000..1614e30678 --- /dev/null +++ b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_EntityIdInputForOnGraphStart.scriptcanvas @@ -0,0 +1,1047 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp index 2a563b0904..df29a56fd5 100644 --- a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp +++ b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp @@ -89,6 +89,11 @@ TEST_F(ScriptCanvasTestFixture, ProveError) EXPECT_TRUE(false); } +TEST_F(ScriptCanvasTestFixture, EntityIdInputForOnGraphStart) +{ + RunUnitTestGraph("LY_SC_UnitTest_EntityIdInputForOnGraphStart"); +} + TEST_F(ScriptCanvasTestFixture, ParseErrorOnKnownNull) { ExpectParseError("LY_SC_UnitTest_ParseErrorOnKnownNull"); diff --git a/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp b/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp index 6739a54ddd..42fa05473c 100644 --- a/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp +++ b/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp @@ -62,7 +62,7 @@ namespace StartingPointInput ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true) ->Attribute("BrowseIcon", ":/stylesheet/img/UI20/browse-edit-select-files.svg") ->Attribute("EditButton", "") - ->Attribute("EditDescription", "Open in Input Bindings Editor") + ->Attribute("EditDescription", "Open in Asset Editor") ->DataElement(AZ::Edit::UIHandlers::SpinBox, &InputConfigurationComponent::m_localPlayerIndex, "Local player index", "The player index that this component will receive input from (0 based, -1 means all controllers).\n" "Will only work on platforms such as PC where the local user id corresponds to the local player index.\n" diff --git a/Gems/WhiteBox/Code/Include/WhiteBox/WhiteBoxToolApi.h b/Gems/WhiteBox/Code/Include/WhiteBox/WhiteBoxToolApi.h index 02b563c970..941368e5ac 100644 --- a/Gems/WhiteBox/Code/Include/WhiteBox/WhiteBoxToolApi.h +++ b/Gems/WhiteBox/Code/Include/WhiteBox/WhiteBoxToolApi.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include namespace AZ::IO { diff --git a/Tools/LyTestTools/ly_test_tools/benchmark/data_aggregator.py b/Tools/LyTestTools/ly_test_tools/benchmark/data_aggregator.py new file mode 100644 index 0000000000..8a62c2e150 --- /dev/null +++ b/Tools/LyTestTools/ly_test_tools/benchmark/data_aggregator.py @@ -0,0 +1,163 @@ +""" +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 +""" + +from argparse import ArgumentParser +import json +from pathlib import Path +import time +import subprocess + +from ly_test_tools.mars.filebeat_client import FilebeatClient + +class BenchmarkPathException(Exception): + """Custom Exception class for invalid benchmark file paths.""" + pass + +class BenchmarkDataAggregator(object): + def __init__(self, workspace, logger, test_suite): + self.build_dir = workspace.paths.build_directory() + self.results_dir = Path(workspace.paths.project(), 'user/Scripts/PerformanceBenchmarks') + self.test_suite = test_suite + self.filebeat_client = FilebeatClient(logger) + + def _update_pass(self, pass_stats, entry): + ''' + Modifies pass_stats dict keyed by pass name with the time recorded in a pass timestamp entry. + + :param pass_stats: dict aggregating statistics from each pass (key: pass name, value: dict with stats) + :param entry: dict representing the timestamp entry of a pass + :return: Time (in nanoseconds) recorded by this pass + ''' + name = entry['passName'] + time_ns = entry['timestampResultInNanoseconds'] + pass_entry = pass_stats.get(name, { 'totalTime': 0, 'maxTime': 0 }) + + pass_entry['maxTime'] = max(time_ns, pass_entry['maxTime']) + pass_entry['totalTime'] += time_ns + pass_stats[name] = pass_entry + return time_ns + + + def _process_benchmark(self, benchmark_dir, benchmark_metadata): + ''' + Aggregates data from results from a single benchmark contained in a subdirectory of self.results_dir. + + :param benchmark_dir: Path of directory containing the benchmark results + :param benchmark_metadata: Dict with benchmark metadata mutated with additional info from metadata file + :return: Tuple with two indexes: + [0]: Dict aggregating statistics from frame times (key: stat name) + [1]: Dict aggregating statistics from pass times (key: pass name, value: dict with stats) + ''' + # Parse benchmark metadata + metadata_file = benchmark_dir / 'benchmark_metadata.json' + if metadata_file.exists(): + data = json.loads(metadata_file.read_text()) + benchmark_metadata.update(data['ClassData']) + else: + raise BenchmarkPathException(f'Metadata file could not be found at {metadata_file}') + + # data structures aggregating statistics from timestamp logs + frame_stats = { 'count': 0, 'totalTime': 0, 'maxTime': 0, 'minTime': float('inf') } + pass_stats = {} # key: pass name, value: dict with totalTime and maxTime keys + + # this allows us to add additional data if necessary, e.g. frame_test_timestamps.json + is_timestamp_file = lambda file: file.name.startswith('frame') and file.name.endswith('_timestamps.json') + + # parse benchmark files + for file in benchmark_dir.iterdir(): + if file.is_dir() or not is_timestamp_file(file): + continue + + data = json.loads(file.read_text()) + entries = data['ClassData']['timestampEntries'] + + frame_time = sum(self._update_pass(pass_stats, entry) for entry in entries) + + frame_stats['totalTime'] += frame_time + frame_stats['maxTime'] = max(frame_time, frame_stats['maxTime']) + frame_stats['minTime'] = min(frame_time, frame_stats['minTime']) + frame_stats['count'] += 1 + + if frame_stats['count'] < 1: + raise BenchmarkPathException(f'No frame timestamp logs were found in {benchmark_dir}') + + return frame_stats, pass_stats + + def _generate_payloads(self, benchmark_metadata, frame_stats, pass_stats): + ''' + Generates payloads to send to Filebeat based on aggregated stats and metadata. + + :param benchmark_metadata: Dict of benchmark metadata + :param frame_stats: Dict of aggregated frame statistics + :param pass_stats: Dict of aggregated pass statistics + :return payloads: List of tuples, each with two indexes: + [0]: Elasticsearch index suffix associated with the payload + [1]: Payload dict to deliver to Filebeat + ''' + ns_to_ms = lambda ns: ns / 1e6 + payloads = [] + + # calculate statistics based on aggregated frame data + frame_time_avg = frame_stats['totalTime'] / frame_stats['count'] + frame_payload = { + 'frameTime': { + 'avg': ns_to_ms(frame_time_avg), + 'max': ns_to_ms(frame_stats['maxTime']), + 'min': ns_to_ms(frame_stats['minTime']) + } + } + # add benchmark metadata to payload + frame_payload.update(benchmark_metadata) + payloads.append(('frame_data', frame_payload)) + + # calculate statistics for each pass + for name, stat in pass_stats.items(): + avg_ms = ns_to_ms(stat['totalTime'] / frame_stats['count']) + max_ms = ns_to_ms(stat['maxTime']) + + pass_payload = { + 'passName': name, + 'passTime': { + 'avg': avg_ms, + 'max': max_ms + } + } + # add benchmark metadata to payload + pass_payload.update(benchmark_metadata) + payloads.append(('pass_data', pass_payload)) + + return payloads + + def upload_metrics(self, rhi): + ''' + Uploads metrics aggregated from all the benchmarks run in a test suite to filebeat. + + :param rhi: The RHI the benchmarks were run on + ''' + start_timestamp = time.time() + + git_commit_data = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=self.build_dir) + git_commit_hash = git_commit_data.decode('ascii').strip() + build_date = time.strftime('%m/%d/%y', time.localtime(start_timestamp)) # use gmtime if GMT is preferred + + for benchmark_dir in self.results_dir.iterdir(): + if not benchmark_dir.is_dir(): + continue + + benchmark_metadata = { + 'gitCommitAndBuildDate': f'{git_commit_hash} {build_date}', + 'RHI': rhi + } + frame_stats, pass_stats = self._process_benchmark(benchmark_dir, benchmark_metadata) + payloads = self._generate_payloads(benchmark_metadata, frame_stats, pass_stats) + + for index_suffix, payload in payloads: + self.filebeat_client.send_event( + payload, + f'ly_atom.performance_metrics.{self.test_suite}.{index_suffix}', + start_timestamp + ) diff --git a/cmake/3rdParty/FindWwise.cmake b/cmake/3rdParty/FindWwise.cmake index fa73ced3cc..8cd6db31dd 100644 --- a/cmake/3rdParty/FindWwise.cmake +++ b/cmake/3rdParty/FindWwise.cmake @@ -44,7 +44,7 @@ foreach(test_path ${WWISE_SDK_PATHS}) is_valid_sdk(${test_path} found_sdk) if(found_sdk) # Update the Wwise Install Path cache variable - set(LY_WWISE_INSTALL_PATH "${test_path}" CACHE PATH "Path to Wwise version ${WWISE_VERSION} installation." FORCE) + set(LY_WWISE_INSTALL_PATH "${test_path}") break() endif() endforeach() @@ -52,12 +52,10 @@ endforeach() if(NOT found_sdk) # If we don't find a path that appears to be a valid Wwise install, we can bail here. # No 3rdParty::Wwise target will exist, so that can be checked elsewhere. - message(STATUS "Wwise SDK version ${WWISE_VERSION} was not found.") return() -else() - message(STATUS "Using Wwise SDK at ${LY_WWISE_INSTALL_PATH}") endif() +message(STATUS "Using Wwise SDK at ${LY_WWISE_INSTALL_PATH}") set(WWISE_COMMON_LIB_NAMES # Core AK diff --git a/cmake/3rdPartyPackages.cmake b/cmake/3rdPartyPackages.cmake index 2228e36653..efe67b4d24 100644 --- a/cmake/3rdPartyPackages.cmake +++ b/cmake/3rdPartyPackages.cmake @@ -40,22 +40,18 @@ endif() # If you keep packages after downloading, then they can be moved to a network share # or checked into source control so that others on the same project can avoid re-downloading set(LY_PACKAGE_KEEP_AFTER_DOWNLOADING TRUE CACHE BOOL "If enabled, packages will be kept after downloading them for later re-use") -set(LY_PACKAGE_DOWNLOAD_CACHE_LOCATION ${LY_3RDPARTY_PATH}/downloaded_packages CACHE PATH "You can make it store the packages in a folder of your choosing") +set(LY_PACKAGE_DOWNLOAD_CACHE_LOCATION @LY_3RDPARTY_PATH@/downloaded_packages CACHE PATH "Download location for packages (Defaults to @LY_3RDPARTY_PATH@/downloaded_packages)") if (DEFINED ENV{LY_PACKAGE_DOWNLOAD_CACHE_LOCATION}) set(LY_PACKAGE_DOWNLOAD_CACHE_LOCATION $ENV{LY_PACKAGE_DOWNLOAD_CACHE_LOCATION}) endif() +string(CONFIGURE ${LY_PACKAGE_DOWNLOAD_CACHE_LOCATION} LY_PACKAGE_DOWNLOAD_CACHE_LOCATION @ONLY) # LY_PACKAGE_UNPACK_LOCATION - you can change this to any path reachable. -set(LY_PACKAGE_UNPACK_LOCATION ${LY_3RDPARTY_PATH}/packages CACHE PATH "Location to unpack downloaded packages to") +set(LY_PACKAGE_UNPACK_LOCATION @LY_3RDPARTY_PATH@/packages CACHE PATH "Unpack location of downloaded packages (Defaults to @LY_3RDPARTY_PATH@/packages)") if (DEFINED ENV{LY_PACKAGE_UNPACK_LOCATION}) set(LY_PACKAGE_UNPACK_LOCATION $ENV{LY_PACKAGE_UNPACK_LOCATION}) endif() - -# note that sometimes the user configures first without populating LY_3RDPARTY_PATH -# in that case, we'll try overwriting the cache value, only if it is blank: -if (NOT LY_PACKAGE_UNPACK_LOCATION) - set(LY_PACKAGE_UNPACK_LOCATION ${LY_3RDPARTY_PATH}/packages CACHE PATH "Location to unpack downloaded packages to" FORCE ) -endif() +string(CONFIGURE ${LY_PACKAGE_UNPACK_LOCATION} LY_PACKAGE_UNPACK_LOCATION @ONLY) # while developing you can set one or both to true to force auto downloads from your local cache set(LY_PACKAGE_VALIDATE_CONTENTS FALSE CACHE BOOL "If enabled, will fully validate every file in every package based on the SHA256SUMS file from the package") diff --git a/cmake/Deployment.cmake b/cmake/Deployment.cmake index ab18e3bfec..b6b7aa1869 100644 --- a/cmake/Deployment.cmake +++ b/cmake/Deployment.cmake @@ -9,6 +9,6 @@ # Define options that control the different options for deployment for target platforms set(LY_ASSET_DEPLOY_MODE "LOOSE" CACHE STRING "Set the Asset deployment when deploying to the target platform (LOOSE, PAK, VFS)") -set(LY_OVERRIDE_PAK_FOLDER_ROOT "" CACHE STRING "Optional root path to where Pak file folders are stored. By default, blank will use a predefined 'paks' root.") +set(LY_ASSET_OVERRIDE_PAK_FOLDER_ROOT "" CACHE STRING "Optional root path to where Pak file folders are stored. By default, blank will use a predefined 'paks' root.") diff --git a/cmake/EngineJson.cmake b/cmake/EngineJson.cmake index 180a6395b0..f175ff5a8b 100644 --- a/cmake/EngineJson.cmake +++ b/cmake/EngineJson.cmake @@ -10,7 +10,8 @@ include_guard() -set(LY_EXTERNAL_SUBDIRS "" CACHE STRING "List of subdirectories to recurse into when running cmake against the engine's CMakeLists.txt") +set(LY_EXTERNAL_SUBDIRS "" CACHE STRING "Additional list of subdirectory to recurse into via the cmake `add_subdirectory()` command. \ + The subdirectories are included after the restricted platform folders have been visited by a call to `add_subdirectory(restricted/\${restricted_platform})`") #! read_engine_external_subdirs # Read the external subdirectories from the engine.json file diff --git a/cmake/FileUtil.cmake b/cmake/FileUtil.cmake index 69a6ecc377..4607e14452 100644 --- a/cmake/FileUtil.cmake +++ b/cmake/FileUtil.cmake @@ -110,7 +110,7 @@ platform=${PAL_PLATFORM_NAME} game_projects=${LY_PROJECTS_TARGET_NAME} asset_deploy_mode=${LY_ASSET_DEPLOY_MODE} asset_deploy_type=${LY_ASSET_DEPLOY_ASSET_TYPE} -override_pak_root=${LY_OVERRIDE_PAK_FOLDER_ROOT} +override_pak_root=${LY_ASSET_OVERRIDE_PAK_FOLDER_ROOT} ") endfunction() diff --git a/cmake/LYTestWrappers.cmake b/cmake/LYTestWrappers.cmake index cafefb201c..535f00f58d 100644 --- a/cmake/LYTestWrappers.cmake +++ b/cmake/LYTestWrappers.cmake @@ -276,7 +276,7 @@ endfunction() # function(ly_add_pytest) - if(NOT PAL_TRAIT_TEST_PYTEST_SUPPORTED) + if(NOT PAL_TRAIT_TEST_PYTEST_SUPPORTED OR NOT PAL_TRAIT_TEST_LYTESTTOOLS_SUPPORTED) return() endif() diff --git a/cmake/O3DEJson.cmake b/cmake/O3DEJson.cmake index af55075fb8..daae3b4529 100644 --- a/cmake/O3DEJson.cmake +++ b/cmake/O3DEJson.cmake @@ -8,8 +8,6 @@ include_guard() -set(LY_EXTERNAL_SUBDIRS "" CACHE STRING "List of subdirectories to recurse into when running cmake against the engine's CMakeLists.txt") - #! read_json_external_subdirs # Read the "external_subdirectories" array from a *.json file # External subdirectories are any folders with CMakeLists.txt in them diff --git a/cmake/Platform/Linux/PAL_linux.cmake b/cmake/Platform/Linux/PAL_linux.cmake index 2f60b7e2c8..c137538ac0 100644 --- a/cmake/Platform/Linux/PAL_linux.cmake +++ b/cmake/Platform/Linux/PAL_linux.cmake @@ -37,3 +37,7 @@ set(LY_ASSET_DEPLOY_ASSET_TYPE "pc" CACHE STRING "Set the asset type for deploym # Set the python cmd tool ly_set(LY_PYTHON_CMD ${CMAKE_CURRENT_SOURCE_DIR}/python/python.sh) + +# Set the default window manager that applications should be using on Linux +# Note: Only ("xcb", "wayland", or "xlib" should be considered) +set(PAL_TRAIT_LINUX_WINDOW_MANAGER "xcb" CACHE STRING "Sets the Window Manager type to use when configuring Linux (xcb, wayland, or xlib)") diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index d5663dadfb..7c62a4984c 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -6,11 +6,11 @@ # # -set(CPACK_WIX_ROOT "" CACHE PATH "Path to the WiX install path") +set(LY_INSTALLER_WIX_ROOT "" CACHE PATH "Path to the WiX install path") -if(CPACK_WIX_ROOT) - if(NOT EXISTS ${CPACK_WIX_ROOT}) - message(FATAL_ERROR "Invalid path supplied for CPACK_WIX_ROOT argument") +if(LY_INSTALLER_WIX_ROOT) + if(NOT EXISTS ${LY_INSTALLER_WIX_ROOT}) + message(FATAL_ERROR "Invalid path supplied for LY_INSTALLER_WIX_ROOT argument") endif() else() # early out as no path to WiX has been supplied effectively disabling support diff --git a/cmake/TestImpactFramework/LYTestImpactFramework.cmake b/cmake/TestImpactFramework/LYTestImpactFramework.cmake index 48bc5bf3df..61cc8c200a 100644 --- a/cmake/TestImpactFramework/LYTestImpactFramework.cmake +++ b/cmake/TestImpactFramework/LYTestImpactFramework.cmake @@ -6,11 +6,8 @@ # # -# Switch to enable/disable test impact analysis (and related build targets) -option(LY_TEST_IMPACT_ACTIVE "Enable test impact framework" OFF) - # Path to test instrumentation binary -option(LY_TEST_IMPACT_INSTRUMENTATION_BIN "Path to test impact framework instrumentation binary" OFF) +set(LY_TEST_IMPACT_INSTRUMENTATION_BIN "" CACHE PATH "Path to test impact framework instrumentation binary") # Name of test impact framework console static library target set(LY_TEST_IMPACT_CONSOLE_STATIC_TARGET "TestImpact.Frontend.Console.Static") @@ -213,9 +210,9 @@ function(ly_test_impact_extract_python_test_params COMPOSITE_TEST COMPOSITE_SUIT list(GET suite_components 2 test_timeout) # Get python script path relative to repo root ly_test_impact_rebase_file_to_repo_root( - ${script_path} + "${script_path}" script_path - ${LY_ROOT_FOLDER} + "${LY_ROOT_FOLDER}" ) set(suite_params "{ \"suite\": \"${test_suite}\", \"script\": \"${script_path}\", \"timeout\": ${test_timeout} }") list(APPEND test_suites "${suite_params}") @@ -259,7 +256,8 @@ function(ly_test_impact_write_test_enumeration_file TEST_ENUMERATION_TEMPLATE_FI ly_test_impact_extract_google_test_params(${test} "${test_params}" test_name test_suites) list(APPEND google_benchmarks " { \"name\": \"${test_name}\", \"launch_method\": \"${launch_method}\", \"suites\": [${test_suites}] }") else() - message("${test_name} is of unknown type (TEST_LIBRARY property is empty)") + ly_test_impact_extract_python_test_params(${test} "${test_params}" test_name test_suites) + message("${test_name} is of unknown type (TEST_LIBRARY property is \"${test_type}\")") list(APPEND unknown_tests " { \"name\": \"${test}\", \"type\": \"${test_type}\" }") endif() endforeach() @@ -440,7 +438,7 @@ endfunction() #! ly_test_impact_post_step: runs the post steps to be executed after all other cmake scripts have been executed. function(ly_test_impact_post_step) - if(NOT ${LY_TEST_IMPACT_ACTIVE}) + if(NOT LY_TEST_IMPACT_INSTRUMENTATION_BIN) return() endif() diff --git a/engine.json b/engine.json index 07b4b7baa2..5d862779c0 100644 --- a/engine.json +++ b/engine.json @@ -19,6 +19,7 @@ "Gems/AWSCore", "Gems/AWSGameLift", "Gems/AWSMetrics", + "Gems/BarrierInput", "Gems/Blast", "Gems/Camera", "Gems/CameraFramework", diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index ae6ac59c3c..9aa09e136e 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -266,32 +266,42 @@ def CheckoutRepo(boolean disableSubmodules = false) { palRm('commitdate') } +def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) { + unstash name: 'incremental_build_script' + + def pythonCmd = '' + if(env.IS_UNIX) pythonCmd = 'sudo -E python3 -u ' + else pythonCmd = 'python3 -u ' + + if(recreateVolume) { + palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume', winSlashReplacement=false) + } + timeout(5) { + palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false) + } + + if(env.IS_UNIX) { + sh label: 'Setting volume\'s ownership', + script: """ + if sudo test ! -d "${workspace}"; then + sudo mkdir -p ${workspace} + cd ${workspace}/.. + sudo chown -R lybuilder:root . + fi + """ + } +} + def PreBuildCommonSteps(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean mount = true, boolean disableSubmodules = false) { echo 'Starting pre-build common steps...' if (mount) { - unstash name: 'incremental_build_script' - - def pythonCmd = '' - if(env.IS_UNIX) pythonCmd = 'sudo -E python3 -u ' - else pythonCmd = 'python3 -u ' - - if(env.RECREATE_VOLUME?.toBoolean()) { - palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume', winSlashReplacement=false) - } - timeout(5) { - palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false) - } - - if(env.IS_UNIX) { - sh label: 'Setting volume\'s ownership', - script: """ - if sudo test ! -d "${workspace}"; then - sudo mkdir -p ${workspace} - cd ${workspace}/.. - sudo chown -R lybuilder:root . - fi - """ + if(env.RECREATE_VOLUME?.toBoolean()){ + echo 'Starting to recreating drive...' + HandleDriveMount(snapshot, repositoryName, projectName, pipeline, branchName, platform, buildType, workspace, true) + } else { + echo 'Starting to mounting drive...' + HandleDriveMount(snapshot, repositoryName, projectName, pipeline, branchName, platform, buildType, workspace, false) } } @@ -399,10 +409,14 @@ def PostBuildCommonSteps(String workspace, boolean mount = true) { } } -def CreateSetupStage(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipelineName, String branchName, String platformName, String jobName, Map environmentVars) { +def CreateSetupStage(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipelineName, String branchName, String platformName, String jobName, Map environmentVars, boolean onlyMountEBSVolume = false) { return { stage('Setup') { - PreBuildCommonSteps(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME']) + if(onlyMountEBSVolume) { + HandleDriveMount(snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], false) + } else { + PreBuildCommonSteps(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME']) + } } } } @@ -439,6 +453,128 @@ def CreateTeardownStage(Map environmentVars) { } } +def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName, boolean onlyMountEBSVolume = false) { + def nodeLabel = envVars['NODE_LABEL'] + return { + node("${nodeLabel}") { + if(isUnix()) { // Has to happen inside a node + envVars['IS_UNIX'] = 1 + } + withEnv(GetEnvStringList(envVars)) { + def build_job_name = build_job.key + try { + CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call() + + if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages + build_job.value.steps.each { build_step -> + build_job_name = build_step + CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call() + } + } else { + CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call() + } + } + catch(Exception e) { + // https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java + // {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED} + def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE' + if (currentResult == 'FAILURE') { + currentBuild.result = 'FAILURE' + error "FAILURE: ${e}" + } else if (currentResult == 'UNSTABLE') { + currentBuild.result = 'UNSTABLE' + unstable(message: "UNSTABLE: ${e}") + } + } + finally { + def params = platform.value.build_types[build_job_name].PARAMETERS + if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') { + def output_directory = params.OUTPUT_DIRECTORY + def configuration = params.CONFIGURATION + CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call() + } + if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') { + CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call() + } + CreateTeardownStage(envVars).call() + } + } + } + } +} + +// Used in CreateBuildJobs() to preprocess the build_job steps to programically create +// Node sections with a set of steps that can run on that node. +class PipeStepJobData { + String m_nodeLabel = "" + def m_steps = [] + + PipeStepJobData(String label) { + this.m_nodeLabel = label + } + + def addStep(def step) { + this.m_steps.add(step) + } +} + +def CreateBuildJobs(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName) { + + // if this is a pipeline, split jobs based on the NODE_LABEL + if(build_job.value.steps) { + def defaultLabel = envVars['NODE_LABEL'] + def lastNodeLable = "" + def jobList = [] + def currentIdx = -1; + + // iterate the steps to build the order of node label + steps sets. + // Order matters, as it is executed from first to last. + // example layout. + // node A + // step 1 + // step 2 + // node B + // step 3 + // node C + // step 4 + build_job.value.steps.each { build_step -> + //if node label defined + if(platform.value.build_types[build_step] && platform.value.build_types[build_step].PIPELINE_ENV && + platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL']) { + + //if the last node label doen't match the new one, append it. + if(platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL'] != lastNodeLable) { + lastNodeLable = platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL'] + jobList.add(new PipeStepJobData(lastNodeLable)) + currentIdx++ + } + } + //no label define, so it needs to run on the default node label + else if(lastNodeLable != defaultLabel) { //if the last node is not the default, append default + lastNodeLable = defaultLabel + jobList.add(new PipeStepJobData(lastNodeLable)) + currentIdx++ + } + //add the build_step to the current node + jobList[currentIdx].addStep(build_step) + } + + return { + jobList.eachWithIndex{ element, idx -> + //update the node label + steps to the discovered data + envVars['NODE_LABEL'] = element.m_nodeLabel + build_job.value.steps = element.m_steps + //no any additional nodes just mount the drive, do not handle clean parameters as that will be done by the first node. + boolean onlyMountEBSVolume = idx != 0; + //add this node + CreateSingleNode(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName, onlyMountEBSVolume).call() + } + } + } else { + return CreateSingleNode(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName) + } +} + def projectName = '' def pipelineName = '' def branchName = '' @@ -527,55 +663,9 @@ try { if (IsJobEnabled(branchName, build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName) envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this - def nodeLabel = envVars['NODE_LABEL'] someBuildHappened = true - buildConfigs["${platform.key} [${build_job.key}]"] = { - node("${nodeLabel}") { - if(isUnix()) { // Has to happen inside a node - envVars['IS_UNIX'] = 1 - } - withEnv(GetEnvStringList(envVars)) { - def build_job_name = build_job.key - try { - CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars).call() - - if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages - build_job.value.steps.each { build_step -> - build_job_name = build_step - CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call() - } - } else { - CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call() - } - } - catch(Exception e) { - // https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java - // {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED} - def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE' - if (currentResult == 'FAILURE') { - currentBuild.result = 'FAILURE' - error "FAILURE: ${e}" - } else if (currentResult == 'UNSTABLE') { - currentBuild.result = 'UNSTABLE' - unstable(message: "UNSTABLE: ${e}") - } - } - finally { - def params = platform.value.build_types[build_job_name].PARAMETERS - if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') { - def output_directory = params.OUTPUT_DIRECTORY - def configuration = params.CONFIGURATION - CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call() - } - if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') { - CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call() - } - CreateTeardownStage(envVars).call() - } - } - } - } + buildConfigs["${platform.key} [${build_job.key}]"] = CreateBuildJobs(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName) } } } diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index 0e8a6b8746..235e1406ab 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -132,7 +132,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_TEST_IMPACT_ACTIVE=1 -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=!TEST_IMPACT_WIN_BINARY!", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=!TEST_IMPACT_WIN_BINARY!", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -328,7 +328,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DCPACK_WIX_ROOT=\"!WIX! \"", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"", "EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=https://dkb1uj4hs9ikv.cloudfront.net -DLY_INSTALLER_LICENSE_URL=https://www.o3debinaries.org/license -DLY_INSTALLER_3RD_PARTY_LICENSE_URL=https://dkb1uj4hs9ikv.cloudfront.net/SPDX-Licenses.txt", "CPACK_BUCKET": "spectra-prism-staging-us-west-2", "CMAKE_LY_PROJECTS": "", diff --git a/scripts/ctest/CMakeLists.txt b/scripts/ctest/CMakeLists.txt index 45fded24ed..98ea21e938 100644 --- a/scripts/ctest/CMakeLists.txt +++ b/scripts/ctest/CMakeLists.txt @@ -42,5 +42,6 @@ ly_add_test( TEST_COMMAND ${LY_PYTHON_CMD} ${CMAKE_CURRENT_LIST_DIR}/ctest_driver_test.py -x ${CMAKE_CTEST_COMMAND} --build-path ${CMAKE_BINARY_DIR} + TEST_LIBRARY pytest )