Merge remote-tracking branch 'upstream/development' into Atom/santorac/FixMaterialInitializationBug-ATOM-16476

This commit is contained in:
santorac
2021-09-17 19:07:02 -07:00
50 changed files with 1801 additions and 514 deletions
@@ -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;
}
@@ -122,12 +122,14 @@ namespace TestImpact
};
//! Base class for all sequence report types.
template<typename PolicyStateType>
template<SequenceReportType Type, typename PolicyStateType>
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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const PolicyStateType& policyState,
AZStd::optional<AZStd::chrono::milliseconds> testTargetTimeout,
AZStd::optional<AZStd::chrono::milliseconds> 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<AZStd::chrono::milliseconds> m_testTargetTimeout;
AZStd::optional<AZStd::chrono::milliseconds> m_globalTimeout;
@@ -296,58 +313,27 @@ namespace TestImpact
//! Report type for regular test sequences.
class RegularSequenceReport
: public SequenceReportBase<SequencePolicyState>
: public SequenceReportBase<SequenceReportType::RegularSequence, SequencePolicyState>
{
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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const SequencePolicyState& policyState,
SuiteType suiteType,
const TestRunSelection& selectedTestRuns,
TestRunReport&& selectedTestRunReport);
using SequenceReportBase::SequenceReportBase;
};
//! Report type for seed test sequences.
class SeedSequenceReport
: public SequenceReportBase<SequencePolicyState>
: public SequenceReportBase<SequenceReportType::SeedSequence, SequencePolicyState>
{
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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& 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<typename PolicyStateType>
template<SequenceReportType Type, typename PolicyStateType>
class DraftingSequenceReportBase
: public SequenceReportBase<PolicyStateType>
: public SequenceReportBase<Type, PolicyStateType>
{
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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const PolicyStateType& policyState,
AZStd::optional<AZStd::chrono::milliseconds> testTargetTimeout,
AZStd::optional<AZStd::chrono::milliseconds> globalTimeout,
PolicyStateType policyState,
SuiteType suiteType,
const TestRunSelection& selectedTestRuns,
const AZStd::vector<AZStd::string>& draftedTestRuns,
TestRunSelection selectedTestRuns,
AZStd::vector<AZStd::string> draftedTestRuns,
TestRunReport&& selectedTestRunReport,
TestRunReport&& draftedTestRunReport)
: SequenceReportBase<PolicyStateType> (
type,
: SequenceReportBase<Type, PolicyStateType>(
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<Type, PolicyStateType>&& report,
AZStd::vector<AZStd::string> draftedTestRuns,
TestRunReport&& draftedTestRunReport)
: SequenceReportBase<Type, PolicyStateType>(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<ImpactAnalysisSequencePolicyState>
: public DraftingSequenceReportBase<SequenceReportType::ImpactAnalysisSequence, ImpactAnalysisSequencePolicyState>
{
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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const ImpactAnalysisSequencePolicyState& policyState,
AZStd::optional<AZStd::chrono::milliseconds> testTargetTimeout,
AZStd::optional<AZStd::chrono::milliseconds> globalTimeout,
ImpactAnalysisSequencePolicyState policyState,
SuiteType suiteType,
const TestRunSelection& selectedTestRuns,
const AZStd::vector<AZStd::string>& discardedTestRuns,
const AZStd::vector<AZStd::string>& draftedTestRuns,
TestRunSelection selectedTestRuns,
AZStd::vector<AZStd::string> discardedTestRuns,
AZStd::vector<AZStd::string> draftedTestRuns,
TestRunReport&& selectedTestRunReport,
TestRunReport&& draftedTestRunReport);
ImpactAnalysisSequenceReport(DraftingSequenceReportBase&& report, AZStd::vector<AZStd::string> discardedTestRuns);
//! Returns the test runs discarded from running in the sequence.
const AZStd::vector<AZStd::string>& 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<SafeImpactAnalysisSequencePolicyState>
: public DraftingSequenceReportBase<SequenceReportType::SafeImpactAnalysisSequence, SafeImpactAnalysisSequencePolicyState>
{
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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const SafeImpactAnalysisSequencePolicyState& policyState,
AZStd::optional<AZStd::chrono::milliseconds> testTargetTimeout,
AZStd::optional<AZStd::chrono::milliseconds> globalTimeout,
SafeImpactAnalysisSequencePolicyState policyState,
SuiteType suiteType,
const TestRunSelection& selectedTestRuns,
const TestRunSelection& discardedTestRuns,
const AZStd::vector<AZStd::string>& draftedTestRuns,
TestRunSelection selectedTestRuns,
TestRunSelection discardedTestRuns,
AZStd::vector<AZStd::string> 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;
@@ -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
@@ -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
@@ -159,69 +159,35 @@ namespace TestImpact
return m_totalNumDisabledTests;
}
RegularSequenceReport::RegularSequenceReport(
ImpactAnalysisSequenceReport::ImpactAnalysisSequenceReport(
size_t maxConcurrency,
const AZStd::optional<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const SequencePolicyState& policyState,
AZStd::optional<AZStd::chrono::milliseconds> testTargetTimeout,
AZStd::optional<AZStd::chrono::milliseconds> 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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& 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<AZStd::string> discardedTestRuns,
AZStd::vector<AZStd::string> 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<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const ImpactAnalysisSequencePolicyState& policyState,
SuiteType suiteType,
const TestRunSelection& selectedTestRuns,
const AZStd::vector<AZStd::string>& discardedTestRuns,
const AZStd::vector<AZStd::string>& 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<AZStd::string> discardedTestRuns)
: DraftingSequenceReportBase(AZStd::move(report))
, m_discardedTestRuns(AZStd::move(discardedTestRuns))
{
}
@@ -232,25 +198,24 @@ namespace TestImpact
SafeImpactAnalysisSequenceReport::SafeImpactAnalysisSequenceReport(
size_t maxConcurrency,
const AZStd::optional<AZStd::chrono::milliseconds>& testTargetTimeout,
const AZStd::optional<AZStd::chrono::milliseconds>& globalTimeout,
const SafeImpactAnalysisSequencePolicyState& policyState,
AZStd::optional<AZStd::chrono::milliseconds> testTargetTimeout,
AZStd::optional<AZStd::chrono::milliseconds> globalTimeout,
SafeImpactAnalysisSequencePolicyState policyState,
SuiteType suiteType,
const TestRunSelection& selectedTestRuns,
const TestRunSelection& discardedTestRuns,
const AZStd::vector<AZStd::string>& draftedTestRuns,
TestRunSelection selectedTestRuns,
TestRunSelection discardedTestRuns,
AZStd::vector<AZStd::string> 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() });
@@ -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<typename PolicyStateType>
template<typename SequenceReportBaseType>
void SerializeSequenceReportBaseMembers(
const Client::SequenceReportBase<PolicyStateType>& sequenceReport, rapidjson::PrettyWriter<rapidjson::StringBuffer>& writer)
const SequenceReportBaseType& sequenceReport, rapidjson::PrettyWriter<rapidjson::StringBuffer>& 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<typename PolicyStateType>
template<typename DraftingSequenceReportBaseType>
void SerializeDraftingSequenceReportMembers(
const Client::DraftingSequenceReportBase<PolicyStateType>& sequenceReport, rapidjson::PrettyWriter<rapidjson::StringBuffer>& writer)
const DraftingSequenceReportBaseType& sequenceReport, rapidjson::PrettyWriter<rapidjson::StringBuffer>& 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<Client::Test> DeserializeTests(const rapidjson::Value& serialTests)
{
AZStd::vector<Client::Test> 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<typename TestRunType>
AZStd::vector<TestRunType> DeserializeTestRuns(const rapidjson::Value& serialTestRuns)
{
AZStd::vector<TestRunType> testRuns;
testRuns.reserve(serialTestRuns.GetArray().Size());
for (const auto& testRun : serialTestRuns.GetArray())
{
testRuns.emplace_back(DeserializeTestRunBase(testRun));
}
return testRuns;
}
template<typename CompletedTestRunType>
AZStd::vector<CompletedTestRunType> DeserializeCompletedTestRuns(const rapidjson::Value& serialCompletedTestRuns)
{
AZStd::vector<CompletedTestRunType> 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<Client::PassingTestRun>(
serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::PassingTestRuns]]),
DeserializeCompletedTestRuns<Client::FailingTestRun>(
serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::FailingTestRuns]]),
DeserializeTestRuns<Client::TestRunWithExecutionFailure>(
serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::ExecutionFailureTestRuns]]),
DeserializeTestRuns<Client::TimedOutTestRun>(
serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::TimedOutTestRuns]]),
DeserializeTestRuns<Client::UnexecutedTestRun>(
serialTestRunReport[SequenceReportFields::Keys[SequenceReportFields::UnexecutedTestRuns]]));
}
Client::TestRunSelection DeserializeTestSelection(const rapidjson::Value& serialTestRunSelection)
{
const auto extractTestTargetNames = [](const rapidjson::Value& serialTestTargets)
{
AZStd::vector<AZStd::string> 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<typename PolicyStateType>
PolicyStateType DeserializePolicyStateType(const rapidjson::Value& serialPolicyStateType)
{
if constexpr (AZStd::is_same_v<PolicyStateType, SequencePolicyState>)
{
return DeserializePolicyStateMembers(serialPolicyStateType);
}
else if constexpr (AZStd::is_same_v<PolicyStateType, SafeImpactAnalysisSequencePolicyState>)
{
return DeserializeSafeImpactAnalysisPolicyStateMembers(serialPolicyStateType);
}
else if constexpr (AZStd::is_same_v<PolicyStateType, ImpactAnalysisSequencePolicyState>)
{
return DeserializeImpactAnalysisSequencePolicyStateMembers(serialPolicyStateType);
}
else
{
static_assert(false, "Template paramater must be a valid policy state type");
}
}
template<typename SequenceReportBaseType>
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<AZStd::chrono::milliseconds>{ testTargetTimeout } : AZStd::nullopt,
globalTimeout ? AZStd::optional<AZStd::chrono::milliseconds>{ globalTimeout } : AZStd::nullopt,
DeserializePolicyStateType<SequenceReportBaseType::PolicyState>(serialSequenceReportBase),
SuiteTypeFromString(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::Suite]].GetString()),
DeserializeTestSelection(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::SelectedTestRuns]]),
DeserializeTestRunReport(serialSequenceReportBase[SequenceReportFields::Keys[SequenceReportFields::SelectedTestRunReport]]));
}
template<typename DerivedDraftingSequenceReportType>
Client::DraftingSequenceReportBase<DerivedDraftingSequenceReportType::ReportType, typename DerivedDraftingSequenceReportType::PolicyState>
DeserializeDraftingSequenceReportBase(const rapidjson::Value& serialDraftingSequenceReportBase)
{
AZStd::vector<AZStd::string> 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<DerivedDraftingSequenceReportType::ReportType, typename DerivedDraftingSequenceReportType::PolicyState>;
using DraftingSequenceBase =
Client::DraftingSequenceReportBase<DerivedDraftingSequenceReportType::ReportType, typename DerivedDraftingSequenceReportType::PolicyState>;
return DraftingSequenceBase(
DeserialiseSequenceReportBase<SequenceBase>(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<Client::RegularSequenceReport>(doc);
}
Client::SeedSequenceReport DeserializeSeedSequenceReport(const AZStd::string& sequenceReportJson)
{
const auto doc = OpenSequenceReportJson(sequenceReportJson);
return DeserialiseSequenceReportBase<Client::SeedSequenceReport>(doc);
}
Client::ImpactAnalysisSequenceReport DeserializeImpactAnalysisSequenceReport(const AZStd::string& sequenceReportJson)
{
const auto doc = OpenSequenceReportJson(sequenceReportJson);
AZStd::vector<AZStd::string> 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<Client::ImpactAnalysisSequenceReport>(doc), AZStd::move(discardedTestRuns));
}
Client::SafeImpactAnalysisSequenceReport DeserializeSafeImpactAnalysisSequenceReport(const AZStd::string& sequenceReportJson)
{
const auto doc = OpenSequenceReportJson(sequenceReportJson);
return Client::SafeImpactAnalysisSequenceReport(
DeserializeDraftingSequenceReportBase<Client::SafeImpactAnalysisSequenceReport>(doc),
DeserializeTestSelection(doc[SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRuns]]),
DeserializeTestRunReport(doc[SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRunReport]]));
}
} // namespace TestImpact
@@ -243,4 +243,256 @@ namespace TestImpact
throw(Exception(AZStd::string::format("Unexpected client test case result: %u", aznumeric_cast<AZ::u32>(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
@@ -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(
@@ -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 <astcenc.h>
#include <AzCore/Jobs/JobCompletion.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/PlatformIncl.h>
#include <Atom/ImageProcessing/ImageObject.h>
#include <Compressors/ASTCCompressor.h>
#include <Processing/ImageFlags.h>
#include <Processing/ImageToProcess.h>
#include <Processing/PixelFormatInfo.h>
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<void**>(&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<void**>(&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
@@ -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 <Compressors/Compressor.h>
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
@@ -8,6 +8,7 @@
#include <AzCore/PlatformIncl.h>
#include <Compressors/ASTCCompressor.h>
#include <Compressors/CTSquisher.h>
#include <Compressors/PVRTC.h>
#include <Compressors/ETC2.h>
@@ -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
@@ -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:
@@ -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;
}
}
@@ -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;
}
@@ -117,6 +117,11 @@ namespace ImageProcessingAtom
}
}
const ImageConvertProcessDescriptor* ImageConvertProcess::GetInputDesc() const
{
return m_input.get();
}
ImageConvertProcess::ImageConvertProcess(AZStd::unique_ptr<ImageConvertProcessDescriptor>&& 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;
@@ -122,6 +122,8 @@ namespace ImageProcessingAtom
// Get output JobProducts and append them to the outProducts vector.
void GetAppendOutputProducts(AZStd::vector<AssetBuilderSDK::JobProduct>& outProducts);
const ImageConvertProcessDescriptor* GetInputDesc() const;
private:
//input image and settings
AZStd::shared_ptr<ImageConvertProcessDescriptor> m_input;
@@ -12,8 +12,12 @@
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <AzCore/AzCore_Traits_Platform.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerComponent.h>
#include <AzCore/Jobs/JobContext.h>
#include <AzCore/Jobs/JobManager.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/RTTI/ReflectionManager.h>
@@ -123,6 +127,9 @@ namespace UnitTest
AZStd::string m_outputRootFolder;
AZStd::string m_outputFolder;
AZStd::unique_ptr<AZ::JobManager> m_jobManager;
AZStd::unique_ptr<AZ::JobContext> 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<JobManager>(jobManagerDesc);
m_jobContext = AZStd::make_unique<JobContext>(*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<AssetBuilderSDK::JobProduct> 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)
{
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:120aaf43057b07fb3c784264eb48b899cc612f30917d316fe843bb839220dc22
size 203062
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:646a9a9035cc3f4dfd57babc0055710d2f5bb8aee0a792f2b65d69b4fd6a94b3
size 786450
@@ -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
@@ -1421,17 +1421,16 @@ namespace EMotionFX
// extract sorted active items
void Recorder::ExtractNodeHistoryItems(const ActorInstanceData& actorInstanceData, float timeValue, bool sort, EValueType valueType, AZStd::vector<ExtractedNodeHistoryItem>* outItems, AZStd::vector<size_t>* 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;
}
}
}
@@ -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); }
@@ -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();
+11
View File
@@ -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
@@ -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 <gmock/gmock.h>
#include <AzCore/Component/ComponentApplication.h>
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
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));
};
}
@@ -8,4 +8,7 @@
#include <AzTest/AzTest.h>
// Include any public mocks here to ensure they get compiled as a part of the test project.
#include <LmbrCentral/Shape/MockShapes.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -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
)
@@ -82,4 +82,18 @@ namespace PhysX
};
using EditorColliderComponentRequestBus = AZ::EBus<EditorColliderComponentRequests>;
/// <EditorColliderValidationRequests>
/// This is a Bus in order to communicate the status of the meshes of the collider and avoid dependencies with the rigidbody
/// </EditorColliderValidationRequests>
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<EditorColliderValidationRequests>;
}
@@ -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<Pipeline::MeshAsset>(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<PhysX::EditorRigidBodyComponent>();
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<AZStd::shared_ptr<Physics::Shape>> 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<PhysX::Shape>(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<PhysX::Shape>(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<AZ::Data::AssetData> asset)
@@ -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;
@@ -286,6 +286,9 @@ namespace PhysX
}
CreateEditorWorldRigidBody();
PhysX::EditorColliderValidationRequestBus::Event(
GetEntityId(), &PhysX::EditorColliderValidationRequestBus::Events::ValidateRigidBodyMeshGeometryType);
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId());
}
+13
View File
@@ -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
)
@@ -11,59 +11,9 @@
#include <AzCore/Component/ComponentApplication.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
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());
};
}
@@ -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();
@@ -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 };
@@ -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));
@@ -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<AZStd::shared_mutex> 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<AZStd::shared_mutex> 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<AZStd::shared_mutex> 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<TerrainFeatureProcessor>();
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)
{
terrainFeatureProcessor->UpdateTerrainData(
transform, m_currentSettings.m_worldBounds, m_currentSettings.m_heightQueryResolution.GetX(), width, height,
pixels);
}
}
}
}
@@ -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;
@@ -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<TerrainAreaHeightRequests>;
@@ -15,7 +15,12 @@
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
#include <AzTest/AzTest.h>
#include <TerrainMocks.h>
#include <Terrain/MockTerrain.h>
#include <MockAxisAlignedBoxShapeComponent.h>
using ::testing::NiceMock;
using ::testing::AtLeast;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::AtLeast;
@@ -29,7 +34,7 @@ protected:
AZStd::unique_ptr<AZ::Entity> m_entity;
Terrain::TerrainLayerSpawnerComponent* m_layerSpawnerComponent;
UnitTest::MockBoxShapeComponent* m_shapeComponent;
UnitTest::MockAxisAlignedBoxShapeComponent* m_shapeComponent;
AZStd::unique_ptr<NiceMock<UnitTest::MockTerrainSystemService>> m_terrainSystem;
void SetUp() override
@@ -67,7 +72,7 @@ protected:
m_layerSpawnerComponent = m_entity->CreateComponent<Terrain::TerrainLayerSpawnerComponent>(config);
m_app.RegisterComponentDescriptor(m_layerSpawnerComponent->CreateDescriptor());
m_shapeComponent = m_entity->CreateComponent<UnitTest::MockBoxShapeComponent>();
m_shapeComponent = m_entity->CreateComponent<UnitTest::MockAxisAlignedBoxShapeComponent>();
m_app.RegisterComponentDescriptor(m_shapeComponent->CreateDescriptor());
ASSERT_TRUE(m_layerSpawnerComponent);
@@ -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 <gmock/gmock.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <LmbrCentral/Shape/MockShapes.h>
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"));
}
};
}
+149 -13
View File
@@ -9,20 +9,23 @@
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/MemoryComponent.h>
#include <TerrainSystem/TerrainSystem.h>
#include <AzTest/AzTest.h>
#include <TerrainMocks.h>
#include <TerrainSystem/TerrainSystem.h>
#include <Components/TerrainLayerSpawnerComponent.h>
#include <Components/TerrainHeightGradientListComponent.h>
#include <Terrain/MockTerrain.h>
#include <MockAxisAlignedBoxShapeComponent.h>
using ::testing::AtLeast;
using ::testing::NiceMock;
using ::testing::Return;
class TerrainSystemTest : public ::testing::Test
{
protected:
AZ::ComponentApplication m_app;
AZStd::unique_ptr<AZ::Entity> m_entity;
AZStd::unique_ptr<Terrain::TerrainSystem> m_terrainSystem;
void SetUp() override
@@ -41,28 +44,46 @@ protected:
m_app.Destroy();
}
void CreateEntity()
AZStd::unique_ptr<AZ::Entity> CreateEntity()
{
m_entity = AZStd::make_unique<AZ::Entity>();
m_entity->Init();
ASSERT_TRUE(m_entity);
return AZStd::make_unique<AZ::Entity>();
}
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<typename Component, typename Configuration>
AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config)
{
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>(config);
}
template<typename Component>
AZ::Component* CreateComponent(AZ::Entity* entity)
{
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>();
}
};
TEST_F(TerrainSystemTest, TrivialCreateDestroy)
{
// Trivially verify that the terrain system can successfully be constructed and destructed without errors.
m_terrainSystem = AZStd::make_unique<Terrain::TerrainSystem>();
}
TEST_F(TerrainSystemTest, TrivialActivateDeactivate)
{
// Verify that the terrain system can be activated and deactivated without errors.
m_terrainSystem = AZStd::make_unique<Terrain::TerrainSystem>();
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<UnitTest::MockTerrainDataNotificationListener> 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<UnitTest::MockTerrainDataNotificationListener> 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<Terrain::TerrainSystem>();
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<UnitTest::MockAxisAlignedBoxShapeComponent>(entity.get());
CreateComponent<Terrain::TerrainLayerSpawnerComponent>(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<UnitTest::MockBoxShapeComponentRequests> boxShapeRequests(entity->GetId());
NiceMock<UnitTest::MockShapeComponentRequests> 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<UnitTest::MockTerrainAreaHeightRequests> 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<Terrain::TerrainSystem>();
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);
}
}
}
}
@@ -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
)
+1 -1
View File
@@ -7,8 +7,8 @@
#
set(FILES
Tests/TerrainMocks.h
Tests/TerrainTest.cpp
Tests/TerrainSystemTest.cpp
Tests/LayerSpawnerTests.cpp
Tests/MockAxisAlignedBoxShapeComponent.h
)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
@@ -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)
+2 -2
View File
@@ -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/)
+1 -1
View File
@@ -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)
+17 -7
View File
@@ -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)