diff --git a/Assets/Editor/UI/releaseTranslations.py b/Assets/Editor/UI/releaseTranslations.py deleted file mode 100755 index 9ab0345d98..0000000000 --- a/Assets/Editor/UI/releaseTranslations.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# -import os - -os.chdir('qml') -startDir = os.getcwd() - -# since it's a .exe file it will only work on windows, but we may as well -# construct the path in a platform-independent way. -lreleaseCmd = os.path.join(startDir, '..', '..', '..', - 'Code', 'SDKs', 'Qt', 'x64', 'bin', 'lrelease.exe ') - -print(startDir) - -# Korean, Japanese and Simplified Chinese -targetLanguages = ['ko', 'ja', 'zh_CN'] - -for lang in targetLanguages: - os.chdir(startDir) - tgtLang = '-target-language ' + lang - os.system(lreleaseCmd + 'this_' + lang + '.ts') - - for fileName in os.listdir(): - if not fileName.endswith(".ts"): - continue - - os.system(lreleaseCmd + ' ' + fileName) - - print(('Finished processing: ' + fileName)) diff --git a/AutomatedTesting/Config/aws_resource_mappings.json b/AutomatedTesting/Config/default_aws_resource_mappings.json similarity index 100% rename from AutomatedTesting/Config/aws_resource_mappings.json rename to AutomatedTesting/Config/default_aws_resource_mappings.json diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py index 511d9b3ecd..d8fd8b145a 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py @@ -112,7 +112,7 @@ def remove_file(file_path: str) -> None: @pytest.mark.parametrize('project', ['AutomatedTesting']) @pytest.mark.parametrize('level', ['AWS/Metrics']) @pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME]) -@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json']) +@pytest.mark.parametrize('resource_mappings_filename', ['default_aws_resource_mappings.json']) @pytest.mark.parametrize('profile_name', ['AWSAutomationTest']) @pytest.mark.parametrize('region_name', ['us-west-2']) @pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py index f8aa5b85eb..a477e89821 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_anonymous_credentials.py @@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) @pytest.mark.usefixtures('cdk') @pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME]) @pytest.mark.usefixtures('resource_mappings') -@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json']) +@pytest.mark.parametrize('resource_mappings_filename', ['default_aws_resource_mappings.json']) @pytest.mark.usefixtures('aws_utils') @pytest.mark.parametrize('region_name', ['us-west-2']) @pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py index 28b17fdeee..a0bcf56b0d 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py @@ -35,7 +35,7 @@ logger = logging.getLogger(__name__) @pytest.mark.usefixtures('cdk') @pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME]) @pytest.mark.usefixtures('resource_mappings') -@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json']) +@pytest.mark.parametrize('resource_mappings_filename', ['default_aws_resource_mappings.json']) @pytest.mark.usefixtures('aws_utils') @pytest.mark.parametrize('region_name', ['us-west-2']) @pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) diff --git a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt index 834254134e..4441b4ac23 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt @@ -15,7 +15,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE main TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} - PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" + PYTEST_MARKS "SUITE_main and not REQUIRES_gpu" TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor @@ -30,7 +30,23 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} - PYTEST_MARKS "SUITE_periodic" + PYTEST_MARKS "SUITE_periodic and not REQUIRES_gpu" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + Editor + ) + + ly_add_pytest( + NAME AutomatedTesting::EditorTests_Main_GPU + TEST_SUITE main + TEST_SERIAL + TEST_REQUIRES gpu + PATH ${CMAKE_CURRENT_LIST_DIR} + PYTEST_MARKS "SUITE_main and REQUIRES_gpu" TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor diff --git a/AutomatedTesting/Gem/PythonTests/editor/test_BasicEditorWorkflows.py b/AutomatedTesting/Gem/PythonTests/editor/test_BasicEditorWorkflows.py index f65401f007..1f0842dbaa 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/test_BasicEditorWorkflows.py +++ b/AutomatedTesting/Gem/PythonTests/editor/test_BasicEditorWorkflows.py @@ -66,3 +66,35 @@ class TestBasicEditorWorkflows(object): timeout=log_monitor_timeout, auto_test_mode=False ) + + @pytest.mark.test_case_id("C6351273", "C6384955", "C16929880", "C15167490", "C15167491") + @pytest.mark.SUITE_main + @pytest.mark.REQUIRES_gpu + def test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(self, request, editor, level, launcher_platform): + + # Skip test if running against Debug build + if "debug" in internal_plugin.build_directory: + pytest.skip("Does not execute against debug builds.") + + expected_lines = [ + "Create and load new level: True", + "New entity creation: True", + "Create entity hierarchy: True", + "Add component: True", + "Component update: True", + "Remove component: True", + "Save and Export: True", + "BasicEditorWorkflows_LevelEntityComponent: result=SUCCESS", + ] + + hydra.launch_and_validate_results( + request, + test_directory, + editor, + "BasicEditorWorkflows_LevelEntityComponentCRUD.py", + expected_lines, + cfg_args=[level], + timeout=log_monitor_timeout, + auto_test_mode=False, + null_renderer=False + ) diff --git a/AutomatedTesting/Registry/awscoreconfiguration.setreg b/AutomatedTesting/Registry/awscoreconfiguration.setreg index b7c60b0fb9..1927909328 100644 --- a/AutomatedTesting/Registry/awscoreconfiguration.setreg +++ b/AutomatedTesting/Registry/awscoreconfiguration.setreg @@ -4,7 +4,7 @@ "AWSCore": { "ProfileName": "AWSAutomationTest", - "ResourceMappingConfigFileName": "aws_resource_mappings.json" + "ResourceMappingConfigFileName": "default_aws_resource_mappings.json" } } } \ No newline at end of file diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StorageDrive_Windows.h b/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StorageDrive_Windows.h index 479828c9d3..d51fdc36f3 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StorageDrive_Windows.h +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StorageDrive_Windows.h @@ -44,8 +44,7 @@ namespace AZ::IO //! make adjustments. For the most optimal performance align read buffers to the physicalSectorSize. u8 m_enableUnbufferedReads : 1; //! Globally enable file sharing. This allows files to used outside AZ::IO::Streamer, including other applications - //! while in use by AZ::IO::Streamer. File sharing can negatively impact performance and is recommended for - //! development only. + //! while in use by AZ::IO::Streamer. u8 m_enableSharing : 1; //! If true, only information that's explicitly requested or issues are reported. If false, status information //! such as when drives are created and destroyed is reported as well. diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StreamerConfiguration_Windows.cpp b/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StreamerConfiguration_Windows.cpp index 5a19bc4f54..28831a00b0 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StreamerConfiguration_Windows.cpp +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/IO/Streamer/StreamerConfiguration_Windows.cpp @@ -312,7 +312,7 @@ namespace AZ::IO { if (reportHardware) { - AZ_Printf("Streamer", "Skipping drive '%s' because to no paths make use of it.\n", driveIt); + AZ_Printf("Streamer", "Skipping drive '%s' because no paths make use of it.\n", driveIt); } while (*driveIt++); continue; diff --git a/Code/Framework/AzCore/Tests/Platform/Windows/Tests/IO/Streamer/StorageDriveTests_Windows.cpp b/Code/Framework/AzCore/Tests/Platform/Windows/Tests/IO/Streamer/StorageDriveTests_Windows.cpp index bab531deb6..ecf6873052 100644 --- a/Code/Framework/AzCore/Tests/Platform/Windows/Tests/IO/Streamer/StorageDriveTests_Windows.cpp +++ b/Code/Framework/AzCore/Tests/Platform/Windows/Tests/IO/Streamer/StorageDriveTests_Windows.cpp @@ -46,6 +46,7 @@ namespace AZ::IO options.m_hasSeekPenalty = HasSeekPenalty; options.m_enableUnbufferedReads = TestEnableUnbufferReads; options.m_enableSharing = TestEnableSharedReads; + options.m_minimalReporting = true; return StorageDriveWin({ "c:/" }, TestMaxFileHandles, TestMaxMetaDataEntries, TestPhysicalSectorSize, TestLogicalSectorSize, TestMaxIOChannels, TestOverCommit, options); @@ -151,6 +152,7 @@ namespace AZ::IO m_configurationOptions.m_hasSeekPenalty = HasSeekPenalty; m_configurationOptions.m_enableUnbufferedReads = TestEnableUnbufferReads; m_configurationOptions.m_enableSharing = TestEnableSharedReads; + m_configurationOptions.m_minimalReporting = true; m_storageDriveWin = AZStd::make_shared(AZStd::vector{drive}, TestMaxFileHandles, TestMaxMetaDataEntries, TestPhysicalSectorSize, TestLogicalSectorSize, TestMaxIOChannels, overCommit, m_configurationOptions); @@ -1148,3 +1150,142 @@ namespace AZ::IO azfree(buffers[numRequests - 1]); } } // namespace AZ::IO + +#if defined(HAVE_BENCHMARK) + +#include + +namespace Benchmark +{ + class StorageDriveWindowsFixture : public benchmark::Fixture + { + public: + constexpr static char* TestFileName = "StreamerBenchmark.bin"; + constexpr static size_t FileSize = 64_mib; + + void SetupStreamer(bool enableFileSharing) + { + using namespace AZ::IO; + + m_fileIO = new UnitTest::TestFileIOBase(); + m_previousFileIO = AZ::IO::FileIOBase::GetInstance(); + AZ::IO::FileIOBase::SetInstance(nullptr); + AZ::IO::FileIOBase::SetInstance(m_fileIO); + + SystemFile file; + file.Open(TestFileName, SystemFile::OpenMode::SF_OPEN_CREATE | SystemFile::OpenMode::SF_OPEN_READ_WRITE); + AZStd::unique_ptr buffer(new char[FileSize]); + ::memset(buffer.get(), 'c', FileSize); + + file.Write(buffer.get(), FileSize); + file.Close(); + + AZStd::optional absolutePath = AZ::Utils::ConvertToAbsolutePath(TestFileName); + if (absolutePath.has_value()) + { + AZStd::string drive; + AZ::StringFunc::Path::GetDrive(absolutePath->c_str(), drive); + + m_absolutePath = *absolutePath; + + StorageDriveWin::ConstructionOptions options; + options.m_hasSeekPenalty = false; + options.m_enableUnbufferedReads = true; // Leave this on otherwise repeated loads will be using the Windows cache instead. + options.m_enableSharing = enableFileSharing; + options.m_minimalReporting = true; + AZStd::shared_ptr storageDriveWin = + AZStd::make_shared(AZStd::vector{ drive }, 32, 32, 4_kib, 512, 8, 0, options); + + AZStd::unique_ptr stack = AZStd::make_unique(AZStd::move(storageDriveWin)); + m_streamer = aznew Streamer(AZStd::thread_desc{}, AZStd::move(stack)); + } + } + + void TearDown([[maybe_unused]] const ::benchmark::State& state) override + { + using namespace AZ::IO; + + AZStd::string temp; + m_absolutePath.swap(temp); + + delete m_streamer; + + SystemFile::Delete(TestFileName); + + AZ::IO::FileIOBase::SetInstance(nullptr); + AZ::IO::FileIOBase::SetInstance(m_previousFileIO); + delete m_fileIO; + } + + void RepeatedlyReadFile(benchmark::State& state) + { + using namespace AZ::IO; + using namespace AZStd::chrono; + + AZStd::unique_ptr buffer(new char[FileSize]); + + for (auto _ : state) + { + AZStd::binary_semaphore waitForReads; + AZStd::atomic end; + auto callback = [&end, &waitForReads]([[maybe_unused]] FileRequestHandle request) + { + benchmark::DoNotOptimize(end = high_resolution_clock::now()); + waitForReads.release(); + }; + + FileRequestPtr request = m_streamer->Read(m_absolutePath, buffer.get(), state.range(0), state.range(0)); + m_streamer->SetRequestCompleteCallback(request, callback); + + system_clock::time_point start; + benchmark::DoNotOptimize(start = high_resolution_clock::now()); + m_streamer->QueueRequest(request); + + waitForReads.try_acquire_for(AZStd::chrono::seconds(5)); + auto durationInSeconds = duration_cast>(end.load() - start); + + state.SetIterationTime(durationInSeconds.count()); + + m_streamer->QueueRequest(m_streamer->FlushCaches()); + } + } + + AZStd::string m_absolutePath; + AZ::IO::Streamer* m_streamer{}; + AZ::IO::FileIOBase* m_previousFileIO{}; + UnitTest::TestFileIOBase* m_fileIO{}; + }; + + BENCHMARK_DEFINE_F(StorageDriveWindowsFixture, ReadsBaseline)(benchmark::State& state) + { + constexpr bool EnableFileSharing = false; + SetupStreamer(EnableFileSharing); + RepeatedlyReadFile(state); + } + + BENCHMARK_DEFINE_F(StorageDriveWindowsFixture, ReadsWithFileReadSharingEnabled)(benchmark::State& state) + { + using namespace AZ::IO; + + constexpr bool EnableFileSharing = true; + SetupStreamer(EnableFileSharing); + RepeatedlyReadFile(state); + } + + // For these benchmarks the CPU stat doesn't provide useful information because it uses GetThreadTimes on Window but since the main + // thread is mostly sleeping while waiting for the read on the Streamer thread to complete this will report values (close to) zero. + + BENCHMARK_REGISTER_F(StorageDriveWindowsFixture, ReadsBaseline) + ->RangeMultiplier(8) + ->Range(1024, 64_mib) + ->UseManualTime() + ->Unit(benchmark::kMillisecond); + + BENCHMARK_REGISTER_F(StorageDriveWindowsFixture, ReadsWithFileReadSharingEnabled) + ->RangeMultiplier(8) + ->Range(1024, 64_mib) + ->UseManualTime() + ->Unit(benchmark::kMillisecond); + +} // namespace Benchmark +#endif // HAVE_BENCHMARK diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc index 33acfa9e1b..014a098a91 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc @@ -31,5 +31,6 @@ CarrotArrowDown.svg Summary.svg WindowClose.svg + Warning.svg diff --git a/Code/Tools/ProjectManager/Resources/Warning.svg b/Code/Tools/ProjectManager/Resources/Warning.svg new file mode 100644 index 0000000000..28f7bc5f42 --- /dev/null +++ b/Code/Tools/ProjectManager/Resources/Warning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 0e3a4ba95d..93f8895079 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -206,7 +206,11 @@ namespace O3DE::ProjectManager PythonBindingsInterface::Get()->AddProject(projectInfo.m_path); #ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED - m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path); + if (!m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path)) + { + QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for template.")); + return; + } #endif // TEMPLATE_GEM_CONFIGURATION_ENABLED projectInfo.m_needsBuild = true; diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp index cf597745ea..4efa9d1d0f 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include @@ -46,28 +46,28 @@ namespace O3DE::ProjectManager m_engineVersion->lineEdit()->setReadOnly(true); layout->addWidget(m_engineVersion); - m_thirdParty = new FormBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this); + m_thirdParty = new FormFolderBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this); m_thirdParty->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); m_thirdParty->lineEdit()->setReadOnly(true); m_thirdParty->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); connect(m_thirdParty->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); layout->addWidget(m_thirdParty); - m_defaultProjects = new FormBrowseEditWidget(tr("Default Projects Folder"), engineInfo.m_defaultProjectsFolder, this); + m_defaultProjects = new FormFolderBrowseEditWidget(tr("Default Projects Folder"), engineInfo.m_defaultProjectsFolder, this); m_defaultProjects->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); m_defaultProjects->lineEdit()->setReadOnly(true); m_defaultProjects->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); connect(m_defaultProjects->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); layout->addWidget(m_defaultProjects); - m_defaultGems = new FormBrowseEditWidget(tr("Default Gems Folder"), engineInfo.m_defaultGemsFolder, this); + m_defaultGems = new FormFolderBrowseEditWidget(tr("Default Gems Folder"), engineInfo.m_defaultGemsFolder, this); m_defaultGems->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); m_defaultGems->lineEdit()->setReadOnly(true); m_defaultGems->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); connect(m_defaultGems->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); layout->addWidget(m_defaultGems); - m_defaultProjectTemplates = new FormBrowseEditWidget(tr("Default Project Templates Folder"), engineInfo.m_defaultTemplatesFolder, this); + m_defaultProjectTemplates = new FormFolderBrowseEditWidget(tr("Default Project Templates Folder"), engineInfo.m_defaultTemplatesFolder, this); m_defaultProjectTemplates->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); m_defaultProjectTemplates->lineEdit()->setReadOnly(true); m_defaultProjectTemplates->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp index c30d6a7b30..9a2227cf4a 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp @@ -11,13 +11,9 @@ */ #include -#include + #include #include -#include -#include -#include -#include namespace O3DE::ProjectManager { @@ -30,20 +26,4 @@ namespace O3DE::ProjectManager connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton); m_frameLayout->addWidget(browseButton); } - - void FormBrowseEditWidget::HandleBrowseButton() - { - QString defaultPath = m_lineEdit->text(); - if (defaultPath.isEmpty()) - { - defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - } - - QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath)); - if (!directory.isEmpty()) - { - m_lineEdit->setText(directory); - } - - } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h index 887fc29dd9..1eba97654b 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h @@ -27,7 +27,7 @@ namespace O3DE::ProjectManager explicit FormBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr); ~FormBrowseEditWidget() = default; - private slots: - void HandleBrowseButton(); + protected slots: + virtual void HandleBrowseButton() = 0; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormFolderBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormFolderBrowseEditWidget.cpp new file mode 100644 index 0000000000..2c4cad5adb --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormFolderBrowseEditWidget.cpp @@ -0,0 +1,42 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include +#include + +namespace O3DE::ProjectManager +{ + FormFolderBrowseEditWidget::FormFolderBrowseEditWidget(const QString& labelText, const QString& valueText, QWidget* parent) + : FormBrowseEditWidget(labelText, valueText, parent) + { + } + + void FormFolderBrowseEditWidget::HandleBrowseButton() + { + QString defaultPath = m_lineEdit->text(); + if (defaultPath.isEmpty()) + { + defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + } + + QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath)); + if (!directory.isEmpty()) + { + m_lineEdit->setText(directory); + } + + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormFolderBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormFolderBrowseEditWidget.h new file mode 100644 index 0000000000..a99fe3b5c2 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormFolderBrowseEditWidget.h @@ -0,0 +1,33 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class FormFolderBrowseEditWidget + : public FormBrowseEditWidget + { + Q_OBJECT + + public: + explicit FormFolderBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr); + ~FormFolderBrowseEditWidget() = default; + + protected: + void HandleBrowseButton() override; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormImageBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormImageBrowseEditWidget.cpp new file mode 100644 index 0000000000..5bb9d61dd6 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormImageBrowseEditWidget.cpp @@ -0,0 +1,35 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include + +namespace O3DE::ProjectManager +{ + FormImageBrowseEditWidget::FormImageBrowseEditWidget(const QString& labelText, const QString& valueText, QWidget* parent) + : FormBrowseEditWidget(labelText, valueText, parent) + { + } + + void FormImageBrowseEditWidget::HandleBrowseButton() + { + QString file = QDir::toNativeSeparators(QFileDialog::getOpenFileName( + this, tr("Select Image"), m_lineEdit->text(), tr("PNG (*.png)"))); + if (!file.isEmpty()) + { + m_lineEdit->setText(file); + } + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormImageBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormImageBrowseEditWidget.h new file mode 100644 index 0000000000..e01e9254f8 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/FormImageBrowseEditWidget.h @@ -0,0 +1,33 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class FormImageBrowseEditWidget + : public FormBrowseEditWidget + { + Q_OBJECT + + public: + explicit FormImageBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr); + ~FormImageBrowseEditWidget() = default; + + protected: + void HandleBrowseButton() override; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 2c92af4e51..763c9dfb75 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -144,12 +145,23 @@ namespace O3DE::ProjectManager } } - void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath) + bool GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath) { IPythonBindings* pythonBindings = PythonBindingsInterface::Get(); QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); + if (m_gemModel->DoGemsToBeAddedHaveRequirements()) + { + GemRequirementDialog* confirmRequirementsDialog = new GemRequirementDialog(m_gemModel, toBeAdded, this); + confirmRequirementsDialog->exec(); + + if (confirmRequirementsDialog->GetButtonResult() != QDialogButtonBox::ApplyRole) + { + return false; + } + } + for (const QModelIndex& modelIndex : toBeAdded) { const QString gemPath = GemModel::GetPath(modelIndex); @@ -158,6 +170,8 @@ namespace O3DE::ProjectManager { QMessageBox::critical(nullptr, "Operation failed", QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + + return false; } } @@ -169,8 +183,12 @@ namespace O3DE::ProjectManager { QMessageBox::critical(nullptr, "Operation failed", QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + + return false; } } + + return true; } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index f5092e837a..fc771d9168 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -33,7 +33,7 @@ namespace O3DE::ProjectManager ProjectManagerScreen GetScreenEnum() override; void ReinitForProject(const QString& projectPath, bool isNewProject); - void EnableDisableGemsForProject(const QString& projectPath); + bool EnableDisableGemsForProject(const QString& projectPath); private: void FillModel(const QString& projectPath, bool isNewProject); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 722783ece1..f99e9689f3 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -73,6 +73,7 @@ namespace O3DE::ProjectManager Platforms m_platforms; Types m_types; //! Asset and/or Code and/or Tool QStringList m_features; + QString m_requirement; QString m_directoryLink; QString m_documentationLink; QString m_version = "Unknown Version"; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 3ecc18231e..119ec68d3a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -70,6 +71,22 @@ namespace O3DE::ProjectManager m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex)); m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex)); + if (m_model->HasRequirement(modelIndex)) + { + m_reqirementsIconLabel->show(); + m_reqirementsTitleLabel->show(); + m_reqirementsTextLabel->show(); + + m_reqirementsTitleLabel->setText("Requirement"); + m_reqirementsTextLabel->setText(m_model->GetRequirement(modelIndex)); + } + else + { + m_reqirementsIconLabel->hide(); + m_reqirementsTitleLabel->hide(); + m_reqirementsTextLabel->hide(); + } + // Depending and conflicting gems m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex)); m_conflictingGems->Update("Conflicting Gems", "The following Gems will be automatically disabled with this Gem.", m_model->GetConflictingGemNames(modelIndex)); @@ -134,6 +151,28 @@ namespace O3DE::ProjectManager m_mainLayout->addSpacing(10); + // Requirements + m_reqirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor); + + QHBoxLayout* requrementsLayout = new QHBoxLayout(); + requrementsLayout->setAlignment(Qt::AlignTop); + requrementsLayout->setMargin(0); + requrementsLayout->setSpacing(0); + + m_reqirementsIconLabel = new QLabel(); + m_reqirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24)); + requrementsLayout->addWidget(m_reqirementsIconLabel); + + m_reqirementsTextLabel = GemInspector::CreateStyledLabel(requrementsLayout, 10, s_textColor); + m_reqirementsTextLabel->setWordWrap(true); + + QSpacerItem* reqirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding); + requrementsLayout->addSpacerItem(reqirementsSpacer); + + m_mainLayout->addLayout(requrementsLayout); + + m_mainLayout->addSpacing(20); + // Depending and conflicting gems m_dependingGems = new GemsSubWidget(); m_mainLayout->addWidget(m_dependingGems); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index 69c065c81e..4363ea3bc1 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -76,6 +76,11 @@ namespace O3DE::ProjectManager LinkLabel* m_directoryLinkLabel = nullptr; LinkLabel* m_documentationLinkLabel = nullptr; + // Requirements + QLabel* m_reqirementsTitleLabel = nullptr; + QLabel* m_reqirementsIconLabel = nullptr; + QLabel* m_reqirementsTextLabel = nullptr; + // Depending and conflicting gems GemsSubWidget* m_dependingGems = nullptr; GemsSubWidget* m_conflictingGems = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index 03787de7e8..6529e4cf3d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -11,7 +11,7 @@ */ #include -#include "GemModel.h" +#include #include #include #include diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h index 48f173ec3f..a155d9ece0 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h @@ -62,7 +62,7 @@ namespace O3DE::ProjectManager inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2; inline constexpr static qreal s_buttonFontSize = 10.0; - private: + protected: void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; @@ -71,6 +71,7 @@ namespace O3DE::ProjectManager QAbstractItemModel* m_model = nullptr; + private: // Platform icons void AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath); inline constexpr static int s_platformIconSize = 12; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp index 2838277696..575c09db05 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.cpp @@ -10,11 +10,9 @@ * */ -#include "GemListView.h" -#include "GemItemDelegate.h" +#include +#include #include -#include -#include namespace O3DE::ProjectManager { diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h index 178de2395f..5f1a018b9f 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListView.h @@ -13,7 +13,6 @@ #pragma once #if !defined(Q_MOC_RUN) -#include "GemInfo.h" #include #include #include diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 5dc40723c9..7d9d86e3a7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -49,6 +49,7 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); item->setData(gemInfo.m_features, RoleFeatures); item->setData(gemInfo.m_path, RolePath); + item->setData(gemInfo.m_requirement, RoleRequirement); appendRow(item); @@ -183,6 +184,11 @@ namespace O3DE::ProjectManager return modelIndex.data(RolePath).toString(); } + QString GemModel::GetRequirement(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleRequirement).toString(); + } + bool GemModel::IsAdded(const QModelIndex& modelIndex) { return modelIndex.data(RoleIsAdded).toBool(); @@ -208,6 +214,24 @@ namespace O3DE::ProjectManager return (modelIndex.data(RoleWasPreviouslyAdded).toBool() && !modelIndex.data(RoleIsAdded).toBool()); } + bool GemModel::HasRequirement(const QModelIndex& modelIndex) + { + return !modelIndex.data(RoleRequirement).toString().isEmpty(); + } + + bool GemModel::DoGemsToBeAddedHaveRequirements() const + { + for (int row = 0; row < rowCount(); ++row) + { + const QModelIndex modelIndex = index(row, 0); + if (NeedsToBeAdded(modelIndex) && HasRequirement(modelIndex)) + { + return true; + } + } + return false; + } + QVector GemModel::GatherGemsToBeAdded() const { QVector result; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 2e05472cdf..301053a9bf 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -53,12 +53,16 @@ namespace O3DE::ProjectManager static int GetBinarySizeInKB(const QModelIndex& modelIndex); static QStringList GetFeatures(const QModelIndex& modelIndex); static QString GetPath(const QModelIndex& modelIndex); + static QString GetRequirement(const QModelIndex& modelIndex); static bool IsAdded(const QModelIndex& modelIndex); static void SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded); static void SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded); static bool NeedsToBeAdded(const QModelIndex& modelIndex); static bool NeedsToBeRemoved(const QModelIndex& modelIndex); + static bool HasRequirement(const QModelIndex& modelIndex); + + bool DoGemsToBeAddedHaveRequirements() const; QVector GatherGemsToBeAdded() const; QVector GatherGemsToBeRemoved() const; @@ -84,7 +88,8 @@ namespace O3DE::ProjectManager RoleBinarySize, RoleFeatures, RoleTypes, - RolePath + RolePath, + RoleRequirement }; QHash m_nameToIndexMap; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp new file mode 100644 index 0000000000..07024a799b --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.cpp @@ -0,0 +1,93 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +namespace O3DE::ProjectManager +{ + GemRequirementDelegate::GemRequirementDelegate(QAbstractItemModel* model, QObject* parent) + : GemItemDelegate(model, parent) + { + } + + void GemRequirementDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const + { + if (!modelIndex.isValid()) + { + return; + } + + QStyleOptionViewItem options(option); + initStyleOption(&options, modelIndex); + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + + QRect fullRect, itemRect, contentRect; + CalcRects(options, fullRect, itemRect, contentRect); + + QFont standardFont(options.font); + standardFont.setPixelSize(s_fontSize); + QFontMetrics standardFontMetrics(standardFont); + + painter->save(); + painter->setClipping(true); + painter->setClipRect(fullRect); + painter->setFont(options.font); + + // Draw background + painter->fillRect(fullRect, m_backgroundColor); + + // Draw item background + const QColor itemBackgroundColor = m_itemBackgroundColor; + painter->fillRect(itemRect, itemBackgroundColor); + + // Gem name + QString gemName = GemModel::GetName(modelIndex); + QFont gemNameFont(options.font); + const int firstColumnMaxTextWidth = s_summaryStartX - 30; + gemName = QFontMetrics(gemNameFont).elidedText(gemName, Qt::TextElideMode::ElideRight, firstColumnMaxTextWidth); + gemNameFont.setPixelSize(s_gemNameFontSize); + gemNameFont.setBold(true); + QRect gemNameRect = GetTextRect(gemNameFont, gemName, s_gemNameFontSize); + gemNameRect.moveTo(contentRect.left(), contentRect.center().y() - s_gemNameFontSize); + + painter->setFont(gemNameFont); + painter->setPen(m_textColor); + painter->drawText(gemNameRect, Qt::TextSingleLine, gemName); + + // Gem requirement + const QSize requirementSize = QSize(contentRect.width() - s_summaryStartX - s_itemMargins.right(), contentRect.height()); + const QRect requirementRect = QRect(QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), requirementSize); + + painter->setFont(standardFont); + painter->setPen(m_textColor); + + const QString requirement = GemModel::GetRequirement(modelIndex); + painter->drawText(requirementRect, Qt::AlignLeft | Qt::TextWordWrap, requirement); + + painter->restore(); + } + + bool GemRequirementDelegate::editorEvent( + [[maybe_unused]] QEvent* event, + [[maybe_unused]] QAbstractItemModel* model, + [[maybe_unused]] const QStyleOptionViewItem& option, + [[maybe_unused]] const QModelIndex& modelIndex) + { + // Do nothing here + return false; + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h new file mode 100644 index 0000000000..b221dcb8fe --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDelegate.h @@ -0,0 +1,37 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + + +namespace O3DE::ProjectManager +{ + class GemRequirementDelegate + : public GemItemDelegate + { + Q_OBJECT // AUTOMOC + + public: + explicit GemRequirementDelegate(QAbstractItemModel* model, QObject* parent = nullptr); + ~GemRequirementDelegate() = default; + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override; + bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override; + + const QColor m_backgroundColor = QColor("#444444"); // Outside of the actual gem item + const QColor m_itemBackgroundColor = QColor("#393939"); // Background color of the gem item + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.cpp new file mode 100644 index 0000000000..ad0e64b1ca --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.cpp @@ -0,0 +1,92 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + GemRequirementDialog::GemRequirementDialog(GemModel* model, const QVector& gemsToAdd, QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Manual setup is required")); + setModal(true); + + QVBoxLayout* vLayout = new QVBoxLayout(); + vLayout->setMargin(0); + vLayout->setContentsMargins(25, 10, 25, 10); + vLayout->setSizeConstraint(QLayout::SetFixedSize); + setLayout(vLayout); + + QHBoxLayout* instructionLayout = new QHBoxLayout(); + instructionLayout->setMargin(0); + + QLabel* instructionIconLabel = new QLabel(); + instructionIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(32, 32)); + instructionLayout->addWidget(instructionIconLabel); + + instructionLayout->addSpacing(10); + + QLabel* instructionLabel = new QLabel(tr("The following Gem(s) require manual setup before the project can be built successfully.")); + instructionLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + instructionLayout->addWidget(instructionLabel); + + QSpacerItem* instructionSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); + instructionLayout->addSpacerItem(instructionSpacer); + + vLayout->addLayout(instructionLayout); + + vLayout->addSpacing(20); + + GemRequirementFilterProxyModel* proxModel = new GemRequirementFilterProxyModel(model, gemsToAdd, this); + + GemRequirementListView* m_gemListView = new GemRequirementListView(proxModel, proxModel->GetSelectionModel(), this); + vLayout->addWidget(m_gemListView); + + QDialogButtonBox* dialogButtons = new QDialogButtonBox(); + dialogButtons->setObjectName("footer"); + vLayout->addWidget(dialogButtons); + + QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + cancelButton->setProperty("secondary", true); + QPushButton* continueButton = dialogButtons->addButton(tr("Continue"), QDialogButtonBox::ApplyRole); + + connect(cancelButton, &QPushButton::clicked, this, &GemRequirementDialog::CancelButtonPressed); + connect(continueButton, &QPushButton::clicked, this, &GemRequirementDialog::ContinueButtonPressed); + } + + QDialogButtonBox::ButtonRole GemRequirementDialog::GetButtonResult() + { + return m_buttonResult; + } + + void GemRequirementDialog::CancelButtonPressed() + { + m_buttonResult = QDialogButtonBox::RejectRole; + close(); + } + + void GemRequirementDialog::ContinueButtonPressed() + { + m_buttonResult = QDialogButtonBox::ApplyRole; + close(); + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h new file mode 100644 index 0000000000..4295c5d586 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementDialog.h @@ -0,0 +1,41 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +#include +#endif + +namespace O3DE::ProjectManager +{ + QT_FORWARD_DECLARE_CLASS(GemModel) + + class GemRequirementDialog + : public QDialog + { + Q_OBJECT // AUTOMOC + public: + explicit GemRequirementDialog(GemModel* model, const QVector& gemsToAdd, QWidget *parent = nullptr); + ~GemRequirementDialog() = default; + + QDialogButtonBox::ButtonRole GetButtonResult(); + + private: + void CancelButtonPressed(); + void ContinueButtonPressed(); + + QDialogButtonBox::ButtonRole m_buttonResult = QDialogButtonBox::RejectRole; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.cpp new file mode 100644 index 0000000000..120ec63313 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.cpp @@ -0,0 +1,51 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include + +namespace O3DE::ProjectManager +{ + GemRequirementFilterProxyModel::GemRequirementFilterProxyModel(GemModel* sourceModel, const QVector& addedGems, QObject* parent) + : QSortFilterProxyModel(parent) + , m_sourceModel(sourceModel) + , m_addedGems(addedGems) + { + setSourceModel(sourceModel); + m_selectionProxyModel = new AzQtComponents::SelectionProxyModel(sourceModel->GetSelectionModel(), this, parent); + } + + bool GemRequirementFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const + { + // Do not use sourceParent->child because an invalid parent does not produce valid children (which our index function does) + QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent); + if (!sourceIndex.isValid()) + { + return false; + } + + if (!m_addedGems.contains(sourceIndex)) + { + return false; + } + + if (!m_sourceModel->HasRequirement(sourceIndex)) + { + return false; + } + + return true; + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h new file mode 100644 index 0000000000..a891d63d0c --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementFilterProxyModel.h @@ -0,0 +1,44 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QItemSelectionModel) + +namespace O3DE::ProjectManager +{ + QT_FORWARD_DECLARE_CLASS(GemModel) + + class GemRequirementFilterProxyModel + : public QSortFilterProxyModel + { + Q_OBJECT // AUTOMOC + + public: + GemRequirementFilterProxyModel(GemModel* sourceModel, const QVector& addedGems, QObject* parent = nullptr); + + AzQtComponents::SelectionProxyModel* GetSelectionModel() const { return m_selectionProxyModel; } + + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + + private: + GemModel* m_sourceModel = nullptr; + AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr; + + QVector m_addedGems; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.cpp new file mode 100644 index 0000000000..a86ae876c2 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.cpp @@ -0,0 +1,30 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include + +namespace O3DE::ProjectManager +{ + GemRequirementListView::GemRequirementListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent) + : QListView(parent) + { + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + + setStyleSheet("background-color: #444444;"); + + setModel(model); + setSelectionModel(selectionModel); + setItemDelegate(new GemRequirementDelegate(model, this)); + } +} // namespace O3DE::ProjectManager diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/StaticMeshMotionVector.azsl b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h similarity index 52% rename from Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/StaticMeshMotionVector.azsl rename to Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h index 11bc528f3b..25b2837e30 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/StaticMeshMotionVector.azsl +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemRequirementListView.h @@ -10,23 +10,23 @@ * */ -#include "./MeshMotionVectorCommon.azsli" +#pragma once -struct VSInput +#if !defined(Q_MOC_RUN) +#include +#include +#include +#endif + +namespace O3DE::ProjectManager { - float3 m_position : POSITION; -}; - -VSOutput MainVS(VSInput IN) -{ - VSOutput OUT; - - OUT.m_worldPos = mul(SceneSrg::GetObjectToWorldMatrix(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz; - OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(OUT.m_worldPos, 1.0)); - OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz; - - return OUT; -} - - + class GemRequirementListView + : public QListView + { + Q_OBJECT // AUTOMOC + public: + explicit GemRequirementListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr); + ~GemRequirementListView() = default; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp index 5faa6cb8bd..efe34f6fa6 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp @@ -12,8 +12,8 @@ #include #include -#include #include +#include #include #include #include diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp index 8cdab93c6a..62f879963f 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -30,8 +31,6 @@ namespace O3DE::ProjectManager { // 10 Minutes constexpr int MaxBuildTimeMSecs = 600000; - static const QString BuildPathPostfix = "windows_vs2019"; - static const QString ErrorLogPathPostfix = "CMakeFiles/CMakeProjectBuildError.log"; ProjectBuilderWorker::ProjectBuilderWorker(const ProjectInfo& projectInfo) : QObject() @@ -83,7 +82,7 @@ namespace O3DE::ProjectManager QStringList { "-B", - QDir(m_projectInfo.m_path).filePath(BuildPathPostfix), + QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), "-S", m_projectInfo.m_path, "-G", @@ -123,7 +122,7 @@ namespace O3DE::ProjectManager QStringList { "--build", - QDir(m_projectInfo.m_path).filePath(BuildPathPostfix), + QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), "--target", m_projectInfo.m_projectName + ".GameLauncher", "Editor", @@ -159,8 +158,8 @@ namespace O3DE::ProjectManager QString ProjectBuilderWorker::LogFilePath() const { QDir logFilePath(m_projectInfo.m_path); - logFilePath.cd(BuildPathPostfix); - return logFilePath.filePath(ErrorLogPathPostfix); + logFilePath.cd(ProjectBuildPathPostfix); + return logFilePath.filePath(ProjectBuildErrorLogPathPostfix); } void ProjectBuilderWorker::WriteErrorLog(const QString& log) diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index 3bde0a310d..aaf87fd9cf 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -22,12 +23,11 @@ #include #include #include +#include +#include namespace O3DE::ProjectManager { - inline constexpr static int s_projectImageWidth = 210; - inline constexpr static int s_projectImageHeight = 280; - LabelButton::LabelButton(QWidget* parent) : QLabel(parent) { @@ -92,11 +92,6 @@ namespace O3DE::ProjectManager : QFrame(parent) , m_projectInfo(projectInfo) { - if (m_projectInfo.m_imagePath.isEmpty()) - { - m_projectInfo.m_imagePath = ":/DefaultProjectImage.png"; - } - BaseSetup(); if (processing) { @@ -118,20 +113,25 @@ namespace O3DE::ProjectManager setLayout(vLayout); m_projectImageLabel = new LabelButton(this); - m_projectImageLabel->setFixedSize(s_projectImageWidth, s_projectImageHeight); + m_projectImageLabel->setFixedSize(ProjectPreviewImageWidth, ProjectPreviewImageHeight); m_projectImageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); }); vLayout->addWidget(m_projectImageLabel); - m_projectImageLabel->setPixmap( - QPixmap(m_projectInfo.m_imagePath).scaled(m_projectImageLabel->size(), Qt::KeepAspectRatioByExpanding)); + QString projectPreviewPath = QDir(m_projectInfo.m_path).filePath(m_projectInfo.m_iconPath); + QFileInfo doesPreviewExist(projectPreviewPath); + if (!doesPreviewExist.exists() || !doesPreviewExist.isFile()) + { + projectPreviewPath = ":/DefaultProjectImage.png"; + } + m_projectImageLabel->setPixmap(QPixmap(projectPreviewPath).scaled(m_projectImageLabel->size(), Qt::KeepAspectRatioByExpanding)); m_projectFooter = new QFrame(this); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setContentsMargins(0, 0, 0, 0); m_projectFooter->setLayout(hLayout); { - QLabel* projectNameLabel = new QLabel(m_projectInfo.m_displayName, this); + QLabel* projectNameLabel = new QLabel(m_projectInfo.GetProjectDisplayName(), this); hLayout->addWidget(projectNameLabel); } diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp index 99649cbfdf..e7d8a6d0e7 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp @@ -10,33 +10,74 @@ * */ -#include "ProjectInfo.h" +#include +#include + +#include namespace O3DE::ProjectManager { - ProjectInfo::ProjectInfo(const QString& path, const QString& projectName, const QString& displayName, - const QString& origin, const QString& summary, const QString& imagePath, const QString& backgroundImagePath, + ProjectInfo::ProjectInfo( + const QString& path, + const QString& projectName, + const QString& displayName, + const QString& origin, + const QString& summary, + const QString& iconPath, + const QString& newPreviewImagePath, + const QString& newBackgroundImagePath, bool needsBuild) : m_path(path) , m_projectName(projectName) , m_displayName(displayName) , m_origin(origin) , m_summary(summary) - , m_imagePath(imagePath) - , m_backgroundImagePath(backgroundImagePath) + , m_iconPath(iconPath) + , m_newPreviewImagePath(newPreviewImagePath) + , m_newBackgroundImagePath(newBackgroundImagePath) , m_needsBuild(needsBuild) { } - bool ProjectInfo::operator==(const ProjectInfo& rhs) + bool ProjectInfo::operator==(const ProjectInfo& rhs) const { - return m_path == rhs.m_path - && m_projectName == rhs.m_projectName - && m_imagePath == rhs.m_imagePath - && m_backgroundImagePath == rhs.m_backgroundImagePath; + if (m_path != rhs.m_path) + { + return false; + } + if (m_projectName != rhs.m_projectName) + { + return false; + } + if (m_displayName != rhs.m_displayName) + { + return false; + } + if (m_origin != rhs.m_origin) + { + return false; + } + if (m_summary != rhs.m_summary) + { + return false; + } + if (m_iconPath != rhs.m_iconPath) + { + return false; + } + if (m_newPreviewImagePath != rhs.m_newPreviewImagePath) + { + return false; + } + if (m_newBackgroundImagePath != rhs.m_newBackgroundImagePath) + { + return false; + } + + return true; } - bool ProjectInfo::operator!=(const ProjectInfo& rhs) + bool ProjectInfo::operator!=(const ProjectInfo& rhs) const { return !operator==(rhs); } @@ -45,4 +86,16 @@ namespace O3DE::ProjectManager { return !m_path.isEmpty() && !m_projectName.isEmpty(); } + + const QString& ProjectInfo::GetProjectDisplayName() const + { + if (!m_displayName.isEmpty()) + { + return m_displayName; + } + else + { + return m_projectName; + } + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.h b/Code/Tools/ProjectManager/Source/ProjectInfo.h index 184916a514..72f64408f4 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.h +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.h @@ -31,14 +31,16 @@ namespace O3DE::ProjectManager const QString& displayName, const QString& origin, const QString& summary, - const QString& imagePath, - const QString& backgroundImagePath, + const QString& iconPath, + const QString& newPreviewImagePath, + const QString& newBackgroundImagePath, bool needsBuild); - bool operator==(const ProjectInfo& rhs); - bool operator!=(const ProjectInfo& rhs); + bool operator==(const ProjectInfo& rhs) const; + bool operator!=(const ProjectInfo& rhs) const; bool IsValid() const; + const QString& GetProjectDisplayName() const; // from o3de_manifest.json and o3de_projects.json QString m_path; @@ -48,14 +50,14 @@ namespace O3DE::ProjectManager QString m_displayName; QString m_origin; QString m_summary; + QString m_iconPath; QStringList m_userTags; - // Used on projects home screen - QString m_imagePath; - QString m_backgroundImagePath; + // Used as temp variable for replace images + QString m_newPreviewImagePath; + QString m_newBackgroundImagePath; // Used in project creation - bool m_needsBuild = false; //! Does this project need to be built }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h new file mode 100644 index 0000000000..eafab28a60 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h @@ -0,0 +1,24 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ +#pragma once + +#include + +namespace O3DE::ProjectManager +{ + inline constexpr static int ProjectPreviewImageWidth = 210; + inline constexpr static int ProjectPreviewImageHeight = 280; + + static const QString ProjectBuildPathPostfix = "Windows_VS2019"; + static const QString ProjectBuildErrorLogPathPostfix = "CMakeFiles/CMakeProjectBuildError.log"; + static const QString ProjectPreviewImagePath = "preview.png"; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp index b198724353..9dbbf26aa4 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include #include @@ -47,7 +47,7 @@ namespace O3DE::ProjectManager connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::ValidateProjectName); m_verticalLayout->addWidget(m_projectName); - m_projectPath = new FormBrowseEditWidget(tr("Project Location"), "", this); + m_projectPath = new FormFolderBrowseEditWidget(tr("Project Location"), "", this); m_projectPath->lineEdit()->setReadOnly(true); connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::Validate); m_verticalLayout->addWidget(m_projectPath); diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h index 0d75bbbc64..1b1f03051d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h @@ -32,9 +32,9 @@ namespace O3DE::ProjectManager ~ProjectSettingsScreen() = default; ProjectManagerScreen GetScreenEnum() override; - ProjectInfo GetProjectInfo(); + virtual ProjectInfo GetProjectInfo(); - bool Validate(); + virtual bool Validate(); protected slots: virtual bool ValidateProjectName(); diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp index 3e2b3c13e1..91e7f0a719 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp @@ -29,11 +29,8 @@ namespace O3DE::ProjectManager if (!QDir(path).isEmpty()) { QMessageBox::StandardButton warningResult = QMessageBox::warning( - parent, - QObject::tr("Overwrite Directory"), - QObject::tr("Directory is not empty! Are you sure you want to overwrite it?"), - QMessageBox::No | QMessageBox::Yes - ); + parent, QObject::tr("Overwrite Directory"), + QObject::tr("Directory is not empty! Are you sure you want to overwrite it?"), QMessageBox::No | QMessageBox::Yes); if (warningResult != QMessageBox::Yes) { @@ -53,14 +50,13 @@ namespace O3DE::ProjectManager { if (ancestor == descendent) { - return false; + return true; } descendent.cdUp(); - } - while (!descendent.isRoot()); + } while (!descendent.isRoot()); - return true; + return false; } static bool CopyDirectory(const QString& origPath, const QString& newPath) @@ -138,7 +134,7 @@ namespace O3DE::ProjectManager bool CopyProject(const QString& origPath, const QString& newPath) { // Disallow copying from or into subdirectory - if (!IsDirectoryDescedent(origPath, newPath) || !IsDirectoryDescedent(newPath, origPath)) + if (IsDirectoryDescedent(origPath, newPath) || IsDirectoryDescedent(newPath, origPath)) { return false; } @@ -173,20 +169,66 @@ namespace O3DE::ProjectManager return false; } - bool MoveProject(const QString& origPath, const QString& newPath, QWidget* parent) + bool MoveProject(QString origPath, QString newPath, QWidget* parent, bool ignoreRegister) { - if (!WarnDirectoryOverwrite(newPath, parent) || !UnregisterProject(origPath)) + origPath = QDir::toNativeSeparators(origPath); + newPath = QDir::toNativeSeparators(newPath); + + if (!WarnDirectoryOverwrite(newPath, parent) || (!ignoreRegister && !UnregisterProject(origPath))) { return false; } - QDir directory; - if (directory.rename(origPath, newPath)) + QDir newDirectory(newPath); + if (!newDirectory.removeRecursively()) { - return directory.rename(origPath, newPath); + return false; + } + if (!newDirectory.rename(origPath, newPath)) + { + // Likely failed because trying to move to another partition, try copying + if (!CopyProject(origPath, newPath)) + { + return false; + } + + DeleteProjectFiles(origPath, true); } - if (!RegisterProject(newPath)) + if (!ignoreRegister && !RegisterProject(newPath)) + { + return false; + } + + return true; + } + + bool ReplaceFile(const QString& origFile, const QString& newFile, QWidget* parent, bool interactive) + { + QFileInfo original(origFile); + if (original.exists()) + { + if (interactive) + { + QMessageBox::StandardButton warningResult = QMessageBox::warning( + parent, + QObject::tr("Overwrite File?"), + QObject::tr("Replacing this will overwrite the current file on disk. Are you sure?"), + QMessageBox::No | QMessageBox::Yes); + + if (warningResult == QMessageBox::No) + { + return false; + } + } + + if (!QFile::remove(origFile)) + { + return false; + } + } + + if (!QFile::copy(newFile, origFile)) { return false; } diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.h b/Code/Tools/ProjectManager/Source/ProjectUtils.h index 9c711ad187..2fa1258e8f 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.h +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.h @@ -24,7 +24,9 @@ namespace O3DE::ProjectManager bool CopyProjectDialog(const QString& origPath, QWidget* parent = nullptr); bool CopyProject(const QString& origPath, const QString& newPath); bool DeleteProjectFiles(const QString& path, bool force = false); - bool MoveProject(const QString& origPath, const QString& newPath, QWidget* parent = nullptr); + bool MoveProject(QString origPath, QString newPath, QWidget* parent = nullptr, bool ignoreRegister = false); + + bool ReplaceFile(const QString& origFile, const QString& newFile, QWidget* parent = nullptr, bool interactive = true); bool IsVS2019Installed(); diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 6633558406..d0534c1e76 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -12,6 +12,7 @@ #include +#include #include #include #include @@ -35,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -218,16 +218,7 @@ namespace O3DE::ProjectManager ProjectButton* ProjectsScreen::CreateProjectButton(ProjectInfo& project, QLayout* flowLayout, bool processing) { - ProjectButton* projectButton; - - QString projectPreviewPath = project.m_path + m_projectPreviewImagePath; - QFileInfo doesPreviewExist(projectPreviewPath); - if (doesPreviewExist.exists() && doesPreviewExist.isFile()) - { - project.m_imagePath = projectPreviewPath; - } - - projectButton = new ProjectButton(project, this, processing); + ProjectButton* projectButton = new ProjectButton(project, this, processing); flowLayout->addWidget(projectButton); @@ -438,7 +429,7 @@ namespace O3DE::ProjectManager { QMessageBox::information(this, tr("Project Should be rebuilt."), - projectInfo.m_projectName + tr(" project likely needs to be rebuilt.")); + projectInfo.GetProjectDisplayName() + tr(" project likely needs to be rebuilt.")); } } @@ -499,8 +490,8 @@ namespace O3DE::ProjectManager { QMessageBox::StandardButton buildProject = QMessageBox::information( this, - tr("Building \"%1\"").arg(projectInfo.m_projectName), - tr("Ready to build \"%1\"?").arg(projectInfo.m_projectName), + tr("Building \"%1\"").arg(projectInfo.GetProjectDisplayName()), + tr("Ready to build \"%1\"?").arg(projectInfo.GetProjectDisplayName()), QMessageBox::No | QMessageBox::Yes); if (buildProject == QMessageBox::Yes) diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.h b/Code/Tools/ProjectManager/Source/ProjectsScreen.h index bc28d4ef30..b86c2b0240 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.h @@ -80,8 +80,6 @@ namespace O3DE::ProjectManager QQueue m_buildQueue; ProjectBuilderController* m_currentBuilder = nullptr; - const QString m_projectPreviewImagePath = "/preview.png"; - inline constexpr static int s_contentMargins = 80; inline constexpr static int s_spacerSize = 20; }; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 0e00319b6b..db376fb195 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -12,6 +12,7 @@ #include +#include // Qt defines slots, which interferes with the use here. #pragma push_macro("slots") @@ -660,6 +661,7 @@ namespace O3DE::ProjectManager gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name); gemInfo.m_summary = Py_To_String_Optional(data, "Summary", ""); gemInfo.m_version = Py_To_String_Optional(data, "Version", ""); + gemInfo.m_requirement = Py_To_String_Optional(data, "Requirements", ""); if (data.contains("Tags")) { @@ -693,6 +695,7 @@ namespace O3DE::ProjectManager projectInfo.m_displayName = Py_To_String_Optional(projectData, "display_name", projectInfo.m_projectName); projectInfo.m_origin = Py_To_String_Optional(projectData, "origin", projectInfo.m_origin); projectInfo.m_summary = Py_To_String_Optional(projectData, "summary", projectInfo.m_summary); + projectInfo.m_iconPath = Py_To_String_Optional(projectData, "icon", ProjectPreviewImagePath); if (projectData.contains("user_tags")) { for (auto tag : projectData["user_tags"]) @@ -786,7 +789,7 @@ namespace O3DE::ProjectManager pybind11::str(projectInfo.m_origin.toStdString()), // new_origin pybind11::str(projectInfo.m_displayName.toStdString()), // new_display pybind11::str(projectInfo.m_summary.toStdString()), // new_summary - pybind11::str(projectInfo.m_imagePath.toStdString()), // new_icon + pybind11::str(projectInfo.m_iconPath.toStdString()), // new_icon pybind11::none(), // add_tags not used pybind11::none(), // remove_tags not used pybind11::list(pybind11::cast(newTags))); // replace_tags diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index 409c51315d..e51d9e4996 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -101,8 +103,11 @@ namespace O3DE::ProjectManager void UpdateProjectCtrl::HandleGemsButton() { - m_stack->setCurrentWidget(m_gemCatalogScreen); - Update(); + if (UpdateProjectSettings(true)) + { + m_stack->setCurrentWidget(m_gemCatalogScreen); + Update(); + } } void UpdateProjectCtrl::HandleBackButton() @@ -114,7 +119,10 @@ namespace O3DE::ProjectManager } else { - emit GotoPreviousScreenRequest(); + if (UpdateProjectSettings(true)) + { + emit GotoPreviousScreenRequest(); + } } } @@ -124,44 +132,19 @@ namespace O3DE::ProjectManager if (m_stack->currentIndex() == ScreenOrder::Settings && m_updateSettingsScreen) { - if (m_updateSettingsScreen) + if (!UpdateProjectSettings()) { - if (!m_updateSettingsScreen->Validate()) - { - QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings")); - return; - } - - ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo(); - - // Update project if settings changed - if (m_projectInfo != newProjectSettings) - { - auto result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings); - if (!result.IsSuccess()) - { - QMessageBox::critical(this, tr("Project update failed"), tr(result.GetError().c_str())); - return; - } - } - - // Check if project path has changed and move it - if (newProjectSettings.m_path != m_projectInfo.m_path) - { - if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path)) - { - QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project.")); - return; - } - } - - m_projectInfo = newProjectSettings; + return; } } else if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen) { // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. - m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); + if (!m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path)) + { + QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for project.")); + return; + } shouldRebuild = true; } @@ -190,14 +173,15 @@ namespace O3DE::ProjectManager { if (m_stack->currentIndex() == ScreenOrder::Gems) { - m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName)); + + m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName())); m_header->setSubTitle(QString(tr("Configure Gems"))); - m_nextButton->setText(tr("Finalize")); + m_nextButton->setText(tr("Save")); } else { m_header->setTitle(""); - m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName)); + m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName())); m_nextButton->setText(tr("Save")); } } @@ -207,4 +191,70 @@ namespace O3DE::ProjectManager m_updateSettingsScreen->SetProjectInfo(m_projectInfo); } + bool UpdateProjectCtrl::UpdateProjectSettings(bool shouldConfirm) + { + AZ_Assert(m_updateSettingsScreen, "Update settings screen is nullptr.") + + ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo(); + + if (m_projectInfo != newProjectSettings) + { + if (shouldConfirm) + { + QMessageBox::StandardButton warningResult = QMessageBox::warning( + this, + QObject::tr("Unsaved Changes!"), + QObject::tr("Would you like to save your changes to project settings?"), + QMessageBox::No | QMessageBox::Yes + ); + + if (warningResult == QMessageBox::No) + { + return true; + } + } + + if (!m_updateSettingsScreen->Validate()) + { + QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings")); + return false; + } + + // Update project if settings changed + { + auto result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings); + if (!result.IsSuccess()) + { + QMessageBox::critical(this, tr("Project update failed"), tr(result.GetError().c_str())); + return false; + } + } + + // Check if project path has changed and move it + if (newProjectSettings.m_path != m_projectInfo.m_path) + { + if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path)) + { + QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project.")); + return false; + } + } + + if (!newProjectSettings.m_newPreviewImagePath.isEmpty()) + { + if (!ProjectUtils::ReplaceFile( + QDir(newProjectSettings.m_path).filePath(newProjectSettings.m_iconPath), newProjectSettings.m_newPreviewImagePath)) + { + QMessageBox::critical(this, tr("File replace failed"), tr("Failed to replace project preview image.")); + return false; + } + m_updateSettingsScreen->ResetProjectPreviewPath(); + } + + m_projectInfo = newProjectSettings; + } + + return true; + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h index 231bfb8f19..b8b57c0c20 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h @@ -46,6 +46,7 @@ namespace O3DE::ProjectManager private: void Update(); void UpdateSettingsScreen(); + bool UpdateProjectSettings(bool shouldConfirm = false); enum ScreenOrder { diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp index c29be3c7fd..f7be7e84c3 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -11,17 +11,43 @@ */ #include -#include +#include +#include #include +#include #include #include +#include +#include namespace O3DE::ProjectManager { UpdateProjectSettingsScreen::UpdateProjectSettingsScreen(QWidget* parent) : ProjectSettingsScreen(parent) + , m_userChangedPreview(false) { + m_projectPreview = new FormImageBrowseEditWidget(tr("Project Preview"), "", this); + m_projectPreview->lineEdit()->setReadOnly(true); + connect(m_projectPreview->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::Validate); + connect(m_projectPreview->lineEdit(), &QLineEdit::textChanged, this, &UpdateProjectSettingsScreen::PreviewPathChanged); + connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &UpdateProjectSettingsScreen::UpdateProjectPreviewPath); + m_verticalLayout->addWidget(m_projectPreview); + + QVBoxLayout* previewExtrasLayout = new QVBoxLayout(this); + previewExtrasLayout->setAlignment(Qt::AlignLeft); + previewExtrasLayout->setContentsMargins(50, 0, 0, 0); + + QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") + .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); + previewExtrasLayout->addWidget(projectPreviewLabel); + + m_projectPreviewImage = new QLabel(this); + m_projectPreviewImage->setFixedSize(ProjectPreviewImageWidth, ProjectPreviewImageHeight); + m_projectPreviewImage->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + previewExtrasLayout->addWidget(m_projectPreviewImage); + + m_verticalLayout->addLayout(previewExtrasLayout); } ProjectManagerScreen UpdateProjectSettingsScreen::GetScreenEnum() @@ -29,10 +55,58 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::UpdateProjectSettings; } + ProjectInfo UpdateProjectSettingsScreen::GetProjectInfo() + { + m_projectInfo.m_displayName = m_projectName->lineEdit()->text(); + m_projectInfo.m_path = m_projectPath->lineEdit()->text(); + + if (m_userChangedPreview) + { + m_projectInfo.m_iconPath = ProjectPreviewImagePath; + m_projectInfo.m_newPreviewImagePath = m_projectPreview->lineEdit()->text(); + } + return m_projectInfo; + } + void UpdateProjectSettingsScreen::SetProjectInfo(const ProjectInfo& projectInfo) { - m_projectName->lineEdit()->setText(projectInfo.m_projectName); + m_projectInfo = projectInfo; + + m_projectName->lineEdit()->setText(projectInfo.GetProjectDisplayName()); + m_projectPath->lineEdit()->setText(projectInfo.m_path); + UpdateProjectPreviewPath(); + } + + void UpdateProjectSettingsScreen::UpdateProjectPreviewPath() + { + if (!m_userChangedPreview) + { + m_projectPreview->lineEdit()->setText(QDir(m_projectPath->lineEdit()->text()).filePath(m_projectInfo.m_iconPath)); + // Setting the text sets m_userChangedPreview to true + // Set it back to false because it should only be true when changed by user + m_userChangedPreview = false; + } + } + + bool UpdateProjectSettingsScreen::Validate() + { + return ProjectSettingsScreen::Validate() && ValidateProjectPreview(); + } + + void UpdateProjectSettingsScreen::ResetProjectPreviewPath() + { + m_userChangedPreview = false; + UpdateProjectPreviewPath(); + } + + void UpdateProjectSettingsScreen::PreviewPathChanged() + { + m_userChangedPreview = true; + + // Update with latest image + m_projectPreviewImage->setPixmap( + QPixmap(m_projectPreview->lineEdit()->text()).scaled(m_projectPreviewImage->size(), Qt::KeepAspectRatioByExpanding)); } bool UpdateProjectSettingsScreen::ValidateProjectPath() @@ -48,4 +122,39 @@ namespace O3DE::ProjectManager return projectPathIsValid; } + bool UpdateProjectSettingsScreen::ValidateProjectPreview() + { + bool projectPreviewIsValid = true; + + if (m_projectPreview->lineEdit()->text().isEmpty()) + { + projectPreviewIsValid = false; + m_projectPreview->setErrorLabelText(tr("Please select a file.")); + } + else + { + if (m_userChangedPreview) + { + QFileInfo previewFile(m_projectPreview->lineEdit()->text()); + if (!previewFile.exists() || !previewFile.isFile()) + { + projectPreviewIsValid = false; + m_projectPreview->setErrorLabelText(tr("Please select a valid png file.")); + } + else + { + QString fileType = previewFile.completeSuffix().toLower(); + if (fileType != "png") + { + projectPreviewIsValid = false; + m_projectPreview->setErrorLabelText(tr("Please select a png image.")); + } + } + } + } + + m_projectPreview->setErrorLabelVisible(!projectPreviewIsValid); + return projectPreviewIsValid; + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h index 95bbceb9c6..1961648b5b 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h @@ -15,6 +15,8 @@ #include #endif +QT_FORWARD_DECLARE_CLASS(QLabel) + namespace O3DE::ProjectManager { class UpdateProjectSettingsScreen @@ -25,10 +27,26 @@ namespace O3DE::ProjectManager ~UpdateProjectSettingsScreen() = default; ProjectManagerScreen GetScreenEnum() override; + ProjectInfo GetProjectInfo() override; void SetProjectInfo(const ProjectInfo& projectInfo); + bool Validate() override; + + void ResetProjectPreviewPath(); + + public slots: + void UpdateProjectPreviewPath(); + void PreviewPathChanged(); + protected: bool ValidateProjectPath() override; + virtual bool ValidateProjectPreview(); + + FormBrowseEditWidget* m_projectPreview; + QLabel* m_projectPreviewImage; + + ProjectInfo m_projectInfo; + bool m_userChangedPreview; //! Did the user change the project preview path }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 587b5907bb..7eaeed0b96 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -13,6 +13,7 @@ set(FILES Source/Application.h Source/Application.cpp + Source/ProjectManagerDefs.h Source/ScreenDefs.h Source/ScreenFactory.h Source/ScreenFactory.cpp @@ -25,6 +26,10 @@ set(FILES Source/FormLineEditWidget.cpp Source/FormBrowseEditWidget.h Source/FormBrowseEditWidget.cpp + Source/FormFolderBrowseEditWidget.h + Source/FormFolderBrowseEditWidget.cpp + Source/FormImageBrowseEditWidget.h + Source/FormImageBrowseEditWidget.cpp Source/PathValidator.h Source/PathValidator.cpp Source/ProjectManagerWindow.h @@ -82,6 +87,14 @@ set(FILES Source/GemCatalog/GemListHeaderWidget.cpp Source/GemCatalog/GemModel.h Source/GemCatalog/GemModel.cpp + Source/GemCatalog/GemRequirementDialog.h + Source/GemCatalog/GemRequirementDialog.cpp + Source/GemCatalog/GemRequirementDelegate.h + Source/GemCatalog/GemRequirementDelegate.cpp + Source/GemCatalog/GemRequirementFilterProxyModel.h + Source/GemCatalog/GemRequirementFilterProxyModel.cpp + Source/GemCatalog/GemRequirementListView.h + Source/GemCatalog/GemRequirementListView.cpp Source/GemCatalog/GemSortFilterProxyModel.h Source/GemCatalog/GemSortFilterProxyModel.cpp ) diff --git a/Code/Tools/ProjectManager/project_manager_tests_files.cmake b/Code/Tools/ProjectManager/project_manager_tests_files.cmake index e1e84a43a7..e340469bcc 100644 --- a/Code/Tools/ProjectManager/project_manager_tests_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_tests_files.cmake @@ -14,4 +14,5 @@ set(FILES Resources/ProjectManager.qss tests/ApplicationTests.cpp tests/main.cpp + tests/UtilsTests.cpp ) diff --git a/Code/Tools/ProjectManager/tests/UtilsTests.cpp b/Code/Tools/ProjectManager/tests/UtilsTests.cpp new file mode 100644 index 0000000000..e248d37bee --- /dev/null +++ b/Code/Tools/ProjectManager/tests/UtilsTests.cpp @@ -0,0 +1,120 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + namespace ProjectUtils + { + class ProjectManagerUtilsTests + : public ::UnitTest::ScopedAllocatorSetupFixture + { + public: + ProjectManagerUtilsTests() + { + m_application = AZStd::make_unique(); + m_application->Init(false); + + QDir dir; + dir.mkdir("ProjectA"); + dir.mkdir("ProjectB"); + + QFile origFile("ProjectA/origFile.txt"); + if (origFile.open(QIODevice::ReadWrite)) + { + QTextStream stream(&origFile); + stream << "orig" << Qt::endl; + origFile.close(); + } + + QFile replaceFile("ProjectA/replaceFile.txt"); + if (replaceFile.open(QIODevice::ReadWrite)) + { + QTextStream stream(&replaceFile); + stream << "replace" << Qt::endl; + replaceFile.close(); + } + } + + ~ProjectManagerUtilsTests() + { + QDir dirA("ProjectA"); + dirA.removeRecursively(); + + QDir dirB("ProjectB"); + dirB.removeRecursively(); + + m_application.reset(); + } + + AZStd::unique_ptr m_application; + }; + +#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS + TEST_F(ProjectManagerUtilsTests, DISABLED_MoveProject_Succeeds) +#else + TEST_F(ProjectManagerUtilsTests, MoveProject_Succeeds) +#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS + { + EXPECT_TRUE(MoveProject( + QDir::currentPath() + QDir::separator() + "ProjectA", + QDir::currentPath() + QDir::separator() + "ProjectB", + nullptr, true)); + + QFileInfo origFile("ProjectA/origFile.txt"); + EXPECT_TRUE(!origFile.exists()); + + QFileInfo replaceFile("ProjectA/replaceFile.txt"); + EXPECT_TRUE(!replaceFile.exists()); + + QFileInfo origFileMoved("ProjectB/origFile.txt"); + EXPECT_TRUE(origFileMoved.exists() && origFileMoved.isFile()); + + QFileInfo replaceFileMoved("ProjectB/replaceFile.txt"); + EXPECT_TRUE(replaceFileMoved.exists() && replaceFileMoved.isFile()); + } + +#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS + TEST_F(ProjectManagerUtilsTests, DISABLED_ReplaceFile_Succeeds) +#else + TEST_F(ProjectManagerUtilsTests, ReplaceFile_Succeeds) +#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS + { + EXPECT_TRUE(ReplaceFile("ProjectA/origFile.txt", "ProjectA/replaceFile.txt", nullptr, false)); + + QFile origFile("ProjectA/origFile.txt"); + if (origFile.open(QIODevice::ReadOnly)) + { + QTextStream stream(&origFile); + QString line = stream.readLine(); + EXPECT_EQ(line, "replace"); + + origFile.close(); + } + else + { + FAIL(); + } + } + } // namespace ProjectUtils +} // namespace O3DE::ProjectManager diff --git a/Gems/AWSClientAuth/Code/Source/Authentication/AWSCognitoAuthenticationProvider.cpp b/Gems/AWSClientAuth/Code/Source/Authentication/AWSCognitoAuthenticationProvider.cpp index 74865c0044..20f5bf68cc 100644 --- a/Gems/AWSClientAuth/Code/Source/Authentication/AWSCognitoAuthenticationProvider.cpp +++ b/Gems/AWSClientAuth/Code/Source/Authentication/AWSCognitoAuthenticationProvider.cpp @@ -146,12 +146,12 @@ namespace AWSClientAuth void AWSCognitoAuthenticationProvider::DeviceCodeGrantSignInAsync() { - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } void AWSCognitoAuthenticationProvider::DeviceCodeGrantConfirmSignInAsync() { - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } void AWSCognitoAuthenticationProvider::RefreshTokensAsync() diff --git a/Gems/AWSClientAuth/Code/Source/Authentication/AuthenticationProviderManager.cpp b/Gems/AWSClientAuth/Code/Source/Authentication/AuthenticationProviderManager.cpp index d4d2d0d67b..fe8c526a11 100644 --- a/Gems/AWSClientAuth/Code/Source/Authentication/AuthenticationProviderManager.cpp +++ b/Gems/AWSClientAuth/Code/Source/Authentication/AuthenticationProviderManager.cpp @@ -53,7 +53,7 @@ namespace AWSClientAuth if (!m_settingsRegistry->MergeSettingsFile(resolvedPath.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch)) { - AZ_Error("AuthenticationProviderManager", true, "Error merging settings registry for path: %s", resolvedPath.data()); + AZ_Error("AuthenticationProviderManager", false, "Error merging settings registry for path: %s", resolvedPath.data()); return false; } @@ -199,7 +199,7 @@ namespace AWSClientAuth { return enumValue.value(); } - AZ_Warning("AuthenticationProviderManager", true, "Incorrect string value for enum: %s", name.c_str()); + AZ_Warning("AuthenticationProviderManager", false, "Incorrect string value for enum: %s", name.c_str()); return ProviderNameEnum::None; } diff --git a/Gems/AWSClientAuth/Code/Source/Authentication/GoogleAuthenticationProvider.cpp b/Gems/AWSClientAuth/Code/Source/Authentication/GoogleAuthenticationProvider.cpp index 7762b3919b..efabaf5fc1 100644 --- a/Gems/AWSClientAuth/Code/Source/Authentication/GoogleAuthenticationProvider.cpp +++ b/Gems/AWSClientAuth/Code/Source/Authentication/GoogleAuthenticationProvider.cpp @@ -39,7 +39,7 @@ namespace AWSClientAuth { if (!settingsRegistry.lock()->GetObject(m_settings.get(), azrtti_typeid(m_settings.get()), GoogleSettingsPath)) { - AZ_Warning("AWSCognitoAuthenticationProvider", true, "Failed to get Google settings object for path %s", GoogleSettingsPath); + AZ_Warning("AWSCognitoAuthenticationProvider", false, "Failed to get Google settings object for path %s", GoogleSettingsPath); return false; } return true; @@ -49,21 +49,21 @@ namespace AWSClientAuth { AZ_UNUSED(username); AZ_UNUSED(password); - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } void GoogleAuthenticationProvider::PasswordGrantMultiFactorSignInAsync(const AZStd::string& username, const AZStd::string& password) { AZ_UNUSED(username); AZ_UNUSED(password); - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } void GoogleAuthenticationProvider::PasswordGrantMultiFactorConfirmSignInAsync(const AZStd::string& username, const AZStd::string& confirmationCode) { AZ_UNUSED(username); AZ_UNUSED(confirmationCode); - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } // Call Google authentication provider device code end point. diff --git a/Gems/AWSClientAuth/Code/Source/Authentication/LWAAuthenticationProvider.cpp b/Gems/AWSClientAuth/Code/Source/Authentication/LWAAuthenticationProvider.cpp index a86e01a58e..f43611f2c0 100644 --- a/Gems/AWSClientAuth/Code/Source/Authentication/LWAAuthenticationProvider.cpp +++ b/Gems/AWSClientAuth/Code/Source/Authentication/LWAAuthenticationProvider.cpp @@ -38,7 +38,7 @@ namespace AWSClientAuth { if (!settingsRegistry.lock()->GetObject(m_settings.get(), azrtti_typeid(m_settings.get()), LwaSettingsPath)) { - AZ_Warning("AWSCognitoAuthenticationProvider", true, "Failed to get login with Amazon settings object for path %s", LwaSettingsPath); + AZ_Warning("AWSCognitoAuthenticationProvider", false, "Failed to get login with Amazon settings object for path %s", LwaSettingsPath); return false; } return true; @@ -48,21 +48,21 @@ namespace AWSClientAuth { AZ_UNUSED(username); AZ_UNUSED(password); - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } void LWAAuthenticationProvider::PasswordGrantMultiFactorSignInAsync(const AZStd::string& username, const AZStd::string& password) { AZ_UNUSED(username); AZ_UNUSED(password); - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } void LWAAuthenticationProvider::PasswordGrantMultiFactorConfirmSignInAsync(const AZStd::string& username, const AZStd::string& confirmationCode) { AZ_UNUSED(username); AZ_UNUSED(confirmationCode); - AZ_Assert(true, "Not supported"); + AZ_Assert(false, "Not supported"); } // Call LWA authentication provider device code end point. diff --git a/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp b/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp index 5e2c07bdbb..1cae2bd0b1 100644 --- a/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp +++ b/Gems/AWSClientAuth/Code/Source/Authorization/AWSCognitoAuthorizationController.cpp @@ -149,7 +149,7 @@ namespace AWSClientAuth } else { - AZ_Warning("AWSCognitoAuthorizationController", true, "No logins found. Fetching anonymous/unauthenticated credentials"); + AZ_Warning("AWSCognitoAuthorizationController", false, "No logins found. Fetching anonymous/unauthenticated credentials"); } AZ::JobContext* jobContext = nullptr; @@ -277,7 +277,7 @@ namespace AWSClientAuth // Check anonymous credentials as they are optional settings in Cognito Identity pool. if (!m_cognitoCachingAnonymousCredentialsProvider->GetAWSCredentials().IsEmpty()) { - AZ_Warning("AWSCognitoAuthorizationCredentialHandler", true, "No logins found. Using Anonymous credential provider"); + AZ_Warning("AWSCognitoAuthorizationCredentialHandler", false, "No logins found. Using Anonymous credential provider"); return m_cognitoCachingAnonymousCredentialsProvider; } diff --git a/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerScriptCanvasBusTest.cpp b/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerScriptCanvasBusTest.cpp index 7673840299..6aef72af7e 100644 --- a/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerScriptCanvasBusTest.cpp +++ b/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerScriptCanvasBusTest.cpp @@ -257,5 +257,5 @@ TEST_F(AuthenticationProviderManagerScriptCanvasTest, Initialize_Fail_InvalidPat { AZ_TEST_START_TRACE_SUPPRESSION; ASSERT_FALSE(m_mockController->Initialize(m_enabledProviderNames, "")); - AZ_TEST_STOP_TRACE_SUPPRESSION(1); + AZ_TEST_STOP_TRACE_SUPPRESSION(2); } diff --git a/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerTest.cpp b/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerTest.cpp index 4b5bdfb841..ce91e29a1a 100644 --- a/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerTest.cpp +++ b/Gems/AWSClientAuth/Code/Tests/Authentication/AuthenticationProviderManagerTest.cpp @@ -256,5 +256,5 @@ TEST_F(AuthenticationProviderManagerTest, Initialize_Fail_InvalidPath) { AZ_TEST_START_TRACE_SUPPRESSION; ASSERT_FALSE(m_mockController->Initialize(m_enabledProviderNames, "")); - AZ_TEST_STOP_TRACE_SUPPRESSION(1); + AZ_TEST_STOP_TRACE_SUPPRESSION(2); } diff --git a/Gems/AWSClientAuth/cdk/README.md b/Gems/AWSClientAuth/cdk/README.md index 4fe668cb9d..3bc2b59fc1 100644 --- a/Gems/AWSClientAuth/cdk/README.md +++ b/Gems/AWSClientAuth/cdk/README.md @@ -51,6 +51,13 @@ To add additional dependencies, for example other CDK libraries, just add them to your requirements.txt file and rerun the `..\..\..\Lumberyard\python\pip.cmd install -r .\Gems\AWSClientAuth\cdk\requirements.txt` command. + +## Update Authorization Permissions +To give permissions to call AWS resources, please update CognitoIdentityPoolRole class with correct policy statements. + +An example IAM permission policy is provided to grant both authenticated and unauthenticated the permission to list S3 buckets in the project. +However, it is expected that developers replace these permissions with those required by your users to use your resources. + ## Useful commands * `cdk ls` list all stacks in the app diff --git a/Gems/AWSClientAuth/cdk/auth/cognito_identity_pool_role.py b/Gems/AWSClientAuth/cdk/auth/cognito_identity_pool_role.py index 3a2e413617..52df897db9 100755 --- a/Gems/AWSClientAuth/cdk/auth/cognito_identity_pool_role.py +++ b/Gems/AWSClientAuth/cdk/auth/cognito_identity_pool_role.py @@ -53,14 +53,17 @@ class CognitoIdentityPoolRole: } }, assume_role_action='sts:AssumeRoleWithWebIdentity')) - # basic permissions + # The above role is created for developers to add custom permissions that they need to provide authorized + # clients. Developers should update the policy statements below to add their required permissions. + # As an example s3:ListBuckets permissions are provided. + # Note: There must be at least one policy statement here. stack_statement = iam.PolicyStatement( actions=[ 's3:ListBuckets' ], effect=iam.Effect.ALLOW, resources=[ - '*' + f'arn:aws:s3:::{project_name}/*' ], sid=name_utils.format_aws_resource_sid(feature_name, project_name, iam.PolicyStatement.__name__) ) diff --git a/Gems/AWSClientAuth/cdk/auth/cognito_user_pool_sms_role.py b/Gems/AWSClientAuth/cdk/auth/cognito_user_pool_sms_role.py index 286b439a76..c4a442f481 100755 --- a/Gems/AWSClientAuth/cdk/auth/cognito_user_pool_sms_role.py +++ b/Gems/AWSClientAuth/cdk/auth/cognito_user_pool_sms_role.py @@ -32,12 +32,18 @@ class CognitoUserPoolSMSRole: name_utils.format_aws_resource_id(feature_name, project_name, env, iam.Role.__name__), description='Role permissions used by Cognito user pool to send sms', assumed_by=iam.ServicePrincipal("cognito-idp.amazonaws.com"), + # Deny all others and then allow only for the current sms role. inline_policies={ 'SNSRoleInlinePolicy': iam.PolicyDocument( statements=[ + # SMS role will be used by CognitoIDP tp allow to publish to SNS topic owned by CognitoIDP + # team to push a sms. + # Need to use * as the resource name used by CognitoIDP principal service is unknown. iam.PolicyStatement( - actions=["sns:Publish"], resources=["*"] + effect=iam.Effect.ALLOW, + actions=['sns:Publish'], + resources=['*'] ) ] ) diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp index 7114b50906..7697b7820b 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp @@ -72,22 +72,43 @@ namespace AZ static constexpr uint32_t ShaderVariantJobVariantParam = 3; static constexpr uint32_t ShouldExitEarlyFromProcessJobParam = 4; - static void AddShaderAssetJobDependency( - AssetBuilderSDK::JobDescriptor& jobDescriptor, - const AssetBuilderSDK::PlatformInfo& platformInfo, - const AZStd::string& shaderVariantListFilePath, - const AZStd::string& shaderFilePath) + //! Adds source file dependencies for every place a referenced file may appear, and detects if one of + //! those possible paths resolves to the expected file. + //! @param currentFilePath - the full path to the file being processed + //! @param referencedParentPath - the path to a reference file, which may be relative to the @currentFilePath, or may be a full asset path. + //! @param sourceFileDependencies - new source file dependencies will be added to this list + //! @param foundSourceFile - if one of the source file dependencies is found, the highest priority one will be indicated here, otherwise this will be empty. + //! @return true if the referenced file was found and @foundSourceFile was set + bool LocateReferencedSourceFile( + AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath, + AZStd::vector& sourceFileDependencies, + AZStd::string& foundSourceFile) { - AZStd::vector possibleDependencies = AZ::RPI::AssetUtils::GetPossibleDepenencyPaths(shaderVariantListFilePath, shaderFilePath); + foundSourceFile.clear(); + + bool found = false; + + AZStd::vector possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath); for (auto& file : possibleDependencies) { - AssetBuilderSDK::JobDependency jobDependency; - jobDependency.m_jobKey = ShaderAssetBuilder::ShaderAssetBuilderJobKey; - jobDependency.m_platformIdentifier = platformInfo.m_identifier; - jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; - jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; - jobDescriptor.m_jobDependencyList.push_back(jobDependency); + AssetBuilderSDK::SourceFileDependency sourceFileDependency; + sourceFileDependency.m_sourceFileDependencyPath = file; + sourceFileDependencies.push_back(sourceFileDependency); + + if (!found) + { + AZ::Data::AssetInfo sourceInfo; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(found, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, file.c_str(), sourceInfo, watchFolder); + + if (found) + { + foundSourceFile = file; + } + } } + + return found; } //! Returns true if @sourceFileFullPath starts with a valid asset processor scan folder, false otherwise. @@ -334,6 +355,9 @@ namespace AZ response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; return; } + + AZStd::string foundShaderFile; + LocateReferencedSourceFile(variantListFullPath, shaderVariantList.m_shaderFilePath, response.m_sourceFileDependencyList, foundShaderFile); for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) { @@ -349,8 +373,16 @@ namespace AZ jobDescriptor.m_jobKey = GetShaderVariantTreeAssetJobKey(); jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); - - AddShaderAssetJobDependency(jobDescriptor, info, variantListFullPath, shaderVariantList.m_shaderFilePath); + + if (!foundShaderFile.empty()) + { + AssetBuilderSDK::JobDependency jobDependency; + jobDependency.m_jobKey = ShaderAssetBuilder::ShaderAssetBuilderJobKey; + jobDependency.m_platformIdentifier = info.m_identifier; + jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + jobDependency.m_sourceFile.m_sourceFileDependencyPath = foundShaderFile; + jobDescriptor.m_jobDependencyList.push_back(jobDependency); + } jobDescriptor.m_jobParameters.emplace(ShaderSourceFilePathJobParam, shaderSourceFileFullPath); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp index a7721c84a2..9a3eda055e 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/SrgLayoutBuilder.cpp @@ -203,6 +203,16 @@ namespace AZ // queue up AzslBuilder dependencies: for (RHI::ShaderPlatformInterface* shaderPlatformInterface : platformInterfaces) { + const bool isAzsli = AzFramework::StringFunc::Path::IsExtension(fullPath.c_str(), "azsli"); + if (isAzsli) + { + auto skipCheck = ShaderBuilderUtility::ShouldSkipFileForSrgProcessing(SrgLayoutBuilderName, fullPath); + if (skipCheck != ShaderBuilderUtility::SrgSkipFileResult::ContinueProcess) + { + continue; + } + } + AddAzslBuilderJobDependency(jobDescriptor, info.m_identifier, shaderPlatformInterface->GetAPIName().GetCStr(), fullPath); } response.m_createJobOutputs.push_back(jobDescriptor); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index c635f94d56..a71fc65e2a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -1465,14 +1465,9 @@ "file": "./EnhancedPBR_DepthPass_WithPS.shader", "tag": "DepthPass_WithPS" }, - // [GFX TODO][ATOM-4726] Use an "isSkinnedMesh" external material property and a functor that enables/disables the appropriate motion-vector shader { - "file": "Shaders/MotionVector/StaticMeshMotionVector.shader", - "tag": "StaticMeshMotionVector" - }, - { - "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", - "tag": "SkinnedMeshMotionVector" + "file": "Shaders/MotionVector/MeshMotionVector.shader", + "tag": "MeshMotionVector" }, // Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency { @@ -1669,4 +1664,3 @@ "UV1": "Unwrapped" } } - diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index dfe2fad60f..fe86576cf8 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -980,14 +980,9 @@ "file": "Shaders/Depth/DepthPass.shader", "tag": "DepthPass" }, - // [GFX TODO][ATOM-4726] Use an "isSkinnedMesh" external material property and a functor that enables/disables the appropriate motion-vector shader { - "file": "Shaders/MotionVector/StaticMeshMotionVector.shader", - "tag": "StaticMeshMotionVector" - }, - { - "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", - "tag": "SkinnedMeshMotionVector" + "file": "Shaders/MotionVector/MeshMotionVector.shader", + "tag": "MeshMotionVector" } ], "functors": [ diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index d9a21e7662..bccb530eb4 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -2632,14 +2632,9 @@ "file": "./StandardMultilayerPBR_DepthPass_WithPS.shader", "tag": "DepthPass_WithPS" }, - // [GFX TODO][ATOM-4726] Use an "isSkinnedMesh" external material property and a functor that enables/disables the appropriate motion-vector shader { - "file": "Shaders/MotionVector/StaticMeshMotionVector.shader", - "tag": "StaticMeshMotionVector" - }, - { - "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", - "tag": "SkinnedMeshMotionVector" + "file": "Shaders/MotionVector/MeshMotionVector.shader", + "tag": "MeshMotionVector" } ], "functors": [ @@ -3103,4 +3098,3 @@ "UV1": "Unwrapped" } } - diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index fd2c74dae0..93220973df 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1030,14 +1030,9 @@ "file": "./StandardPBR_DepthPass_WithPS.shader", "tag": "DepthPass_WithPS" }, - // [GFX TODO][ATOM-4726] Use an "isSkinnedMesh" external material property and a functor that enables/disables the appropriate motion-vector shader { - "file": "Shaders/MotionVector/StaticMeshMotionVector.shader", - "tag": "StaticMeshMotionVector" - }, - { - "file": "Shaders/MotionVector/SkinnedMeshMotionVector.shader", - "tag": "SkinnedMeshMotionVector" + "file": "Shaders/MotionVector/MeshMotionVector.shader", + "tag": "MeshMotionVector" }, // Used by the light culling system to produce accurate depth bounds for this object when it uses blended transparency { @@ -1187,4 +1182,3 @@ "UV1": "Unwrapped" } } - 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 893df85e3e..03d00d21f9 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 @@ -427,6 +427,7 @@ float DirectionalLightShadow::SamplePcfBicubic() shadowCoord.y >= 0. && shadowCoord.y * size < size - PixelMargin && shadowCoord.z < 1. - DepthMargin) { + m_debugInfo.m_cascadeIndex = indexOfCascade; return SamplePcfBicubic(shadowCoord, indexOfCascade); } } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVector.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVector.azsl new file mode 100644 index 0000000000..3fb3fc0fd8 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVector.azsl @@ -0,0 +1,86 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +#include +#include + +struct VSInput +{ + float3 m_position : POSITION; + + // This gets set automatically by the system at runtime only if it's available. + // There is a soft naming convention that associates this with o_prevPosition_isBound, which will be set to true whenever m_optional_prevPosition is available. + // (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention). + // [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream. + // Vertex position of last frame to capture small scale motion due to vertex animation + float3 m_optional_prevPosition : POSITIONT; +}; + +struct VSOutput +{ + float4 m_position : SV_Position; + float3 m_worldPos : TEXCOORD0; + float3 m_worldPosPrev: TEXCOORD1; +}; + +struct PSOutput +{ + float2 m_motion : SV_Target0; +}; + +// Indicates whether the vertex input struct's "m_optional_prevPosition" is bound. If false, it is not safe to read from m_optional_prevPosition. +// This option gets set automatically by the system at runtime; there is a soft naming convention that associates it with m_optional_prevPosition. +// (search "m_optional_" in ShaderVariantAssetBuilder for details on the naming convention). +// [GFX TODO][ATOM-14475]: Come up with a more elegant way to associate the isBound flag with the input stream. +option bool o_prevPosition_isBound; + +VSOutput MainVS(VSInput IN) +{ + VSOutput OUT; + + OUT.m_worldPos = mul(SceneSrg::GetObjectToWorldMatrix(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz; + OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(OUT.m_worldPos, 1.0)); + + if (o_prevPosition_isBound) + { + OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_optional_prevPosition, 1.0)).xyz; + } + else + { + OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz; + } + + return OUT; +} + +PSOutput MainPS(VSOutput IN) +{ + PSOutput OUT; + + // Current clip position + float4 clipPos = mul(ViewSrg::m_viewProjectionMatrix, float4(IN.m_worldPos, 1.0)); + + // Reprojected last frame's clip position, for skinned mesh it also implies last key frame + float4 clipPosPrev = mul(ViewSrg::m_viewProjectionPrevMatrix, float4(IN.m_worldPosPrev, 1.0)); + + float2 motion = (clipPos.xy / clipPos.w - clipPosPrev.xy / clipPosPrev.w) * 0.5; + + OUT.m_motion = motion; + + // Flip y to line up with uv coordinates + OUT.m_motion.y = -OUT.m_motion.y; + + return OUT; +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/StaticMeshMotionVector.shader b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVector.shader similarity index 89% rename from Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/StaticMeshMotionVector.shader rename to Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVector.shader index 0d580b1b10..c585060f3d 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/StaticMeshMotionVector.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVector.shader @@ -1,5 +1,5 @@ { - "Source" : "StaticMeshMotionVector", + "Source" : "MeshMotionVector", "DepthStencilState" : { "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVectorCommon.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVectorCommon.azsli deleted file mode 100644 index ff2758af87..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/MeshMotionVectorCommon.azsli +++ /dev/null @@ -1,49 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#include -#include - -#include -#include - -struct VSOutput -{ - float4 m_position : SV_Position; - float3 m_worldPos : TEXCOORD0; - float3 m_worldPosPrev: TEXCOORD1; -}; - -struct PSOutput -{ - float2 m_motion : SV_Target0; -}; - -PSOutput MainPS(VSOutput IN) -{ - PSOutput OUT; - - // Current clip position - float4 clipPos = mul(ViewSrg::m_viewProjectionMatrix, float4(IN.m_worldPos, 1.0)); - - // Reprojected last frame's clip position, for skinned mesh it also implies last key frame - float4 clipPosPrev = mul(ViewSrg::m_viewProjectionPrevMatrix, float4(IN.m_worldPosPrev, 1.0)); - - float2 motion = (clipPos.xy / clipPos.w - clipPosPrev.xy / clipPosPrev.w) * 0.5; - - OUT.m_motion = motion; - - // Flip y to line up with uv coordinates - OUT.m_motion.y = -OUT.m_motion.y; - - return OUT; -} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/SkinnedMeshMotionVector.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/SkinnedMeshMotionVector.azsl deleted file mode 100644 index dcbba22b2a..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/SkinnedMeshMotionVector.azsl +++ /dev/null @@ -1,34 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#include "./MeshMotionVectorCommon.azsli" - -struct VSInput -{ - float3 m_position : POSITION; - // Vertex position of last frame to capture small scale motion due to vertex animation - float3 m_prevPosition : POSITIONT; -}; - -VSOutput MainVS(VSInput IN) -{ - VSOutput OUT; - - OUT.m_worldPos = mul(SceneSrg::GetObjectToWorldMatrix(ObjectSrg::m_objectId), float4(IN.m_position, 1.0)).xyz; - OUT.m_position = mul(ViewSrg::m_viewProjectionMatrix, float4(OUT.m_worldPos, 1.0)); - OUT.m_worldPosPrev = mul(SceneSrg::GetObjectToWorldMatrixPrev(ObjectSrg::m_objectId), float4(IN.m_prevPosition, 1.0)).xyz; - - return OUT; -} - - - diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/SkinnedMeshMotionVector.shader b/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/SkinnedMeshMotionVector.shader deleted file mode 100644 index 66d2fd88c5..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Shaders/MotionVector/SkinnedMeshMotionVector.shader +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Source" : "SkinnedMeshMotionVector", - - "DepthStencilState" : { - "Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" } - }, - - "DrawList" : "motion", - - "ProgramSettings": - { - "EntryPoints": - [ - { - "name": "MainVS", - "type": "Vertex" - }, - { - "name": "MainPS", - "type": "Fragment" - } - ] - } -} diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 3dfabc586a..6a2721d4af 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -354,11 +354,8 @@ set(FILES Shaders/MorphTargets/MorphTargetSRG.azsli Shaders/MotionVector/CameraMotionVector.azsl Shaders/MotionVector/CameraMotionVector.shader - Shaders/MotionVector/MeshMotionVectorCommon.azsli - Shaders/MotionVector/SkinnedMeshMotionVector.azsl - Shaders/MotionVector/SkinnedMeshMotionVector.shader - Shaders/MotionVector/StaticMeshMotionVector.azsl - Shaders/MotionVector/StaticMeshMotionVector.shader + Shaders/MotionVector/MeshMotionVector.azsl + Shaders/MotionVector/MeshMotionVector.shader Shaders/PostProcessing/AcesOutputTransformLut.azsl Shaders/PostProcessing/AcesOutputTransformLut.shader Shaders/PostProcessing/ApplyShaperLookupTable.azsl diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h index f1156cfe37..391a60a68d 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h @@ -72,7 +72,6 @@ namespace AZ void UpdateDrawPackets(bool forceUpdate = false); void BuildCullable(); void UpdateCullBounds(const TransformServiceFeatureProcessor* transformService); - void SelectMotionVectorShader(Data::Instance material); void UpdateObjectSrg(); bool MaterialRequiresForwardPassIblSpecular(Data::Instance material) const; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h index d5a6ac1c3e..7cf9ab9685 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessorInterface.h @@ -32,7 +32,6 @@ namespace AZ using RequiresCloneCallback = AZStd::function& modelAsset)>; Data::Asset m_modelAsset; - bool m_isSkinnedMeshWithMotion = false; bool m_isRayTracingEnabled = true; bool m_useForwardPassIblSpecular = false; RequiresCloneCallback m_requiresCloneCallback = {}; diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index f51defc0f3..7867faf02a 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -158,11 +158,6 @@ namespace AZ MeshHandle meshDataHandle = m_meshData.emplace(); meshDataHandle->m_descriptor = descriptor; - - // Always disable ray tracing flag on skinned meshes - // [GFX TODO][ATOM-13067] Enable raytracing on skinned meshes - meshDataHandle->m_descriptor.m_isRayTracingEnabled &= !descriptor.m_isSkinnedMeshWithMotion; - meshDataHandle->m_scene = GetParentScene(); meshDataHandle->m_materialAssignments = materials; meshDataHandle->m_objectId = m_transformService->ReserveObjectId(); @@ -665,8 +660,6 @@ namespace AZ } } - SelectMotionVectorShader(material); - // setup the mesh draw packet RPI::MeshDrawPacket drawPacket(modelLod, meshIndex, material, m_shaderResourceGroup, materialAssignment.m_matModUvOverrides); @@ -1091,29 +1084,6 @@ namespace AZ m_cullBoundsNeedsUpdate = false; } - void MeshDataInstance::SelectMotionVectorShader(Data::Instance material) - { - // Two motion vector shaders are defined in the material for static mesh (only animated by transform matrix) - // and skinned mesh (per vertex animation) respectively, it's because they have different input signatures - // (skinned mesh needs two streaming channels while static mesh only needs one) that cannot be addressed by shader option - // itself. Therefore this function is used to pick one to use and disable the other one depending on the type of the mesh - // so it won't cause errors due to missing input streaming channel. - - //[GFX TODO][ATOM-4726] Replace this with a "isSkinnedMesh" external material property and a functor that enables/disables the appropriate shader - for (auto& shaderItem : material->GetShaderCollection()) - { - if (shaderItem.GetShaderAsset()->GetName() == Name{ "StaticMeshMotionVector" } && m_descriptor.m_isSkinnedMeshWithMotion) - { - shaderItem.SetEnabled(false); - } - - if (shaderItem.GetShaderAsset()->GetName() == Name{ "SkinnedMeshMotionVector" } && (!m_descriptor.m_isSkinnedMeshWithMotion)) - { - shaderItem.SetEnabled(false); - } - } - } - void MeshDataInstance::UpdateObjectSrg() { if (!m_shaderResourceGroup) diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp index 2d4b6ee85f..690a2d63c2 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurChildPass.cpp @@ -47,8 +47,12 @@ namespace AZ m_updateSrg = true; } - params.m_viewportState = RHI::Viewport(0, static_cast(m_imageSize.m_width), 0, static_cast(m_imageSize.m_height)); - params.m_scissorState = RHI::Scissor(0, 0, m_imageSize.m_width, m_imageSize.m_height); + float inverseScale = 1.0f / m_outputScale; + uint32_t outputWidth = m_imageSize.m_width * inverseScale; + uint32_t outputHeight = m_imageSize.m_height * inverseScale; + + params.m_viewportState = RHI::Viewport(0, static_cast(outputWidth), 0, static_cast(outputHeight)); + params.m_scissorState = RHI::Scissor(0, 0, outputWidth, outputHeight); FullscreenTrianglePass::FrameBeginInternal(params); } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h index 554d20dede..4db2187551 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Common/AssetUtils.h @@ -66,6 +66,7 @@ namespace AZ //! it's possible that b.json could be found in either MyGem/Assets/Foo/Bar/a.json or in MyGem/Assets/Bar/a.json. //! @param originatingSourceFilePath Path to a file that references referencedSourceFilePath. May be absolute or relative to asset-root. //! @param referencedSourceFilePath The referenced path as it appears in the originating file. May be relative to the originating file location or relative to asset-root. + //! @return the list of possible paths, ordered from highest priority to lowest priority AZStd::vector GetPossibleDepenencyPaths(const AZStd::string& originatingSourceFilePath, const AZStd::string& referencedSourceFilePath); // Definitions... diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h index fa1bb57166..6cffe4a47d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Material/Material.h @@ -105,7 +105,6 @@ namespace AZ ChangeId GetCurrentChangeId() const; //! Return the set of shaders to be run by this material. - ShaderCollection& GetShaderCollection(); const ShaderCollection& GetShaderCollection() const; //! Attempts to set the value of a system-level shader option that is controlled by this material. diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index d31b6b3802..d52bba2482 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -67,16 +67,41 @@ namespace AZ BusDisconnect(); } - void AddPossibleJobDependencies(const char* jobKey, AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath, AZStd::vector& jobDependencies) + //! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path. + //! This will usually include multiple source dependencies and a single job dependency, but will include only source dependencies if the file is not found. + //! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back + //! to the AssetBuilderSDK::CreateJobsResponse. + void AddPossibleDependencies( + AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath, + AZStd::vector& sourceFileDependencies, + const char* jobKey, AZStd::vector& jobDependencies) { - AZStd::vector possibleDependencies = AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath); + bool dependencyFileFound = false; + + AZStd::vector possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath); for (auto& file : possibleDependencies) { - AssetBuilderSDK::JobDependency jobDependency; - jobDependency.m_jobKey = jobKey; - jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; - jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; - jobDependencies.push_back(jobDependency); + AssetBuilderSDK::SourceFileDependency sourceFileDependency; + sourceFileDependency.m_sourceFileDependencyPath = file; + sourceFileDependencies.push_back(sourceFileDependency); + + // The first path found is the highest priority, and will have a job dependency, as this is the one + // the builder will actually use + if (!dependencyFileFound) + { + AZ::Data::AssetInfo sourceInfo; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(dependencyFileFound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, file.c_str(), sourceInfo, watchFolder); + + if (dependencyFileFound) + { + AssetBuilderSDK::JobDependency jobDependency; + jobDependency.m_jobKey = jobKey; + jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order; + jobDependency.m_sourceFile.m_sourceFileDependencyPath = file; + jobDependencies.push_back(jobDependency); + } + } } } @@ -123,7 +148,7 @@ namespace AZ // We'll build up this one JobDescriptor and reuse it to register each of the platforms AssetBuilderSDK::JobDescriptor outputJobDescriptor; outputJobDescriptor.m_jobKey = JobKey; - + // Load the file so we can detect and report dependencies. // If the file is a .materialtype, report dependencies on the .shader files. // If the file is a .material, report a dependency on the .materialtype and parent .material file @@ -152,7 +177,9 @@ namespace AZ for (auto& shader : materialTypeSourceData.GetValue().m_shaderCollection) { - AddPossibleJobDependencies("Shader Asset", request.m_sourceFile, shader.m_shaderFilePath, outputJobDescriptor.m_jobDependencyList); + AddPossibleDependencies(request.m_sourceFile, shader.m_shaderFilePath, + response.m_sourceFileDependencyList, "Shader Asset", + outputJobDescriptor.m_jobDependencyList); } for (auto& functor : materialTypeSourceData.GetValue().m_materialFunctorSourceData) @@ -161,7 +188,9 @@ namespace AZ for (const MaterialFunctorSourceData::AssetDependency& dependency : dependencies) { - AddPossibleJobDependencies(dependency.m_jobKey.c_str(), request.m_sourceFile, dependency.m_sourceFilePath, outputJobDescriptor.m_jobDependencyList); + AddPossibleDependencies(request.m_sourceFile, dependency.m_sourceFilePath, + response.m_sourceFileDependencyList, + dependency.m_jobKey.c_str(), outputJobDescriptor.m_jobDependencyList); } } } @@ -196,7 +225,9 @@ namespace AZ // Register dependency on the parent material source file so we can load it and use it's data to build this variant material. // Note, we don't need a direct dependency on the material type because the parent material will depend on it. - AddPossibleJobDependencies(JobKey, request.m_sourceFile, parentMaterialPath, outputJobDescriptor.m_jobDependencyList); + AddPossibleDependencies(request.m_sourceFile, parentMaterialPath, + response.m_sourceFileDependencyList, + JobKey, outputJobDescriptor.m_jobDependencyList); } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp index 32cffc771a..8fa0094a53 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Common/AssetUtils.cpp @@ -109,31 +109,16 @@ namespace AZ AZStd::vector GetPossibleDepenencyPaths(const AZStd::string& originatingSourceFilePath, const AZStd::string& referencedSourceFilePath) { - // We potentially add the parent dependency as both a direct path and a relative path rather than use AssetUtils::ResolvePathReference - // because there is no guarantee that the Asset Processor has seen the parent file yet (which ResolvePathReference requires). - // In that case, we have to add both possible locations because we don't know where it will show up. - AZStd::vector results; - // The first dependency we add is using the referencedSourceFilePath as a relative path. This gives relative paths priority over asset-root paths. + // Use the referencedSourceFilePath as a relative path starting at originatingSourceFilePath AZStd::string combinedPath = originatingSourceFilePath; AzFramework::StringFunc::Path::StripFullName(combinedPath); AzFramework::StringFunc::Path::Join(combinedPath.c_str(), referencedSourceFilePath.c_str(), combinedPath); results.push_back(combinedPath); - // If the parent file exists at the relative path, then there is no need to report a dependency on the asset-root path. - bool assetFound = false; - AZ::Data::AssetInfo sourceInfo; - AZStd::string watchFolder; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(assetFound, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, combinedPath.c_str(), sourceInfo, watchFolder); - - if (!assetFound) - { - // The parent file wasn't found at the relative path, so we need a dependency on the asset-root path in case the file - // exists there. Note, we still keep the relative path dependency above because we don't know whether it's missing because - // it doesn't exist, or just because the AP hasn't found it yet. - results.push_back(referencedSourceFilePath); - } + // Use the referencedSourceFilePath as a standard asset path + results.push_back(referencedSourceFilePath); return results; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp index 3302190156..3f376ccf25 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp @@ -164,11 +164,6 @@ namespace AZ Data::AssetBus::Handler::BusDisconnect(); } - ShaderCollection& Material::GetShaderCollection() - { - return m_shaderCollection; - } - const ShaderCollection& Material::GetShaderCollection() const { return m_shaderCollection; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp index 67a6fcaa5b..5c76b084b1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp @@ -24,6 +24,7 @@ #include #include +#include namespace AZ { @@ -181,19 +182,15 @@ namespace AZ RHI::Size targetImageSize = outputAttachment->m_descriptor.m_image.m_size; - m_viewportState = params.m_viewportState; - if (m_viewportState.IsNull()) - { - // compute viewport from target attachment - m_viewportState = RHI::Viewport(0, static_cast(targetImageSize.m_width), 0, static_cast(targetImageSize.m_height)); - } + m_viewportState.m_minX = 0.0f; + m_viewportState.m_minY = 0.0f; + m_viewportState.m_maxX = AZStd::min(static_cast(params.m_viewportState.m_maxX), targetImageSize.m_width); + m_viewportState.m_maxY = AZStd::min(static_cast(params.m_viewportState.m_maxY), targetImageSize.m_height); - m_scissorState = params.m_scissorState; - if (m_scissorState.IsNull()) - { - // compute scissor from target attachment - m_scissorState = RHI::Scissor(0, 0, targetImageSize.m_width, targetImageSize.m_height); - } + m_scissorState.m_minX = 0.0f; + m_scissorState.m_minY = 0.0f; + m_scissorState.m_maxX = AZStd::min(static_cast(params.m_scissorState.m_maxX), targetImageSize.m_width); + m_scissorState.m_maxY = AZStd::min(static_cast(params.m_scissorState.m_maxY), targetImageSize.m_height); RenderPass::FrameBeginInternal(params); } diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp index 620d5d1fb8..1233f3846c 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp @@ -904,7 +904,7 @@ namespace AZ::AtomBridge { // Draw circle with single radius. const float step = DegToRad(10.0f); - const float maxAngle = DegToRad(360.0f) + step; + const float maxAngle = DegToRad(360.0f); SingleColorStaticSizeLineHelper<40> lines; // hard code 40 lines until DegToRad is constexpr. AZ::Vector3 radiusV3 = AZ::Vector3(radius); @@ -1134,7 +1134,7 @@ namespace AZ::AtomBridge // This matches Cry behavior, the DrawWireSphere above may need modifying to use the same approach. // Draw 3 axis aligned circles const float step = DegToRad(10.0f); - const float maxAngle = DegToRad(360.0f) + step; + const float maxAngle = DegToRad(360.0f); SingleColorStaticSizeLineHelper<40*3> lines; // hard code to 40 lines * 3 circles until DegToRad is constexpr. // Z Axis @@ -1160,8 +1160,8 @@ namespace AZ::AtomBridge // Draw 3 axis aligned circles const float stepAngle = DegToRad(11.25f); const float startAngle = DegToRad(0.0f); - const float stopAngle = DegToRad(360.0f) + startAngle; - SingleColorDynamicSizeLineHelper lines(2+static_cast(360.0f/11.25f)); // num disk segments + 1 for azis line + 1 for spare + const float stopAngle = DegToRad(360.0f); + SingleColorDynamicSizeLineHelper lines(2 + static_cast(360.0f / 11.25f)); // num disk segments + 1 for axis line + 1 for spare const AZ::Vector3 radiusV3 = AZ::Vector3(radius); CreateArbitraryAxisArc( lines, diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h index 69d0fc6d96..1f9d514c2f 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h @@ -332,6 +332,15 @@ namespace AZ::AtomBridge p0 = p1; ++segmentIndex; } + // Complete the arc by drawing the last bit + sinCos.SetElement(circleAxis1, sinf(maxAngle)); + sinCos.SetElement(circleAxis2, cosf(maxAngle)); + p1 = position + radiusV3 * sinCos; + p1 = ToWorldSpacePosition(p1); + if (filterFunc(p0, p1, segmentIndex)) + { + lines.AddLineSegment(p0, p1); + } } template @@ -369,5 +378,13 @@ namespace AZ::AtomBridge p0 = p1; ++segmentIndex; } + // Complete the arc by drawing the last bit + AZ::SinCos(maxAngle, sinVF, cosVF); + p1 = position + radiusV3 * (cosVF * a + sinVF * b); + p1 = ToWorldSpacePosition(p1); + if (filterFunc(p0, p1, segmentIndex)) + { + lines.AddLineSegment(p0, p1); + } } } // namespace AZ::AtomBridge diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index f4d4144796..29dee12489 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -628,7 +628,10 @@ namespace AZ { MeshHandleDescriptor meshDescriptor; meshDescriptor.m_modelAsset = m_skinnedMeshInstance->m_model->GetModelAsset(); - meshDescriptor.m_isSkinnedMeshWithMotion = true; + + // [GFX TODO][ATOM-13067] Enable raytracing on skinned meshes + meshDescriptor.m_isRayTracingEnabled = false; + m_meshHandle = AZStd::make_shared( m_meshFeatureProcessor->AcquireMesh(meshDescriptor, materials)); } diff --git a/Gems/LmbrCentral/Code/CMakeLists.txt b/Gems/LmbrCentral/Code/CMakeLists.txt index 4d03d30923..588502954d 100644 --- a/Gems/LmbrCentral/Code/CMakeLists.txt +++ b/Gems/LmbrCentral/Code/CMakeLists.txt @@ -104,7 +104,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target_files( TARGETS LmbrCentral.Editor - FILES ${QT_LRELEASE_EXECUTABLE} + FILES ${lrelease_files} ) # by default, load the above "Gem::LmbrCentral.Editor" module in dev tools diff --git a/Gems/LmbrCentral/Code/Platform/Linux/lrelease_linux.cmake b/Gems/LmbrCentral/Code/Platform/Linux/lrelease_linux.cmake index 41eaf4a47a..70fbecf851 100644 --- a/Gems/LmbrCentral/Code/Platform/Linux/lrelease_linux.cmake +++ b/Gems/LmbrCentral/Code/Platform/Linux/lrelease_linux.cmake @@ -17,3 +17,7 @@ add_custom_command(TARGET LmbrCentral.Editor POST_BUILD COMMENT "Patching lrelease..." VERBATIM ) + +set(lrelease_files + ${QT_LRELEASE_EXECUTABLE} +) diff --git a/Gems/LmbrCentral/Code/Platform/Mac/lrelease_mac.cmake b/Gems/LmbrCentral/Code/Platform/Mac/lrelease_mac.cmake index 4d5680a30d..53fcdfa02a 100644 --- a/Gems/LmbrCentral/Code/Platform/Mac/lrelease_mac.cmake +++ b/Gems/LmbrCentral/Code/Platform/Mac/lrelease_mac.cmake @@ -8,3 +8,7 @@ # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # + +set(lrelease_files + ${QT_LRELEASE_EXECUTABLE} +) diff --git a/Gems/LmbrCentral/Code/Platform/Windows/lrelease_windows.cmake b/Gems/LmbrCentral/Code/Platform/Windows/lrelease_windows.cmake index 4d5680a30d..dda1715d76 100644 --- a/Gems/LmbrCentral/Code/Platform/Windows/lrelease_windows.cmake +++ b/Gems/LmbrCentral/Code/Platform/Windows/lrelease_windows.cmake @@ -8,3 +8,8 @@ # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # + +set(lrelease_files + ${QT_LRELEASE_EXECUTABLE} + ${QT_PATH}/bin/Qt5Core.dll # this is a dependency of lrelease. Even in debug we use the release version +) diff --git a/Gems/MultiplayerCompression/gem.json b/Gems/MultiplayerCompression/gem.json index 2dc0a2d89a..77a86b04a2 100644 --- a/Gems/MultiplayerCompression/gem.json +++ b/Gems/MultiplayerCompression/gem.json @@ -1,16 +1,10 @@ { "gem_name": "MultiplayerCompression", - "GemFormatVersion": 4, - "Uuid": "1d353c8ca3c74ed193fd6c6783ae41cc", - "Name": "MultiplayerCompression", - "DisplayName": "Multiplayer Compression", - "Version": "0.1.0", - "Summary": "The Multiplayer Compression gem provides an open source Compressor for use with the Multiplayer Gem.", - "Tags": ["GridMate","Multiplayer","Networking"], - "IconPath": "preview.png", - "Modules": [ - { - "Type": "GameModule" - } - ] + "display_name": "Multiplayer Compression", + "summary": "The Multiplayer Compression gem provides an open source Compressor for use with AzNetworking's transport layer.", + "canonical_tags": ["Multiplayer", "Networking", "Utility"], + "user_tags": ["MultiplayerCompression"], + "icon_path": "preview.png", + "type": "Code", + "provider": "Open 3D Foundation" } diff --git a/Registry/Platform/Windows/streamer.editor.setreg b/Registry/Platform/Windows/streamer.editor.setreg index 0f7eb3e63d..42f3fc9b7f 100644 --- a/Registry/Platform/Windows/streamer.editor.setreg +++ b/Registry/Platform/Windows/streamer.editor.setreg @@ -13,33 +13,19 @@ [ { "$type": "AZ::IO::StorageDriveConfig", - // The maximum number of file handles that the drive will cache. "MaxFileHandles": 1024 }, { "$type": "AZ::IO::WindowsStorageDriveConfig", - // The maximum number of file handles that the drive will cache. "MaxFileHandles": 1024, - // The maximum number of files to keep the meta data such as the size around for. "MaxMetaDataCache": 1024, - // Number of requests the drive keeps after its queue is full. - // Overcommitting allows for requests to be immediately available after a request completes without needing - // any scheduling, but this also doesn't allow these requests to be rescheduled or updated. "Overcommit": 8, - // Allows files to be shared. This can be needed if the file needs to be opened in multiple locations, such - // as the editor and the Asset Processor. Turning this feature on comes at a small performance cost. "EnableFileSharing": true, - // Unbuffered reads bypass the OS file cache for faster file reads. This helps speed up initial file loads - // and is best for applications that only read a file once such as the game. For applications that frequently - // re-read files such as the editor it's better to turn this feature off. "EnableUnbufferedReads": false, - // If true, only information that's explicitly requested or issues are reported. If false, status information - // such as when drives are created and destroyed is reported as well. "MinimalReporting": false }, { "$type": "AzFramework::RemoteStorageDriveConfig", - // The maximum number of file handles that the drive will cache. "MaxFileHandles": 1024 } ] @@ -48,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/Registry/Platform/Windows/streamer.game.debug.setreg b/Registry/Platform/Windows/streamer.game.debug.setreg new file mode 100644 index 0000000000..9165829b1b --- /dev/null +++ b/Registry/Platform/Windows/streamer.game.debug.setreg @@ -0,0 +1,74 @@ +{ + "Amazon": + { + "AzCore": + { + "Streamer": + { + "Profiles": + { + "Generic": + { + "Stack": + [ + { + "$type": "AZ::IO::WindowsStorageDriveConfig", + "MaxFileHandles": 32, + "MaxMetaDataCache": 32, + "Overcommit": 8, + "EnableFileSharing": true, + "EnableUnbufferedReads": true, + "MinimalReporting": false + }, + { + "$type": "AZ::IO::ReadSplitterConfig", + "BufferSizeMib": 6, + "SplitSize": "MaxTransfer", + "AdjustOffset": true, + "SplitAlignedRequests": false + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + }, + { + "$type": "AZ::IO::BlockCacheConfig", + "CacheSizeMib": 10, + "BlockSize": "MaxTransfer" + }, + { + "$type": "AZ::IO::DedicatedCacheConfig", + "CacheSizeMib": 2, + "BlockSize": "MemoryAlignment", + "WriteOnlyEpilog": true + }, + { + "$type": "AZ::IO::FullFileDecompressorConfig", + "MaxNumReads": 2, + "MaxNumJobs": 2 + } + ] + }, + "DevMode": + { + "Stack": + [ + { + "$type": "AZ::IO::WindowsStorageDriveConfig", + "MaxFileHandles": 1024, + "MaxMetaDataCache": 1024, + "Overcommit": 8, + "EnableFileSharing": true, + "EnableUnbufferedReads": false + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + } + ] + } + } + } + } + } +} diff --git a/Registry/Platform/Windows/streamer.game.profile.setreg b/Registry/Platform/Windows/streamer.game.profile.setreg new file mode 100644 index 0000000000..9165829b1b --- /dev/null +++ b/Registry/Platform/Windows/streamer.game.profile.setreg @@ -0,0 +1,74 @@ +{ + "Amazon": + { + "AzCore": + { + "Streamer": + { + "Profiles": + { + "Generic": + { + "Stack": + [ + { + "$type": "AZ::IO::WindowsStorageDriveConfig", + "MaxFileHandles": 32, + "MaxMetaDataCache": 32, + "Overcommit": 8, + "EnableFileSharing": true, + "EnableUnbufferedReads": true, + "MinimalReporting": false + }, + { + "$type": "AZ::IO::ReadSplitterConfig", + "BufferSizeMib": 6, + "SplitSize": "MaxTransfer", + "AdjustOffset": true, + "SplitAlignedRequests": false + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + }, + { + "$type": "AZ::IO::BlockCacheConfig", + "CacheSizeMib": 10, + "BlockSize": "MaxTransfer" + }, + { + "$type": "AZ::IO::DedicatedCacheConfig", + "CacheSizeMib": 2, + "BlockSize": "MemoryAlignment", + "WriteOnlyEpilog": true + }, + { + "$type": "AZ::IO::FullFileDecompressorConfig", + "MaxNumReads": 2, + "MaxNumJobs": 2 + } + ] + }, + "DevMode": + { + "Stack": + [ + { + "$type": "AZ::IO::WindowsStorageDriveConfig", + "MaxFileHandles": 1024, + "MaxMetaDataCache": 1024, + "Overcommit": 8, + "EnableFileSharing": true, + "EnableUnbufferedReads": false + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + } + ] + } + } + } + } + } +} diff --git a/Registry/Platform/Windows/streamer.game.setreg b/Registry/Platform/Windows/streamer.game.setreg index 7788227fb5..29f85d1727 100644 --- a/Registry/Platform/Windows/streamer.game.setreg +++ b/Registry/Platform/Windows/streamer.game.setreg @@ -27,8 +27,7 @@ // will avoid saturating the IO controller which can be needed if the drive is used by other applications. "Overcommit": 8, // Globally enable file sharing. This allows files to used outside AZ::IO::Streamer, including other - // applications while in use by AZ::IO::Streamer. File sharing can negatively impact performance and is - // recommended for development only. + // applications while in use by AZ::IO::Streamer. "EnableFileSharing": false, // Use unbuffered reads for the fastest possible read speeds by bypassing the Windows file cache. This // results in a faster read the first time a file is read, but subsequent reads will possibly be slower as @@ -63,27 +62,9 @@ "MaxNumJobs": 2 } ] - }, - "DevMode": - { - "Stack": - [ - { - "$type": "AZ::IO::WindowsStorageDriveConfig", - "MaxFileHandles": 1024, - "MaxMetaDataCache": 1024, - "Overcommit": 8, - "EnableFileSharing": true, - "EnableUnbufferedReads": false - }, - { - "$type": "AzFramework::RemoteStorageDriveConfig", - "MaxFileHandles": 1024 - } - ] } } } } } -} \ No newline at end of file +} diff --git a/Registry/Platform/Windows/streamer.test.setreg b/Registry/Platform/Windows/streamer.test.setreg index 13e4fa2d57..c9a225a0b0 100644 --- a/Registry/Platform/Windows/streamer.test.setreg +++ b/Registry/Platform/Windows/streamer.test.setreg @@ -54,4 +54,4 @@ } } } -} \ No newline at end of file +} diff --git a/Registry/streamer.editor.setreg b/Registry/streamer.editor.setreg index 33eff0e5b6..7acc3f5486 100644 --- a/Registry/streamer.editor.setreg +++ b/Registry/streamer.editor.setreg @@ -27,4 +27,4 @@ } } } -} \ No newline at end of file +} diff --git a/Registry/streamer.game.debug.setreg b/Registry/streamer.game.debug.setreg new file mode 100644 index 0000000000..be1be3c073 --- /dev/null +++ b/Registry/streamer.game.debug.setreg @@ -0,0 +1,65 @@ +{ + "Amazon": + { + "AzCore": + { + "Streamer": + { + "Profiles": + { + "Generic": + { + "Stack": + [ + { + "$type": "AZ::IO::StorageDriveConfig", + "MaxFileHandles": 32 + }, + { + "$type": "AZ::IO::ReadSplitterConfig", + "BufferSizeMib": 6, + "SplitSize": "MaxTransfer", + "AdjustOffset": true, + "SplitAlignedRequests": false + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + }, + { + "$type": "AZ::IO::BlockCacheConfig", + "CacheSizeMib": 10, + "BlockSize": "MaxTransfer" + }, + { + "$type": "AZ::IO::DedicatedCacheConfig", + "CacheSizeMib": 2, + "BlockSize": "MemoryAlignment", + "WriteOnlyEpilog": true + }, + { + "$type": "AZ::IO::FullFileDecompressorConfig", + "MaxNumReads": 2, + "MaxNumJobs": 2 + } + ] + }, + "DevMode": + { + "Stack": + [ + { + "$type": "AZ::IO::StorageDriveConfig", + "MaxFileHandles": 1024 + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + } + ] + } + } + } + } + } +} diff --git a/Registry/streamer.game.profile.setreg b/Registry/streamer.game.profile.setreg new file mode 100644 index 0000000000..be1be3c073 --- /dev/null +++ b/Registry/streamer.game.profile.setreg @@ -0,0 +1,65 @@ +{ + "Amazon": + { + "AzCore": + { + "Streamer": + { + "Profiles": + { + "Generic": + { + "Stack": + [ + { + "$type": "AZ::IO::StorageDriveConfig", + "MaxFileHandles": 32 + }, + { + "$type": "AZ::IO::ReadSplitterConfig", + "BufferSizeMib": 6, + "SplitSize": "MaxTransfer", + "AdjustOffset": true, + "SplitAlignedRequests": false + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + }, + { + "$type": "AZ::IO::BlockCacheConfig", + "CacheSizeMib": 10, + "BlockSize": "MaxTransfer" + }, + { + "$type": "AZ::IO::DedicatedCacheConfig", + "CacheSizeMib": 2, + "BlockSize": "MemoryAlignment", + "WriteOnlyEpilog": true + }, + { + "$type": "AZ::IO::FullFileDecompressorConfig", + "MaxNumReads": 2, + "MaxNumJobs": 2 + } + ] + }, + "DevMode": + { + "Stack": + [ + { + "$type": "AZ::IO::StorageDriveConfig", + "MaxFileHandles": 1024 + }, + { + "$type": "AzFramework::RemoteStorageDriveConfig", + "MaxFileHandles": 1024 + } + ] + } + } + } + } + } +} diff --git a/Registry/streamer.game.setreg b/Registry/streamer.game.setreg index 972fb80fb2..69f3189d46 100644 --- a/Registry/streamer.game.setreg +++ b/Registry/streamer.game.setreg @@ -57,23 +57,9 @@ "MaxNumJobs": 2 } ] - }, - "DevMode": - { - "Stack": - [ - { - "$type": "AZ::IO::StorageDriveConfig", - "MaxFileHandles": 1024 - }, - { - "$type": "AzFramework::RemoteStorageDriveConfig", - "MaxFileHandles": 1024 - } - ] } } } } } -} \ No newline at end of file +} diff --git a/Registry/streamer.setreg b/Registry/streamer.setreg index 79e80bbf5c..48177628c2 100644 --- a/Registry/streamer.setreg +++ b/Registry/streamer.setreg @@ -26,4 +26,4 @@ } } } -} \ No newline at end of file +} diff --git a/Registry/streamer.test.setreg b/Registry/streamer.test.setreg index 21504f659a..0adb01fca8 100644 --- a/Registry/streamer.test.setreg +++ b/Registry/streamer.test.setreg @@ -45,4 +45,4 @@ } } } -} \ No newline at end of file +} diff --git a/cmake/Tools/Platform/Android/android_support.py b/cmake/Tools/Platform/Android/android_support.py index 75e0cd2970..d4aff062ee 100755 --- a/cmake/Tools/Platform/Android/android_support.py +++ b/cmake/Tools/Platform/Android/android_support.py @@ -331,7 +331,7 @@ android_gradle_plugin={android_gradle_plugin_version} NATIVE_CMAKE_SECTION_ANDROID_FORMAT = """ externalNativeBuild {{ cmake {{ - buildStagingDirectory "." + buildStagingDirectory "{native_build_path}" version "{cmake_version}" path "{absolute_cmakelist_path}" }} @@ -447,8 +447,8 @@ class AndroidProjectGenerator(object): def __init__(self, engine_root, build_dir, android_sdk_path, build_tool, android_sdk_platform, android_native_api_level, android_ndk, project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, gradle_version, gradle_plugin_version, - override_ninja_path, include_assets_in_apk, asset_mode, asset_type, signing_config, is_test_project=False, - overwrite_existing=True): + override_ninja_path, include_assets_in_apk, asset_mode, asset_type, signing_config, native_build_path, is_test_project=False, + overwrite_existing=True, unity_build_enabled=False): """ Initialize the object with all the required parameters needed to create an Android Project. The parameters should be verified before initializing this object @@ -509,6 +509,8 @@ class AndroidProjectGenerator(object): self.include_assets_in_apk = include_assets_in_apk + self.native_build_path = native_build_path + self.asset_mode = asset_mode self.asset_type = asset_type @@ -519,6 +521,8 @@ class AndroidProjectGenerator(object): self.overwrite_existing = overwrite_existing + self.unity_build_enabled = unity_build_enabled + def execute(self): """ Execute the android project creation workflow @@ -756,6 +760,9 @@ class AndroidProjectGenerator(object): template_engine_root = common.normalize_path_for_settings(self.engine_root) template_third_party_path = common.normalize_path_for_settings(self.third_party_path) template_ndk_path = common.normalize_path_for_settings(os.path.join(self.android_sdk_path, self.android_ndk.location)) + template_unity_build = 1 if self.unity_build_enabled else 0 + + native_build_path = pathlib.Path(self.native_build_path).resolve().as_posix() if self.native_build_path else '.' gradle_build_env = dict() @@ -766,7 +773,7 @@ class AndroidProjectGenerator(object): gradle_build_env['TARGET_TYPE'] = 'application' gradle_build_env['PROJECT_DEPENDENCIES'] = PROJECT_DEPENDENCIES_VALUE_FORMAT.format(dependencies='\n'.join(gradle_project_dependencies)) - gradle_build_env['NATIVE_CMAKE_SECTION_ANDROID'] = NATIVE_CMAKE_SECTION_ANDROID_FORMAT.format(cmake_version=str(self.cmake_version), absolute_cmakelist_path=absolute_cmakelist_path) + gradle_build_env['NATIVE_CMAKE_SECTION_ANDROID'] = NATIVE_CMAKE_SECTION_ANDROID_FORMAT.format(cmake_version=str(self.cmake_version), native_build_path=native_build_path, absolute_cmakelist_path=absolute_cmakelist_path) gradle_build_env['NATIVE_CMAKE_SECTION_DEFAULT_CONFIG'] = NATIVE_CMAKE_SECTION_DEFAULT_CONFIG_NDK_FORMAT_STR.format(abi=ANDROID_ARCH) gradle_build_env['OVERRIDE_JAVA_SOURCESET'] = OVERRIDE_JAVA_SOURCESET_STR.format(absolute_azandroid_path=absolute_azandroid_path) @@ -784,7 +791,8 @@ class AndroidProjectGenerator(object): f'"-S{template_engine_root}"', f'"-DCMAKE_BUILD_TYPE={native_config_lower}"', f'"-DCMAKE_TOOLCHAIN_FILE={template_engine_root}/cmake/Platform/Android/Toolchain_Android.cmake"', - f'"-DLY_3RDPARTY_PATH={template_third_party_path}"'] + f'"-DLY_3RDPARTY_PATH={template_third_party_path}"', + f'"-DLY_UNITY_BUILD={template_unity_build}"'] if not self.is_test_project: cmake_argument_list.append(f'"-DLY_PROJECTS={pathlib.PurePath(self.project_path).as_posix()}"') diff --git a/cmake/Tools/Platform/Android/generate_android_project.py b/cmake/Tools/Platform/Android/generate_android_project.py index 5a52ac385e..8e6adc0ecc 100755 --- a/cmake/Tools/Platform/Android/generate_android_project.py +++ b/cmake/Tools/Platform/Android/generate_android_project.py @@ -215,6 +215,11 @@ def main(args): default=None, required=False) + parser.add_argument('--native-build-path', + help='Custom path to place native build artifacts.', + default=None, + required=False) + # Asset Options parser.add_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME, action='store_true', @@ -258,6 +263,10 @@ def main(args): action='store_true', help='Option to overwrite existing scripts in the target build folder if they exist already.') + parser.add_argument('--enable-unity-build', + action='store_true', + help='Enable unity build') + parsed_args = parser.parse_args(args) wrap_parsed_args(parsed_args) @@ -395,7 +404,9 @@ def main(args): asset_type=parsed_args.get_argument(ASSET_TYPE_ARGUMENT_NAME), signing_config=signing_config, is_test_project=is_test_project, - overwrite_existing=parsed_args.overwrite_existing) + overwrite_existing=parsed_args.overwrite_existing, + unity_build_enabled=parsed_args.enable_unity_build, + native_build_path=parsed_args.native_build_path) generator.execute() diff --git a/scripts/build/Platform/Android/gradle_windows.cmd b/scripts/build/Platform/Android/gradle_windows.cmd index dd5285bdbf..4a91426094 100644 --- a/scripts/build/Platform/Android/gradle_windows.cmd +++ b/scripts/build/Platform/Android/gradle_windows.cmd @@ -135,11 +135,11 @@ IF "%GENERATE_SIGNED_APK%"=="true" ( ECHO Using keystore file at %CI_ANDROID_KEYSTORE_FILE_ABS% ) - ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing - CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --enable-unity-build --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --enable-unity-build --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing ) ELSE ( - ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% %GRADLE_OVERRIDE_OPTION% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing - CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% %GRADLE_OVERRIDE_OPTION% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --enable-unity-build %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --enable-unity-build %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing ) REM Validate the android project generation