diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp index 85676df243..91d2359ca6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ComponentEditor.cpp @@ -395,12 +395,6 @@ namespace AzToolsFramework AzQtComponents::CardNotification* ComponentEditor::CreateNotificationForWarningComponents(const QString& message) { AzQtComponents::CardNotification * notification = CreateNotification(message); - const QPushButton * featureButton = notification->addButtonFeature(tr("Continue")); - - connect(featureButton, &QPushButton::clicked, this, [notification]() - { - notification->close(); - }); return notification; } diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h index b39c2c8bcb..b668bda155 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h @@ -122,12 +122,14 @@ namespace TestImpact }; //! Base class for all sequence report types. - template + template class SequenceReportBase { public: + static constexpr SequenceReportType ReportType = Type; + using PolicyState = PolicyStateType; + //! Constructs the report for a sequence of selected tests. - //! @param type The type of sequence this report is generated for. //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). @@ -136,33 +138,49 @@ namespace TestImpact //! @param selectedTestRuns The target names of the selected test runs. //! @param selectedTestRunReport The report for the set of selected test runs. SequenceReportBase( - SequenceReportType type, size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const PolicyStateType& policyState, + AZStd::optional testTargetTimeout, + AZStd::optional globalTimeout, + PolicyStateType policyState, SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - TestRunReport&& selectedTestRunReport) - : m_type(type) - , m_maxConcurrency(maxConcurrency) - , m_testTargetTimeout(testTargetTimeout) - , m_globalTimeout(globalTimeout) - , m_policyState(policyState) + TestRunSelection selectedTestRuns, + TestRunReport selectedTestRunReport) + : m_maxConcurrency(maxConcurrency) + , m_testTargetTimeout(AZStd::move(testTargetTimeout)) + , m_globalTimeout(AZStd::move(globalTimeout)) + , m_policyState(AZStd::move(policyState)) , m_suite(suiteType) - , m_selectedTestRuns(selectedTestRuns) + , m_selectedTestRuns(AZStd::move(selectedTestRuns)) , m_selectedTestRunReport(AZStd::move(selectedTestRunReport)) { } - virtual ~SequenceReportBase() = default; - - //! Returns the identifying type for this sequence report. - SequenceReportType GetType() const + SequenceReportBase(SequenceReportBase&& report) + : SequenceReportBase( + AZStd::move(report.m_maxConcurrency), + AZStd::move(report.m_testTargetTimeout), + AZStd::move(report.m_globalTimeout), + AZStd::move(report.m_policyState), + AZStd::move(report.m_suite), + AZStd::move(report.m_selectedTestRuns), + AZStd::move(report.m_selectedTestRunReport)) { - return m_type; } + SequenceReportBase(const SequenceReportBase& report) + : SequenceReportBase( + report.m_maxConcurrency, + report.m_testTargetTimeout, + report.m_globalTimeout, + report.m_policyState, + report.m_suite, + report.m_selectedTestRuns, + report.m_selectedTestRunReport) + { + } + + virtual ~SequenceReportBase() = default; + //! Returns the maximum concurrency for this sequence. size_t GetMaxConcurrency() const { @@ -284,7 +302,6 @@ namespace TestImpact } private: - SequenceReportType m_type; size_t m_maxConcurrency = 0; AZStd::optional m_testTargetTimeout; AZStd::optional m_globalTimeout; @@ -296,58 +313,27 @@ namespace TestImpact //! Report type for regular test sequences. class RegularSequenceReport - : public SequenceReportBase + : public SequenceReportBase { public: - //! Constructs the report for a regular sequence. - //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. - //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). - //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). - //! @param policyState The policy state this sequence was executed under. - //! @param suiteType The suite from which the tests have been selected from. - //! @param selectedTestRuns The target names of the selected test runs. - //! @param selectedTestRunReport The report for the set of selected test runs. - RegularSequenceReport( - size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const SequencePolicyState& policyState, - SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - TestRunReport&& selectedTestRunReport); + using SequenceReportBase::SequenceReportBase; }; //! Report type for seed test sequences. class SeedSequenceReport - : public SequenceReportBase + : public SequenceReportBase { public: - //! Constructs the report for a seed sequence. - //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. - //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). - //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). - //! @param policyState The policy state this sequence was executed under. - //! @param suiteType The suite from which the tests have been selected from. - //! @param selectedTestRuns The target names of the selected test runs. - //! @param selectedTestRunReport The report for the set of selected test runs. - SeedSequenceReport( - size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const SequencePolicyState& policyState, - SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - TestRunReport&& selectedTestRunReport); + using SequenceReportBase::SequenceReportBase; }; //! Report detailing a test run sequence of selected and drafted tests. - template + template class DraftingSequenceReportBase - : public SequenceReportBase + : public SequenceReportBase { public: //! Constructs the report for sequences that draft in previously failed/newly added test targets. - //! @param type The type of sequence this report is generated for. //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). @@ -358,18 +344,16 @@ namespace TestImpact //! @param selectedTestRunReport The report for the set of selected test runs. //! @param draftedTestRunReport The report for the set of drafted test runs. DraftingSequenceReportBase( - SequenceReportType type, size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const PolicyStateType& policyState, + AZStd::optional testTargetTimeout, + AZStd::optional globalTimeout, + PolicyStateType policyState, SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - const AZStd::vector& draftedTestRuns, + TestRunSelection selectedTestRuns, + AZStd::vector draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& draftedTestRunReport) - : SequenceReportBase ( - type, + : SequenceReportBase( maxConcurrency, testTargetTimeout, globalTimeout, @@ -377,7 +361,17 @@ namespace TestImpact suiteType, selectedTestRuns, AZStd::move(selectedTestRunReport)) - , m_draftedTestRuns(draftedTestRuns) + , m_draftedTestRuns(AZStd::move(draftedTestRuns)) + , m_draftedTestRunReport(AZStd::move(draftedTestRunReport)) + { + } + + DraftingSequenceReportBase( + SequenceReportBase&& report, + AZStd::vector draftedTestRuns, + TestRunReport&& draftedTestRunReport) + : SequenceReportBase(AZStd::move(report)) + , m_draftedTestRuns(AZStd::move(draftedTestRuns)) , m_draftedTestRunReport(AZStd::move(draftedTestRunReport)) { } @@ -456,7 +450,7 @@ namespace TestImpact //! Report detailing an impact analysis sequence of selected, discarded and drafted tests. class ImpactAnalysisSequenceReport - : public DraftingSequenceReportBase + : public DraftingSequenceReportBase { public: //! Constructs the report for an impact analysis sequence. @@ -471,16 +465,18 @@ namespace TestImpact //! @param draftedTestRunReport The report for the set of drafted test runs. ImpactAnalysisSequenceReport( size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const ImpactAnalysisSequencePolicyState& policyState, + AZStd::optional testTargetTimeout, + AZStd::optional globalTimeout, + ImpactAnalysisSequencePolicyState policyState, SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - const AZStd::vector& discardedTestRuns, - const AZStd::vector& draftedTestRuns, + TestRunSelection selectedTestRuns, + AZStd::vector discardedTestRuns, + AZStd::vector draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& draftedTestRunReport); + ImpactAnalysisSequenceReport(DraftingSequenceReportBase&& report, AZStd::vector discardedTestRuns); + //! Returns the test runs discarded from running in the sequence. const AZStd::vector& GetDiscardedTestRuns() const; private: @@ -489,7 +485,7 @@ namespace TestImpact //! Report detailing an impact analysis sequence of selected, discarded and drafted test runs. class SafeImpactAnalysisSequenceReport - : public DraftingSequenceReportBase + : public DraftingSequenceReportBase { public: //! Constructs the report for a sequence of selected, discarded and drafted test runs. @@ -506,17 +502,20 @@ namespace TestImpact //! @param draftedTestRunReport The report for the set of drafted test runs. SafeImpactAnalysisSequenceReport( size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const SafeImpactAnalysisSequencePolicyState& policyState, + AZStd::optional testTargetTimeout, + AZStd::optional globalTimeout, + SafeImpactAnalysisSequencePolicyState policyState, SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - const TestRunSelection& discardedTestRuns, - const AZStd::vector& draftedTestRuns, + TestRunSelection selectedTestRuns, + TestRunSelection discardedTestRuns, + AZStd::vector draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& discardedTestRunReport, TestRunReport&& draftedTestRunReport); + SafeImpactAnalysisSequenceReport( + DraftingSequenceReportBase&& report, TestRunSelection discardedTestRuns, TestRunReport&& discardedTestRunReport); + // SequenceReport overrides ... AZStd::chrono::milliseconds GetDuration() const override; TestSequenceResult GetResult() const override; diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h index fc226588e7..06bb0cdaa1 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h @@ -14,15 +14,27 @@ namespace TestImpact { - //! Serializes a regular sequence report to JSON format. + //! Serializes a regular sequence report to Json format. AZStd::string SerializeSequenceReport(const Client::RegularSequenceReport& sequenceReport); - //! Serializes a seed sequence report to JSON format. + //! Serializes a seed sequence report to Json format. AZStd::string SerializeSequenceReport(const Client::SeedSequenceReport& sequenceReport); - //! Serializes an impact analysis sequence report to JSON format. + //! Serializes an impact analysis sequence report to Json format. AZStd::string SerializeSequenceReport(const Client::ImpactAnalysisSequenceReport& sequenceReport); - //! Serializes a safe impact analysis sequence report to JSON format. + //! Serializes a safe impact analysis sequence report to Json format. AZStd::string SerializeSequenceReport(const Client::SafeImpactAnalysisSequenceReport& sequenceReport); + + //! Deserialize a regular sequence report from Json format. + Client::RegularSequenceReport DeserializeRegularSequenceReport(const AZStd::string& sequenceReportJson); + + //! Deserialize a seed sequence report from Json format. + Client::SeedSequenceReport DeserializeSeedSequenceReport(const AZStd::string& sequenceReportJson); + + //! Deserialize an impact analysis sequence report from Json format. + Client::ImpactAnalysisSequenceReport DeserializeImpactAnalysisSequenceReport(const AZStd::string& sequenceReportJson); + + //! Deserialize a safe impact analysis sequence report from Json format. + Client::SafeImpactAnalysisSequenceReport DeserializeSafeImpactAnalysisSequenceReport(const AZStd::string& sequenceReportJson); } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h index c4625e9d2c..f7afd49b26 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h @@ -103,4 +103,43 @@ namespace TestImpact //! User-friendly names for the client test result types. AZStd::string ClientTestResultAsString(Client::TestResult result); + + //! User-friendly names for the suite types. + SuiteType SuiteTypeFromString(const AZStd::string& suiteType); + + //! Returns the sequence report type for the specified string. + Client::SequenceReportType SequenceReportTypeFromString(const AZStd::string& type); + + //! Returns the test run result for the specified string. + Client::TestRunResult TestRunResultFromString(const AZStd::string& result); + + //! Returns the test result for the specified string. + Client::TestResult TestResultFromString(const AZStd::string& result); + + //! Returns the test sequence result for the specified string. + TestSequenceResult TestSequenceResultFromString(const AZStd::string& result); + + //! Returns the execution failure policy for the specified string. + Policy::ExecutionFailure ExecutionFailurePolicyFromString(const AZStd::string& executionFailurePolicy); + + //! Returns the failed test coverage policy for the specified string. + Policy::FailedTestCoverage FailedTestCoveragePolicyFromString(const AZStd::string& failedTestCoveragePolicy); + + //! Returns the test prioritization policy for the specified string. + Policy::TestPrioritization TestPrioritizationPolicyFromString(const AZStd::string& testPrioritizationPolicy); + + //! Returns the test failure policy for the specified string. + Policy::TestFailure TestFailurePolicyFromString(const AZStd::string& testFailurePolicy); + + //! Returns the integrity failure policy for the specified string. + Policy::IntegrityFailure IntegrityFailurePolicyFromString(const AZStd::string& integrityFailurePolicy); + + //! Returns the dynamic dependency map policy for the specified string. + Policy::DynamicDependencyMap DynamicDependencyMapPolicyFromString(const AZStd::string& dynamicDependencyMapPolicy); + + //! Returns the test sharding policy for the specified string. + Policy::TestSharding TestShardingPolicyFromString(const AZStd::string& testShardingPolicy); + + //! Returns the target output capture policy for the specified string. + Policy::TargetOutputCapture TargetOutputCapturePolicyFromString(const AZStd::string& targetOutputCapturePolicy); } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp index 3f4656758a..17157e70fc 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp @@ -159,69 +159,35 @@ namespace TestImpact return m_totalNumDisabledTests; } - RegularSequenceReport::RegularSequenceReport( + ImpactAnalysisSequenceReport::ImpactAnalysisSequenceReport( size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const SequencePolicyState& policyState, + AZStd::optional testTargetTimeout, + AZStd::optional globalTimeout, + ImpactAnalysisSequencePolicyState policyState, SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - TestRunReport&& selectedTestRunReport) - : SequenceReportBase( - SequenceReportType::RegularSequence, - maxConcurrency, - testTargetTimeout, - globalTimeout, - policyState, - suiteType, - selectedTestRuns, - AZStd::move(selectedTestRunReport)) - { - } - - SeedSequenceReport::SeedSequenceReport( - size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const SequencePolicyState& policyState, - SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - TestRunReport&& selectedTestRunReport) - : SequenceReportBase( - SequenceReportType::SeedSequence, - maxConcurrency, - testTargetTimeout, - globalTimeout, - policyState, - suiteType, - selectedTestRuns, - AZStd::move(selectedTestRunReport)) + TestRunSelection selectedTestRuns, + AZStd::vector discardedTestRuns, + AZStd::vector draftedTestRuns, + TestRunReport&& selectedTestRunReport, + TestRunReport&& draftedTestRunReport) + : DraftingSequenceReportBase( + maxConcurrency, + AZStd::move(testTargetTimeout), + AZStd::move(globalTimeout), + AZStd::move(policyState), + suiteType, + AZStd::move(selectedTestRuns), + AZStd::move(draftedTestRuns), + AZStd::move(selectedTestRunReport), + AZStd::move(draftedTestRunReport)) + , m_discardedTestRuns(discardedTestRuns) { } ImpactAnalysisSequenceReport::ImpactAnalysisSequenceReport( - size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const ImpactAnalysisSequencePolicyState& policyState, - SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - const AZStd::vector& discardedTestRuns, - const AZStd::vector& draftedTestRuns, - TestRunReport&& selectedTestRunReport, - TestRunReport&& draftedTestRunReport) - : DraftingSequenceReportBase( - SequenceReportType::ImpactAnalysisSequence, - maxConcurrency, - testTargetTimeout, - globalTimeout, - policyState, - suiteType, - selectedTestRuns, - draftedTestRuns, - AZStd::move(selectedTestRunReport), - AZStd::move(draftedTestRunReport)) - , m_discardedTestRuns(discardedTestRuns) + DraftingSequenceReportBase&& report, AZStd::vector discardedTestRuns) + : DraftingSequenceReportBase(AZStd::move(report)) + , m_discardedTestRuns(AZStd::move(discardedTestRuns)) { } @@ -232,25 +198,24 @@ namespace TestImpact SafeImpactAnalysisSequenceReport::SafeImpactAnalysisSequenceReport( size_t maxConcurrency, - const AZStd::optional& testTargetTimeout, - const AZStd::optional& globalTimeout, - const SafeImpactAnalysisSequencePolicyState& policyState, + AZStd::optional testTargetTimeout, + AZStd::optional globalTimeout, + SafeImpactAnalysisSequencePolicyState policyState, SuiteType suiteType, - const TestRunSelection& selectedTestRuns, - const TestRunSelection& discardedTestRuns, - const AZStd::vector& draftedTestRuns, + TestRunSelection selectedTestRuns, + TestRunSelection discardedTestRuns, + AZStd::vector draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& discardedTestRunReport, TestRunReport&& draftedTestRunReport) : DraftingSequenceReportBase( - SequenceReportType::SafeImpactAnalysisSequence, maxConcurrency, - testTargetTimeout, - globalTimeout, - policyState, + AZStd::move(testTargetTimeout), + AZStd::move(globalTimeout), + AZStd::move(policyState), suiteType, - selectedTestRuns, - draftedTestRuns, + AZStd::move(selectedTestRuns), + AZStd::move(draftedTestRuns), AZStd::move(selectedTestRunReport), AZStd::move(draftedTestRunReport)) , m_discardedTestRuns(discardedTestRuns) @@ -258,6 +223,14 @@ namespace TestImpact { } + SafeImpactAnalysisSequenceReport::SafeImpactAnalysisSequenceReport( + DraftingSequenceReportBase&& report, TestRunSelection discardedTestRuns, TestRunReport&& discardedTestRunReport) + : DraftingSequenceReportBase(AZStd::move(report)) + , m_discardedTestRuns(AZStd::move(discardedTestRuns)) + , m_discardedTestRunReport(AZStd::move(discardedTestRunReport)) + { + } + TestSequenceResult SafeImpactAnalysisSequenceReport::GetResult() const { return CalculateMultiTestSequenceResult({ DraftingSequenceReportBase::GetResult(), m_discardedTestRunReport.GetResult() }); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp index 99a187fe16..944fd4ac38 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp @@ -21,7 +21,7 @@ namespace TestImpact { namespace SequenceReportFields { - // Keys for pertinent JSON node and attribute names + // Keys for pertinent Json node and attribute names constexpr const char* Keys[] = { "name", @@ -417,13 +417,13 @@ namespace TestImpact writer.String(DynamicDependencyMapPolicyAsString(policyState.m_dynamicDependencyMap).c_str()); } - template + template void SerializeSequenceReportBaseMembers( - const Client::SequenceReportBase& sequenceReport, rapidjson::PrettyWriter& writer) + const SequenceReportBaseType& sequenceReport, rapidjson::PrettyWriter& writer) { // Type writer.Key(SequenceReportFields::Keys[SequenceReportFields::Type]); - writer.String(SequenceReportTypeAsString(sequenceReport.GetType()).c_str()); + writer.String(SequenceReportTypeAsString(sequenceReport.ReportType).c_str()); // Test target timeout writer.Key(SequenceReportFields::Keys[SequenceReportFields::TestTargetTimeout]); @@ -510,9 +510,9 @@ namespace TestImpact writer.Uint64(sequenceReport.GetTotalNumDisabledTests()); } - template + template void SerializeDraftingSequenceReportMembers( - const Client::DraftingSequenceReportBase& sequenceReport, rapidjson::PrettyWriter& writer) + const DraftingSequenceReportBaseType& sequenceReport, rapidjson::PrettyWriter& writer) { SerializeSequenceReportBaseMembers(sequenceReport, writer); @@ -603,4 +603,254 @@ namespace TestImpact return stringBuffer.GetString(); } + + AZStd::chrono::high_resolution_clock::time_point TimePointFromMsInt64(AZ::s64 ms) + { + return AZStd::chrono::high_resolution_clock::time_point(AZStd::chrono::milliseconds(ms)); + } + + AZStd::vector DeserializeTests(const rapidjson::Value& serialTests) + { + AZStd::vector tests; + tests.reserve(serialTests[SequenceReportFields::Keys[SequenceReportFields::Tests]].GetArray().Size()); + for (const auto& test : serialTests[SequenceReportFields::Keys[SequenceReportFields::Tests]].GetArray()) + { + const AZStd::string name = test[SequenceReportFields::Keys[SequenceReportFields::Name]].GetString(); + const auto result = TestResultFromString(test[SequenceReportFields::Keys[SequenceReportFields::Result]].GetString()); + tests.emplace_back(name, result); + } + + return tests; + } + + Client::TestRunBase DeserializeTestRunBase(const rapidjson::Value& serialTestRun) + { + return Client::TestRunBase( + serialTestRun[SequenceReportFields::Keys[SequenceReportFields::Name]].GetString(), + serialTestRun[SequenceReportFields::Keys[SequenceReportFields::CommandArgs]].GetString(), + TimePointFromMsInt64(serialTestRun[SequenceReportFields::Keys[SequenceReportFields::StartTime]].GetInt64()), + AZStd::chrono::milliseconds(serialTestRun[SequenceReportFields::Keys[SequenceReportFields::Duration]].GetInt64()), + TestRunResultFromString(serialTestRun[SequenceReportFields::Keys[SequenceReportFields::Result]].GetString())); + } + + template + AZStd::vector DeserializeTestRuns(const rapidjson::Value& serialTestRuns) + { + AZStd::vector testRuns; + testRuns.reserve(serialTestRuns.GetArray().Size()); + for (const auto& testRun : serialTestRuns.GetArray()) + { + testRuns.emplace_back(DeserializeTestRunBase(testRun)); + } + + return testRuns; + } + + template + AZStd::vector DeserializeCompletedTestRuns(const rapidjson::Value& serialCompletedTestRuns) + { + AZStd::vector testRuns; + testRuns.reserve(serialCompletedTestRuns.GetArray().Size()); + for (const auto& testRun : serialCompletedTestRuns.GetArray()) + { + testRuns.emplace_back( + DeserializeTestRunBase(testRun), DeserializeTests(testRun[SequenceReportFields::Keys[SequenceReportFields::Tests]])); + } + + return testRuns; + } + + Client::TestRunReport DeserializeTestRunReport(const rapidjson::Value& serialTestRunReport) + { + return Client::TestRunReport( + TestSequenceResultFromString(serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::Result]].GetString()), + TimePointFromMsInt64(serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::StartTime]].GetInt64()), + AZStd::chrono::milliseconds(serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::Duration]].GetInt64()), + DeserializeCompletedTestRuns( + serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::PassingTestRuns]]), + DeserializeCompletedTestRuns( + serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::FailingTestRuns]]), + DeserializeTestRuns( + serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::ExecutionFailureTestRuns]]), + DeserializeTestRuns( + serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::TimedOutTestRuns]]), + DeserializeTestRuns( + serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::UnexecutedTestRuns]])); + } + + Client::TestRunSelection DeserializeTestSelection(const rapidjson::Value& serialTestRunSelection) + { + const auto extractTestTargetNames = [](const rapidjson::Value& serialTestTargets) + { + AZStd::vector testTargets; + testTargets.reserve(serialTestTargets.GetArray().Size()); + for (const auto& testTarget : serialTestTargets.GetArray()) + { + testTargets.emplace_back(testTarget.GetString()); + } + + return testTargets; + }; + + return Client::TestRunSelection( + extractTestTargetNames(serialTestRunSelection[SequenceReportFields::Keys[SequenceReportFields::IncludedTestRuns]]), + extractTestTargetNames(serialTestRunSelection[SequenceReportFields::Keys[SequenceReportFields::ExcludedTestRuns]])); + } + + PolicyStateBase DeserializePolicyStateBaseMembers(const rapidjson::Value& serialPolicyState) + { + return + { + ExecutionFailurePolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::ExecutionFailure]].GetString()), + FailedTestCoveragePolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::CoverageFailure]].GetString()), + TestFailurePolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::TestFailure]].GetString()), + IntegrityFailurePolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::IntegrityFailure]].GetString()), + TestShardingPolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::TestSharding]].GetString()), + TargetOutputCapturePolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::TargetOutputCapture]].GetString()) + }; + } + + SequencePolicyState DeserializePolicyStateMembers(const rapidjson::Value& serialPolicyState) + { + return { DeserializePolicyStateBaseMembers(serialPolicyState) }; + } + + SafeImpactAnalysisSequencePolicyState DeserializeSafeImpactAnalysisPolicyStateMembers(const rapidjson::Value& serialPolicyState) + { + return + { + DeserializePolicyStateBaseMembers(serialPolicyState), + TestPrioritizationPolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::TestPrioritization]].GetString()) + }; + } + + ImpactAnalysisSequencePolicyState DeserializeImpactAnalysisSequencePolicyStateMembers(const rapidjson::Value& serialPolicyState) + { + return + { + DeserializePolicyStateBaseMembers(serialPolicyState), + TestPrioritizationPolicyFromString(serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::TestPrioritization]].GetString()), + DynamicDependencyMapPolicyFromString( + serialPolicyState[SequenceReportFields::Keys[SequenceReportFields::DynamicDependencyMap]].GetString()) + }; + } + + template + PolicyStateType DeserializePolicyStateType(const rapidjson::Value& serialPolicyStateType) + { + if constexpr (AZStd::is_same_v) + { + return DeserializePolicyStateMembers(serialPolicyStateType); + } + else if constexpr (AZStd::is_same_v) + { + return DeserializeSafeImpactAnalysisPolicyStateMembers(serialPolicyStateType); + } + else if constexpr (AZStd::is_same_v) + { + return DeserializeImpactAnalysisSequencePolicyStateMembers(serialPolicyStateType); + } + else + { + static_assert(false, "Template paramater must be a valid policy state type"); + } + } + + template + SequenceReportBaseType DeserialiseSequenceReportBase(const rapidjson::Value& serialSequenceReportBase) + { + const auto type = SequenceReportTypeFromString(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::Type]].GetString()); + AZ_TestImpact_Eval( + type == SequenceReportBaseType::ReportType, + SequenceReportException, AZStd::string::format( + "The JSON sequence report type '%s' does not match the constructed report type", + serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::Type]].GetString())); + + const auto testTargetTimeout = + serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::TestTargetTimeout]].GetUint64(); + const auto globalTimeout = + serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::GlobalTimeout]].GetUint64(); + + return SequenceReportBaseType( + serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::MaxConcurrency]].GetUint64(), + testTargetTimeout ? AZStd::optional{ testTargetTimeout } : AZStd::nullopt, + globalTimeout ? AZStd::optional{ globalTimeout } : AZStd::nullopt, + DeserializePolicyStateType(serialSequenceReportBase), + SuiteTypeFromString(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::Suite]].GetString()), + DeserializeTestSelection(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::SelectedTestRuns]]), + DeserializeTestRunReport(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::SelectedTestRunReport]])); + } + + template + Client::DraftingSequenceReportBase + DeserializeDraftingSequenceReportBase(const rapidjson::Value& serialDraftingSequenceReportBase) + { + AZStd::vector draftingTestRuns; + draftingTestRuns.reserve( + serialDraftingSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::DraftedTestRuns]].GetArray().Size()); + for (const auto& testRun : + serialDraftingSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::DraftedTestRuns]].GetArray()) + { + draftingTestRuns.emplace_back(testRun.GetString()); + } + + using SequenceBase = + Client::SequenceReportBase; + using DraftingSequenceBase = + Client::DraftingSequenceReportBase; + + return DraftingSequenceBase( + DeserialiseSequenceReportBase(serialDraftingSequenceReportBase), + AZStd::move(draftingTestRuns), + DeserializeTestRunReport(serialDraftingSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::DraftedTestRunReport]])); + } + + rapidjson::Document OpenSequenceReportJson(const AZStd::string& sequenceReportJson) + { + rapidjson::Document doc; + + if (doc.Parse<0>(sequenceReportJson.c_str()).HasParseError()) + { + throw SequenceReportException("Could not parse sequence report data"); + } + + return doc; + } + + Client::RegularSequenceReport DeserializeRegularSequenceReport(const AZStd::string& sequenceReportJson) + { + const auto doc = OpenSequenceReportJson(sequenceReportJson); + return DeserialiseSequenceReportBase(doc); + } + + Client::SeedSequenceReport DeserializeSeedSequenceReport(const AZStd::string& sequenceReportJson) + { + const auto doc = OpenSequenceReportJson(sequenceReportJson); + return DeserialiseSequenceReportBase(doc); + } + + Client::ImpactAnalysisSequenceReport DeserializeImpactAnalysisSequenceReport(const AZStd::string& sequenceReportJson) + { + const auto doc = OpenSequenceReportJson(sequenceReportJson); + + AZStd::vector discardedTestRuns; + discardedTestRuns.reserve(doc[SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRuns]].GetArray().Size()); + for (const auto& testRun : doc[SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRuns]].GetArray()) + { + discardedTestRuns.emplace_back(testRun.GetString()); + } + + return Client::ImpactAnalysisSequenceReport( + DeserializeDraftingSequenceReportBase(doc), AZStd::move(discardedTestRuns)); + } + + Client::SafeImpactAnalysisSequenceReport DeserializeSafeImpactAnalysisSequenceReport(const AZStd::string& sequenceReportJson) + { + const auto doc = OpenSequenceReportJson(sequenceReportJson); + + return Client::SafeImpactAnalysisSequenceReport( + DeserializeDraftingSequenceReportBase(doc), + DeserializeTestSelection(doc[SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRuns]]), + DeserializeTestRunReport(doc[SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRunReport]])); + } } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp index 3f6c9dafd4..ecfdec287b 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp @@ -243,4 +243,256 @@ namespace TestImpact throw(Exception(AZStd::string::format("Unexpected client test case result: %u", aznumeric_cast(result)))); } } + + SuiteType SuiteTypeFromString(const AZStd::string& suiteType) + { + if (suiteType == SuiteTypeAsString(SuiteType::Main)) + { + return SuiteType::Main; + } + else if (suiteType == SuiteTypeAsString(SuiteType::Periodic)) + { + return SuiteType::Periodic; + } + else if (suiteType == SuiteTypeAsString(SuiteType::Sandbox)) + { + return SuiteType::Sandbox; + } + else + { + throw Exception(AZStd::string::format("Unexpected suite type: '%s'", suiteType.c_str())); + } + } + + Client::SequenceReportType SequenceReportTypeFromString(const AZStd::string& type) + { + if (type == SequenceReportTypeAsString(Client::SequenceReportType::ImpactAnalysisSequence)) + { + return Client::SequenceReportType::ImpactAnalysisSequence; + } + else if (type == SequenceReportTypeAsString(Client::SequenceReportType::RegularSequence)) + { + return Client::SequenceReportType::RegularSequence; + } + else if (type == SequenceReportTypeAsString(Client::SequenceReportType::SafeImpactAnalysisSequence)) + { + return Client::SequenceReportType::SafeImpactAnalysisSequence; + } + else if (type == SequenceReportTypeAsString(Client::SequenceReportType::SeedSequence)) + { + return Client::SequenceReportType::SeedSequence; + } + else + { + throw Exception(AZStd::string::format("Unexpected sequence report type: '%s'", type.c_str())); + } + } + + Client::TestRunResult TestRunResultFromString(const AZStd::string& result) + { + if (result == TestRunResultAsString(Client::TestRunResult::AllTestsPass)) + { + return Client::TestRunResult::AllTestsPass; + } + else if (result == TestRunResultAsString(Client::TestRunResult::FailedToExecute)) + { + return Client::TestRunResult::FailedToExecute; + } + else if (result == TestRunResultAsString(Client::TestRunResult::NotRun)) + { + return Client::TestRunResult::NotRun; + } + else if (result == TestRunResultAsString(Client::TestRunResult::TestFailures)) + { + return Client::TestRunResult::TestFailures; + } + else if (result == TestRunResultAsString(Client::TestRunResult::Timeout)) + { + return Client::TestRunResult::Timeout; + } + else + { + throw Exception(AZStd::string::format("Unexpected client test run result: '%s'", result.c_str())); + } + } + + Client::TestResult TestResultFromString(const AZStd::string& result) + { + if (result == ClientTestResultAsString(Client::TestResult::Failed)) + { + return Client::TestResult::Failed; + } + else if (result == ClientTestResultAsString(Client::TestResult::NotRun)) + { + return Client::TestResult::NotRun; + } + else if (result == ClientTestResultAsString(Client::TestResult::Passed)) + { + return Client::TestResult::Passed; + } + else + { + throw Exception(AZStd::string::format("Unexpected client test result: '%s'", result.c_str())); + } + } + + TestSequenceResult TestSequenceResultFromString(const AZStd::string& result) + { + if (result == TestSequenceResultAsString(TestSequenceResult::Failure)) + { + return TestSequenceResult::Failure; + } + else if (result == TestSequenceResultAsString(TestSequenceResult::Success)) + { + return TestSequenceResult::Success; + } + else if (result == TestSequenceResultAsString(TestSequenceResult::Timeout)) + { + return TestSequenceResult::Timeout; + } + else + { + throw Exception(AZStd::string::format("Unexpected test sequence result: '%s'", result.c_str())); + } + } + + Policy::ExecutionFailure ExecutionFailurePolicyFromString(const AZStd::string& executionFailurePolicy) + { + if (executionFailurePolicy == ExecutionFailurePolicyAsString(Policy::ExecutionFailure::Abort)) + { + return Policy::ExecutionFailure::Abort; + } + else if (executionFailurePolicy == ExecutionFailurePolicyAsString(Policy::ExecutionFailure::Continue)) + { + return Policy::ExecutionFailure::Continue; + } + else if (executionFailurePolicy == ExecutionFailurePolicyAsString(Policy::ExecutionFailure::Ignore)) + { + return Policy::ExecutionFailure::Ignore; + } + else + { + throw Exception(AZStd::string::format("Unexpected execution failure policy: '%s'", executionFailurePolicy.c_str())); + } + } + + Policy::FailedTestCoverage FailedTestCoveragePolicyFromString(const AZStd::string& failedTestCoveragePolicy) + { + if (failedTestCoveragePolicy == FailedTestCoveragePolicyAsString(Policy::FailedTestCoverage::Discard)) + { + return Policy::FailedTestCoverage::Discard; + } + else if (failedTestCoveragePolicy == FailedTestCoveragePolicyAsString(Policy::FailedTestCoverage::Keep)) + { + return Policy::FailedTestCoverage::Keep; + } + else + { + throw Exception(AZStd::string::format("Unexpected failed test coverage policy: '%s'", failedTestCoveragePolicy.c_str())); + } + } + + Policy::TestPrioritization TestPrioritizationPolicyFromString(const AZStd::string& testPrioritizationPolicy) + { + if (testPrioritizationPolicy == TestPrioritizationPolicyAsString(Policy::TestPrioritization::DependencyLocality)) + { + return Policy::TestPrioritization::DependencyLocality; + } + else if (testPrioritizationPolicy == TestPrioritizationPolicyAsString(Policy::TestPrioritization::None)) + { + return Policy::TestPrioritization::None; + } + else + { + throw Exception(AZStd::string::format("Unexpected test prioritization policy: '%s'", testPrioritizationPolicy.c_str())); + } + } + + Policy::TestFailure TestFailurePolicyFromString(const AZStd::string& testFailurePolicy) + { + if (testFailurePolicy == TestFailurePolicyAsString(Policy::TestFailure::Abort)) + { + return Policy::TestFailure::Abort; + } + else if (testFailurePolicy == TestFailurePolicyAsString(Policy::TestFailure::Continue)) + { + return Policy::TestFailure::Continue; + } + else + { + throw Exception(AZStd::string::format("Unexpected test failure policy: '%s'", testFailurePolicy.c_str())); + } + } + + Policy::IntegrityFailure IntegrityFailurePolicyFromString(const AZStd::string& integrityFailurePolicy) + { + if (integrityFailurePolicy == IntegrityFailurePolicyAsString(Policy::IntegrityFailure::Abort)) + { + return Policy::IntegrityFailure::Abort; + } + else if (integrityFailurePolicy == IntegrityFailurePolicyAsString(Policy::IntegrityFailure::Continue)) + { + return Policy::IntegrityFailure::Continue; + } + else + { + throw Exception(AZStd::string::format("Unexpected integration failure policy: '%s'", integrityFailurePolicy.c_str())); + } + } + + Policy::DynamicDependencyMap DynamicDependencyMapPolicyFromString(const AZStd::string& dynamicDependencyMapPolicy) + { + if (dynamicDependencyMapPolicy == DynamicDependencyMapPolicyAsString(Policy::DynamicDependencyMap::Discard)) + { + return Policy::DynamicDependencyMap::Discard; + } + else if (dynamicDependencyMapPolicy == DynamicDependencyMapPolicyAsString(Policy::DynamicDependencyMap::Update)) + { + return Policy::DynamicDependencyMap::Update; + } + else + { + throw Exception(AZStd::string::format("Unexpected dynamic dependency map policy: '%s'", dynamicDependencyMapPolicy.c_str())); + } + } + + Policy::TestSharding TestShardingPolicyFromString(const AZStd::string& testShardingPolicy) + { + if (testShardingPolicy == TestShardingPolicyAsString(Policy::TestSharding::Always)) + { + return Policy::TestSharding::Always; + } + else if (testShardingPolicy == TestShardingPolicyAsString(Policy::TestSharding::Never)) + { + return Policy::TestSharding::Never; + } + else + { + throw Exception(AZStd::string::format("Unexpected test sharding policy: '%s'", testShardingPolicy.c_str())); + } + } + + Policy::TargetOutputCapture TargetOutputCapturePolicyFromString(const AZStd::string& targetOutputCapturePolicy) + { + if (targetOutputCapturePolicy == TargetOutputCapturePolicyAsString(Policy::TargetOutputCapture::File)) + { + return Policy::TargetOutputCapture::File; + } + else if (targetOutputCapturePolicy == TargetOutputCapturePolicyAsString(Policy::TargetOutputCapture::None)) + { + return Policy::TargetOutputCapture::None; + } + else if (targetOutputCapturePolicy == TargetOutputCapturePolicyAsString(Policy::TargetOutputCapture::StdOut)) + { + return Policy::TargetOutputCapture::StdOut; + } + else if (targetOutputCapturePolicy == TargetOutputCapturePolicyAsString(Policy::TargetOutputCapture::StdOutAndFile)) + { + return Policy::TargetOutputCapture::StdOutAndFile; + } + else + { + throw Exception(AZStd::string::format("Unexpected target output capture policy: '%s'", targetOutputCapturePolicy.c_str())); + } + } } // namespace TestImpact diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt b/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt index dfeee011cc..1463124e27 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt @@ -62,6 +62,7 @@ ly_add_target( 3rdParty::Qt::Core 3rdParty::Qt::Widgets 3rdParty::Qt::Gui + 3rdParty::astc-encoder 3rdParty::etc2comp 3rdParty::PVRTexTool 3rdParty::squish-ccr @@ -77,8 +78,6 @@ ly_add_target( Gem::Atom_RPI.Public Gem::Atom_RHI.Reflect Gem::Atom_Utils.Static - RUNTIME_DEPENDENCIES - 3rdParty::ASTCEncoder ) ly_add_source_properties( diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.cpp new file mode 100644 index 0000000000..4ef47c043d --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.cpp @@ -0,0 +1,322 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace ImageProcessingAtom +{ + bool ASTCCompressor::IsCompressedPixelFormatSupported(EPixelFormat fmt) + { + return IsASTCFormat(fmt); + } + + bool ASTCCompressor::IsUncompressedPixelFormatSupported(EPixelFormat fmt) + { + // astc encoder requires the compress input image or decompress output image to have four channels + switch (fmt) + { + // uint 8 + case ePixelFormat_R8G8B8A8: + case ePixelFormat_R8G8B8X8: + // fp16 + case ePixelFormat_R16G16B16A16F: + // fp32 + case ePixelFormat_R32G32B32A32F: + return true; + default: + return false; + } + } + + EPixelFormat ASTCCompressor::GetSuggestedUncompressedFormat([[maybe_unused]] EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const + { + if (IsUncompressedPixelFormatSupported(uncompressedfmt)) + { + return uncompressedfmt; + } + + auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(uncompressedfmt); + switch (formatInfo->eSampleType) + { + case ESampleType::eSampleType_Half: + return ePixelFormat_R16G16B16A16F; + case ESampleType::eSampleType_Float: + return ePixelFormat_R32G32B32A32F; + } + + return ePixelFormat_R8G8B8A8; + } + + ColorSpace ASTCCompressor::GetSupportedColorSpace([[maybe_unused]] EPixelFormat compressFormat) const + { + return ColorSpace::autoSelect; + } + + const char* ASTCCompressor::GetName() const + { + return "ASTCCompressor"; + } + + bool ASTCCompressor::DoesSupportDecompress([[maybe_unused]] EPixelFormat fmtDst) + { + return true; + } + + astcenc_profile GetAstcProfile(bool isSrgb, EPixelFormat pixelFormat) + { + // select profile depends on LDR or HDR, SRGB or Linear + // ASTCENC_PRF_LDR + // ASTCENC_PRF_LDR_SRGB + // ASTCENC_PRF_HDR_RGB_LDR_A + // ASTCENC_PRF_HDR + + auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat); + bool isHDR = formatInfo->eSampleType == ESampleType::eSampleType_Half || formatInfo->eSampleType == ESampleType::eSampleType_Float; + astcenc_profile profile; + if (isHDR) + { + // HDR is not support in core vulkan 1.1 for android. + // https://arm-software.github.io/vulkan-sdk/_a_s_t_c.html + profile = isSrgb?ASTCENC_PRF_HDR_RGB_LDR_A:ASTCENC_PRF_HDR; + } + else + { + + profile = isSrgb?ASTCENC_PRF_LDR_SRGB:ASTCENC_PRF_LDR; + } + return profile; + } + + astcenc_type GetAstcDataType(EPixelFormat pixelFormat) + { + auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat); + astcenc_type dataType = ASTCENC_TYPE_U8; + + switch (formatInfo->eSampleType) + { + case ESampleType::eSampleType_Uint8: + dataType = ASTCENC_TYPE_U8; + break; + case ESampleType::eSampleType_Half: + dataType = ASTCENC_TYPE_F16; + break; + case ESampleType::eSampleType_Float: + dataType = ASTCENC_TYPE_F32; + break; + default: + dataType = ASTCENC_TYPE_U8; + AZ_Assert(false, "Unsupport uncompressed format %s", formatInfo->szName); + break; + } + + return dataType; + } + + float GetAstcCompressQuality(ICompressor::EQuality quality) + { + switch (quality) + { + case ICompressor::EQuality::eQuality_Fast: + return ASTCENC_PRE_FAST; + case ICompressor::EQuality::eQuality_Slow: + return ASTCENC_PRE_THOROUGH; + case ICompressor::EQuality::eQuality_Preview: + case ICompressor::EQuality::eQuality_Normal: + default: + return ASTCENC_PRE_MEDIUM; + } + } + + IImageObjectPtr ASTCCompressor::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const + { + //validate input + EPixelFormat fmtSrc = srcImage->GetPixelFormat(); + + //src format need to be uncompressed and dst format need to compressed. + if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst)) + { + return nullptr; + } + + astcenc_swizzle swizzle {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, compressOption->discardAlpha? ASTCENC_SWZ_1:ASTCENC_SWZ_A}; + AZ::u32 flags = 0; + if (srcImage->HasImageFlags(EIF_RenormalizedTexture)) + { + ImageToProcess imageToProcess(srcImage); + imageToProcess.ConvertFormatUncompressed(ePixelFormat_R8G8B8X8); + srcImage = imageToProcess.Get(); + fmtSrc = srcImage->GetPixelFormat(); + + flags = ASTCENC_FLG_MAP_NORMAL; + swizzle = astcenc_swizzle{ ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_R, ASTCENC_SWZ_G }; + } + + auto dstFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtDst); + + const float quality = GetAstcCompressQuality(compressOption->compressQuality); + const astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), fmtSrc); + + astcenc_config config; + astcenc_error status; + status = astcenc_config_init(profile, dstFormatInfo->blockWidth, dstFormatInfo->blockHeight, 1, quality, flags, &config); + + //ASTCENC_FLG_MAP_NORMAL + AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec config init failed: %s\n", astcenc_get_error_string(status)); + + // Create a context based on the configuration + astcenc_context* context; + AZ::u32 blockCount = ((srcImage->GetWidth(0)+ dstFormatInfo->blockWidth-1)/dstFormatInfo->blockWidth) * ((srcImage->GetHeight(0) + dstFormatInfo->blockHeight-1)/dstFormatInfo->blockHeight); + AZ::u32 threadCount = AZStd::min(AZStd::thread::hardware_concurrency(), blockCount); + status = astcenc_context_alloc(&config, threadCount, &context); + AZ_Assert( status == ASTCENC_SUCCESS, "ERROR: Codec context alloc failed: %s\n", astcenc_get_error_string(status)); + + const astcenc_type dataType =GetAstcDataType(fmtSrc); + + // Compress the image for each mips + IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst)); + const AZ::u32 dstMips = dstImage->GetMipCount(); + for (AZ::u32 mip = 0; mip < dstMips; ++mip) + { + astcenc_image image; + image.dim_x = srcImage->GetWidth(mip); + image.dim_y = srcImage->GetHeight(mip); + image.dim_z = 1; + image.data_type = dataType; + + AZ::u8* srcMem; + AZ::u32 srcPitch; + srcImage->GetImagePointer(mip, srcMem, srcPitch); + image.data = reinterpret_cast(&srcMem); + + AZ::u8* dstMem; + AZ::u32 dstPitch; + dstImage->GetImagePointer(mip, dstMem, dstPitch); + AZ::u32 dataSize = dstImage->GetMipBufSize(mip); + + // Create jobs for each compression thread + auto completionJob = aznew AZ::JobCompletion(); + for (AZ::u32 threadIdx = 0; threadIdx < threadCount; threadIdx++) + { + const auto jobLambda = [&status, context, &image, &swizzle, dstMem, dataSize, threadIdx]() + { + astcenc_error error = astcenc_compress_image(context, &image, &swizzle, dstMem, dataSize, threadIdx); + if (error != ASTCENC_SUCCESS) + { + status = error; + } + }; + + AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes + simulationJob->SetDependent(completionJob); + simulationJob->Start(); + } + + if (completionJob) + { + completionJob->StartAndWaitForCompletion(); + delete completionJob; + completionJob = nullptr; + } + + if (status != ASTCENC_SUCCESS) + { + AZ_Error("Image Processing", false, "ASTCCompressor::CompressImage failed: %s\n", astcenc_get_error_string(status)); + astcenc_context_free(context); + return nullptr; + } + + // Need to reset to compress next mip + astcenc_compress_reset(context); + } + astcenc_context_free(context); + + return dstImage; + } + + IImageObjectPtr ASTCCompressor::DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const + { + //validate input + EPixelFormat fmtSrc = srcImage->GetPixelFormat(); //compressed + auto srcFormatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtSrc); + + if (!IsCompressedPixelFormatSupported(fmtSrc) || !IsUncompressedPixelFormatSupported(fmtDst)) + { + return nullptr; + } + + const float quality = ASTCENC_PRE_MEDIUM; + astcenc_swizzle swizzle {ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A}; + if (srcImage->HasImageFlags(EIF_RenormalizedTexture)) + { + swizzle = astcenc_swizzle{ASTCENC_SWZ_R, ASTCENC_SWZ_A, ASTCENC_SWZ_Z, ASTCENC_SWZ_1}; + } + + astcenc_config config; + astcenc_error status; + astcenc_profile profile = GetAstcProfile(srcImage->HasImageFlags(EIF_SRGBRead), fmtDst); + AZ::u32 flags = ASTCENC_FLG_DECOMPRESS_ONLY; + status = astcenc_config_init(profile, srcFormatInfo->blockWidth, srcFormatInfo->blockHeight, 1, quality, flags, &config); + + //ASTCENC_FLG_MAP_NORMAL + AZ_Assert( status == ASTCENC_SUCCESS, "astcenc_config_init failed: %s\n", astcenc_get_error_string(status)); + + // Create a context based on the configuration + const AZ::u32 threadCount = 1; // Decompress function doesn't support multiple threads + astcenc_context* context; + status = astcenc_context_alloc(&config, threadCount, &context); + AZ_Assert( status == ASTCENC_SUCCESS, "astcenc_context_alloc failed: %s\n", astcenc_get_error_string(status)); + + astcenc_type dataType =GetAstcDataType(fmtDst); + + // Decompress the image for each mips + IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst)); + const AZ::u32 dstMips = dstImage->GetMipCount(); + for (AZ::u32 mip = 0; mip < dstMips; ++mip) + { + astcenc_image image; + image.dim_x = srcImage->GetWidth(mip); + image.dim_y = srcImage->GetHeight(mip); + image.dim_z = 1; + image.data_type = dataType; + + AZ::u8* srcMem; + AZ::u32 srcPitch; + srcImage->GetImagePointer(mip, srcMem, srcPitch); + AZ::u32 srcDataSize = srcImage->GetMipBufSize(mip); + + AZ::u8* dstMem; + AZ::u32 dstPitch; + dstImage->GetImagePointer(mip, dstMem, dstPitch); + image.data = reinterpret_cast(&dstMem); + + status = astcenc_decompress_image(context, srcMem, srcDataSize, &image, &swizzle, 0); + + if (status != ASTCENC_SUCCESS) + { + AZ_Error("Image Processing", false, "ASTCCompressor::DecompressImage failed: %s\n", astcenc_get_error_string(status)); + astcenc_context_free(context); + return nullptr; + } + } + + astcenc_context_free(context); + + return dstImage; + } +} //namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.h new file mode 100644 index 0000000000..cf191c7072 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ASTCCompressor.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace ImageProcessingAtom +{ + class ASTCCompressor + : public ICompressor + { + public: + static bool IsCompressedPixelFormatSupported(EPixelFormat fmt); + static bool IsUncompressedPixelFormatSupported(EPixelFormat fmt); + static bool DoesSupportDecompress(EPixelFormat fmtDst); + + IImageObjectPtr CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const override; + IImageObjectPtr DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const override; + + EPixelFormat GetSuggestedUncompressedFormat(EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const override; + ColorSpace GetSupportedColorSpace(EPixelFormat compressFormat) const final; + const char* GetName() const final; + }; +} // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp index 9013bff1db..1dd18faaf3 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -15,11 +16,8 @@ namespace ImageProcessingAtom { - ICompressorPtr ICompressor::FindCompressor(EPixelFormat fmt, [[maybe_unused]] ColorSpace colorSpace, bool isCompressing) + ICompressorPtr ICompressor::FindCompressor(EPixelFormat fmt, ColorSpace colorSpace, bool isCompressing) { - // The ISPC texture compressor is able to compress BC1, BC3, BC6H and BC7 formats, and all of the ASTC formats. - // Note: The ISPC texture compressor is only able to compress images that are a multiple of the compressed format's blocksize. - // Another limitation is that the compressor requires LDR source images to be in sRGB colorspace. if (ISPCCompressor::IsCompressedPixelFormatSupported(fmt)) { if ((isCompressing && ISPCCompressor::IsSourceColorSpaceSupported(colorSpace, fmt)) || (!isCompressing && ISPCCompressor::DoesSupportDecompress(fmt))) @@ -35,6 +33,14 @@ namespace ImageProcessingAtom return ICompressorPtr(new CTSquisher()); } } + + if (ASTCCompressor::IsCompressedPixelFormatSupported(fmt)) + { + if (isCompressing || (!isCompressing && ASTCCompressor::DoesSupportDecompress(fmt))) + { + return ICompressorPtr(new ASTCCompressor()); + } + } // Both ETC2Compressor and PVRTCCompressor can process ETC formats // According to Mobile team, Etc2Com is faster than PVRTexLib, so we check with ETC2Compressor before PVRTCCompressor diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h index dff71e59db..6920ae0cc1 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.h @@ -38,8 +38,7 @@ namespace ImageProcessingAtom EQuality compressQuality = eQuality_Normal; //required for CTSquisher AZ::Vector3 rgbWeight = AZ::Vector3(0.3333f, 0.3334f, 0.3333f); - //required for ISPC texture compressor - bool ispcDiscardAlpha = false; + bool discardAlpha = false; }; public: diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ISPCTextureCompressor.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ISPCTextureCompressor.cpp index 5e96c7274d..b1b41d98bc 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ISPCTextureCompressor.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ISPCTextureCompressor.cpp @@ -57,6 +57,12 @@ namespace ImageProcessingAtom bool ISPCCompressor::IsCompressedPixelFormatSupported(EPixelFormat fmt) { + // Even though the ISPC compressor support ASTC formats. But it has restrictions + // 1. Only supports LDR color profile + // 2. Only supports a subset of 2D block sizes + // Also it has overall lower quality compare to astc-encoder + // So we won't add ASTC as part of supported formats here + // Ref: https://solidpixel.github.io/2020/03/02/astc-compared.html switch (fmt) { case ePixelFormat_BC3: @@ -152,7 +158,7 @@ namespace ImageProcessingAtom if (compressOption) { quality = compressOption->compressQuality; - discardAlpha = compressOption->ispcDiscardAlpha; + discardAlpha = compressOption->discardAlpha; } // Get the compression profile @@ -230,24 +236,12 @@ namespace ImageProcessingAtom } break; default: - if (IsASTCFormat(destinationFormat)) - { - const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo(destinationFormat); - astc_enc_settings settings = {}; - - const auto setProfile = compressionProfile->GetASTC(discardAlpha); - setProfile(&settings, info->blockWidth, info->blockHeight); - - // Compress with ASTC - CompressBlocksASTC(&sourceSurface, destinationImageData, &settings); - } - else - { - // No valid pixel format - AZ_Assert(false, "Unhandled pixel format %d", destinationFormat); - return nullptr; - } - break; + { + // No valid pixel format + AZ_Assert(false, "Unhandled pixel format %d", destinationFormat); + return nullptr; + } + break; } } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp index 7c899e4ad2..f3a630e8c1 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp @@ -19,7 +19,7 @@ namespace ImageProcessingAtom { - // Note: PVRTexLib supports ASTC formats, ETC formats, PVRTC formats and BC formats + // Note: PVRTexLib supports ETC formats, PVRTC formats and BC formats // We haven't tested the performace to compress BC formats compare to CTSquisher // For PVRTC formats, we only added PVRTC 1 support for now // The compression for ePVRTPF_EAC_R11 and ePVRTPF_EAC_RG11 are very slow. It takes 7 and 14 minutes for a 2048x2048 texture. @@ -27,34 +27,6 @@ namespace ImageProcessingAtom { switch (fmt) { - case ePixelFormat_ASTC_4x4: - return ePVRTPF_ASTC_4x4; - case ePixelFormat_ASTC_5x4: - return ePVRTPF_ASTC_5x4; - case ePixelFormat_ASTC_5x5: - return ePVRTPF_ASTC_5x5; - case ePixelFormat_ASTC_6x5: - return ePVRTPF_ASTC_6x5; - case ePixelFormat_ASTC_6x6: - return ePVRTPF_ASTC_6x6; - case ePixelFormat_ASTC_8x5: - return ePVRTPF_ASTC_8x5; - case ePixelFormat_ASTC_8x6: - return ePVRTPF_ASTC_8x6; - case ePixelFormat_ASTC_8x8: - return ePVRTPF_ASTC_8x8; - case ePixelFormat_ASTC_10x5: - return ePVRTPF_ASTC_10x5; - case ePixelFormat_ASTC_10x6: - return ePVRTPF_ASTC_10x6; - case ePixelFormat_ASTC_10x8: - return ePVRTPF_ASTC_10x8; - case ePixelFormat_ASTC_10x10: - return ePVRTPF_ASTC_10x10; - case ePixelFormat_ASTC_12x10: - return ePVRTPF_ASTC_12x10; - case ePixelFormat_ASTC_12x12: - return ePVRTPF_ASTC_12x12; case ePixelFormat_PVRTC2: return ePVRTPF_PVRTCI_2bpp_RGBA; case ePixelFormat_PVRTC4: @@ -156,26 +128,7 @@ namespace ImageProcessingAtom internalQuality = pvrtexture::eETCSlow; } } - else if (IsASTCFormat(fmtDst)) - { - if (quality == eQuality_Preview) - { - internalQuality = pvrtexture::eASTCVeryFast; - } - else if (quality == eQuality_Fast) - { - internalQuality = pvrtexture::eASTCFast; - } - else if (quality == eQuality_Normal) - { - internalQuality = pvrtexture::eASTCMedium; - } - else - { - internalQuality = pvrtexture::eASTCThorough; - } - } - else + else { if (quality == eQuality_Preview) { @@ -252,7 +205,7 @@ namespace ImageProcessingAtom if (!isSuccess) { - AZ_Error("Image Processing", false, "Failed to compress image with PVRTexLib. You may not have astcenc.exe for compressing ASTC formates"); + AZ_Error("Image Processing", false, "Failed to compress image with PVRTexLib."); return nullptr; } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp index 9b41645280..5995146c59 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.cpp @@ -117,6 +117,11 @@ namespace ImageProcessingAtom } } + const ImageConvertProcessDescriptor* ImageConvertProcess::GetInputDesc() const + { + return m_input.get(); + } + ImageConvertProcess::ImageConvertProcess(AZStd::unique_ptr&& descriptor) : m_image(nullptr) , m_progressStep(0) @@ -554,12 +559,6 @@ namespace ImageProcessingAtom // pixel format conversion bool ImageConvertProcess::ConvertPixelformat() { - //For ASTC compression we need to clear out the alpha to get accurate rgb compression. - if(m_alphaImage && IsASTCFormat(m_input->m_presetSetting.m_pixelFormat)) - { - m_image->Get()->Swizzle("rgb1"); - } - //set up compress option ICompressor::EQuality quality; if (m_input->m_isPreview) @@ -574,7 +573,14 @@ namespace ImageProcessingAtom // set the compression options m_image->GetCompressOption().compressQuality = quality; m_image->GetCompressOption().rgbWeight = m_input->m_presetSetting.GetColorWeight(); - m_image->GetCompressOption().ispcDiscardAlpha = m_input->m_presetSetting.m_discardAlpha; + m_image->GetCompressOption().discardAlpha = m_input->m_presetSetting.m_discardAlpha; + + //For ASTC compression we need to clear out the alpha to get accurate rgb compression. + if(m_alphaImage && IsASTCFormat(m_input->m_presetSetting.m_pixelFormat)) + { + m_image->GetCompressOption().discardAlpha = true; + } + m_image->ConvertFormat(m_input->m_presetSetting.m_pixelFormat); return true; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h index 190e68ca31..6ab866ee09 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageConvert.h @@ -122,6 +122,8 @@ namespace ImageProcessingAtom // Get output JobProducts and append them to the outProducts vector. void GetAppendOutputProducts(AZStd::vector& outProducts); + const ImageConvertProcessDescriptor* GetInputDesc() const; + private: //input image and settings AZStd::shared_ptr m_input; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp index 0656fb4e46..c4240306c4 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp @@ -12,8 +12,12 @@ #include +#include + #include #include +#include +#include #include #include #include @@ -123,6 +127,9 @@ namespace UnitTest AZStd::string m_outputRootFolder; AZStd::string m_outputFolder; + AZStd::unique_ptr m_jobManager; + AZStd::unique_ptr m_jobContext; + void SetUp() override { AllocatorsBase::SetupAllocator(); @@ -159,6 +166,27 @@ namespace UnitTest m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get()); BuilderPluginComponent::Reflect(m_jsonRegistrationContext.get()); + // Setup job context for job system + JobManagerDesc jobManagerDesc; + JobManagerThreadDesc threadDesc; +#if AZ_TRAIT_SET_JOB_PROCESSOR_ID + threadDesc.m_cpuId = 0; // Don't set processors IDs on windows +#endif + + uint32_t numWorkerThreads = AZStd::thread::hardware_concurrency(); + + for (unsigned int i = 0; i < numWorkerThreads; ++i) + { + jobManagerDesc.m_workerThreads.push_back(threadDesc); +#if AZ_TRAIT_SET_JOB_PROCESSOR_ID + threadDesc.m_cpuId++; +#endif + } + + m_jobManager = AZStd::make_unique(jobManagerDesc); + m_jobContext = AZStd::make_unique(*m_jobManager); + JobContext::SetGlobalContext(m_jobContext.get()); + // Startup default local FileIO (hits OSAllocator) if not already setup. if (AZ::IO::FileIOBase::GetInstance() == nullptr) { @@ -192,6 +220,10 @@ namespace UnitTest delete AZ::IO::FileIOBase::GetInstance(); AZ::IO::FileIOBase::SetInstance(nullptr); + JobContext::SetGlobalContext(nullptr); + m_jobContext = nullptr; + m_jobManager = nullptr; + m_jsonRegistrationContext->EnableRemoveReflection(); m_jsonSystemComponent->Reflect(m_jsonRegistrationContext.get()); BuilderPluginComponent::Reflect(m_jsonRegistrationContext.get()); @@ -223,7 +255,7 @@ namespace UnitTest Image_512X288_RGB8_Tga, Image_1024X1024_RGB8_Tif, Image_UpperCase_Tga, - Image_512x512_Normal_Tga, // QImage doesn't support loading this file. + Image_1024x1024_normal_tiff, Image_128x128_Transparent_Tga, Image_237x177_RGB_Jpg, Image_GreyScale_Png, @@ -251,7 +283,7 @@ namespace UnitTest m_imagFileNameMap[Image_512X288_RGB8_Tga] = m_testFileFolder + "512x288_24bit.tga"; m_imagFileNameMap[Image_1024X1024_RGB8_Tif] = m_testFileFolder + "1024x1024_24bit.tif"; m_imagFileNameMap[Image_UpperCase_Tga] = m_testFileFolder + "uppercase.TGA"; - m_imagFileNameMap[Image_512x512_Normal_Tga] = m_testFileFolder + "512x512_RGB_N.tga"; + m_imagFileNameMap[Image_1024x1024_normal_tiff] = m_testFileFolder + "1024x1024_normal.tiff"; m_imagFileNameMap[Image_128x128_Transparent_Tga] = m_testFileFolder + "128x128_RGBA8.tga"; m_imagFileNameMap[Image_237x177_RGB_Jpg] = m_testFileFolder + "237x177_RGB.jpg"; m_imagFileNameMap[Image_GreyScale_Png] = m_testFileFolder + "greyscale.png"; @@ -801,8 +833,7 @@ namespace UnitTest auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat); if (formatInfo->bCompressed) { - // exclude astc formats until we add astc compressor to all platforms - // exclude pvrtc formats (deprecating) + // skip ASTC formats which are tested in TestConvertASTCCompressor if (!IsASTCFormat(pixelFormat) && pixelFormat != ePixelFormat_PVRTC2 && pixelFormat != ePixelFormat_PVRTC4 && !IsETCFormat(pixelFormat)) // skip ETC since it's very slow @@ -830,32 +861,121 @@ namespace UnitTest continue; } - [[maybe_unused]] auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat); - imageToProcess.Set(srcImage); - imageToProcess.ConvertFormat(pixelFormat); + auto formatInfo = CPixelFormats::GetInstance().GetPixelFormatInfo(pixelFormat); + ColorSpace sourceColorSpace = srcImage->HasImageFlags(EIF_SRGBRead) ? ColorSpace::sRGB : ColorSpace::linear; + ICompressorPtr compressor = ICompressor::FindCompressor(pixelFormat, sourceColorSpace, true); - if (!imageToProcess.Get()) + if (!compressor) { AZ_Warning("test", false, "unsupported format: %s", formatInfo->szName); continue; } + + imageToProcess.Set(srcImage); + imageToProcess.ConvertFormat(pixelFormat); + ASSERT_TRUE(imageToProcess.Get()); ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == pixelFormat); - // Get compressor name - ColorSpace sourceColorSpace = srcImage->HasImageFlags(EIF_SRGBRead) ? ColorSpace::sRGB : ColorSpace::linear; - ICompressorPtr compressor = ICompressor::FindCompressor(pixelFormat, sourceColorSpace, true); + //convert back to an uncompressed format and expect it will be successful + imageToProcess.ConvertFormat(srcImage->GetPixelFormat()); + ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat()); - //save the image to a file so we can check the visual result + // Save the image to a file so we can check the visual result AZStd::string outputName = AZStd::string::format("%s_%s", imageName.c_str(), compressor->GetName()); SaveImageToFile(imageToProcess.Get(), outputName, 1); - - //convert back to an uncompressed format and expect it will be successful - imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8); - ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == ePixelFormat_R8G8B8A8); } } } + + TEST_F(ImageProcessingTest, Test_ConvertAllAstc_Success) + { + // Compress/Decompress to all astc formats (LDR) + auto imageIdx = Image_237x177_RGB_Jpg; + IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[imageIdx])); + QFileInfo fi(m_imagFileNameMap[imageIdx].c_str()); + AZStd::string imageName = fi.baseName().toUtf8().constData(); + for (uint32 i = 0; i < ePixelFormat_Count; i++) + { + EPixelFormat pixelFormat = (EPixelFormat)i; + if (IsASTCFormat(pixelFormat)) + { + ImageToProcess imageToProcess(srcImage); + imageToProcess.ConvertFormat(pixelFormat); + + ASSERT_TRUE(imageToProcess.Get()); + ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == pixelFormat); + ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == srcImage->GetWidth(0)); + ASSERT_TRUE(imageToProcess.Get()->GetHeight(0) == srcImage->GetHeight(0)); + + // convert back to an uncompressed format and expect it will be successful + imageToProcess.ConvertFormat(srcImage->GetPixelFormat()); + ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat()); + + // save the image to a file so we can check the visual result + AZStd::string outputName = AZStd::string::format("ASTC_%s", imageName.c_str()); + SaveImageToFile(imageToProcess.Get(), outputName, 1); + } + } + } + + TEST_F(ImageProcessingTest, Test_ConvertHdrToAstc_Success) + { + // Compress/Decompress HDR + auto imageIdx = Image_defaultprobe_cm_1536x256_64bits_tif; + IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(m_imagFileNameMap[imageIdx])); + + EPixelFormat dstFormat = ePixelFormat_ASTC_4x4; + ImageToProcess imageToProcess(srcImage); + imageToProcess.ConvertFormat(ePixelFormat_ASTC_4x4); + + ASSERT_TRUE(imageToProcess.Get()); + ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == dstFormat); + ASSERT_TRUE(imageToProcess.Get()->GetWidth(0) == srcImage->GetWidth(0)); + ASSERT_TRUE(imageToProcess.Get()->GetHeight(0) == srcImage->GetHeight(0)); + + //convert back to an uncompressed format and expect it will be successful + imageToProcess.ConvertFormat(srcImage->GetPixelFormat()); + ASSERT_TRUE(imageToProcess.Get()->GetPixelFormat() == srcImage->GetPixelFormat()); + + //save the image to a file so we can check the visual result + SaveImageToFile(imageToProcess.Get(), "ASTC_HDR", 1); + } + + TEST_F(ImageProcessingTest, Test_AstcNormalPreset_Success) + { + // Normal.preset which uses ASTC as output format + // This test compress a normal texture and its mipmaps + + auto outcome = BuilderSettingManager::Instance()->LoadConfigFromFolder(m_defaultSettingFolder); + ASSERT_TRUE(outcome.IsSuccess()); + + AZStd::string inputFile; + AZStd::vector outProducts; + + inputFile = m_imagFileNameMap[Image_1024x1024_normal_tiff]; + IImageObjectPtr srcImage = IImageObjectPtr(LoadImageFromFile(inputFile)); + + ImageConvertProcess* process = CreateImageConvertProcess(inputFile, m_outputFolder, "ios", outProducts, m_context.get()); + + const PresetSettings* preset = &process->GetInputDesc()->m_presetSetting; + + if (process != nullptr) + { + process->ProcessAll(); + + //get process result + ASSERT_TRUE(process->IsSucceed()); + auto outputImage = process->GetOutputImage(); + ASSERT_TRUE(outputImage->GetPixelFormat() == preset->m_pixelFormat); + ASSERT_TRUE(outputImage->GetWidth(0) == srcImage->GetWidth(0)); + ASSERT_TRUE(outputImage->GetHeight(0) == srcImage->GetHeight(0)); + + SaveImageToFile(outputImage, "ASTC_Normal", 10); + + delete process; + } + } TEST_F(ImageProcessingTest, DISABLED_TestImageFilter) { diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/1024x1024_normal.tiff b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/1024x1024_normal.tiff new file mode 100644 index 0000000000..8120514ddc --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/1024x1024_normal.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:120aaf43057b07fb3c784264eb48b899cc612f30917d316fe843bb839220dc22 +size 203062 diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/512x512_RGB_N.tga b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/512x512_RGB_N.tga deleted file mode 100644 index d47f00912a..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/TestAssets/512x512_RGB_N.tga +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:646a9a9035cc3f4dfd57babc0055710d2f5bb8aee0a792f2b65d69b4fd6a94b3 -size 786450 diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake index 55ccdf1d89..ba9e9f29b7 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake @@ -114,6 +114,8 @@ set(FILES ../External/CubeMapGen/CImageSurface.cpp ../External/CubeMapGen/CImageSurface.h ../External/CubeMapGen/VectorMacros.h + Source/Compressors/ASTCCompressor.cpp + Source/Compressors/ASTCCompressor.h Source/Compressors/Compressor.h Source/Compressors/Compressor.cpp Source/Compressors/CTSquisher.h diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.cpp index 085d164a94..54fb130ad3 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.cpp @@ -1421,17 +1421,16 @@ namespace EMotionFX // extract sorted active items void Recorder::ExtractNodeHistoryItems(const ActorInstanceData& actorInstanceData, float timeValue, bool sort, EValueType valueType, AZStd::vector* outItems, AZStd::vector* outMap) const { - // clear the map array + // Reinit the item array. const size_t maxIndex = CalcMaxNodeHistoryTrackIndex(actorInstanceData); outItems->resize(maxIndex + 1); for (size_t i = 0; i <= maxIndex; ++i) { - ExtractedNodeHistoryItem item; - item.m_trackIndex = i; - item.m_value = 0.0f; + ExtractedNodeHistoryItem& item = (*outItems)[i]; + item.m_nodeHistoryItem = nullptr; + item.m_trackIndex = i; item.m_keyTrackSampleTime = 0.0f; - item.m_nodeHistoryItem = nullptr; - outItems->emplace(AZStd::next(begin(*outItems), i), AZStd::move(item)); + item.m_value = 0.0f; } // find all node history items @@ -1440,7 +1439,7 @@ namespace EMotionFX { if (curItem->m_startTime <= timeValue && curItem->m_endTime > timeValue) { - ExtractedNodeHistoryItem item; + ExtractedNodeHistoryItem& item = (*outItems)[curItem->m_trackIndex]; item.m_trackIndex = curItem->m_trackIndex; item.m_keyTrackSampleTime = timeValue - curItem->m_startTime; item.m_nodeHistoryItem = curItem; @@ -1463,8 +1462,6 @@ namespace EMotionFX MCORE_ASSERT(false); // unsupported mode item.m_value = curItem->m_globalWeights.GetValueAtTime(item.m_keyTrackSampleTime, nullptr, nullptr, m_recordSettings.m_interpolate); } - - outItems->emplace(AZStd::next(begin(*outItems), curItem->m_trackIndex), item); } } @@ -1472,7 +1469,7 @@ namespace EMotionFX outMap->resize(maxIndex + 1); for (size_t i = 0; i <= maxIndex; ++i) { - outMap->emplace(AZStd::next(begin(*outMap), i), i); + (*outMap)[i] = i; } // sort if desired @@ -1482,7 +1479,7 @@ namespace EMotionFX for (size_t i = 0; i <= maxIndex; ++i) { - outMap->emplace(AZStd::next(begin(*outMap), outItems->at(i).m_trackIndex), i); + (*outMap)[outItems->at(i).m_trackIndex] = i; } } } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.h b/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.h index fe87d36ea1..c486427159 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Recorder.h @@ -168,10 +168,10 @@ namespace EMotionFX struct EMFX_API ExtractedNodeHistoryItem { - NodeHistoryItem* m_nodeHistoryItem; - size_t m_trackIndex; - float m_value; - float m_keyTrackSampleTime; + NodeHistoryItem* m_nodeHistoryItem = nullptr; + size_t m_trackIndex = 0; + float m_value = 0.0f; + float m_keyTrackSampleTime = 0.0f; friend bool operator< (const ExtractedNodeHistoryItem& a, const ExtractedNodeHistoryItem& b) { return (a.m_value > b.m_value); } friend bool operator==(const ExtractedNodeHistoryItem& a, const ExtractedNodeHistoryItem& b) { return (a.m_value == b.m_value); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.cpp index 3f3c7c1c7d..c8ce48fe18 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/ParameterWindow.cpp @@ -1461,10 +1461,15 @@ namespace EMStudio // show the create window auto createWindow = new ParameterCreateRenameWindow("Create Group", "Please enter the group name:", uniqueGroupName.c_str(), "", invalidNames, this); - connect(createWindow, &QDialog::finished, this, [this, createWindow]() + connect(createWindow, &QDialog::finished, this, [this, createWindow](int resultCode) { createWindow->deleteLater(); + if (resultCode == QDialog::Rejected) + { + return; + } + AZStd::string command = AZStd::string::format("AnimGraphAddGroupParameter -animGraphID %i -name \"%s\"", m_animGraph->GetID(), createWindow->GetName().c_str()); const EMotionFX::GroupParameter* parentGroup = nullptr; const EMotionFX::Parameter* selectedParameter = GetSingleSelectedParameter(); diff --git a/Gems/LmbrCentral/Code/CMakeLists.txt b/Gems/LmbrCentral/Code/CMakeLists.txt index 00176ae2c2..b047a9f65e 100644 --- a/Gems/LmbrCentral/Code/CMakeLists.txt +++ b/Gems/LmbrCentral/Code/CMakeLists.txt @@ -114,6 +114,16 @@ endif() # Tests ################################################################################ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_target( + NAME LmbrCentral.Mocks HEADERONLY + NAMESPACE Gem + FILES_CMAKE + lmbrcentral_mocks_files.cmake + INCLUDE_DIRECTORIES + INTERFACE + Mocks + ) + ly_add_target( NAME LmbrCentral.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAMESPACE Gem @@ -131,6 +141,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) Legacy::CryCommon AZ::AzFramework Gem::LmbrCentral.Static + Gem::LmbrCentral.Mocks ) ly_add_googletest( NAME Gem::LmbrCentral.Tests diff --git a/Gems/LmbrCentral/Code/Mocks/LmbrCentral/Shape/MockShapes.h b/Gems/LmbrCentral/Code/Mocks/LmbrCentral/Shape/MockShapes.h new file mode 100644 index 0000000000..20be74dd90 --- /dev/null +++ b/Gems/LmbrCentral/Code/Mocks/LmbrCentral/Shape/MockShapes.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +#include +#include +#include + +namespace UnitTest +{ + class MockBoxShapeComponentRequests + : public LmbrCentral::BoxShapeComponentRequestsBus::Handler + { + public: + MockBoxShapeComponentRequests(AZ::EntityId entityId) + { + LmbrCentral::BoxShapeComponentRequestsBus::Handler::BusConnect(entityId); + } + + ~MockBoxShapeComponentRequests() + { + LmbrCentral::BoxShapeComponentRequestsBus::Handler::BusDisconnect(); + } + + MOCK_METHOD0(GetBoxConfiguration, LmbrCentral::BoxShapeConfig()); + MOCK_METHOD0(GetBoxDimensions, AZ::Vector3()); + MOCK_METHOD1(SetBoxDimensions, void(const AZ::Vector3& newDimensions)); + }; + + class MockShapeComponentRequests + : public LmbrCentral::ShapeComponentRequestsBus::Handler + { + public: + MockShapeComponentRequests(AZ::EntityId entityId) + { + LmbrCentral::ShapeComponentRequestsBus::Handler::BusConnect(entityId); + } + + ~MockShapeComponentRequests() + { + LmbrCentral::ShapeComponentRequestsBus::Handler::BusDisconnect(); + } + + MOCK_METHOD0(GetShapeType, AZ::Crc32()); + MOCK_METHOD0(GetEncompassingAabb, AZ::Aabb()); + MOCK_METHOD2(GetTransformAndLocalBounds, void(AZ::Transform& transform, AZ::Aabb& bounds)); + MOCK_METHOD1(IsPointInside, bool(const AZ::Vector3& point)); + MOCK_METHOD1(DistanceSquaredFromPoint, float(const AZ::Vector3& point)); + MOCK_METHOD1(GenerateRandomPointInside, AZ::Vector3(AZ::RandomDistributionType randomDistribution)); + MOCK_METHOD3(IntersectRay, bool(const AZ::Vector3& src, const AZ::Vector3& dir, float& distance)); + }; +} + diff --git a/Gems/LmbrCentral/Code/Tests/LmbrCentralTest.cpp b/Gems/LmbrCentral/Code/Tests/LmbrCentralTest.cpp index 40217ff9bc..f36978f22d 100644 --- a/Gems/LmbrCentral/Code/Tests/LmbrCentralTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/LmbrCentralTest.cpp @@ -8,4 +8,7 @@ #include +// Include any public mocks here to ensure they get compiled as a part of the test project. +#include + AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Gems/LmbrCentral/Code/lmbrcentral_mocks_files.cmake b/Gems/LmbrCentral/Code/lmbrcentral_mocks_files.cmake new file mode 100644 index 0000000000..c3a5cca3f8 --- /dev/null +++ b/Gems/LmbrCentral/Code/lmbrcentral_mocks_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Mocks/LmbrCentral/Shape/MockShapes.h +) diff --git a/Gems/PhysX/Code/Include/PhysX/EditorColliderComponentRequestBus.h b/Gems/PhysX/Code/Include/PhysX/EditorColliderComponentRequestBus.h index 2cfb48effa..20b02b7098 100644 --- a/Gems/PhysX/Code/Include/PhysX/EditorColliderComponentRequestBus.h +++ b/Gems/PhysX/Code/Include/PhysX/EditorColliderComponentRequestBus.h @@ -82,4 +82,18 @@ namespace PhysX }; using EditorColliderComponentRequestBus = AZ::EBus; + + /// + /// This is a Bus in order to communicate the status of the meshes of the collider and avoid dependencies with the rigidbody + /// + class EditorColliderValidationRequests : public AZ::ComponentBus + { + public: + /// Checks if the the mesh in the collider is correct with the current state of the Rigidbody! + virtual void ValidateRigidBodyMeshGeometryType() = 0; + }; + + using EditorColliderValidationRequestBus = AZ::EBus; } + + diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp index 4aa240eaae..566903d84a 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp @@ -118,11 +118,10 @@ namespace PhysX AZ::u32 EditorProxyShapeConfig::OnShapeTypeChanged() { - //reset the physics asset if the shape type was Physics Asset - if (m_shapeType != Physics::ShapeType::PhysicsAsset && - m_lastShapeType == Physics::ShapeType::PhysicsAsset) + // reset the physics asset if the shape type was Physics Asset + if (m_shapeType != Physics::ShapeType::PhysicsAsset && m_lastShapeType == Physics::ShapeType::PhysicsAsset) { - //clean up any reference to a physics assets, and re-initialize to an empty Pipeline::MeshAsset asset. + // clean up any reference to a physics assets, and re-initialize to an empty Pipeline::MeshAsset asset. m_physicsAsset.m_pxAsset.Reset(); m_physicsAsset.m_pxAsset = AZ::Data::Asset(AZ::Data::AssetLoadBehavior::QueueLoad); @@ -212,6 +211,7 @@ namespace PhysX ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_shapeConfiguration, "Shape Configuration", "Configuration of the shape") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged) + ->Attribute(AZ::Edit::Attributes::RemoveNotify, &EditorColliderComponent::ValidateRigidBodyMeshGeometryType) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_componentModeDelegate, "Component Mode", "Collider Component Mode") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_colliderDebugDraw, @@ -383,6 +383,7 @@ namespace PhysX ColliderShapeRequestBus::Handler::BusConnect(GetEntityId()); AZ::Render::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId()); EditorColliderComponentRequestBus::Handler::BusConnect(AZ::EntityComponentIdPair(GetEntityId(), GetId())); + EditorColliderValidationRequestBus::Handler::BusConnect(GetEntityId()); m_nonUniformScaleChangedHandler = AZ::NonUniformScaleChangedEvent::Handler( [this](const AZ::Vector3& scale) {OnNonUniformScaleChanged(scale); }); AZ::NonUniformScaleRequestBus::Event(GetEntityId(), &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent, @@ -427,6 +428,7 @@ namespace PhysX m_colliderDebugDraw.Disconnect(); AZ::Data::AssetBus::MultiHandler::BusDisconnect(); m_nonUniformScaleChangedHandler.Disconnect(); + EditorColliderValidationRequestBus::Handler::BusDisconnect(); EditorColliderComponentRequestBus::Handler::BusDisconnect(); AZ::Render::MeshComponentNotificationBus::Handler::BusDisconnect(); ColliderShapeRequestBus::Handler::BusDisconnect(); @@ -466,6 +468,7 @@ namespace PhysX UpdateShapeConfigurationScale(); CreateStaticEditorCollider(); + ValidateRigidBodyMeshGeometryType(); m_colliderDebugDraw.ClearCachedGeometry(); @@ -768,15 +771,16 @@ namespace PhysX { m_componentWarnings.clear(); m_configuration.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); + AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( + &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); } - AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); } void EditorColliderComponent::ValidateRigidBodyMeshGeometryType() { const PhysX::EditorRigidBodyComponent* entityRigidbody = m_entity->FindComponent(); - if (m_shapeConfiguration.m_physicsAsset.m_configuration.GetShapeType() == Physics::ShapeType::PhysicsAsset && entityRigidbody) + if (m_shapeConfiguration.m_physicsAsset.m_pxAsset && (m_shapeConfiguration.m_shapeType == Physics::ShapeType::PhysicsAsset) && entityRigidbody) { AZStd::vector> shapes; Utils::GetShapesFromAsset(m_shapeConfiguration.m_physicsAsset.m_configuration, m_configuration, m_hasNonUniformScale, @@ -784,17 +788,32 @@ namespace PhysX if (shapes.empty()) { + m_componentWarnings.clear(); + AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( + &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); return; } - //We grab the first shape to check if it is a triangle mesh. - auto shape = AZStd::rtti_pointer_cast(shapes[0]); + //We check if the shapes are triangle meshes, if any mesh is a triangle mesh we activate the warning. + bool shapeIsTriangleMesh = false; - if (shape && - shape->GetPxShape()->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH && - entityRigidbody->GetRigidBody() && - entityRigidbody->GetRigidBody()->IsKinematic() == false) + for (const auto& shape : shapes) { + auto current_shape = AZStd::rtti_pointer_cast(shape); + if (current_shape && + current_shape->GetPxShape()->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH && + entityRigidbody->GetRigidBody() && + entityRigidbody->GetRigidBody()->IsKinematic() == false) + { + shapeIsTriangleMesh = true; + break; + } + } + + if (shapeIsTriangleMesh) + { + m_componentWarnings.clear(); + AZStd::string assetPath = m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset.GetHint().c_str(); const size_t lastSlash = assetPath.rfind('/'); if (lastSlash != AZStd::string::npos) @@ -816,6 +835,10 @@ namespace PhysX { m_componentWarnings.clear(); } + + AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( + &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); + } void EditorColliderComponent::OnAssetReloaded(AZ::Data::Asset asset) diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.h b/Gems/PhysX/Code/Source/EditorColliderComponent.h index 1331fd08b6..773dd0a9b8 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.h @@ -58,8 +58,8 @@ namespace PhysX //! Proxy container for only displaying a specific shape configuration depending on the shapeType selected. struct EditorProxyShapeConfig { - AZ_CLASS_ALLOCATOR(PhysX::EditorProxyShapeConfig, AZ::SystemAllocator, 0); - AZ_RTTI(PhysX::EditorProxyShapeConfig, "{531FB42A-42A9-4234-89BA-FD349EF83D0C}"); + AZ_CLASS_ALLOCATOR(EditorProxyShapeConfig, AZ::SystemAllocator, 0); + AZ_RTTI(EditorProxyShapeConfig, "{531FB42A-42A9-4234-89BA-FD349EF83D0C}"); static void Reflect(AZ::ReflectContext* context); EditorProxyShapeConfig() = default; @@ -106,6 +106,7 @@ namespace PhysX , private PhysX::ColliderShapeRequestBus::Handler , private AZ::Render::MeshComponentNotificationBus::Handler , private PhysX::EditorColliderComponentRequestBus::Handler + , private PhysX::EditorColliderValidationRequestBus::Handler , private AzPhysics::SimulatedBodyComponentRequestsBus::Handler { public: @@ -197,6 +198,9 @@ namespace PhysX void SetAssetScale(const AZ::Vector3& scale) override; AZ::Vector3 GetAssetScale() override; + // PhysX::EditorColliderValidationRequestBus overrides ... + void ValidateRigidBodyMeshGeometryType() override; + AZ::Transform GetColliderLocalTransform() const; EditorProxyShapeConfig m_shapeConfiguration; @@ -223,8 +227,6 @@ namespace PhysX void BuildDebugDrawMesh() const; - void ValidateRigidBodyMeshGeometryType(); - AZ::ComponentDescriptor::StringWarningArray GetComponentWarnings() const { return m_componentWarnings; }; using ComponentModeDelegate = AzToolsFramework::ComponentModeFramework::ComponentModeDelegate; diff --git a/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp b/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp index ce812a9f31..447c2755c6 100644 --- a/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp @@ -286,6 +286,9 @@ namespace PhysX } CreateEditorWorldRigidBody(); + PhysX::EditorColliderValidationRequestBus::Event( + GetEntityId(), &PhysX::EditorColliderValidationRequestBus::Events::ValidateRigidBodyMeshGeometryType); + AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId()); } diff --git a/Gems/Terrain/Code/CMakeLists.txt b/Gems/Terrain/Code/CMakeLists.txt index 0feacdaf71..fc07294dab 100644 --- a/Gems/Terrain/Code/CMakeLists.txt +++ b/Gems/Terrain/Code/CMakeLists.txt @@ -83,6 +83,15 @@ endif() ################################################################################ # See if globally, tests are supported if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_target( + NAME Terrain.Mocks HEADERONLY + NAMESPACE Gem + FILES_CMAKE + terrain_mocks_files.cmake + INCLUDE_DIRECTORIES + INTERFACE + Mocks + ) ly_add_target( NAME Terrain.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAMESPACE Gem @@ -97,6 +106,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE AZ::AzTest AZ::AzFramework + Gem::LmbrCentral.Mocks + Gem::Terrain.Mocks Gem::Terrain.Static ) @@ -120,6 +131,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) BUILD_DEPENDENCIES PRIVATE AZ::AzTest + Gem::LmbrCentral.Mocks + Gem::Terrain.Mocks Gem::Terrain.Editor ) diff --git a/Gems/Terrain/Code/Tests/TerrainMocks.h b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h similarity index 57% rename from Gems/Terrain/Code/Tests/TerrainMocks.h rename to Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h index 03a2eccf1d..ea0413be9a 100644 --- a/Gems/Terrain/Code/Tests/TerrainMocks.h +++ b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h @@ -11,59 +11,9 @@ #include #include -#include namespace UnitTest { - static const AZ::Uuid BoxShapeComponentTypeId = "{5EDF4B9E-0D3D-40B8-8C91-5142BCFC30A6}"; - - class MockBoxShapeComponent - : public AZ::Component - { - public: - AZ_COMPONENT(MockBoxShapeComponent, BoxShapeComponentTypeId) - static void Reflect([[maybe_unused]] AZ::ReflectContext* context) - { - } - - void Activate() override - { - } - - void Deactivate() override - { - } - - bool ReadInConfig([[maybe_unused]] const AZ::ComponentConfig* baseConfig) override - { - return true; - } - - bool WriteOutConfig([[maybe_unused]] AZ::ComponentConfig* outBaseConfig) const override - { - return true; - } - - private: - static void GetProvidedServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& provided) - { - provided.push_back(AZ_CRC("ShapeService", 0xe86aa5fe)); - provided.push_back(AZ_CRC("BoxShapeService", 0x946a0032)); - provided.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService")); - } - - static void GetIncompatibleServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible) - { - } - - static void GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) - { - } - - static void GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) - { - } - }; class MockTerrainSystemService : private Terrain::TerrainSystemServiceRequestBus::Handler { @@ -106,4 +56,40 @@ namespace UnitTest MOCK_METHOD2(OnTerrainDataChanged, void(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)); }; + class MockTerrainAreaHeightRequests : public Terrain::TerrainAreaHeightRequestBus::Handler + { + public: + MockTerrainAreaHeightRequests(AZ::EntityId entityId) + { + Terrain::TerrainAreaHeightRequestBus::Handler::BusConnect(entityId); + } + + ~MockTerrainAreaHeightRequests() + { + Terrain::TerrainAreaHeightRequestBus::Handler::BusDisconnect(); + } + + MOCK_METHOD3(GetHeight, void( + const AZ::Vector3& inPosition, + AZ::Vector3& outPosition, + bool& terrainExists)); + + }; + + class MockTerrainSpawnerRequests : public Terrain::TerrainSpawnerRequestBus::Handler + { + public: + MockTerrainSpawnerRequests(AZ::EntityId entityId) + { + Terrain::TerrainSpawnerRequestBus::Handler::BusConnect(entityId); + } + + ~MockTerrainSpawnerRequests() + { + Terrain::TerrainSpawnerRequestBus::Handler::BusDisconnect(); + } + + MOCK_METHOD2(GetPriority, void(AZ::u32& outLayer, AZ::u32& outPriority)); + MOCK_METHOD0(GetUseGroundPlane, bool()); + }; } diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp index 4d0cb6576d..4ea16018d1 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -142,11 +142,15 @@ namespace Terrain return false; } - float TerrainHeightGradientListComponent::GetHeight(float x, float y) + void TerrainHeightGradientListComponent::GetHeight( + const AZ::Vector3& inPosition, + AZ::Vector3& outPosition, + bool& terrainExists) { float maxSample = 0.0f; + terrainExists = false; - GradientSignal::GradientSampleParams params(AZ::Vector3(x, y, 0.0f)); + GradientSignal::GradientSampleParams params(AZ::Vector3(inPosition.GetX(), inPosition.GetY(), 0.0f)); // Right now, when the list contains multiple entries, we will use the highest point from each gradient. // This is needed in part because gradients don't really have world bounds, so they exist everywhere but generally have a value @@ -155,49 +159,20 @@ namespace Terrain // make this list a prioritized list from top to bottom for any points that overlap. for (auto& gradientId : m_configuration.m_gradientEntities) { + // If gradients ever provide bounds, or if we add a value threshold in this component, it would be possible for terrain + // to *not* exist at a specific point. + terrainExists = true; + float sample = 0.0f; - GradientSignal::GradientRequestBus::EventResult(sample, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params); + GradientSignal::GradientRequestBus::EventResult( + sample, gradientId, &GradientSignal::GradientRequestBus::Events::GetValue, params); maxSample = AZ::GetMax(maxSample, sample); } const float height = AZ::Lerp(m_cachedShapeBounds.GetMin().GetZ(), m_cachedShapeBounds.GetMax().GetZ(), maxSample); - - return AZ::GetClamp(height, m_cachedMinWorldHeight, m_cachedMaxWorldHeight); + outPosition.SetZ(AZ::GetClamp(height, m_cachedMinWorldHeight, m_cachedMaxWorldHeight)); } - void TerrainHeightGradientListComponent::GetHeight( - const AZ::Vector3& inPosition, - AZ::Vector3& outPosition, - [[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter = - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT) - { - const float height = GetHeight(inPosition.GetX(), inPosition.GetY()); - outPosition.SetZ(height); - } - - void TerrainHeightGradientListComponent::GetNormal( - const AZ::Vector3& inPosition, - AZ::Vector3& outNormal, - [[maybe_unused]] AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter = - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT) - { - const float x = inPosition.GetX(); - const float y = inPosition.GetY(); - - if ((x >= m_cachedShapeBounds.GetMin().GetX()) && (x <= m_cachedShapeBounds.GetMax().GetX()) && - (y >= m_cachedShapeBounds.GetMin().GetY()) && (y <= m_cachedShapeBounds.GetMax().GetY())) - { - AZ::Vector2 fRange = (m_cachedHeightQueryResolution / 2.0f) + AZ::Vector2(0.05f); - - AZ::Vector3 v1(x - fRange.GetX(), y - fRange.GetY(), GetHeight(x - fRange.GetX(), y - fRange.GetY())); - AZ::Vector3 v2(x - fRange.GetX(), y + fRange.GetY(), GetHeight(x - fRange.GetX(), y + fRange.GetY())); - AZ::Vector3 v3(x + fRange.GetX(), y - fRange.GetY(), GetHeight(x + fRange.GetX(), y - fRange.GetY())); - AZ::Vector3 v4(x + fRange.GetX(), y + fRange.GetY(), GetHeight(x + fRange.GetX(), y + fRange.GetY())); - outNormal = (v3 - v2).Cross(v4 - v1).GetNormalized(); - } - } - - void TerrainHeightGradientListComponent::OnCompositionChanged() { RefreshMinMaxHeights(); diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h index 509f003afa..6c3fd7b820 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.h @@ -64,14 +64,7 @@ namespace Terrain TerrainHeightGradientListComponent() = default; ~TerrainHeightGradientListComponent() = default; - void GetHeight( - const AZ::Vector3& inPosition, - AZ::Vector3& outPosition, - AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter) override; - void GetNormal( - const AZ::Vector3& inPosition, - AZ::Vector3& outNormal, - AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter) override; + void GetHeight(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists) override; ////////////////////////////////////////////////////////////////////////// // AZ::Component interface implementation @@ -91,11 +84,7 @@ namespace Terrain private: TerrainHeightGradientListConfig m_configuration; - /////////////////////////////////////////// - void GetNormalSynchronous(float x, float y, AZ::Vector3& normal); - void RefreshMinMaxHeights(); - float GetHeight(float x, float y); float m_cachedMinWorldHeight{ 0.0f }; float m_cachedMaxWorldHeight{ 0.0f }; diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp index 89ffa6364f..7233d08281 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldDebuggerComponent.cpp @@ -285,13 +285,13 @@ namespace Terrain AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( z00, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y, - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists); + AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( z01, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y1, - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists); + AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( z10, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x1, y, - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists); + AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); sector.m_lineVertices.push_back(AZ::Vector3(x, y, z00)); sector.m_lineVertices.push_back(AZ::Vector3(x1, y, z10)); diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index 674af376e4..5ec6af6f37 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -152,27 +152,69 @@ AZ::Vector2 TerrainSystem::GetTerrainHeightQueryResolution() const return m_currentSettings.m_heightQueryResolution; } +void TerrainSystem::ClampPosition(float x, float y, AZ::Vector2& outPosition, AZ::Vector2& normalizedDelta) const +{ + // Given an input position, clamp the values to our terrain grid, where it will always go to the terrain grid point + // at a lower value, whether positive or negative. Ex: 3.3 -> 3, -3.3 -> -4 + // Also, return the normalized delta as a value of [0-1) describing what fraction of a grid point the value moved. + + // Scale the position by the query resolution, so that integer values represent exact steps on the grid, + // and fractional values are the amount in-between each grid point, in the range [0-1). + AZ::Vector2 normalizedPosition = AZ::Vector2(x, y) / m_currentSettings.m_heightQueryResolution; + normalizedDelta = AZ::Vector2( + normalizedPosition.GetX() - floor(normalizedPosition.GetX()), normalizedPosition.GetY() - floor(normalizedPosition.GetY())); + + // Remove the fractional part, then scale back down into world space. + outPosition = (normalizedPosition - normalizedDelta) * m_currentSettings.m_heightQueryResolution; +} + float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const { bool terrainExists = false; - - AZ::Vector3 inPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ()); - AZ::Vector3 outPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ()); + float height = m_currentSettings.m_worldBounds.GetMin().GetZ(); AZStd::shared_lock lock(m_areaMutex); - for (auto& [areaId, areaBounds] : m_registeredAreas) + switch (sampler) { - inPosition.SetZ(areaBounds.GetMin().GetZ()); - if (areaBounds.Contains(inPosition)) + // Get the value at the requested location, using the terrain grid to bilinear filter between sample grid points. + case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR: { - Terrain::TerrainAreaHeightRequestBus::Event( - areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeight, inPosition, outPosition, sampler); + // pos0 contains one corner of our grid square, pos1 contains the opposite corner, and normalizedDelta is the fractional + // amount the position exists between those corners. + // Ex: (3.3, 4.4) would have a pos0 of (3, 4), a pos1 of (4, 5), and a delta of (0.3, 0.4). + AZ::Vector2 normalizedDelta; + AZ::Vector2 pos0; + ClampPosition(x, y, pos0, normalizedDelta); + const AZ::Vector2 pos1 = pos0 + m_currentSettings.m_heightQueryResolution; - terrainExists = true; - - break; + const float heightX0Y0 = GetTerrainAreaHeight(pos0.GetX(), pos0.GetY(), terrainExists); + const float heightX1Y0 = GetTerrainAreaHeight(pos1.GetX(), pos0.GetY(), terrainExists); + const float heightX0Y1 = GetTerrainAreaHeight(pos0.GetX(), pos1.GetY(), terrainExists); + const float heightX1Y1 = GetTerrainAreaHeight(pos1.GetX(), pos1.GetY(), terrainExists); + const float heightXY0 = AZ::Lerp(heightX0Y0, heightX1Y0, normalizedDelta.GetX()); + const float heightXY1 = AZ::Lerp(heightX0Y1, heightX1Y1, normalizedDelta.GetX()); + height = AZ::Lerp(heightXY0, heightXY1, normalizedDelta.GetY()); } + break; + + //! Clamp the input point to the terrain sample grid, then get the height at the given grid location. + case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP: + { + AZ::Vector2 normalizedDelta; + AZ::Vector2 clampedPosition; + ClampPosition(x, y, clampedPosition, normalizedDelta); + + height = GetTerrainAreaHeight(clampedPosition.GetX(), clampedPosition.GetY(), terrainExists); + } + break; + + //! Directly get the value at the location, regardless of terrain sample grid density. + case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT: + [[fallthrough]]; + default: + height = GetTerrainAreaHeight(x, y, terrainExists); + break; } if (terrainExistsPtr) @@ -181,7 +223,30 @@ float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, boo } return AZ::GetClamp( - outPosition.GetZ(), m_currentSettings.m_worldBounds.GetMin().GetZ(), m_currentSettings.m_worldBounds.GetMax().GetZ()); + height, m_currentSettings.m_worldBounds.GetMin().GetZ(), m_currentSettings.m_worldBounds.GetMax().GetZ()); +} + +float TerrainSystem::GetTerrainAreaHeight(float x, float y, bool& terrainExists) const +{ + AZ::Vector3 inPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ()); + float height = m_currentSettings.m_worldBounds.GetMin().GetZ(); + + AZStd::shared_lock lock(m_areaMutex); + + for (auto& [areaId, areaBounds] : m_registeredAreas) + { + inPosition.SetZ(areaBounds.GetMin().GetZ()); + if (areaBounds.Contains(inPosition)) + { + AZ::Vector3 outPosition; + Terrain::TerrainAreaHeightRequestBus::Event( + areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeight, inPosition, outPosition, terrainExists); + height = outPosition.GetZ(); + break; + } + } + + return height; } float TerrainSystem::GetHeight(AZ::Vector3 position, Sampler sampler, bool* terrainExistsPtr) const @@ -203,24 +268,24 @@ bool TerrainSystem::GetIsHoleFromFloats(float x, float y, Sampler sampler) const AZ::Vector3 TerrainSystem::GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const { - bool terrainExists = false; - - AZ::Vector3 inPosition((float)x, (float)y, m_currentSettings.m_worldBounds.GetMin().GetZ()); - AZ::Vector3 outNormal = AZ::Vector3::CreateAxisZ(); - AZStd::shared_lock lock(m_areaMutex); - for (auto& [areaId, areaBounds] : m_registeredAreas) - { - inPosition.SetZ(areaBounds.GetMin().GetZ()); - if (areaBounds.Contains(inPosition)) - { - Terrain::TerrainAreaHeightRequestBus::Event( - areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetNormal, inPosition, outNormal, sampler); - terrainExists = true; - break; - } - } + bool terrainExists = false; + + AZ::Vector3 outNormal = AZ::Vector3::CreateAxisZ(); + + const AZ::Vector2 range = (m_currentSettings.m_heightQueryResolution / 2.0f); + const AZ::Vector2 left (x - range.GetX(), y); + const AZ::Vector2 right(x + range.GetX(), y); + const AZ::Vector2 up (x, y - range.GetY()); + const AZ::Vector2 down (x, y + range.GetY()); + + AZ::Vector3 v1(up.GetX(), up.GetY(), GetHeightSynchronous(up.GetX(), up.GetY(), sampler, &terrainExists)); + AZ::Vector3 v2(left.GetX(), left.GetY(), GetHeightSynchronous(left.GetX(), left.GetY(), sampler, &terrainExists)); + AZ::Vector3 v3(right.GetX(), right.GetY(), GetHeightSynchronous(right.GetX(), right.GetY(), sampler, &terrainExists)); + AZ::Vector3 v4(down.GetX(), down.GetY(), GetHeightSynchronous(down.GetX(), down.GetY(), sampler, &terrainExists)); + + outNormal = (v3 - v2).Cross(v4 - v1).GetNormalized(); if (terrainExistsPtr) { @@ -469,43 +534,30 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { for (uint32_t x = 0; x < width; x++) { - // Find the first terrain layer that covers this position. This will be the highest priority, so others can be ignored. - for (auto& [areaId, areaBounds] : m_registeredAreas) - { - AZ::Vector3 inPosition( - (x * m_currentSettings.m_heightQueryResolution.GetX()) + m_currentSettings.m_worldBounds.GetMin().GetX(), - (y * m_currentSettings.m_heightQueryResolution.GetY()) + m_currentSettings.m_worldBounds.GetMin().GetY(), - areaBounds.GetMin().GetZ()); + bool terrainExists; + float terrainHeight = GetTerrainAreaHeight( + (x * m_currentSettings.m_heightQueryResolution.GetX()) + m_currentSettings.m_worldBounds.GetMin().GetX(), + (y * m_currentSettings.m_heightQueryResolution.GetY()) + m_currentSettings.m_worldBounds.GetMin().GetY(), + terrainExists); - if (!areaBounds.Contains(inPosition)) - { - continue; - } - - AZ::Vector3 outPosition; - const AzFramework::Terrain::TerrainDataRequestBus::Events::Sampler sampleFilter = - AzFramework::Terrain::TerrainDataRequestBus::Events::Sampler::DEFAULT; - - Terrain::TerrainAreaHeightRequestBus::Event( - areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeight, inPosition, outPosition, sampleFilter); - - pixels[(y * width) + x] = (outPosition.GetZ() - m_currentSettings.m_worldBounds.GetMin().GetZ()) / - m_currentSettings.m_worldBounds.GetExtents().GetZ(); - - break; - } + pixels[(y * width) + x] = + (terrainHeight - m_currentSettings.m_worldBounds.GetMin().GetZ()) / + m_currentSettings.m_worldBounds.GetExtents().GetZ(); } } - - const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); - auto terrainFeatureProcessor = scene->GetFeatureProcessor(); - - AZ_Assert(terrainFeatureProcessor, "Unable to find a TerrainFeatureProcessor."); - if (terrainFeatureProcessor) + if (auto rpi = AZ::RPI::RPISystemInterface::Get(); rpi) { - terrainFeatureProcessor->UpdateTerrainData( - transform, m_currentSettings.m_worldBounds, m_currentSettings.m_heightQueryResolution.GetX(), width, height, pixels); + if (auto defaultScene = rpi->GetDefaultScene(); defaultScene) + { + const AZ::RPI::Scene* scene = defaultScene.get(); + if (auto terrainFeatureProcessor = scene->GetFeatureProcessor(); terrainFeatureProcessor) + { + terrainFeatureProcessor->UpdateTerrainData( + transform, m_currentSettings.m_worldBounds, m_currentSettings.m_heightQueryResolution.GetX(), width, height, + pixels); + } + } } } diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index c52165da6b..a9240e02a6 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -94,8 +94,11 @@ namespace Terrain float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const override; private: + void ClampPosition(float x, float y, AZ::Vector2& outPosition, AZ::Vector2& normalizedDelta) const; + float GetHeightSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const; - AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const; + float GetTerrainAreaHeight(float x, float y, bool& terrainExists) const; + AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const; // AZ::TickBus::Handler overrides ... void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h index 1ba63a8f84..cda6d65a1e 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h @@ -65,27 +65,8 @@ namespace Terrain virtual ~TerrainAreaHeightRequests() = default; - enum SurfacePointDataMask - { - POSITION = 0x01, - NORMAL = 0x02, - SURFACE_WEIGHTS = 0x04, - - DEFAULT = POSITION | NORMAL | SURFACE_WEIGHTS - }; - // Synchronous single input location. The Vector3 input position versions are defined to ignore the input Z value. - - virtual void GetHeight( - const AZ::Vector3& inPosition, - AZ::Vector3& outPosition, - AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter = - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT) = 0; - virtual void GetNormal( - const AZ::Vector3& inPosition, - AZ::Vector3& outNormal, - AzFramework::Terrain::TerrainDataRequests::Sampler sampleFilter = - AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT) = 0; + virtual void GetHeight(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists) = 0; }; using TerrainAreaHeightRequestBus = AZ::EBus; diff --git a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp index f0533322c8..ee4b18e2fb 100644 --- a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp +++ b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp @@ -15,7 +15,12 @@ #include #include -#include +#include +#include + +using ::testing::NiceMock; +using ::testing::AtLeast; +using ::testing::_; using ::testing::NiceMock; using ::testing::AtLeast; @@ -29,7 +34,7 @@ protected: AZStd::unique_ptr m_entity; Terrain::TerrainLayerSpawnerComponent* m_layerSpawnerComponent; - UnitTest::MockBoxShapeComponent* m_shapeComponent; + UnitTest::MockAxisAlignedBoxShapeComponent* m_shapeComponent; AZStd::unique_ptr> m_terrainSystem; void SetUp() override @@ -67,7 +72,7 @@ protected: m_layerSpawnerComponent = m_entity->CreateComponent(config); m_app.RegisterComponentDescriptor(m_layerSpawnerComponent->CreateDescriptor()); - m_shapeComponent = m_entity->CreateComponent(); + m_shapeComponent = m_entity->CreateComponent(); m_app.RegisterComponentDescriptor(m_shapeComponent->CreateDescriptor()); ASSERT_TRUE(m_layerSpawnerComponent); diff --git a/Gems/Terrain/Code/Tests/MockAxisAlignedBoxShapeComponent.h b/Gems/Terrain/Code/Tests/MockAxisAlignedBoxShapeComponent.h new file mode 100644 index 0000000000..aeaaa609c4 --- /dev/null +++ b/Gems/Terrain/Code/Tests/MockAxisAlignedBoxShapeComponent.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +#include +#include +#include +#include + +namespace UnitTest +{ + class MockAxisAlignedBoxShapeComponent + : public AZ::Component + { + public: + AZ_COMPONENT(MockAxisAlignedBoxShapeComponent, "{77CBEED3-FAA3-4BC7-85A9-1A2BFC37BC2A}"); + + static void Reflect([[maybe_unused]] AZ::ReflectContext* context) + { + } + + void Activate() override + { + } + + void Deactivate() override + { + } + + private: + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("ShapeService")); + provided.push_back(AZ_CRC_CE("BoxShapeService")); + provided.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService")); + } + }; +} diff --git a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp index 3e557c66a6..91a9744f0b 100644 --- a/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp +++ b/Gems/Terrain/Code/Tests/TerrainSystemTest.cpp @@ -9,20 +9,23 @@ #include #include -#include - #include -#include + +#include +#include +#include + +#include +#include using ::testing::AtLeast; using ::testing::NiceMock; +using ::testing::Return; class TerrainSystemTest : public ::testing::Test { protected: AZ::ComponentApplication m_app; - - AZStd::unique_ptr m_entity; AZStd::unique_ptr m_terrainSystem; void SetUp() override @@ -41,28 +44,46 @@ protected: m_app.Destroy(); } - void CreateEntity() + AZStd::unique_ptr CreateEntity() { - m_entity = AZStd::make_unique(); - m_entity->Init(); - - ASSERT_TRUE(m_entity); + return AZStd::make_unique(); } - void ResetEntity() + void ActivateEntity(AZ::Entity* entity) { - m_entity->Deactivate(); - m_entity->Reset(); + entity->Init(); + EXPECT_EQ(AZ::Entity::State::Init, entity->GetState()); + + entity->Activate(); + EXPECT_EQ(AZ::Entity::State::Active, entity->GetState()); + } + + template + AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config) + { + m_app.RegisterComponentDescriptor(Component::CreateDescriptor()); + return entity->CreateComponent(config); + } + + template + AZ::Component* CreateComponent(AZ::Entity* entity) + { + m_app.RegisterComponentDescriptor(Component::CreateDescriptor()); + return entity->CreateComponent(); } }; TEST_F(TerrainSystemTest, TrivialCreateDestroy) { + // Trivially verify that the terrain system can successfully be constructed and destructed without errors. + m_terrainSystem = AZStd::make_unique(); } TEST_F(TerrainSystemTest, TrivialActivateDeactivate) { + // Verify that the terrain system can be activated and deactivated without errors. + m_terrainSystem = AZStd::make_unique(); m_terrainSystem->Activate(); m_terrainSystem->Deactivate(); @@ -70,6 +91,8 @@ TEST_F(TerrainSystemTest, TrivialActivateDeactivate) TEST_F(TerrainSystemTest, CreateEventsCalledOnActivation) { + // Verify that when the terrain system is activated, the OnTerrainDataCreate* ebus notifications are generated. + NiceMock mockTerrainListener; EXPECT_CALL(mockTerrainListener, OnTerrainDataCreateBegin()).Times(AtLeast(1)); EXPECT_CALL(mockTerrainListener, OnTerrainDataCreateEnd()).Times(AtLeast(1)); @@ -80,6 +103,8 @@ TEST_F(TerrainSystemTest, CreateEventsCalledOnActivation) TEST_F(TerrainSystemTest, DestroyEventsCalledOnDeactivation) { + // Verify that when the terrain system is deactivated, the OnTerrainDataDestroy* ebus notifications are generated. + NiceMock mockTerrainListener; EXPECT_CALL(mockTerrainListener, OnTerrainDataDestroyBegin()).Times(AtLeast(1)); EXPECT_CALL(mockTerrainListener, OnTerrainDataDestroyEnd()).Times(AtLeast(1)); @@ -89,4 +114,115 @@ TEST_F(TerrainSystemTest, DestroyEventsCalledOnDeactivation) m_terrainSystem->Deactivate(); } +TEST_F(TerrainSystemTest, TerrainDoesNotExistWhenNoTerrainLayerSpawnersAreRegistered) +{ + // For the terrain system, terrain should only exist where terrain layer spawners are present. + + // Verify that in the active terrain system, if there are no terrain layer spawners, any arbitrary point + // will return false for terrainExists, returns a height equal to the min world bounds of the terrain system, and returns + // a normal facing up the Z axis. + + // Create the terrain system and give it one tick to fully initialize itself. + m_terrainSystem = AZStd::make_unique(); + m_terrainSystem->Activate(); + AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.f, AZ::ScriptTimePoint{}); + + AZ::Aabb worldBounds = m_terrainSystem->GetTerrainAabb(); + + // Loop through several points within the world bounds, including on the edges, and verify that they all return false for + // terrainExists with default heights and normals. + for (float y = worldBounds.GetMin().GetY(); y <= worldBounds.GetMax().GetY(); y += (worldBounds.GetExtents().GetY() / 4.0f)) + { + for (float x = worldBounds.GetMin().GetX(); x <= worldBounds.GetMax().GetX(); x += (worldBounds.GetExtents().GetX() / 4.0f)) + { + AZ::Vector3 position(x, y, 0.0f); + bool terrainExists = true; + float height = m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); + EXPECT_FALSE(terrainExists); + EXPECT_EQ(height, worldBounds.GetMin().GetZ()); + + terrainExists = true; + AZ::Vector3 normal = m_terrainSystem->GetNormal( + position, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); + EXPECT_FALSE(terrainExists); + EXPECT_EQ(normal, AZ::Vector3::CreateAxisZ()); + + bool isHole = m_terrainSystem->GetIsHoleFromFloats( + position.GetX(), position.GetY(), AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT); + EXPECT_TRUE(isHole); + } + } +} + +TEST_F(TerrainSystemTest, TerrainExistsOnlyWithinTerrainLayerSpawnerBounds) +{ + // Verify that the presence of a TerrainLayerSpawner causes terrain to exist in (and *only* in) the box where the TerrainLayerSpawner + // is defined. + + // The terrain system should only query Heights from the TerrainAreaHeightRequest bus within the + // TerrainLayerSpawner region, and so those values should only get returned from GetHeight for queries inside that region. + + // Create the base entity with a mock Box Shape and a Terrain Layer Spawner. + auto entity = CreateEntity(); + CreateComponent(entity.get()); + CreateComponent(entity.get()); + + // Set up the box shape to return a box from (0,0,5) to (10, 10, 15) + AZ::Aabb spawnerBox = AZ::Aabb::CreateFromMinMaxValues(0.0f, 0.0f, 5.0f, 10.0f, 10.0f, 15.0f); + NiceMock boxShapeRequests(entity->GetId()); + NiceMock shapeRequests(entity->GetId()); + ON_CALL(shapeRequests, GetEncompassingAabb).WillByDefault(Return(spawnerBox)); + + // Set up a mock height provider that always returns 5.0 and a normal of Y-up. + const float spawnerHeight = 5.0f; + NiceMock terrainAreaHeightRequests(entity->GetId()); + ON_CALL(terrainAreaHeightRequests, GetHeight) + .WillByDefault( + [spawnerHeight](const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists) + { + outPosition = inPosition; + outPosition.SetZ(spawnerHeight); + terrainExists = true; + }); + + ActivateEntity(entity.get()); + + // Verify that terrain exists within the layer spawner bounds, and doesn't exist outside of it. + + // Create the terrain system and give it one tick to fully initialize itself. + m_terrainSystem = AZStd::make_unique(); + m_terrainSystem->Activate(); + AZ::TickBus::Broadcast(&AZ::TickBus::Events::OnTick, 0.f, AZ::ScriptTimePoint{}); + + // Create a box that's twice as big as the layer spawner box. Loop through it and verify that points within the layer box contain + // terrain and the expected height & normal values, and points outside the layer box don't contain terrain. + const AZ::Aabb encompassingBox = + AZ::Aabb::CreateFromMinMax(spawnerBox.GetMin() - (spawnerBox.GetExtents() / 2.0f), + spawnerBox.GetMax() + (spawnerBox.GetExtents() / 2.0f)); + + for (float y = encompassingBox.GetMin().GetY(); y < encompassingBox.GetMax().GetY(); y += 1.0f) + { + for (float x = encompassingBox.GetMin().GetX(); x < encompassingBox.GetMax().GetX(); x += 1.0f) + { + AZ::Vector3 position(x, y, 0.0f); + bool heightQueryTerrainExists = false; + float height = + m_terrainSystem->GetHeight(position, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &heightQueryTerrainExists); + bool isHole = m_terrainSystem->GetIsHoleFromFloats( + position.GetX(), position.GetY(), AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT); + + if (spawnerBox.Contains(AZ::Vector3(position.GetX(), position.GetY(), spawnerBox.GetMin().GetZ()))) + { + EXPECT_TRUE(heightQueryTerrainExists); + EXPECT_FALSE(isHole); + EXPECT_EQ(height, spawnerHeight); + } + else + { + EXPECT_FALSE(heightQueryTerrainExists); + EXPECT_TRUE(isHole); + } + } + } +} diff --git a/Gems/Terrain/Code/terrain_mocks_files.cmake b/Gems/Terrain/Code/terrain_mocks_files.cmake new file mode 100644 index 0000000000..2aedd1c5d8 --- /dev/null +++ b/Gems/Terrain/Code/terrain_mocks_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Mocks/Terrain/MockTerrain.h +) diff --git a/Gems/Terrain/Code/terrain_tests_files.cmake b/Gems/Terrain/Code/terrain_tests_files.cmake index 6d1cf97fd9..3ce1d05003 100644 --- a/Gems/Terrain/Code/terrain_tests_files.cmake +++ b/Gems/Terrain/Code/terrain_tests_files.cmake @@ -7,8 +7,8 @@ # set(FILES - Tests/TerrainMocks.h Tests/TerrainTest.cpp Tests/TerrainSystemTest.cpp Tests/LayerSpawnerTests.cpp + Tests/MockAxisAlignedBoxShapeComponent.h ) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index 429665f04a..816ef6db30 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -9,7 +9,6 @@ # shared by other platforms: ly_associate_package(PACKAGE_NAME ilmbase-2.3.0-rev4-multiplatform TARGETS ilmbase PACKAGE_HASH 97547fdf1fbc4d81b8ccf382261f8c25514ed3b3c4f8fd493f0a4fa873bba348) ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform TARGETS assimplib PACKAGE_HASH 1a9113788b893ef4a2ee63ac01eb71b981a92894a5a51175703fa225f5804dec) -ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform TARGETS ASTCEncoder PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326) ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) @@ -44,5 +43,6 @@ ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev2-linux TARGETS azslc PACKAGE_HASH 1ba84d8321a566d35a1e9aa7400211ba8e6d1c11c08e4be3c93e6e74b8f7aef1) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-linux TARGETS zlib PACKAGE_HASH 16f3b9e11cda525efb62144f354c1cfc30a5def9eff020dbe49cb00ee7d8234f) ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux TARGETS squish-ccr PACKAGE_HASH 85fecafbddc6a41a27c5f59ed4a5dfb123a94cb4666782cf26e63c0a4724c530) +ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-linux TARGETS astc-encoder PACKAGE_HASH 2ba97a06474d609945f0ab4419af1f6bbffdd294ca6b869f5fcebec75c573c0f) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-linux TARGETS ISPCTexComp PACKAGE_HASH 065fd12abe4247dde247330313763cf816c3375c221da030bdec35024947f259) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-linux TARGETS lz4 PACKAGE_HASH 5de3dbd3e2a3537c6555d759b3c5bb98e5456cf85c74ff6d046f809b7087290d) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 4900a9d77e..58cafdd4b8 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -9,7 +9,6 @@ # shared by other platforms: ly_associate_package(PACKAGE_NAME ilmbase-2.3.0-rev4-multiplatform TARGETS ilmbase PACKAGE_HASH 97547fdf1fbc4d81b8ccf382261f8c25514ed3b3c4f8fd493f0a4fa873bba348) ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform TARGETS assimplib PACKAGE_HASH 1a9113788b893ef4a2ee63ac01eb71b981a92894a5a51175703fa225f5804dec) -ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform TARGETS ASTCEncoder PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326) ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) @@ -42,6 +41,7 @@ ly_associate_package(PACKAGE_NAME qt-5.15.2-rev5-mac ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-mac TARGETS libsamplerate PACKAGE_HASH b912af40c0ac197af9c43d85004395ba92a6a859a24b7eacd920fed5854a97fe) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-mac TARGETS zlib PACKAGE_HASH 21714e8a6de4f2523ee92a7f52d51fbee29c5f37ced334e00dc3c029115b472e) ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-mac TARGETS squish-ccr PACKAGE_HASH 155bfbfa17c19a9cd2ef025de14c5db598f4290045d5b0d83ab58cb345089a77) +ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-mac TARGETS astc-encoder PACKAGE_HASH 96f6ea8c3e45ec7fe525230c7c53ca665c8300d8e28456cc19bb3159ce6f8dcc) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index 3855b7d712..d02fefc0e9 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -9,7 +9,6 @@ # shared by other platforms: ly_associate_package(PACKAGE_NAME ilmbase-2.3.0-rev4-multiplatform TARGETS ilmbase PACKAGE_HASH 97547fdf1fbc4d81b8ccf382261f8c25514ed3b3c4f8fd493f0a4fa873bba348) ly_associate_package(PACKAGE_NAME assimp-5.0.1-rev11-multiplatform TARGETS assimplib PACKAGE_HASH 1a9113788b893ef4a2ee63ac01eb71b981a92894a5a51175703fa225f5804dec) -ly_associate_package(PACKAGE_NAME ASTCEncoder-2017_11_14-rev2-multiplatform TARGETS ASTCEncoder PACKAGE_HASH c240ffc12083ee39a5ce9dc241de44d116e513e1e3e4cc1d05305e7aa3bdc326) ly_associate_package(PACKAGE_NAME md5-2.0-multiplatform TARGETS md5 PACKAGE_HASH 29e52ad22c78051551f78a40c2709594f0378762ae03b417adca3f4b700affdf) ly_associate_package(PACKAGE_NAME RapidJSON-1.1.0-rev1-multiplatform TARGETS RapidJSON PACKAGE_HASH 2f5e26ecf86c3b7a262753e7da69ac59928e78e9534361f3d00c1ad5879e4023) ly_associate_package(PACKAGE_NAME RapidXML-1.13-rev1-multiplatform TARGETS RapidXML PACKAGE_HASH 4b7b5651e47cfd019b6b295cc17bb147b65e53073eaab4a0c0d20a37ab74a246) @@ -49,5 +48,6 @@ ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-windows ly_associate_package(PACKAGE_NAME Crashpad-0.8.0-rev1-windows TARGETS Crashpad PACKAGE_HASH d162aa3070147bc0130a44caab02c5fe58606910252caf7f90472bd48d4e31e2) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev2-windows TARGETS zlib PACKAGE_HASH 9afab1d67641ed8bef2fb38fc53942da47f2ab339d9e77d3d20704a48af2da0b) ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-windows TARGETS squish-ccr PACKAGE_HASH 5c3d9fa491e488ccaf802304ad23b932268a2b2846e383f088779962af2bfa84) +ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-windows TARGETS astc-encoder PACKAGE_HASH 3addc6fc1a7eb0d6b7f3d530e962af967e6d92b3825ef485da243346357cf78e) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-windows TARGETS ISPCTexComp PACKAGE_HASH b6fa6ea28a2808a9a5524c72c37789c525925e435770f2d94eb2d387360fa2d0) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-windows TARGETS lz4 PACKAGE_HASH 4ea457b833cd8cfaf8e8e06ed6df601d3e6783b606bdbc44a677f77e19e0db16) diff --git a/cmake/AzAutoGen.py b/cmake/AzAutoGen.py index 51c46b53d8..19b9711325 100755 --- a/cmake/AzAutoGen.py +++ b/cmake/AzAutoGen.py @@ -71,10 +71,10 @@ def SearchPaths(filename, paths=[]): return None def ComputeOutputPath(inputFiles, projectDir, outputDir): - commonInputPath = os.path.commonprefix(inputFiles) # If we've globbed many source files, this finds the common prefix + commonInputPath = os.path.commonpath(inputFiles) # If we've globbed many source files, this finds the common path if os.path.isfile(commonInputPath): # If the commonInputPath resolves to an actual file, slice off the filename commonInputPath = os.path.dirname(commonInputPath) - commonPath = os.path.commonprefix([commonInputPath, projectDir]) # Finds the common path between the data source files and our project directory (//depot/dev/Code/Framework/AzCore/) + commonPath = os.path.commonpath([commonInputPath, projectDir]) # Finds the common path between the data source files and our project directory (//depot/dev/Code/Framework/AzCore/) inputRelativePath = os.path.relpath(commonInputPath, commonPath) # Computes the relative path for the project source directory (Code/Framework/AzCore/AutoGen/) return os.path.join(outputDir, inputRelativePath) # Returns a suitable output directory (//depot/dev/Generated/Code/Framework/AzCore/AutoGen/) diff --git a/cmake/Platform/Linux/PAL_linux.cmake b/cmake/Platform/Linux/PAL_linux.cmake index c088163dca..e74adb287e 100644 --- a/cmake/Platform/Linux/PAL_linux.cmake +++ b/cmake/Platform/Linux/PAL_linux.cmake @@ -35,7 +35,7 @@ else() endif() # Set the default asset type for deployment -set(LY_ASSET_DEPLOY_ASSET_TYPE "pc" CACHE STRING "Set the asset type for deployment.") +set(LY_ASSET_DEPLOY_ASSET_TYPE "linux" CACHE STRING "Set the asset type for deployment.") # Set the python cmd tool ly_set(LY_PYTHON_CMD ${CMAKE_CURRENT_SOURCE_DIR}/python/python.sh) diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index 7f2ac6a4fd..d3f4b33b03 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -147,14 +147,24 @@ foreach(project ${LY_PROJECTS}) cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory) set(install_engine_pak_template [=[ if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") - set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}") - message(STATUS "Generating ${install_output_folder}/Engine.pak from @full_directory_path@/Cache") + set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}/@LY_BUILD_PERMUTATION@") + if(NOT DEFINED LY_ASSET_DEPLOY_ASSET_TYPE) + set(LY_ASSET_DEPLOY_ASSET_TYPE @LY_ASSET_DEPLOY_ASSET_TYPE@) + endif() + message(STATUS "Generating ${install_output_folder}/Engine.pak from @full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") file(MAKE_DIRECTORY "${install_output_folder}") - file(ARCHIVE_CREATE OUTPUT "${install_output_folder}/Engine.pak" - PATHS "@full_directory_path@/Cache" - FORMAT zip - ) - message(STATUS "${install_output_folder}/Engine.pak generated") + cmake_path(SET cache_product_path "@full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") + file(GLOB product_assets "${cache_product_path}/*") + if(product_assets) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_output_folder}/Engine.pak" --format=zip -- ${product_assets} + WORKING_DIRECTORY "${cache_product_path}" + RESULT_VARIABLE archive_creation_result + ) + if(archive_creation_result EQUAL 0) + message(STATUS "${install_output_folder}/Engine.pak generated") + endif() + endif() endif() ]=]) string(CONFIGURE "${install_engine_pak_template}" install_engine_pak_code @ONLY)