diff --git a/Code/Editor/Core/QtEditorApplication.cpp b/Code/Editor/Core/QtEditorApplication.cpp index a4aab24be4..2439228476 100644 --- a/Code/Editor/Core/QtEditorApplication.cpp +++ b/Code/Editor/Core/QtEditorApplication.cpp @@ -16,18 +16,11 @@ #include #include #include -#if defined(AZ_PLATFORM_WINDOWS) -#include -#include -#endif + #include #include #include -// AzFramework -#if defined(AZ_PLATFORM_WINDOWS) -# include -#endif // defined(AZ_PLATFORM_WINDOWS) // AzQtComponents #include @@ -39,7 +32,6 @@ #include "Settings.h" #include "CryEdit.h" - enum { // in milliseconds @@ -241,7 +233,6 @@ namespace Editor EditorQtApplication::EditorQtApplication(int& argc, char** argv) : AzQtApplication(argc, argv) - , m_inWinEventFilter(false) , m_stylesheet(new AzQtComponents::O3DEStylesheet(this)) , m_idleTimer(new QTimer(this)) { @@ -368,86 +359,10 @@ namespace Editor UninstallEditorTranslators(); } -#if defined(AZ_PLATFORM_WINDOWS) - bool EditorQtApplication::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long* result) + EditorQtApplication* EditorQtApplication::instance() { - MSG* msg = (MSG*)message; - - if (msg->message == WM_MOVING || msg->message == WM_SIZING) - { - m_isMovingOrResizing = true; - } - else if (msg->message == WM_EXITSIZEMOVE) - { - m_isMovingOrResizing = false; - } - - // Prevent the user from being able to move the window in game mode. - // This is done during the hit test phase to bypass the native window move messages. If the window - // decoration wrapper title bar contains the cursor, set the result to HTCLIENT instead of - // HTCAPTION. - if (msg->message == WM_NCHITTEST && GetIEditor()->IsInGameMode()) - { - const LRESULT defWinProcResult = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); - if (defWinProcResult == 1) - { - if (QWidget* widget = QWidget::find((WId)msg->hwnd)) - { - if (auto wrapper = qobject_cast(widget)) - { - AzQtComponents::TitleBar* titleBar = wrapper->titleBar(); - const short global_x = static_cast(LOWORD(msg->lParam)); - const short global_y = static_cast(HIWORD(msg->lParam)); - - const QPoint globalPos = QHighDpi::fromNativePixels(QPoint(global_x, global_y), widget->window()->windowHandle()); - const QPoint local = titleBar->mapFromGlobal(globalPos); - if (titleBar->draggableRect().contains(local) && !titleBar->isTopResizeArea(globalPos)) - { - *result = HTCLIENT; - return true; - } - } - } - } - } - - // Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system. - // These events are only broadcast in game mode. In Editor mode, RenderViewportWidget creates synthetic - // keyboard and mouse events via Qt. - if (GetIEditor()->IsInGameMode()) - { - if (msg->message == WM_INPUT) - { - UINT rawInputSize; - const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); - GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, nullptr, &rawInputSize, rawInputHeaderSize); - - AZStd::array rawInputBytesArray; - LPBYTE rawInputBytes = rawInputBytesArray.data(); - - [[maybe_unused]] const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); - CRY_ASSERT(bytesCopied == rawInputSize); - - RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; - CRY_ASSERT(rawInput); - - AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput); - - return false; - } - else if (msg->message == WM_DEVICECHANGE) - { - if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED - { - AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent); - } - return true; - } - } - - return false; + return static_cast(QApplication::instance()); } -#endif void EditorQtApplication::OnEditorNotifyEvent(EEditorNotifyEvent event) { @@ -505,11 +420,6 @@ namespace Editor return m_stylesheet->GetColorByName(name); } - EditorQtApplication* EditorQtApplication::instance() - { - return static_cast(QApplication::instance()); - } - bool EditorQtApplication::IsActive() { return applicationState() == Qt::ApplicationActive; @@ -613,42 +523,6 @@ namespace Editor case QEvent::KeyRelease: m_pressedKeys.remove(reinterpret_cast(event)->key()); break; -#ifdef AZ_PLATFORM_WINDOWS - case QEvent::Leave: - { - // if we receive a leave event for a toolbar on Windows - // check first whether we really left it. If we didn't: start checking - // for the tool bar under the mouse by timer to check when we really left. - // Synthesize a new leave event then. Workaround for LY-69788 - auto toolBarAt = [](const QPoint& pos) -> QToolBar* { - QWidget* widget = qApp->widgetAt(pos); - while (widget != nullptr) - { - if (QToolBar* tb = qobject_cast(widget)) - { - return tb; - } - widget = widget->parentWidget(); - } - return nullptr; - }; - if (object == toolBarAt(QCursor::pos())) - { - QTimer* t = new QTimer(object); - t->start(100); - connect(t, &QTimer::timeout, object, [t, object, toolBarAt]() { - if (object != toolBarAt(QCursor::pos())) - { - QEvent event(QEvent::Leave); - qApp->sendEvent(object, &event); - t->deleteLater(); - } - }); - return true; - } - break; - } -#endif default: break; } diff --git a/Code/Editor/Core/QtEditorApplication.h b/Code/Editor/Core/QtEditorApplication.h index 2e3612095e..0d702bf647 100644 --- a/Code/Editor/Core/QtEditorApplication.h +++ b/Code/Editor/Core/QtEditorApplication.h @@ -72,14 +72,12 @@ namespace Editor //// static EditorQtApplication* instance(); + static EditorQtApplication* newInstance(int& argc, char** argv); static bool IsActive(); bool isMovingOrResizing() const; - // QAbstractNativeEventFilter: - bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; - // IEditorNotifyListener: void OnEditorNotifyEvent(EEditorNotifyEvent event) override; @@ -100,6 +98,10 @@ namespace Editor signals: void skinChanged(); + protected: + + bool m_isMovingOrResizing = false; + private: enum TimerResetFlag { @@ -116,8 +118,6 @@ namespace Editor AzQtComponents::O3DEStylesheet* m_stylesheet; - bool m_inWinEventFilter = false; - // Translators void InstallEditorTranslators(); void UninstallEditorTranslators(); @@ -127,7 +127,6 @@ namespace Editor QTranslator* m_editorTranslator = nullptr; QTranslator* m_assetBrowserTranslator = nullptr; QTimer* const m_idleTimer = nullptr; - bool m_isMovingOrResizing = false; AZ::UserSettingsProvider m_localUserSettings; diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index ab80e7d23a..e4138da932 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -4135,9 +4135,9 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) Editor::EditorQtApplication::InstallQtLogHandler(); AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware); - Editor::EditorQtApplication app(argc, argv); + Editor::EditorQtApplication* app = Editor::EditorQtApplication::newInstance(argc, argv); - if (app.arguments().contains("-autotest_mode")) + if (app->arguments().contains("-autotest_mode")) { // Nullroute all stdout to null for automated tests, this way we make sure // that the test result output is not polluted with unrelated output data. @@ -4173,12 +4173,7 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) return -1; } - AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::NotifyQtApplicationAvailable, &app); - - #if defined(AZ_PLATFORM_MAC) - // Native menu bars do not work on macOS due to all the tool dialogs - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); - #endif + AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::NotifyQtApplicationAvailable, app); int exitCode = 0; @@ -4189,9 +4184,9 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) if (didCryEditStart) { - app.EnableOnIdle(); + app->EnableOnIdle(); - ret = app.exec(); + ret = app->exec(); } else { @@ -4202,6 +4197,8 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) } + delete app; + gSettings.Disconnect(); return ret; diff --git a/Code/Editor/Core/QtEditorApplication_linux.cpp b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp similarity index 73% rename from Code/Editor/Core/QtEditorApplication_linux.cpp rename to Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp index 5491134170..ad5e57479b 100644 --- a/Code/Editor/Core/QtEditorApplication_linux.cpp +++ b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp @@ -6,7 +6,7 @@ * */ -#include "QtEditorApplication.h" +#include "QtEditorApplication_linux.h" #ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB #include @@ -14,7 +14,16 @@ namespace Editor { - bool EditorQtApplication::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*) + EditorQtApplication* EditorQtApplication::newInstance(int& argc, char** argv) + { +#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + return new EditorQtApplicationXcb(argc, argv); +#endif + + return nullptr; + } + + bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*) { if (GetIEditor()->IsInGameMode()) { diff --git a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h new file mode 100644 index 0000000000..8c145c3aa7 --- /dev/null +++ b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h @@ -0,0 +1,25 @@ +/* + * 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 + +namespace Editor +{ + class EditorQtApplicationXcb : public EditorQtApplication + { + Q_OBJECT + public: + EditorQtApplicationXcb(int& argc, char** argv) + : EditorQtApplication(argc, argv) + { + } + + // QAbstractNativeEventFilter: + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; + }; +} // namespace Editor diff --git a/Code/Editor/Platform/Linux/platform_linux_files.cmake b/Code/Editor/Platform/Linux/platform_linux_files.cmake index 3baed702c2..875acad1c3 100644 --- a/Code/Editor/Platform/Linux/platform_linux_files.cmake +++ b/Code/Editor/Platform/Linux/platform_linux_files.cmake @@ -7,6 +7,6 @@ # set(FILES - ../../Core/QtEditorApplication_linux.cpp + Editor/Core/QtEditorApplication_linux.cpp ../Common/Unimplemented/Util/Mailer_Unimplemented.cpp ) diff --git a/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.h b/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.h new file mode 100644 index 0000000000..2de7514bbf --- /dev/null +++ b/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.h @@ -0,0 +1,25 @@ +/* + * 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 + +namespace Editor +{ + class EditorQtApplicationMac : public EditorQtApplication + { + Q_OBJECT + public: + EditorQtApplicationMac(int& argc, char** argv) + : EditorQtApplication(argc, argv) + { + } + + // QAbstractNativeEventFilter: + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; + }; +} // namespace Editor diff --git a/Code/Editor/Core/QtEditorApplication_mac.mm b/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm similarity index 78% rename from Code/Editor/Core/QtEditorApplication_mac.mm rename to Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm index 17ad8f7ffd..a7f59b7ac8 100644 --- a/Code/Editor/Core/QtEditorApplication_mac.mm +++ b/Code/Editor/Platform/Mac/Editor/Core/QtEditorApplication_mac.mm @@ -19,7 +19,14 @@ namespace Editor { - bool EditorQtApplication::nativeEventFilter(const QByteArray& eventType, void* message, long* result) + EditorQtApplication* EditorQtApplication::newInstance(int& argc, char** argv) + { + QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); + + return new EditorQtApplicationMac(argc, argv); + } + + bool EditorQtApplicationMac::nativeEventFilter(const QByteArray& eventType, void* message, long* result) { NSEvent* event = (NSEvent*)message; if (GetIEditor()->IsInGameMode()) diff --git a/Code/Editor/Platform/Mac/platform_mac_files.cmake b/Code/Editor/Platform/Mac/platform_mac_files.cmake index 91a0bed574..5549eaf2f9 100644 --- a/Code/Editor/Platform/Mac/platform_mac_files.cmake +++ b/Code/Editor/Platform/Mac/platform_mac_files.cmake @@ -7,7 +7,7 @@ # set(FILES - ../../Core/QtEditorApplication_mac.mm + Editor/Core/QtEditorApplication_mac.mm ../../LogFile_mac.mm ../../WindowObserver_mac.h ../../WindowObserver_mac.mm diff --git a/Code/Editor/Platform/Windows/Editor/Core/QtEditorApplication_windows.cpp b/Code/Editor/Platform/Windows/Editor/Core/QtEditorApplication_windows.cpp new file mode 100644 index 0000000000..f8065af931 --- /dev/null +++ b/Code/Editor/Platform/Windows/Editor/Core/QtEditorApplication_windows.cpp @@ -0,0 +1,165 @@ +/* + * 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 "QtEditorApplication_windows.h" + +// Qt +#include +#include +#include +#include +#include + +#include +#include + +// AzQtComponents +#include +#include + +// AzFramework +#include + +namespace Editor +{ + EditorQtApplication* EditorQtApplication::newInstance(int& argc, char** argv) + { + return new EditorQtApplicationWindows(argc, argv); + } + + bool EditorQtApplicationWindows::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long* result) + { + MSG* msg = (MSG*)message; + + if (msg->message == WM_MOVING || msg->message == WM_SIZING) + { + m_isMovingOrResizing = true; + } + else if (msg->message == WM_EXITSIZEMOVE) + { + m_isMovingOrResizing = false; + } + + // Prevent the user from being able to move the window in game mode. + // This is done during the hit test phase to bypass the native window move messages. If the window + // decoration wrapper title bar contains the cursor, set the result to HTCLIENT instead of + // HTCAPTION. + if (msg->message == WM_NCHITTEST && GetIEditor()->IsInGameMode()) + { + const LRESULT defWinProcResult = DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam); + if (defWinProcResult == 1) + { + if (QWidget* widget = QWidget::find((WId)msg->hwnd)) + { + if (auto wrapper = qobject_cast(widget)) + { + AzQtComponents::TitleBar* titleBar = wrapper->titleBar(); + const short global_x = static_cast(LOWORD(msg->lParam)); + const short global_y = static_cast(HIWORD(msg->lParam)); + + const QPoint globalPos = QHighDpi::fromNativePixels(QPoint(global_x, global_y), widget->window()->windowHandle()); + const QPoint local = titleBar->mapFromGlobal(globalPos); + if (titleBar->draggableRect().contains(local) && !titleBar->isTopResizeArea(globalPos)) + { + *result = HTCLIENT; + return true; + } + } + } + } + } + + // Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system. + // These events are only broadcast in game mode. In Editor mode, RenderViewportWidget creates synthetic + // keyboard and mouse events via Qt. + if (GetIEditor()->IsInGameMode()) + { + if (msg->message == WM_INPUT) + { + UINT rawInputSize; + const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); + GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, nullptr, &rawInputSize, rawInputHeaderSize); + + AZStd::array rawInputBytesArray; + LPBYTE rawInputBytes = rawInputBytesArray.data(); + + [[maybe_unused]] const UINT bytesCopied = + GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); + CRY_ASSERT(bytesCopied == rawInputSize); + + RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; + CRY_ASSERT(rawInput); + + AzFramework::RawInputNotificationBusWindows::Broadcast( + &AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput); + + return false; + } + else if (msg->message == WM_DEVICECHANGE) + { + if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED + { + AzFramework::RawInputNotificationBusWindows::Broadcast( + &AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent); + } + return true; + } + } + + return false; + } + + bool EditorQtApplicationWindows::eventFilter(QObject* object, QEvent* event) + { + switch (event->type()) + { + case QEvent::Leave: + { + // if we receive a leave event for a toolbar on Windows + // check first whether we really left it. If we didn't: start checking + // for the tool bar under the mouse by timer to check when we really left. + // Synthesize a new leave event then. Workaround for LY-69788 + auto toolBarAt = [](const QPoint& pos) -> QToolBar* + { + QWidget* widget = qApp->widgetAt(pos); + while (widget != nullptr) + { + if (QToolBar* tb = qobject_cast(widget)) + { + return tb; + } + widget = widget->parentWidget(); + } + return false; + }; + if (object == toolBarAt(QCursor::pos())) + { + QTimer* t = new QTimer(object); + t->start(100); + connect( + t, &QTimer::timeout, object, + [t, object, toolBarAt]() + { + if (object != toolBarAt(QCursor::pos())) + { + QEvent event(QEvent::Leave); + qApp->sendEvent(object, &event); + t->deleteLater(); + } + }); + return true; + } + break; + } + default: + break; + } + + return EditorQtApplication::eventFilter(object, event); + } +} // namespace Editor diff --git a/Code/Editor/Platform/Windows/Editor/Core/QtEditorApplication_windows.h b/Code/Editor/Platform/Windows/Editor/Core/QtEditorApplication_windows.h new file mode 100644 index 0000000000..6967d5a3b0 --- /dev/null +++ b/Code/Editor/Platform/Windows/Editor/Core/QtEditorApplication_windows.h @@ -0,0 +1,27 @@ +/* + * 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 + +namespace Editor +{ + class EditorQtApplicationWindows : public EditorQtApplication + { + Q_OBJECT + public: + EditorQtApplicationWindows(int& argc, char** argv) + : EditorQtApplication(argc, argv) + { + } + + // QAbstractNativeEventFilter: + bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; + + bool eventFilter(QObject* object, QEvent* event) override; + }; +} // namespace Editor diff --git a/Code/Editor/Platform/Windows/platform_windows_files.cmake b/Code/Editor/Platform/Windows/platform_windows_files.cmake index d7fc96e0a3..df7a3c817f 100644 --- a/Code/Editor/Platform/Windows/platform_windows_files.cmake +++ b/Code/Editor/Platform/Windows/platform_windows_files.cmake @@ -7,5 +7,6 @@ # set(FILES + Editor/Core/QtEditorApplication_windows.cpp Util/Mailer_Windows.cpp ) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp index acb2cd4b1c..6f6f8dccff 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.cpp @@ -80,14 +80,7 @@ namespace AzToolsFramework // set up signals before we start thread. m_shutdownThreadSignal = false; - - // Check to see if the 'p4' command is available at the command line - int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" }); - m_p4ApplicationDetected = (p4VersionExitCode == 0); - if (m_p4ApplicationDetected) - { - m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this)); - } + m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this)); SourceControlConnectionRequestBus::Handler::BusConnect(); SourceControlCommandBus::Handler::BusConnect(); @@ -98,13 +91,10 @@ namespace AzToolsFramework SourceControlCommandBus::Handler::BusDisconnect(); SourceControlConnectionRequestBus::Handler::BusDisconnect(); - if (m_p4ApplicationDetected) - { - m_shutdownThreadSignal = true; // tell the thread to die. - m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal - m_WorkerThread.join(); // wait for the thread to finish. - m_WorkerThread = AZStd::thread(); - } + m_shutdownThreadSignal = true; // tell the thread to die. + m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal + m_WorkerThread.join(); // wait for the thread to finish. + m_WorkerThread = AZStd::thread(); SetConnection(nullptr); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h index e5ccf7ad29..858d9c0103 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/SourceControl/PerforceComponent.h @@ -260,7 +260,5 @@ namespace AzToolsFramework AZStd::atomic_bool m_validConnection; SourceControlState m_connectionState; - - bool m_p4ApplicationDetected { false }; }; } // namespace AzToolsFramework diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 712c01aa02..6694168f2b 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -523,6 +523,14 @@ QProgressBar::chunk { background-color: #444444; } +#gemCatalogMenuButton { + qproperty-flat: true; + max-width:36px; + min-width:36px; + max-height:24px; + min-height:24px; +} + #GemCatalogHeaderLabel { font-size: 12px; color: #FFFFFF; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 0ecea215bf..5da163bafe 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -8,14 +8,13 @@ #include #include -#include - #include #include #include #include -#include #include +#include +#include namespace O3DE::ProjectManager { @@ -406,7 +405,6 @@ namespace O3DE::ProjectManager CartButton* cartButton = new CartButton(gemModel, downloadController); hLayout->addWidget(cartButton); - hLayout->addSpacing(16); // Separating line @@ -418,9 +416,9 @@ namespace O3DE::ProjectManager hLayout->addSpacing(16); QMenu* gemMenu = new QMenu(this); - m_openGemReposAction = gemMenu->addAction(tr("Show Gem Repos")); - - connect(m_openGemReposAction, &QAction::triggered, this,[this](){ emit OpenGemsRepo(); }); + gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); }); + gemMenu->addSeparator(); + gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); }); QPushButton* gemMenuButton = new QPushButton(this); gemMenuButton->setObjectName("gemCatalogMenuButton"); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index fa381e54ae..19adec5607 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -8,24 +8,23 @@ #pragma once -#include - #if !defined(Q_MOC_RUN) +#include #include #include #include #include -#include - #include -#include -#include -#include -#include -#include -#include +#include #endif +QT_FORWARD_DECLARE_CLASS(QPushButton) +QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QVBoxLayout) +QT_FORWARD_DECLARE_CLASS(QHBoxLayout) +QT_FORWARD_DECLARE_CLASS(QHideEvent) +QT_FORWARD_DECLARE_CLASS(QMoveEvent) + namespace O3DE::ProjectManager { class CartOverlayWidget @@ -87,12 +86,11 @@ namespace O3DE::ProjectManager void ReinitForProject(); signals: + void AddGem(); void OpenGemsRepo(); - + private: AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; inline constexpr static int s_height = 60; - - QAction* m_openGemReposAction = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 69c5844e84..b145363460 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -19,6 +19,10 @@ #include #include #include +#include +#include +#include +#include namespace O3DE::ProjectManager { @@ -74,6 +78,7 @@ namespace O3DE::ProjectManager void GemCatalogScreen::ReinitForProject(const QString& projectPath) { m_gemModel->clear(); + m_gemsToRegisterWithProject.clear(); FillModel(projectPath); if (m_filterWidget) @@ -90,6 +95,47 @@ namespace O3DE::ProjectManager connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter); connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); + connect( + m_headerWidget, &GemCatalogHeaderWidget::AddGem, + [&]() + { + EngineInfo engineInfo; + QString defaultPath; + + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + engineInfo = engineInfoResult.GetValue(); + defaultPath = engineInfo.m_defaultGemsFolder; + } + + if (defaultPath.isEmpty()) + { + defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + } + + QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath)); + if (!directory.isEmpty()) + { + // register the gem to the o3de_manifest.json and to the project after the user confirms + // project creation/update + auto registerResult = PythonBindingsInterface::Get()->RegisterGem(directory); + if(!registerResult) + { + QMessageBox::critical(this, tr("Failed to add gem"), registerResult.GetError().c_str()); + } + else + { + m_gemsToRegisterWithProject.insert(directory); + AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->GetGemInfo(directory); + if (gemInfoResult) + { + m_gemModel->AddGem(gemInfoResult.GetValue()); + m_gemModel->UpdateGemDependencies(); + } + } + } + }); // Select the first entry after everything got correctly sized QTimer::singleShot(200, [=]{ @@ -251,6 +297,12 @@ namespace O3DE::ProjectManager return EnableDisableGemsResult::Failed; } + + // register external gems that were added with relative paths + if (m_gemsToRegisterWithProject.contains(gemPath)) + { + pythonBindings->RegisterGem(QDir(projectPath).relativeFilePath(gemPath), projectPath); + } } for (const QModelIndex& modelIndex : toBeRemoved) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 62528ba942..8e9f31c710 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #endif namespace O3DE::ProjectManager @@ -70,5 +72,6 @@ namespace O3DE::ProjectManager GemFilterWidget* m_filterWidget = nullptr; DownloadController* m_downloadController = nullptr; bool m_notificationsEnabled = true; + QSet m_gemsToRegisterWithProject; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index bc8773b0c8..f001195e85 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -556,6 +556,47 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemNames)); } + AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath) + { + bool registrationResult = false; + auto result = ExecuteWithLockErrorHandling( + [&] + { + auto externalProjectPath = projectPath.isEmpty() ? pybind11::none() : QString_To_Py_Path(projectPath); + auto pythonRegistrationResult = m_register.attr("register")( + pybind11::none(), // engine_path + pybind11::none(), // project_path + QString_To_Py_Path(gemPath), // gem folder + pybind11::none(), // external subdirectory + pybind11::none(), // template_path + pybind11::none(), // restricted folder + pybind11::none(), // repo uri + pybind11::none(), // default_engines_folder + pybind11::none(), // default_projects_folder + pybind11::none(), // default_gems_folder + pybind11::none(), // default_templates_folder + pybind11::none(), // default_restricted_folder + pybind11::none(), // default_third_party_folder + pybind11::none(), // external_subdir_engine_path + externalProjectPath // external_subdir_project_path + ); + + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); + + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } + else if (!registrationResult) + { + return AZ::Failure(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData())); + } + + return AZ::Success(); + } + bool PythonBindings::AddProject(const QString& path) { bool registrationResult = false; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 638ce6b1d4..9d2c8c850f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -42,6 +42,7 @@ namespace O3DE::ProjectManager AZ::Outcome, AZStd::string> GetEngineGemInfos() override; AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; + AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 19442540a4..6b9ac39213 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -91,6 +91,14 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0; + /** + * Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given + * @param gemPath the path to the gem + * @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json + * @return An outcome with the success flag as well as an error message in case of a failure. + */ + virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; + // Projects diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl index 69f86cbe9f..1fc8a410a1 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl @@ -54,6 +54,7 @@ VSOutput ShadowCatcherVS(VSInput IN) DirectionalLightShadow::GetShadowCoords( ViewSrg::m_shadowIndexDirectionalLight, worldPosition, + OUT.m_worldNormal, OUT.m_shadowCoords); return OUT; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index 11859de7d6..0f9771e480 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -112,13 +112,15 @@ VSOutput EnhancedPbr_ForwardPassVS(VSInput IN) PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depth) { + const float3 vertexNormal = normalize(IN.m_normal); + // ------- Tangents & Bitangets ------- float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture) || o_detail_normal_useTexture) { - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); + PrepareGeneratedTangent(vertexNormal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Depth & Parallax ------- @@ -137,7 +139,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, + GetParallaxInput(vertexNormal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, IN.m_position.w, displacementIsClipped); @@ -150,7 +152,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, IN.m_shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords); } } } @@ -185,7 +187,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms. float detailLayerNormalFactor = MaterialSrg::m_detail_normal_factor * detailLayerBlendFactor; - surface.vertexNormal = normalize(IN.m_normal); + surface.vertexNormal = vertexNormal; surface.normal = GetDetailedNormalInputWS( isFrontFace, IN.m_normal, tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_normalFactor, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, uvMatrix, o_normal_useTexture, diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl index 6a97c0e785..a53dab7a01 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.azsl @@ -302,6 +302,7 @@ ProcessedMaterialInputs ProcessStandardMaterialInputs(StandardMaterialInputs inp PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC) { + const float3 vertexNormal = normalize(IN.m_normal); depthNDC = IN.m_position.z; s_blendMaskFromVertexStream = IN.m_blendMask; @@ -321,7 +322,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float || o_layer3_o_clearCoat_normal_useTexture ) { - PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); + PrepareGeneratedTangent(vertexNormal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } // ------- Debug Modes ------- @@ -368,7 +369,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, IN.m_shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords); } } } @@ -445,7 +446,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float normalTS = ReorientTangentSpaceNormal(normalTS, lightingInputLayer3.m_normalTS); } // [GFX TODO][ATOM-14591]: This will only work if the normal maps all use the same UV stream. We would need to add support for having them in different UV streams. - surface.vertexNormal = normalize(IN.m_normal); + surface.vertexNormal = vertexNormal; surface.normal = normalize(TangentSpaceToWorld(normalTS, IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex])); // ------- Combine Albedo, roughness, specular, roughness --------- diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 8df5e1ba56..1d5e4ad9e3 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -98,6 +98,8 @@ VSOutput StandardPbr_ForwardPassVS(VSInput IN) PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float depthNDC) { + const float3 vertexNormal = normalize(IN.m_normal); + // ------- Tangents & Bitangets ------- float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; @@ -128,7 +130,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, IN.m_shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, IN.m_worldPosition, vertexNormal, IN.m_shadowCoords); } } } @@ -146,7 +148,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float2 normalUv = IN.m_uv[MaterialSrg::m_normalMapUvIndex]; float3x3 uvMatrix = MaterialSrg::m_normalMapUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); // By design, only UV0 is allowed to apply transforms. - surface.vertexNormal = normalize(IN.m_normal); + surface.vertexNormal = vertexNormal; surface.normal = GetNormalInputWS(MaterialSrg::m_normalMap, MaterialSrg::m_sampler, normalUv, MaterialSrg::m_flipNormalX, MaterialSrg::m_flipNormalY, isFrontFace, IN.m_normal, tangents[MaterialSrg::m_normalMapUvIndex], bitangents[MaterialSrg::m_normalMapUvIndex], uvMatrix, o_normal_useTexture, MaterialSrg::m_normalFactor); diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/3rdParty/Features/PostProcessing/KelvinToRgb.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/3rdParty/Features/PostProcessing/KelvinToRgb.azsli index aa57f0f4a1..1c8b77df45 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/3rdParty/Features/PostProcessing/KelvinToRgb.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/3rdParty/Features/PostProcessing/KelvinToRgb.azsli @@ -16,39 +16,6 @@ // licensed and released under Creative Commons 3.0 Attribution // https://creativecommons.org/licenses/by/3.0/ -float3 HueToRgb(float hue) -{ - return saturate(float3(abs(hue * 6.0f - 3.0f) - 1.0f, - 2.0f - abs(hue * 6.0f - 2.0f), - 2.0f - abs(hue * 6.0f - 4.0f))); -} - -float3 RgbToHcv(float3 rgb) -{ - // Based on work by Sam Hocevar and Emil Persson - const float4 p = (rgb.g < rgb.b) ? float4(rgb.bg, -1.0f, 2.0f/3.0f) : float4(rgb.gb, 0.0f, -1.0f/3.0f); - const float4 q1 = (rgb.r < p.x) ? float4(p.xyw, rgb.r) : float4(rgb.r, p.yzx); - const float c = q1.x - min(q1.w, q1.y); - const float h = abs((q1.w - q1.y) / (6.0f * c + 0.000001f ) + q1.z); - return float3(h, c, q1.x); -} - -float3 RgbToHsl(float3 rgb) -{ - rgb.xyz = max(rgb.xyz, 0.000001f); - const float3 hcv = RgbToHcv(rgb); - const float L = hcv.z - hcv.y * 0.5f; - const float S = hcv.y / (1.0f - abs(L * 2.0f - 1.0f) + 0.000001f); - return float3(hcv.x, S, L); -} - -float3 HslToRgb(float3 hsl) -{ - const float3 rgb = HueToRgb(hsl.x); - const float c = (1.0f - abs(2.0f * hsl.z - 1.0f)) * hsl.y; - return (rgb - 0.5f) * c + hsl.z; -} - // Color temperature float3 KelvinToRgb(float kelvin) { diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl index a380a0a547..423d447f7b 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl @@ -66,12 +66,21 @@ float3 ColorGradeSaturation (float3 frameColor, float control) return (frameColor - vLuminance) * control + vLuminance; } -float3 ColorGradeKelvinColorTemp(float3 frameColor, float kelvin) +float3 ColorGradeWhiteBalance(float3 frameColor, float kelvin, float tint, float luminancePreservation) { const float3 kColor = TransformColor(KelvinToRgb(kelvin), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg); const float luminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg); - const float3 resHsl = RgbToHsl(frameColor.rgb * kColor.rgb); // Apply Kelvin color and convert to HSL - return HslToRgb(float3(resHsl.xy, luminance)); // Preserve luminance + + // Apply Kelvin color and tint and calculate the new luminance + float3 adjustedColor = frameColor.rgb * kColor.rgb; + adjustedColor.g = max(0.0, adjustedColor.g + tint * 0.001); + const float adjustedLuminance = CalculateLuminance(adjustedColor, ColorSpaceId::ACEScg); + + // Adjust the color based on the difference in luminance. + const float luminanceDifferenceRatio = luminance / adjustedLuminance; + const float3 adjustedColorLumPreserved = adjustedColor * luminanceDifferenceRatio; + + return lerp(adjustedColor, adjustedColorLumPreserved, luminancePreservation); } // pow(f, e) won't work if f is negative, or may cause inf/NAN. @@ -132,7 +141,11 @@ float3 ColorGradeShadowsMidtonesHighlights (float3 frameColor, float shadowsStar float3 ColorGrade(float3 frameColor) { frameColor = lerp(frameColor, ColorGradePostExposure(frameColor, PassSrg::m_colorGradingExposure), PassSrg::m_colorAdjustmentWeight); - frameColor = lerp(frameColor, ColorGradeKelvinColorTemp(frameColor, PassSrg::m_whiteBalanceKelvin), PassSrg::m_whiteBalanceWeight); + frameColor = lerp(frameColor, ColorGradeWhiteBalance( + frameColor, PassSrg::m_whiteBalanceKelvin, + PassSrg::m_whiteBalanceTint, + PassSrg::m_whiteBalanceLuminancePreservation), + PassSrg::m_whiteBalanceWeight); frameColor = lerp(frameColor, ColorGradingContrast(frameColor, AcesCcMidGrey, PassSrg::m_colorGradingContrast), PassSrg::m_colorAdjustmentWeight); frameColor = lerp(frameColor, ColorGradeColorFilter(frameColor, PassSrg::m_colorFilterSwatch.rgb, PassSrg::m_colorFilterMultiply, PassSrg::m_colorFilterIntensity), PassSrg::m_colorAdjustmentWeight); diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli index b53dda13aa..a7122aaf3a 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -14,6 +14,7 @@ #include "ShadowmapAtlasLib.azsli" #include "BicubicPcfFilters.azsli" #include "ReceiverPlaneDepthBias.azsli" +#include "NormalOffsetShadows.azsli" // Before including this azsli file, a PassSrg must be defined with the following members: // Texture2DArray m_directionalLightShadowmap; @@ -45,6 +46,7 @@ class DirectionalLightShadow static void GetShadowCoords( uint lightIndex, float3 worldPosition, + float3 worldNormal, out float3 shadowCoords[ViewSrg::MaxCascadeCount]); //! This calculates visibility ratio of the surface from the light origin. @@ -109,18 +111,22 @@ class DirectionalLightShadow void DirectionalLightShadow::GetShadowCoords( uint lightIndex, float3 worldPosition, + float3 worldNormal, out float3 shadowCoords[ViewSrg::MaxCascadeCount]) { - const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; const float shadowBias = ViewSrg::m_directionalLightShadows[lightIndex].m_shadowBias; + const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_lightViewToShadowmapMatrices; const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_worldToLightViewMatrices; - - for (uint index = 0; index < cascadeCount; ++index) - { - float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition, 1.)); - lightSpacePos.z += shadowBias; + const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount; + const float3 shadowOffset = ComputeNormalShadowOffset(ViewSrg::m_directionalLightShadows[lightIndex].m_normalShadowBias, worldNormal, ViewSrg::m_directionalLightShadows[lightIndex].m_shadowmapSize); + + for (uint index = 0; index < cascadeCount; ++index) + { + float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition + shadowOffset, 1.)); + lightSpacePos.z += shadowBias; + const float4 clipSpacePos = mul(lightViewToShadowmapMatrices[index], lightSpacePos); shadowCoords[index] = clipSpacePos.xyz / clipSpacePos.w; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli new file mode 100644 index 0000000000..1e78cec89e --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/NormalOffsetShadows.azsli @@ -0,0 +1,22 @@ +/* + * 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 + +// Helper functions for normal offset shadow mapping. +// Normal Offset is an alternative to slope-scale depth bias. +// We bias the shadow map lookup by transforming the world-position along the geometric normal before hand. +// http://web.archive.org/web/20140810230446/https://www.dissidentlogic.com/old/#Normal%20Offset%20Shadows +// + +// Apply the following to the world position. Then use this modified world position to look up in the shadow map +float3 ComputeNormalShadowOffset(const float normalOffsetBias, const float3 worldNormal, const float shadowMapDimension) +{ + const float shadowmapSize = 2.0f / shadowMapDimension; + return float3(worldNormal * normalOffsetBias * shadowmapSize); +} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli index 4b5047d4c3..c93986cea1 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Vertex/VertexHelper.azsli @@ -47,6 +47,7 @@ void VertexHelper(in VSInput IN, inout VSOutput OUT, float3 worldPosition, bool DirectionalLightShadow::GetShadowCoords( shadowIndex, worldPosition, + OUT.m_normal, OUT.m_shadowCoords); } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli index 27fa36182c..b3bcc186b6 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/CoreLights/ViewSrg.azsli @@ -81,10 +81,10 @@ partial ShaderResourceGroup ViewSrg uint m_shadowmapArraySlice; // array slice who has shadowmap in the atlas. uint m_shadowFilterMethod; float m_boundaryScale; - uint m_predictionSampleCount; uint m_filteringSampleCount; float2 m_unprojectConstants; float m_bias; + float m_normalShadowBias; float m_esmExponent; float3 m_padding; }; @@ -109,7 +109,7 @@ partial ShaderResourceGroup ViewSrg uint m_shadowmapSize; // width and height of shadowmap uint m_cascadeCount; float m_shadowBias; - uint m_predictionSampleCount; + float m_normalShadowBias; uint m_filteringSampleCount; uint m_debugFlags; uint m_shadowFilterMethod; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/ColorGrading/LutGeneration.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/ColorGrading/LutGeneration.azsl index 6a76f58519..89655a0e91 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/ColorGrading/LutGeneration.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/ColorGrading/LutGeneration.azsl @@ -57,6 +57,7 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback float m_whiteBalanceWeight; float m_whiteBalanceKelvin; float m_whiteBalanceTint; + float m_whiteBalanceLuminancePreservation; float m_splitToneBalance; float m_splitToneWeight; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/HDRColorGrading.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/HDRColorGrading.azsl index bc33a7e920..3167b214dc 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/HDRColorGrading.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/HDRColorGrading.azsl @@ -40,6 +40,7 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback float m_whiteBalanceWeight; float m_whiteBalanceKelvin; float m_whiteBalanceTint; + float m_whiteBalanceLuminancePreservation; float m_splitToneBalance; float m_splitToneWeight; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h index 2bba1338a1..fa987a7156 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h @@ -160,6 +160,9 @@ namespace AZ //! Reduces acne by applying a small amount of bias along shadow-space z. virtual void SetShadowBias(LightHandle handle, float bias) = 0; + + //! Reduces acne by biasing the shadowmap lookup along the geometric normal. + virtual void SetNormalShadowBias(LightHandle handle, float normalShadowBias) = 0; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/ColorGrading/HDRColorGradingParams.inl b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/ColorGrading/HDRColorGradingParams.inl index c5865b0cfe..f41994fa77 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/ColorGrading/HDRColorGradingParams.inl +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/ColorGrading/HDRColorGradingParams.inl @@ -22,6 +22,7 @@ AZ_GFX_VEC3_PARAM(ColorFilterSwatch, m_colorFilterSwatch, AZ::Vector3(1.0f, 0.5f AZ_GFX_FLOAT_PARAM(WhiteBalanceWeight, m_whiteBalanceWeight, 0.0) AZ_GFX_FLOAT_PARAM(WhiteBalanceKelvin, m_whiteBalanceKelvin, 6600.0) AZ_GFX_FLOAT_PARAM(WhiteBalanceTint, m_whiteBalanceTint, 0.0) +AZ_GFX_FLOAT_PARAM(WhiteBalanceLuminancePreservation, m_whiteBalanceLuminancePreservation, 1.0) AZ_GFX_FLOAT_PARAM(SplitToneWeight, m_splitToneWeight, 0.0) AZ_GFX_FLOAT_PARAM(SplitToneBalance, m_splitToneBalance, 0.0) diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h index 46560f435d..1517d655bb 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Shadows/ProjectedShadowFeatureProcessorInterface.h @@ -48,11 +48,13 @@ namespace AZ::Render virtual void SetAspectRatio(ShadowId id, float aspectRatio) = 0; //! Sets the field of view for the shadow in radians in the Y direction. virtual void SetFieldOfViewY(ShadowId id, float fieldOfView) = 0; - //! Sets the maximum resolution of the shadow map + //! Sets the maximum resolution of the shadow map. virtual void SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) = 0; - //! Sets the shadow bias + //! Sets the shadow bias. virtual void SetShadowBias(ShadowId id, float bias) = 0; - //! Sets the shadow filter method + //! Sets the normal shadow bias. + virtual void SetNormalShadowBias(ShadowId id, float normalShadowBias) = 0; + //! Sets the shadow filter method. virtual void SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method) = 0; //! Sets the sample count for filtering of the shadow boundary, max 64. virtual void SetFilteringSampleCount(ShadowId id, uint16_t count) = 0; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 4774ac31a1..6c24b7d35b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -598,6 +598,15 @@ namespace AZ m_shadowBufferNeedsUpdate = true; } + void DirectionalLightFeatureProcessor::SetNormalShadowBias(LightHandle handle, float normalShadowBias) + { + for (auto& it : m_shadowData) + { + it.second.GetData(handle.GetIndex()).m_normalShadowBias = normalShadowBias; + } + m_shadowBufferNeedsUpdate = true; + } + void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline) { PrepareForChangingRenderPipelineAndCameraView(); diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 2cf5b0b1e6..8d7a9d76e4 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -92,8 +92,9 @@ namespace AZ uint32_t m_shadowmapSize = 1; // width and height of shadowmap uint32_t m_cascadeCount = 1; // Reduce acne by applying a small amount of bias to apply along shadow-space z. - float m_shadowBias = 0.0f; - uint32_t m_predictionSampleCount = 0; + float m_shadowBias = 0.0f; + // Reduces acne by biasing the shadowmap lookup along the geometric normal. + float m_normalShadowBias; uint32_t m_filteringSampleCount = 0; uint32_t m_debugFlags = 0; uint32_t m_shadowFilterMethod = 0; @@ -101,6 +102,8 @@ namespace AZ float m_padding[3]; }; + static_assert(sizeof(DirectionalLightShadowData) % 16 == 0); // Structured buffers need alignment to be a multiple of 16 bytes. + class DirectionalLightFeatureProcessor final : public DirectionalLightFeatureProcessorInterface { @@ -216,6 +219,7 @@ namespace AZ void SetFilteringSampleCount(LightHandle handle, uint16_t count) override; void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override; void SetShadowBias(LightHandle handle, float bias) override; + void SetNormalShadowBias(LightHandle handle, float normalShadowBias) override; const Data::Instance GetLightBuffer() const; uint32_t GetLightCount() const; diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.cpp index 88266ba789..a3347d0d84 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.cpp @@ -43,6 +43,7 @@ m_whiteBalanceWeightIndex.Reset(); m_whiteBalanceKelvinIndex.Reset(); m_whiteBalanceTintIndex.Reset(); + m_whiteBalanceLuminancePreservationIndex.Reset(); m_splitToneBalanceIndex.Reset(); m_splitToneWeightIndex.Reset(); @@ -96,7 +97,7 @@ m_shaderResourceGroup->SetConstant(m_whiteBalanceWeightIndex, settings->GetWhiteBalanceWeight()); m_shaderResourceGroup->SetConstant(m_whiteBalanceKelvinIndex, settings->GetWhiteBalanceKelvin()); m_shaderResourceGroup->SetConstant(m_whiteBalanceTintIndex, settings->GetWhiteBalanceTint()); - + m_shaderResourceGroup->SetConstant(m_whiteBalanceLuminancePreservationIndex, settings->GetWhiteBalanceLuminancePreservation()); m_shaderResourceGroup->SetConstant(m_splitToneBalanceIndex, settings->GetSplitToneBalance()); m_shaderResourceGroup->SetConstant(m_splitToneWeightIndex, settings->GetSplitToneWeight()); m_shaderResourceGroup->SetConstant(m_splitToneShadowsColorIndex, AZ::Vector4(settings->GetSplitToneShadowsColor())); diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.h b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.h index dc706b8501..b89fa04531 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.h +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/HDRColorGradingPass.h @@ -55,6 +55,7 @@ namespace AZ RHI::ShaderInputNameIndex m_whiteBalanceWeightIndex = "m_whiteBalanceWeight"; RHI::ShaderInputNameIndex m_whiteBalanceKelvinIndex = "m_whiteBalanceKelvin"; RHI::ShaderInputNameIndex m_whiteBalanceTintIndex = "m_whiteBalanceTint"; + RHI::ShaderInputNameIndex m_whiteBalanceLuminancePreservationIndex = "m_whiteBalanceLuminancePreservation"; RHI::ShaderInputNameIndex m_splitToneBalanceIndex = "m_splitToneBalance"; RHI::ShaderInputNameIndex m_splitToneWeightIndex = "m_splitToneWeight"; diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp index da92cc04e7..7c0b3563c7 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.cpp @@ -151,6 +151,14 @@ namespace AZ::Render shadowProperty.m_bias = bias; } + void ProjectedShadowFeatureProcessor::SetNormalShadowBias(ShadowId id, float normalShadowBias) + { + AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetNormalShadowBias()."); + + ShadowProperty& shadowProperty = GetShadowPropertyFromShadowId(id); + shadowProperty.m_normalShadowBias = normalShadowBias; + } + void ProjectedShadowFeatureProcessor::SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) { AZ_Assert(id.IsValid(), "Invalid ShadowId passed to ProjectedShadowFeatureProcessor::SetShadowmapMaxResolution()."); diff --git a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h index 6269166827..fafcb25a08 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/Shadows/ProjectedShadowFeatureProcessor.h @@ -48,6 +48,7 @@ namespace AZ::Render void SetFieldOfViewY(ShadowId id, float fieldOfViewYRadians) override; void SetShadowmapMaxResolution(ShadowId id, ShadowmapSize size) override; void SetShadowBias(ShadowId id, float bias) override; + void SetNormalShadowBias(ShadowId id, float normalShadowBias) override; void SetShadowFilterMethod(ShadowId id, ShadowFilterMethod method) override; void SetFilteringSampleCount(ShadowId id, uint16_t count) override; void SetShadowProperties(ShadowId id, const ProjectedShadowDescriptor& descriptor) override; @@ -64,10 +65,10 @@ namespace AZ::Render uint32_t m_shadowmapArraySlice = 0; // array slice who has shadowmap in the atlas. uint32_t m_shadowFilterMethod = 0; // filtering method of shadows. float m_boundaryScale = 0.f; // the half of boundary of lit/shadowed areas. (in degrees) - uint32_t m_predictionSampleCount = 0; // sample count to judge whether it is on the shadow boundary or not. uint32_t m_filteringSampleCount = 0; AZStd::array m_unprojectConstants = { {0, 0} }; float m_bias; + float m_normalShadowBias; float m_esmExponent = 87.0f; float m_padding[3]; }; @@ -78,6 +79,7 @@ namespace AZ::Render ProjectedShadowDescriptor m_desc; RPI::ViewPtr m_shadowmapView; float m_bias = 0.1f; + float m_normalShadowBias = 0.0f; ShadowId m_shadowId; }; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h index 14e9968e42..97ac3baa90 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h @@ -14,92 +14,86 @@ namespace AZ { namespace RHI { - /** - * The platform-independent swap chain base class. Swap chains contain a "chain" of images which - * map to a platform-specific window, displayed on a physical monitor. The user is allowed - * to adjust the swap chain outside of the current FrameScheduler frame. Doing so within a frame scheduler - * frame results in undefined behavior. - * - * The frame scheduler controls presentation of the swap chain. The user may attach a swap chain to a scope - * in order to render to the current image. - */ + //! The platform-independent swap chain base class. Swap chains contain a "chain" of images which + //! map to a platform-specific window, displayed on a physical monitor. The user is allowed + //! to adjust the swap chain outside of the current FrameScheduler frame. Doing so within a frame scheduler + //! frame results in undefined behavior. + //! + //! The frame scheduler controls presentation of the swap chain. The user may attach a swap chain to a scope + //! in order to render to the current image. class SwapChain : public ImagePoolBase { public: + AZ_RTTI(SwapChain, "{888B64A5-D956-406F-9C33-CF6A54FC41B0}", Object); + virtual ~SwapChain(); - /// Initializes the swap chain, making it ready for attachment. + //! Initializes the swap chain, making it ready for attachment. ResultCode Init(RHI::Device& device, const SwapChainDescriptor& descriptor); - /// Presents the swap chain to the display, and rotates the images. + //! Presents the swap chain to the display, and rotates the images. void Present(); - /** - * Sets the vertical sync interval for the swap chain. - * 0 - No vsync. - * N - Sync to every N vertical refresh. - * - * A value of 1 syncs to the refresh rate of the monitor. - */ + //! Sets the vertical sync interval for the swap chain. + //! 0 - No vsync. + //! N - Sync to every N vertical refresh. + //! + //! A value of 1 syncs to the refresh rate of the monitor. void SetVerticalSyncInterval(uint32_t verticalSyncInterval); - /** - * Resizes the display resolution of the swap chain. Ideally, this matches the platform window - * resolution. Typically, the resize operation will occur in reaction to a platform window size - * change. Takes effect immediately and results in a GPU pipeline flush. - */ + //! Resizes the display resolution of the swap chain. Ideally, this matches the platform window + //! resolution. Typically, the resize operation will occur in reaction to a platform window size + //! change. Takes effect immediately and results in a GPU pipeline flush. ResultCode Resize(const SwapChainDimensions& dimensions); - /// Returns the number of images in the swap chain. + //! Returns the number of images in the swap chain. uint32_t GetImageCount() const; - /// Returns the current image index of the swap chain. + //! Returns the current image index of the swap chain. uint32_t GetCurrentImageIndex() const; - /// Returns the current image of the swap chain. + //! Returns the current image of the swap chain. Image* GetCurrentImage() const; - /// Returns the image associated with the provided index, where the total number of images - /// is given by GetImageCount(). + //! Returns the image associated with the provided index, where the total number of images + //! is given by GetImageCount(). Image* GetImage(uint32_t index) const; - /// Returns the ID used for the SwapChain's attachment + //! Returns the ID used for the SwapChain's attachment const AttachmentId& GetAttachmentId() const; - /// Returns the descriptor provided when initializing the swap chain. + //! Returns the descriptor provided when initializing the swap chain. const RHI::SwapChainDescriptor& GetDescriptor() const override final; - //! \return True if the swap chain prefers to use exclusive full screen mode. + //! Returns True if the swap chain prefers to use exclusive full screen mode. virtual bool IsExclusiveFullScreenPreferred() const { return false; } - //! \return True if the swap chain prefers exclusive full screen mode and it is currently true, false otherwise. + //! Returns True if the swap chain prefers exclusive full screen mode and it is currently true, false otherwise. virtual bool GetExclusiveFullScreenState() const { return false; } - //! \return True if the swap chain prefers exclusive full screen mode and a transition happened, false otherwise. + //! Return True if the swap chain prefers exclusive full screen mode and a transition happened, false otherwise. virtual bool SetExclusiveFullScreenState([[maybe_unused]]bool fullScreenState) { return false; } - AZ_RTTI(SwapChain, "{888B64A5-D956-406F-9C33-CF6A54FC41B0}", Object); - protected: SwapChain(); struct InitImageRequest { - /// Pointer to the image to initialize. + //! Pointer to the image to initialize. Image* m_image = nullptr; - /// Index of the image in the swap chain. + //! Index of the image in the swap chain. uint32_t m_imageIndex = 0; - /// Descriptor for the image. + //! Descriptor for the image. ImageDescriptor m_descriptor; }; ////////////////////////////////////////////////////////////////////////// // ResourcePool Overrides - /// Called when the pool is shutting down. + //! Called when the pool is shutting down. void ShutdownInternal() override; ////////////////////////////////////////////////////////////////////////// @@ -111,32 +105,29 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// // Platform API - /// Called when the swap chain is initializing. + //! Called when the swap chain is initializing. virtual ResultCode InitInternal(RHI::Device& device, const SwapChainDescriptor& descriptor, SwapChainDimensions* nativeDimensions) = 0; - /// called when the swap chain is initializing an image. + //! called when the swap chain is initializing an image. virtual ResultCode InitImageInternal(const InitImageRequest& request) = 0; - /// Called when the swap chain is resizing. + //! Called when the swap chain is resizing. virtual ResultCode ResizeInternal(const SwapChainDimensions& dimensions, SwapChainDimensions* nativeDimensions) = 0; - /// Called when the swap chain is presenting the currently swap image. - /// Returns the index of the current image after the swap. + //! Called when the swap chain is presenting the currently swap image. + //! Returns the index of the current image after the swap. virtual uint32_t PresentInternal() = 0; - virtual void SetVerticalSyncIntervalInternal(uint32_t previousVerticalSyncInterval) - { - AZ_UNUSED(previousVerticalSyncInterval); - } + virtual void SetVerticalSyncIntervalInternal([[maybe_unused]]uint32_t previousVerticalSyncInterval) {} ////////////////////////////////////////////////////////////////////////// SwapChainDescriptor m_descriptor; - /// Images corresponding to each image in the swap chain. + //! Images corresponding to each image in the swap chain. AZStd::vector> m_images; - /// The current image index. + //! The current image index. uint32_t m_currentImageIndex = 0; }; } diff --git a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp index b0501d937d..ff1f0e69a6 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp @@ -55,7 +55,7 @@ namespace AZ if (resultCode == ResultCode::Success) { m_descriptor = descriptor; - // Ovewrite descriptor dimensions with the native ones (the ones assigned by the platform) returned by InitInternal. + // Overwrite descriptor dimensions with the native ones (the ones assigned by the platform) returned by InitInternal. m_descriptor.m_dimensions = nativeDimensions; m_images.reserve(m_descriptor.m_dimensions.m_imageCount); @@ -129,8 +129,8 @@ namespace AZ while (m_images.size() > static_cast(m_descriptor.m_dimensions.m_imageCount)) { m_images.pop_back(); - } - + } + InitImageRequest request; RHI::ImageDescriptor& imageDescriptor = request.m_descriptor; diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp index 7040defca8..bef2b154e1 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp @@ -61,13 +61,12 @@ namespace AZ void SwapChain::SetVerticalSyncIntervalInternal(uint32_t previousVsyncInterval) { - uint32_t verticalSyncInterval = GetDescriptor().m_verticalSyncInterval; - if (verticalSyncInterval == 0 || previousVsyncInterval == 0) + if (GetDescriptor().m_verticalSyncInterval == 0 || previousVsyncInterval == 0) { // The presentation mode may change when transitioning to or from a vsynced presentation mode // In this case, the swapchain must be recreated. InvalidateNativeSwapChain(); - BuildNativeSwapChain(GetDescriptor().m_dimensions, verticalSyncInterval); + CreateSwapchain(); } } @@ -85,46 +84,21 @@ namespace AZ RHI::DeviceObject::Init(baseDevice); auto& device = static_cast(GetDevice()); - RHI::SwapChainDimensions swapchainDimensions = descriptor.m_dimensions; + m_dimensions = descriptor.m_dimensions; + result = BuildSurface(descriptor); RETURN_RESULT_IF_UNSUCCESSFUL(result); - if (!ValidateSurfaceDimensions(swapchainDimensions)) - { - swapchainDimensions.m_imageHeight = AZStd::clamp(swapchainDimensions.m_imageHeight, m_surfaceCapabilities.minImageExtent.height, m_surfaceCapabilities.maxImageExtent.height); - swapchainDimensions.m_imageWidth = AZStd::clamp(swapchainDimensions.m_imageWidth, m_surfaceCapabilities.minImageExtent.width, m_surfaceCapabilities.maxImageExtent.width); - AZ_Printf("Vulkan", "Resizing swapchain from (%d, %d) to (%d, %d).", - static_cast(descriptor.m_dimensions.m_imageWidth), static_cast(descriptor.m_dimensions.m_imageHeight), - static_cast(swapchainDimensions.m_imageWidth), static_cast(swapchainDimensions.m_imageHeight)); - } auto& presentationQueue = device.GetCommandQueueContext().GetOrCreatePresentationCommandQueue(*this); m_presentationQueue = &presentationQueue; - result = BuildNativeSwapChain(swapchainDimensions, descriptor.m_verticalSyncInterval); - RETURN_RESULT_IF_UNSUCCESSFUL(result); - uint32_t imageCount = 0; - VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &imageCount, nullptr); - AssertSuccess(vkResult); - RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); - m_swapchainNativeImages.resize(imageCount); - - // Retrieve the native images of the swapchain so they are - // available when we init the Images in InitImageInternal - vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &imageCount, m_swapchainNativeImages.data()); - AssertSuccess(vkResult); - RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); - - // Acquire the first image - uint32_t imageIndex = 0; - result = AcquireNewImage(&imageIndex); + result = CreateSwapchain(); RETURN_RESULT_IF_UNSUCCESSFUL(result); if (nativeDimensions) { // Fill out the real swapchain dimensions to return - nativeDimensions->m_imageCount = imageCount; - nativeDimensions->m_imageHeight = swapchainDimensions.m_imageHeight; - nativeDimensions->m_imageWidth = swapchainDimensions.m_imageWidth; + *nativeDimensions = m_dimensions; nativeDimensions->m_imageFormat = ConvertFormat(m_surfaceFormat.format); } @@ -165,51 +139,24 @@ namespace AZ RHI::ResultCode SwapChain::ResizeInternal(const RHI::SwapChainDimensions& dimensions, RHI::SwapChainDimensions* nativeDimensions) { auto& device = static_cast(GetDevice()); + m_dimensions = dimensions; InvalidateNativeSwapChain(); - InvalidateSurface(); - RHI::SwapChainDimensions resizeDimensions = dimensions; - BuildSurface(GetDescriptor()); - if (!ValidateSurfaceDimensions(dimensions)) - { - resizeDimensions.m_imageHeight = AZStd::clamp(dimensions.m_imageHeight, m_surfaceCapabilities.minImageExtent.height, m_surfaceCapabilities.maxImageExtent.height); - resizeDimensions.m_imageWidth = AZStd::clamp(dimensions.m_imageWidth, m_surfaceCapabilities.minImageExtent.width, m_surfaceCapabilities.maxImageExtent.width); - AZ_Printf("Vulkan", "Resizing swapchain from (%d, %d) to (%d, %d).", - static_cast(dimensions.m_imageWidth), static_cast(dimensions.m_imageHeight), - static_cast(resizeDimensions.m_imageWidth), static_cast(resizeDimensions.m_imageHeight)); - } auto& presentationQueue = device.GetCommandQueueContext().GetOrCreatePresentationCommandQueue(*this); m_presentationQueue = &presentationQueue; - BuildNativeSwapChain(resizeDimensions, GetDescriptor().m_verticalSyncInterval); - resizeDimensions.m_imageCount = 0; - VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &resizeDimensions.m_imageCount, nullptr); - RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); - - m_swapchainNativeImages.resize(resizeDimensions.m_imageCount); - - // Retrieve the native images of the swapchain so they are - // available when we init the Images in InitImageInternal - vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &resizeDimensions.m_imageCount, m_swapchainNativeImages.data()); - RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); - - // Do not recycle the semaphore because they may not ever get signaled and since - // we can't recycle Vulkan semaphores we just delete them. - m_currentFrameContext.m_imageAvailableSemaphore->SetRecycleValue(false); - m_currentFrameContext.m_presentableSemaphore->SetRecycleValue(false); - - // Acquire the first image - uint32_t imageIndex = 0; - AcquireNewImage(&imageIndex); + CreateSwapchain(); if (nativeDimensions) { - *nativeDimensions = resizeDimensions; + *nativeDimensions = m_dimensions; // [ATOM-4840] This is a workaround when the windows is minimized (0x0 size). // Add proper support to handle this case. - nativeDimensions->m_imageHeight = AZStd::max(resizeDimensions.m_imageHeight, 1u); - nativeDimensions->m_imageWidth = AZStd::max(resizeDimensions.m_imageWidth, 1u); + nativeDimensions->m_imageHeight = AZStd::max(m_dimensions.m_imageHeight, 1u); + nativeDimensions->m_imageWidth = AZStd::max(m_dimensions.m_imageWidth, 1u); + + nativeDimensions->m_imageFormat = ConvertFormat(m_surfaceFormat.format); } return RHI::ResultCode::Success; @@ -271,20 +218,48 @@ namespace AZ info.pImageIndices = &imageIndex; info.pResults = nullptr; - [[maybe_unused]] const VkResult result = vkQueuePresentKHR(vulkanQueue->GetNativeQueue(), &info); + const VkResult result = vkQueuePresentKHR(vulkanQueue->GetNativeQueue(), &info); - // Resizing window cause recreation of SwapChain after calling this method, - // so VK_SUBOPTIMAL_KHR or VK_ERROR_OUT_OF_DATE_KHR should not happen at this point. - AZ_Assert(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR, "Failed to present swapchain %s", GetName().GetCStr()); - AZ_Warning("Vulkan", result != VK_SUBOPTIMAL_KHR, "Suboptimal presentation of swapchain %s", GetName().GetCStr()); + // Vulkan's definition of the two types of errors. + // VK_ERROR_OUT_OF_DATE_KHR: "A surface has changed in such a way that it is no longer compatible with the swapchain, + // and further presentation requests using the swapchain will fail. Applications must query the new surface + // properties and recreate their swapchain if they wish to continue presenting to the surface." + // VK_SUBOPTIMAL_KHR: "A swapchain no longer matches the surface properties exactly, but can still be used to + // present to the surface successfully." + // + // These result values may occur after resizing or some window operation. We should update the surface info and recreate the swapchain. + // VK_SUBOPTIMAL_KHR is treated as success, but we better update the surface info as well. + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) + { + InvalidateNativeSwapChain(); + CreateSwapchain(); + } + else + { + // Other errors are: + // VK_ERROR_OUT_OF_HOST_MEMORY + // VK_ERROR_OUT_OF_DEVICE_MEMORY + // VK_ERROR_DEVICE_LOST + // VK_ERROR_SURFACE_LOST_KHR + // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT + AZ_Assert(result == VK_SUCCESS, "Unhandled error for swapchain presentation."); + } }; m_presentationQueue->QueueCommand(AZStd::move(presentCommand)); uint32_t acquiredImageIndex = GetCurrentImageIndex(); - AcquireNewImage(&acquiredImageIndex); - - return acquiredImageIndex; + RHI::ResultCode result = AcquireNewImage(&acquiredImageIndex); + if (result == RHI::ResultCode::Fail) + { + InvalidateNativeSwapChain(); + CreateSwapchain(); + return 0; + } + else + { + return acquiredImageIndex; + } } RHI::ResultCode SwapChain::BuildSurface(const RHI::SwapChainDescriptor& descriptor) @@ -293,15 +268,8 @@ namespace AZ surfaceDesc.m_windowHandle = descriptor.m_window; RHI::Ptr surface = WSISurface::Create(); const RHI::ResultCode result = surface->Init(surfaceDesc); - if (result == RHI::ResultCode::Success) - { - m_surface = surface; - auto& device = static_cast(GetDevice()); - const auto& physicalDevice = static_cast(device.GetPhysicalDevice()); - VkResult vkResult = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice.GetNativePhysicalDevice(), m_surface->GetNativeSurface(), &m_surfaceCapabilities); - AssertSuccess(vkResult); - RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); - } + RETURN_RESULT_IF_UNSUCCESSFUL(result); + m_surface = surface; return result; } @@ -373,6 +341,21 @@ namespace AZ return supportedModes[0]; } + VkSurfaceCapabilitiesKHR SwapChain::GetSurfaceCapabilities() + { + AZ_Assert(m_surface, "Surface has not been initialized."); + + auto& device = static_cast(GetDevice()); + const auto& physicalDevice = static_cast(device.GetPhysicalDevice()); + + VkSurfaceCapabilitiesKHR surfaceCapabilities; + VkResult vkResult = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physicalDevice.GetNativePhysicalDevice(), m_surface->GetNativeSurface(), &surfaceCapabilities); + AssertSuccess(vkResult); + + return surfaceCapabilities; + } + VkCompositeAlphaFlagBitsKHR SwapChain::GetSupportedCompositeAlpha() const { VkFlags supportedModesBits = m_surfaceCapabilities.supportedCompositeAlpha; @@ -394,15 +377,9 @@ namespace AZ return VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; } - RHI::ResultCode SwapChain::BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions, uint32_t verticalSyncInterval) + RHI::ResultCode SwapChain::BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions) { AZ_Assert(m_nativeSwapChain == VK_NULL_HANDLE, "Vulkan's native SwapChain has been initialized already."); - auto& device = static_cast(GetDevice()); - auto& queueContext = device.GetCommandQueueContext(); - const VkExtent2D extent = { - dimensions.m_imageWidth, - dimensions.m_imageHeight - }; AZ_Assert(m_surface, "Surface is null."); if (!ValidateSurfaceDimensions(dimensions)) @@ -410,7 +387,11 @@ namespace AZ AZ_Assert(false, "Swapchain dimensions are not supported."); return RHI::ResultCode::InvalidArgument; } - m_surfaceFormat = GetSupportedSurfaceFormat(dimensions.m_imageFormat); + + auto& device = static_cast(GetDevice()); + auto& queueContext = device.GetCommandQueueContext(); + const VkExtent2D extent = { dimensions.m_imageWidth, dimensions.m_imageHeight }; + // If the graphic queue is the same as the presentation queue, then we will always acquire // 1 image at the same time. If it's another queue, we will have 2 at the same time (while the other queue // presents the image) @@ -441,11 +422,11 @@ namespace AZ createInfo.imageArrayLayers = 1; // non-stereoscopic createInfo.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; - createInfo.queueFamilyIndexCount = static_cast(familyIndices.size()); + createInfo.queueFamilyIndexCount = aznumeric_cast(familyIndices.size()); createInfo.pQueueFamilyIndices = familyIndices.empty() ? nullptr : familyIndices.data(); createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; - createInfo.compositeAlpha = GetSupportedCompositeAlpha(); - createInfo.presentMode = GetSupportedPresentMode(verticalSyncInterval); + createInfo.compositeAlpha = m_compositeAlphaFlagBits; + createInfo.presentMode = m_presentMode; createInfo.clipped = VK_FALSE; createInfo.oldSwapchain = VK_NULL_HANDLE; @@ -467,9 +448,6 @@ namespace AZ VK_NULL_HANDLE, acquiredImageIndex); - // Resizing window cause recreation of SwapChain before calling this method, - // so VK_SUBOPTIMAL_KHR or VK_ERROR_OUT_OF_DATE_KHR should not happen. - AssertSuccess(vkResult); RHI::ResultCode result = ConvertResult(vkResult); RETURN_RESULT_IF_UNSUCCESSFUL(result); @@ -484,6 +462,7 @@ namespace AZ } m_currentFrameContext.m_imageAvailableSemaphore = imageAvailableSemaphore; m_currentFrameContext.m_presentableSemaphore = semaphoreAllocator.Allocate(); + return result; } @@ -502,5 +481,70 @@ namespace AZ m_nativeSwapChain = VK_NULL_HANDLE; } } + + RHI::ResultCode SwapChain::CreateSwapchain() + { + auto& device = static_cast(GetDevice()); + + m_surfaceCapabilities = GetSurfaceCapabilities(); + m_surfaceFormat = GetSupportedSurfaceFormat(GetDescriptor().m_dimensions.m_imageFormat); + m_presentMode = GetSupportedPresentMode(GetDescriptor().m_verticalSyncInterval); + m_compositeAlphaFlagBits = GetSupportedCompositeAlpha(); + + if (!ValidateSurfaceDimensions(m_dimensions)) + { + uint32_t oldHeight = m_dimensions.m_imageHeight; + uint32_t oldWidth = m_dimensions.m_imageWidth; + m_dimensions.m_imageHeight = AZStd::clamp( + m_dimensions.m_imageHeight, + m_surfaceCapabilities.minImageExtent.height, + m_surfaceCapabilities.maxImageExtent.height); + m_dimensions.m_imageWidth = AZStd::clamp( + m_dimensions.m_imageWidth, + m_surfaceCapabilities.minImageExtent.width, + m_surfaceCapabilities.maxImageExtent.width); + AZ_Printf( + "Vulkan", "Resizing swapchain from (%u, %u) to (%u, %u).", + oldWidth, oldHeight, m_dimensions.m_imageWidth, m_dimensions.m_imageHeight); + } + + RHI::ResultCode result = BuildNativeSwapChain(m_dimensions); + RETURN_RESULT_IF_UNSUCCESSFUL(result); + AZ_TracePrintf("Swapchain", "Swapchain created. Width: %u, Height: %u.", m_dimensions.m_imageWidth, m_dimensions.m_imageHeight); + + // Do not recycle the semaphore because they may not ever get signaled and since + // we can't recycle Vulkan semaphores we just delete them. + if (m_currentFrameContext.m_imageAvailableSemaphore) + { + m_currentFrameContext.m_imageAvailableSemaphore->SetRecycleValue(false); + } + if (m_currentFrameContext.m_presentableSemaphore) + { + m_currentFrameContext.m_presentableSemaphore->SetRecycleValue(false); + } + + m_dimensions.m_imageCount = 0; + VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &m_dimensions.m_imageCount, nullptr); + AssertSuccess(vkResult); + RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); + + m_swapchainNativeImages.resize(m_dimensions.m_imageCount); + + // Retrieve the native images of the swapchain so they are + // available when we init the images in InitImageInternal + vkResult = vkGetSwapchainImagesKHR( + device.GetNativeDevice(), m_nativeSwapChain, &m_dimensions.m_imageCount, m_swapchainNativeImages.data()); + AssertSuccess(vkResult); + RETURN_RESULT_IF_UNSUCCESSFUL(ConvertResult(vkResult)); + AZ_TracePrintf("Swapchain", "Obtained presentable images."); + + // Acquire the first image + uint32_t imageIndex = 0; + result = AcquireNewImage(&imageIndex); + RETURN_RESULT_IF_UNSUCCESSFUL(result); + AZ_TracePrintf("Swapchain", "Acquired the first image."); + + return RHI::ResultCode::Success; + } } } diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h index 14dfd34b08..ee2ff3c207 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h @@ -70,24 +70,48 @@ namespace AZ ////////////////////////////////////////////////////////////////////// RHI::ResultCode BuildSurface(const RHI::SwapChainDescriptor& descriptor); + + //! Returns true is the swapchain dimensions are supported by the current surface. bool ValidateSurfaceDimensions(const RHI::SwapChainDimensions& dimensions); + //! Returns the corresponding Vulkan format that is supported by the surface. + //! If such format is not found, return the first supported format from the surface. VkSurfaceFormatKHR GetSupportedSurfaceFormat(const RHI::Format format) const; + //! Returns the correct presentation mode. + //! If verticalSyncInterval is non-zero, returns VK_PRESENT_MODE_FIFO_KHR. + //! Otherwise, choose preferred mode if they are supported. + //! If not, the first supported present mode is returned. VkPresentModeKHR GetSupportedPresentMode(uint32_t verticalSyncInterval) const; + //! Returns the preferred alpha compositing modes if they are supported. + //! If not, error will be reported. VkCompositeAlphaFlagBitsKHR GetSupportedCompositeAlpha() const; - RHI::ResultCode BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions, uint32_t verticalSyncInterval); + //! Returns the current surface capabilities. + VkSurfaceCapabilitiesKHR GetSurfaceCapabilities(); + //! Create the swapchain when initializing, or + //! swapchain is no longer compatible or is sub-optimal with the surface. + RHI::ResultCode CreateSwapchain(); + //! Build underlying Vulkan swapchain. + RHI::ResultCode BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions); + //! Retrieve the index of the next available presentable image. RHI::ResultCode AcquireNewImage(uint32_t* acquiredImageIndex); + //! Destroy the surface. void InvalidateSurface(); + //! Destroy the old swapchain. void InvalidateNativeSwapChain(); - VkSwapchainKHR m_nativeSwapChain = VK_NULL_HANDLE; RHI::Ptr m_surface; + VkSwapchainKHR m_nativeSwapChain = VK_NULL_HANDLE; CommandQueue* m_presentationQueue = nullptr; - VkSurfaceFormatKHR m_surfaceFormat = {}; - VkSurfaceCapabilitiesKHR m_surfaceCapabilities; - FrameContext m_currentFrameContext; + //! Swapchain data + VkSurfaceFormatKHR m_surfaceFormat = {}; + VkSurfaceCapabilitiesKHR m_surfaceCapabilities = {}; + VkPresentModeKHR m_presentMode = {}; + VkCompositeAlphaFlagBitsKHR m_compositeAlphaFlagBits = {}; + AZStd::vector m_swapchainNativeImages; + RHI::SwapChainDimensions m_dimensions; + struct SwapChainBarrier { VkPipelineStageFlags m_srcPipelineStages = 0; @@ -95,8 +119,6 @@ namespace AZ VkImageMemoryBarrier m_barrier = {}; bool m_isValid = false; } m_swapChainBarrier; - - AZStd::vector m_swapchainNativeImages; }; } } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index 58b07d8351..e8ec77e7bd 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -36,6 +36,8 @@ #include #include +#include "AtomToolsFramework_Traits_Platform.h" + AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include #include @@ -217,7 +219,7 @@ namespace AtomToolsFramework AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor); #if AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY - _exit(0); + ::_exit(0); #else Base::Destroy(); #endif diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index e43880827c..9672abfd99 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -196,12 +196,8 @@ namespace AtomToolsFramework bool RenderViewportWidget::event(QEvent* event) { - // On some types of QEvents, a resize event is needed to make sure that the current viewport window - // needs to be updated based on a potential new surface dimensions. switch (event->type()) { - case QEvent::ScreenChangeInternal: - case QEvent::UpdateLater: case QEvent::Resize: SendWindowResizeEvent(); break; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h index 9ccc4f329b..3cafc183a5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightBus.h @@ -176,6 +176,14 @@ namespace AZ //! Shadow bias reduces acne by applying a small amount of offset along shadow-space z. //! @param Sets the amount of bias to apply. virtual void SetShadowBias(float bias) = 0; + + //! Reduces acne by biasing the shadowmap lookup along the geometric normal. + //! @return Returns the amount of bias to apply. + virtual float GetNormalShadowBias() const = 0; + + //! Reduces acne by biasing the shadowmap lookup along the geometric normal. + //! @param normalShadowBias Sets the amount of normal shadow bias to apply. + virtual void SetNormalShadowBias(float normalShadowBias) = 0; }; using DirectionalLightRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h index 20073f437c..92d5cc9ac0 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/CoreLights/DirectionalLightComponentConfig.h @@ -101,6 +101,9 @@ namespace AZ //! Method of shadow's filtering. ShadowFilterMethod m_shadowFilterMethod = ShadowFilterMethod::None; + // Reduces acne by biasing the shadowmap lookup along the geometric normal. + float m_normalShadowBias = 0.0f; + //! Sample Count for filtering (from 4 to 64) //! It is used only when the pixel is predicted as on the boundary. uint16_t m_filteringSampleCount = 32; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp index 98d3f838c0..78a9cc21d1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentConfig.cpp @@ -39,7 +39,8 @@ namespace AZ ->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod) ->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount) ->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled) - ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias); + ->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias) + ->Field("Normal Shadow Bias", &DirectionalLightComponentConfig::m_normalShadowBias); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp index c20a9f8e17..e36868c4bb 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.cpp @@ -86,6 +86,8 @@ namespace AZ ->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled) ->Event("GetShadowBias", &DirectionalLightRequestBus::Events::GetShadowBias) ->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias) + ->Event("GetNormalShadowBias", &DirectionalLightRequestBus::Events::GetNormalShadowBias) + ->Event("SetNormalShadowBias", &DirectionalLightRequestBus::Events::SetNormalShadowBias) ->VirtualProperty("Color", "GetColor", "SetColor") ->VirtualProperty("Intensity", "GetIntensity", "SetIntensity") ->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter") @@ -101,7 +103,8 @@ namespace AZ ->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod") ->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount") ->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled") - ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias"); + ->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias") + ->VirtualProperty("NormalShadowBias", "GetNormalShadowBias", "SetNormalShadowBias"); ; } } @@ -423,6 +426,20 @@ namespace AZ return m_configuration.m_shadowBias; } + void DirectionalLightComponentController::SetNormalShadowBias(float bias) + { + m_configuration.m_normalShadowBias = bias; + if (m_featureProcessor) + { + m_featureProcessor->SetNormalShadowBias(m_lightHandle, bias); + } + } + + float DirectionalLightComponentController::GetNormalShadowBias() const + { + return m_configuration.m_normalShadowBias; + } + void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count) { const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast(count)); @@ -517,6 +534,7 @@ namespace AZ SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled); SetShadowFilterMethod(m_configuration.m_shadowFilterMethod); SetShadowBias(m_configuration.m_shadowBias); + SetNormalShadowBias(m_configuration.m_normalShadowBias); SetFilteringSampleCount(m_configuration.m_filteringSampleCount); SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h index a0d552cf99..9a6edda666 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DirectionalLightComponentController.h @@ -81,7 +81,9 @@ namespace AZ bool GetShadowReceiverPlaneBiasEnabled() const override; void SetShadowReceiverPlaneBiasEnabled(bool enable) override; float GetShadowBias() const override; - void SetShadowBias(float width) override; + void SetShadowBias(float bias) override; + float GetNormalShadowBias() const override; + void SetNormalShadowBias(float bias) override; private: friend class EditorDirectionalLightComponent; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp index 1e5b2580f7..db46434d9a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp @@ -136,7 +136,7 @@ namespace AZ ->Attribute(Edit::Attributes::Min, 0.0f) ->Attribute(Edit::Attributes::Max, 100.0f) ->Attribute(Edit::Attributes::SoftMin, 0.0f) - ->Attribute(Edit::Attributes::SoftMax, 1.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->Attribute(Edit::Attributes::Visibility, &AreaLightComponentConfig::SupportsShadows) ->Attribute(Edit::Attributes::ReadOnly, &AreaLightComponentConfig::ShadowsDisabled) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index 99e2cc1485..545064b86f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -134,7 +134,7 @@ namespace AZ ->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF") ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count\n", - "This is used only when the pixel is predicted as on the boundary.\n" + "This is used only when the pixel is predicted to be on the boundary.\n" "Specific to PCF and ESM+PCF.") ->Attribute(Edit::Attributes::Min, 4) ->Attribute(Edit::Attributes::Max, 64) @@ -154,6 +154,13 @@ namespace AZ ->Attribute(Edit::Attributes::Min, 0.f) ->Attribute(Edit::Attributes::Max, 0.2) ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement( + Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_normalShadowBias, "Normal Shadow Bias\n", + "Reduces acne by biasing the shadowmap lookup along the geometric normal.\n" + "If this is 0, no biasing is applied.") + ->Attribute(Edit::Attributes::Min, 0.f) + ->Attribute(Edit::Attributes::Max, 10.0f) + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ; } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp index e32b26f0c5..3cc9535d7a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp @@ -100,9 +100,13 @@ namespace AZ ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_whiteBalanceKelvin, "Temperature", "Temperature in Kelvin") ->Attribute(Edit::Attributes::Min, 1000.0f) ->Attribute(Edit::Attributes::Max, 40000.0f) + ->Attribute(AZ::Edit::Attributes::SliderCurveMidpoint, 0.165f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_whiteBalanceTint, "Tint", "Tint Value") ->Attribute(Edit::Attributes::Min, -100.0f) ->Attribute(Edit::Attributes::Max, 100.0f) + ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_whiteBalanceLuminancePreservation, "Luminance Preservation", "Modulate the preservation of luminance") + ->Attribute(Edit::Attributes::Min, 0.0f) + ->Attribute(Edit::Attributes::Max, 1.0f) ->ClassElement(AZ::Edit::ClassElements::Group, "Split Toning") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli index 4b0fa654e3..ffabf7c298 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLightTypes.azsli @@ -465,7 +465,7 @@ void ApplyLighting(inout Surface surface, inout LightingData lightingData) const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { - DirectionalLightShadow::GetShadowCoords(shadowIndex, surface.position, lightingData.shadowCoords); + DirectionalLightShadow::GetShadowCoords(shadowIndex, surface.position, surface.vertexNormal, lightingData.shadowCoords); } // Light loops application. diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index 91e367500b..750cd2fb29 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -59,6 +59,7 @@ VSOutput TerrainPBR_MainPassVS(VertexInput IN) DirectionalLightShadow::GetShadowCoords( shadowIndex, worldPosition, + OUT.m_normal, OUT.m_shadowCoords); } diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index ee4c27b0a9..b76a950beb 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -83,7 +83,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest", + "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest --no-tests=error", "TEST_RESULTS": "True" } }, @@ -96,7 +96,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest", + "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest --no-tests=error", "TEST_RESULTS": "True" } }, @@ -145,7 +145,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", - "CTEST_OPTIONS": "-L (SUITE_periodic)", + "CTEST_OPTIONS": "-L (SUITE_periodic) --no-tests=error", "TEST_RESULTS": "True" } }, @@ -165,7 +165,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", - "CTEST_OPTIONS": "-L (SUITE_sandbox)" + "CTEST_OPTIONS": "-L (SUITE_sandbox) --no-tests=error" } }, "benchmark_test_profile": { @@ -181,7 +181,7 @@ "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", - "CTEST_OPTIONS": "-L (SUITE_benchmark)", + "CTEST_OPTIONS": "-L (SUITE_benchmark) --no-tests=error", "TEST_RESULTS": "True" } }, diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index 7c39661273..de83294639 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -117,7 +117,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_smoke|SUITE_main)\" -LE \"(REQUIRES_gpu)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_smoke|SUITE_main)\" -LE \"(REQUIRES_gpu)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True" } @@ -166,7 +166,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_smoke|SUITE_main)\" -LE \"(REQUIRES_gpu)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_smoke|SUITE_main)\" -LE \"(REQUIRES_gpu)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True" } @@ -187,7 +187,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_smoke_REQUIRES_gpu|SUITE_main_REQUIRES_gpu)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_smoke_REQUIRES_gpu|SUITE_main_REQUIRES_gpu)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True", "TEST_SCREENSHOTS": "True" @@ -238,7 +238,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_awsi", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_awsi)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_awsi)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True" } @@ -257,7 +257,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_periodic)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_periodic)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True" } @@ -279,7 +279,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_sandbox", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_sandbox)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_sandbox)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True" } @@ -298,7 +298,7 @@ "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", - "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\" -T Test", + "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\" -T Test --no-tests=error", "TEST_METRICS": "True", "TEST_RESULTS": "True" } diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 0de161177c..6ad54a11f3 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -596,7 +596,7 @@ def register(engine_path: pathlib.Path = None, :param default_third_party_folder: default 3rd party cache folder :param external_subdir_engine_path: Path to the engine to use when registering an external subdirectory. The registration occurs in the engine.json file in this case - :param external_subdir_engine_path: Path to the project to use when registering an external subdirectory. + :param external_subdir_project_path: Path to the project to use when registering an external subdirectory. The registrations occurs in the project.json in this case :param remove: add/remove the entries :param force: force update of the engine_path for specified "engine_name" from the engine.json file