Merge branch 'stabilization/2106' of https://github.com/aws-lumberyard/o3de into Network/olex/stab/fix_player_removal_on_disconnect

This commit is contained in:
AMZN-Olex
2021-06-23 09:42:48 -04:00
115 changed files with 2017 additions and 561 deletions
-38
View File
@@ -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))
@@ -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'])
@@ -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'])
@@ -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'])
@@ -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
@@ -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
)
@@ -4,7 +4,7 @@
"AWSCore":
{
"ProfileName": "AWSAutomationTest",
"ResourceMappingConfigFileName": "aws_resource_mappings.json"
"ResourceMappingConfigFileName": "default_aws_resource_mappings.json"
}
}
}
@@ -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.
@@ -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;
@@ -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<AZ::IO::StorageDriveWin>(AZStd::vector<AZStd::string_view>{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 <benchmark/benchmark.h>
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<char[]> buffer(new char[FileSize]);
::memset(buffer.get(), 'c', FileSize);
file.Write(buffer.get(), FileSize);
file.Close();
AZStd::optional<AZ::IO::FixedMaxPathString> 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<StreamStackEntry> storageDriveWin =
AZStd::make_shared<StorageDriveWin>(AZStd::vector<AZStd::string_view>{ drive }, 32, 32, 4_kib, 512, 8, 0, options);
AZStd::unique_ptr<Scheduler> stack = AZStd::make_unique<Scheduler>(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<char[]> buffer(new char[FileSize]);
for (auto _ : state)
{
AZStd::binary_semaphore waitForReads;
AZStd::atomic<system_clock::time_point> 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<duration<double>>(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
@@ -31,5 +31,6 @@
<file>CarrotArrowDown.svg</file>
<file>Summary.svg</file>
<file>WindowClose.svg</file>
<file>Warning.svg</file>
</qresource>
</RCC>
@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="#444444"/>
<rect x="10" y="6" width="4" height="15" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2L22 22H2L12 2ZM13 20V18H11V20H13ZM13 7H11V16.0862H13V7Z" fill="#F0C32D"/>
</svg>

After

Width:  |  Height:  |  Size: 333 B

@@ -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;
@@ -16,7 +16,7 @@
#include <QLineEdit>
#include <QMessageBox>
#include <FormLineEditWidget.h>
#include <FormBrowseEditWidget.h>
#include <FormFolderBrowseEditWidget.h>
#include <PythonBindingsInterface.h>
#include <PathValidator.h>
@@ -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"));
@@ -11,13 +11,9 @@
*/
#include <FormBrowseEditWidget.h>
#include <AzQtComponents/Components/StyledLineEdit.h>
#include <QPushButton>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QLineEdit>
#include <QStandardPaths>
#include <QIcon>
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
@@ -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
@@ -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 <FormFolderBrowseEditWidget.h>
#include <AzQtComponents/Components/StyledLineEdit.h>
#include <QFileDialog>
#include <QLineEdit>
#include <QStandardPaths>
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
@@ -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 <FormBrowseEditWidget.h>
#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
@@ -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 <FormImageBrowseEditWidget.h>
#include <AzQtComponents/Components/StyledLineEdit.h>
#include <QFileDialog>
#include <QLineEdit>
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
@@ -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 <FormBrowseEditWidget.h>
#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
@@ -14,6 +14,7 @@
#include <PythonBindingsInterface.h>
#include <GemCatalog/GemListHeaderWidget.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemRequirementDialog.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
@@ -144,12 +145,23 @@ namespace O3DE::ProjectManager
}
}
void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
bool GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
{
IPythonBindings* pythonBindings = PythonBindingsInterface::Get();
QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
QVector<QModelIndex> 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()
@@ -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);
@@ -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";
@@ -16,6 +16,7 @@
#include <QLabel>
#include <QSpacerItem>
#include <QVBoxLayout>
#include <QIcon>
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);
@@ -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;
@@ -11,7 +11,7 @@
*/
#include <GemCatalog/GemItemDelegate.h>
#include "GemModel.h"
#include <GemCatalog/GemModel.h>
#include <QEvent>
#include <QPainter>
#include <QMouseEvent>
@@ -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;
@@ -10,11 +10,9 @@
*
*/
#include "GemListView.h"
#include "GemItemDelegate.h"
#include <GemCatalog/GemListView.h>
#include <GemCatalog/GemItemDelegate.h>
#include <QStandardItemModel>
#include <QDateTime>
#include <QPalette>
namespace O3DE::ProjectManager
{
@@ -13,7 +13,6 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include "GemInfo.h"
#include <QAbstractItemModel>
#include <QItemSelectionModel>
#include <QListView>
@@ -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<QModelIndex> GemModel::GatherGemsToBeAdded() const
{
QVector<QModelIndex> result;
@@ -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<QModelIndex> GatherGemsToBeAdded() const;
QVector<QModelIndex> GatherGemsToBeRemoved() const;
@@ -84,7 +88,8 @@ namespace O3DE::ProjectManager
RoleBinarySize,
RoleFeatures,
RoleTypes,
RolePath
RolePath,
RoleRequirement
};
QHash<QString, QModelIndex> m_nameToIndexMap;
@@ -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 <GemCatalog/GemRequirementDelegate.h>
#include <GemCatalog/GemModel.h>
#include <QPainter>
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
@@ -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 <GemCatalog/GemItemDelegate.h>
#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
@@ -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 <GemCatalog/GemRequirementDialog.h>
#include <GemCatalog/GemRequirementListView.h>
#include <GemCatalog/GemRequirementFilterProxyModel.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QSpacerItem>
namespace O3DE::ProjectManager
{
GemRequirementDialog::GemRequirementDialog(GemModel* model, const QVector<QModelIndex>& 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
@@ -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 <QDialog>
#include <QDialogButtonBox>
#endif
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(GemModel)
class GemRequirementDialog
: public QDialog
{
Q_OBJECT // AUTOMOC
public:
explicit GemRequirementDialog(GemModel* model, const QVector<QModelIndex>& gemsToAdd, QWidget *parent = nullptr);
~GemRequirementDialog() = default;
QDialogButtonBox::ButtonRole GetButtonResult();
private:
void CancelButtonPressed();
void ContinueButtonPressed();
QDialogButtonBox::ButtonRole m_buttonResult = QDialogButtonBox::RejectRole;
};
} // namespace O3DE::ProjectManager
@@ -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 <GemCatalog/GemRequirementFilterProxyModel.h>
#include <GemCatalog/GemModel.h>
#include <QItemSelectionModel>
namespace O3DE::ProjectManager
{
GemRequirementFilterProxyModel::GemRequirementFilterProxyModel(GemModel* sourceModel, const QVector<QModelIndex>& 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
@@ -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 <AzQtComponents/Utilities/SelectionProxyModel.h>
#include <QtCore/QSortFilterProxyModel>
#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<QModelIndex>& 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<QModelIndex> m_addedGems;
};
} // namespace O3DE::ProjectManager
@@ -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 <GemCatalog/GemRequirementListView.h>
#include <GemCatalog/GemRequirementDelegate.h>
#include <QStandardItemModel>
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
@@ -10,23 +10,23 @@
*
*/
#include "./MeshMotionVectorCommon.azsli"
#pragma once
struct VSInput
#if !defined(Q_MOC_RUN)
#include <QAbstractItemModel>
#include <QItemSelectionModel>
#include <QListView>
#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
@@ -12,8 +12,8 @@
#include <NewProjectSettingsScreen.h>
#include <PythonBindingsInterface.h>
#include <FormLineEditWidget.h>
#include <FormBrowseEditWidget.h>
#include <FormLineEditWidget.h>
#include <TemplateButtonWidget.h>
#include <PathValidator.h>
#include <EngineInfo.h>
@@ -11,6 +11,7 @@
*/
#include <ProjectBuilder.h>
#include <ProjectManagerDefs.h>
#include <ProjectButtonWidget.h>
#include <PythonBindingsInterface.h>
@@ -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)
@@ -11,6 +11,7 @@
*/
#include <ProjectButtonWidget.h>
#include <ProjectManagerDefs.h>
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QVBoxLayout>
@@ -22,12 +23,11 @@
#include <QMenu>
#include <QSpacerItem>
#include <QProgressBar>
#include <QDir>
#include <QFileInfo>
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);
}
@@ -10,33 +10,74 @@
*
*/
#include "ProjectInfo.h"
#include <ProjectInfo.h>
#include <ProjectManagerDefs.h>
#include <QDir>
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
+10 -8
View File
@@ -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
@@ -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 <QString>
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
@@ -11,7 +11,7 @@
*/
#include <ProjectSettingsScreen.h>
#include <FormBrowseEditWidget.h>
#include <FormFolderBrowseEditWidget.h>
#include <FormLineEditWidget.h>
#include <PathValidator.h>
#include <PythonBindingsInterface.h>
@@ -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);
@@ -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();
@@ -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;
}
@@ -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();
@@ -12,6 +12,7 @@
#include <ProjectsScreen.h>
#include <ProjectManagerDefs.h>
#include <ProjectButtonWidget.h>
#include <PythonBindingsInterface.h>
#include <ProjectUtils.h>
@@ -35,7 +36,6 @@
#include <QSpacerItem>
#include <QListWidget>
#include <QListWidgetItem>
#include <QFileInfo>
#include <QScrollArea>
#include <QStackedWidget>
#include <QFrame>
@@ -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)
@@ -80,8 +80,6 @@ namespace O3DE::ProjectManager
QQueue<ProjectInfo> 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;
};
@@ -12,6 +12,7 @@
#include <PythonBindings.h>
#include <ProjectManagerDefs.h>
// 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
@@ -11,6 +11,7 @@
*/
#include <GemCatalog/GemCatalogScreen.h>
#include <ProjectManagerDefs.h>
#include <PythonBindingsInterface.h>
#include <ScreenHeaderWidget.h>
#include <ScreensCtrl.h>
@@ -24,6 +25,7 @@
#include <QStackedWidget>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QDir>
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
@@ -46,6 +46,7 @@ namespace O3DE::ProjectManager
private:
void Update();
void UpdateSettingsScreen();
bool UpdateProjectSettings(bool shouldConfirm = false);
enum ScreenOrder
{
@@ -11,17 +11,43 @@
*/
#include <UpdateProjectSettingsScreen.h>
#include <FormBrowseEditWidget.h>
#include <ProjectManagerDefs.h>
#include <FormImageBrowseEditWidget.h>
#include <FormLineEditWidget.h>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QDir>
#include <QLabel>
#include <QFileInfo>
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
@@ -15,6 +15,8 @@
#include <ProjectSettingsScreen.h>
#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
@@ -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
)
@@ -14,4 +14,5 @@ set(FILES
Resources/ProjectManager.qss
tests/ApplicationTests.cpp
tests/main.cpp
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 <AzCore/UnitTest/TestTypes.h>
#include <Application.h>
#include <ProjectUtils.h>
#include <ProjectManager_Test_Traits_Platform.h>
#include <QFile>
#include <QTextStream>
#include <QTimer>
#include <QApplication>
#include <QKeyEvent>
#include <QDir>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
{
class ProjectManagerUtilsTests
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
ProjectManagerUtilsTests()
{
m_application = AZStd::make_unique<ProjectManager::Application>();
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<ProjectManager::Application> 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
@@ -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()
@@ -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;
}
@@ -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.
@@ -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.
@@ -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;
}
@@ -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);
}
@@ -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);
}
+7
View File
@@ -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
@@ -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__)
)
@@ -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=['*']
)
]
)
@@ -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<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies,
AZStd::string& foundSourceFile)
{
AZStd::vector<AZStd::string> possibleDependencies = AZ::RPI::AssetUtils::GetPossibleDepenencyPaths(shaderVariantListFilePath, shaderFilePath);
foundSourceFile.clear();
bool found = false;
AZStd::vector<AZStd::string> 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);
@@ -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);
@@ -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"
}
}
@@ -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": [
@@ -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"
}
}
@@ -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"
}
}
@@ -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);
}
}
@@ -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 <scenesrg.srgi>
#include <viewsrg.srgi>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
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;
}
@@ -1,5 +1,5 @@
{
"Source" : "StaticMeshMotionVector",
"Source" : "MeshMotionVector",
"DepthStencilState" : {
"Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" }
@@ -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 <scenesrg.srgi>
#include <viewsrg.srgi>
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
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;
}
@@ -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;
}
@@ -1,24 +0,0 @@
{
"Source" : "SkinnedMeshMotionVector",
"DepthStencilState" : {
"Depth" : { "Enable" : true, "CompareFunc" : "GreaterEqual" }
},
"DrawList" : "motion",
"ProgramSettings":
{
"EntryPoints":
[
{
"name": "MainVS",
"type": "Vertex"
},
{
"name": "MainPS",
"type": "Fragment"
}
]
}
}
@@ -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
@@ -72,7 +72,6 @@ namespace AZ
void UpdateDrawPackets(bool forceUpdate = false);
void BuildCullable();
void UpdateCullBounds(const TransformServiceFeatureProcessor* transformService);
void SelectMotionVectorShader(Data::Instance<RPI::Material> material);
void UpdateObjectSrg();
bool MaterialRequiresForwardPassIblSpecular(Data::Instance<RPI::Material> material) const;
@@ -32,7 +32,6 @@ namespace AZ
using RequiresCloneCallback = AZStd::function<bool(const Data::Asset<RPI::ModelAsset>& modelAsset)>;
Data::Asset<RPI::ModelAsset> m_modelAsset;
bool m_isSkinnedMeshWithMotion = false;
bool m_isRayTracingEnabled = true;
bool m_useForwardPassIblSpecular = false;
RequiresCloneCallback m_requiresCloneCallback = {};
@@ -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<RPI::Material> 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)
@@ -47,8 +47,12 @@ namespace AZ
m_updateSrg = true;
}
params.m_viewportState = RHI::Viewport(0, static_cast<float>(m_imageSize.m_width), 0, static_cast<float>(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<float>(outputWidth), 0, static_cast<float>(outputHeight));
params.m_scissorState = RHI::Scissor(0, 0, outputWidth, outputHeight);
FullscreenTrianglePass::FrameBeginInternal(params);
}
@@ -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<AZStd::string> GetPossibleDepenencyPaths(const AZStd::string& originatingSourceFilePath, const AZStd::string& referencedSourceFilePath);
// Definitions...
@@ -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.
@@ -67,16 +67,41 @@ namespace AZ
BusDisconnect();
}
void AddPossibleJobDependencies(const char* jobKey, AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath, AZStd::vector<AssetBuilderSDK::JobDependency>& 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<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies,
const char* jobKey, AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies)
{
AZStd::vector<AZStd::string> possibleDependencies = AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath);
bool dependencyFileFound = false;
AZStd::vector<AZStd::string> 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);
}
}
@@ -109,31 +109,16 @@ namespace AZ
AZStd::vector<AZStd::string> 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<AZStd::string> 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;
}
@@ -164,11 +164,6 @@ namespace AZ
Data::AssetBus::Handler::BusDisconnect();
}
ShaderCollection& Material::GetShaderCollection()
{
return m_shaderCollection;
}
const ShaderCollection& Material::GetShaderCollection() const
{
return m_shaderCollection;
@@ -24,6 +24,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/std/algorithm.h>
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<float>(targetImageSize.m_width), 0, static_cast<float>(targetImageSize.m_height));
}
m_viewportState.m_minX = 0.0f;
m_viewportState.m_minY = 0.0f;
m_viewportState.m_maxX = AZStd::min(static_cast<uint32_t>(params.m_viewportState.m_maxX), targetImageSize.m_width);
m_viewportState.m_maxY = AZStd::min(static_cast<uint32_t>(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<uint32_t>(params.m_scissorState.m_maxX), targetImageSize.m_width);
m_scissorState.m_maxY = AZStd::min(static_cast<uint32_t>(params.m_scissorState.m_maxY), targetImageSize.m_height);
RenderPass::FrameBeginInternal(params);
}
@@ -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<int>(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<int>(360.0f / 11.25f)); // num disk segments + 1 for axis line + 1 for spare
const AZ::Vector3 radiusV3 = AZ::Vector3(radius);
CreateArbitraryAxisArc(
lines,
@@ -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<typename LineStorageType>
@@ -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
@@ -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<MeshFeatureProcessorInterface::MeshHandle>(
m_meshFeatureProcessor->AcquireMesh(meshDescriptor, materials));
}
+1 -1
View File
@@ -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
@@ -17,3 +17,7 @@ add_custom_command(TARGET LmbrCentral.Editor POST_BUILD
COMMENT "Patching lrelease..."
VERBATIM
)
set(lrelease_files
${QT_LRELEASE_EXECUTABLE}
)
@@ -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}
)
@@ -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
)

Some files were not shown because too many files have changed in this diff Show More