Merge branch 'development' into Atom/santorac/OrderOnceDependencyForPassBuilder
This commit is contained in:
@@ -63,11 +63,21 @@ namespace AZ
|
||||
{
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
bool MaterialBuilder::ReportMaterialAssetWarningsAsErrors() const
|
||||
{
|
||||
bool warningsAsErrors = false;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(warningsAsErrors, "/O3DE/Atom/RPI/MaterialBuilder/WarningsAsErrors");
|
||||
}
|
||||
return warningsAsErrors;
|
||||
}
|
||||
|
||||
//! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path.
|
||||
//! This will usually include multiple source dependencies and a single job dependency, but will include only source dependencies if the file is not found.
|
||||
//! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back
|
||||
//! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a materialtype file, the job dependency type
|
||||
//! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a .materialtype file, the job dependency type
|
||||
//! will be set to JobDependencyType::OrderOnce.
|
||||
void AddPossibleDependencies(AZStd::string_view currentFilePath,
|
||||
AZStd::string_view referencedParentPath,
|
||||
@@ -277,8 +287,8 @@ namespace AZ
|
||||
|
||||
return materialTypeAssetOutcome.GetValue();
|
||||
}
|
||||
|
||||
AZ::Data::Asset<MaterialAsset> CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json)
|
||||
|
||||
AZ::Data::Asset<MaterialAsset> MaterialBuilder::CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const
|
||||
{
|
||||
auto material = LoadSourceData<MaterialSourceData>(json, materialSourceFilePath);
|
||||
|
||||
@@ -292,7 +302,7 @@ namespace AZ
|
||||
return {};
|
||||
}
|
||||
|
||||
auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, true);
|
||||
auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, ReportMaterialAssetWarningsAsErrors());
|
||||
if (!materialAssetOutcome.IsSuccess())
|
||||
{
|
||||
return {};
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <AssetBuilderSDK/AssetBuilderBusses.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -37,6 +39,9 @@ namespace AZ
|
||||
|
||||
private:
|
||||
|
||||
AZ::Data::Asset<MaterialAsset> CreateMaterialAsset(AZStd::string_view materialSourceFilePath, const rapidjson::Value& json) const;
|
||||
bool ReportMaterialAssetWarningsAsErrors() const;
|
||||
|
||||
bool m_isShuttingDown = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -948,19 +948,17 @@ namespace AZ
|
||||
{
|
||||
AZStd::vector<uint16_t>& skinJointIndices = productMesh.m_skinJointIndices;
|
||||
AZStd::vector<float>& skinWeights = productMesh.m_skinWeights;
|
||||
const auto& sourceMeshData = sourceMesh.m_meshData;
|
||||
|
||||
size_t numInfluencesAdded = 0;
|
||||
for (const auto& skinData : sourceMesh.m_skinData)
|
||||
{
|
||||
const AZ::u32 controlPointIndex = sourceMeshData->GetControlPointIndex(static_cast<int>(vertexIndex));
|
||||
const size_t numSkinInfluences = skinData->GetLinkCount(controlPointIndex);
|
||||
const size_t numSkinInfluences = skinData->GetLinkCount(vertexIndex);
|
||||
|
||||
size_t numInfluencesExcess = 0;
|
||||
|
||||
for (size_t influenceIndex = 0; influenceIndex < numSkinInfluences; ++influenceIndex)
|
||||
{
|
||||
const AZ::SceneAPI::DataTypes::ISkinWeightData::Link& link = skinData->GetLink(controlPointIndex, influenceIndex);
|
||||
const AZ::SceneAPI::DataTypes::ISkinWeightData::Link& link = skinData->GetLink(vertexIndex, influenceIndex);
|
||||
|
||||
const float weight = link.weight;
|
||||
const AZStd::string& boneName = skinData->GetBoneName(link.boneId);
|
||||
@@ -2088,7 +2086,7 @@ namespace AZ
|
||||
AZ::Vector3 vpos; //note: it seems to be fastest to reuse a local Vector3 rather than constructing new ones each loop iteration
|
||||
for (uint32_t i = 0; i < elementCount; ++i)
|
||||
{
|
||||
vpos.Set(const_cast<float*>(reinterpret_cast<const float*>(&buffer[i])));
|
||||
vpos.Set(reinterpret_cast<const float*>(&buffer[i]));
|
||||
aabb.AddPoint(vpos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace AZ
|
||||
namespace RPI
|
||||
{
|
||||
/**
|
||||
* This is the central component that drive the process of exporting a scene to Model
|
||||
* This is the central component that drive the process of exporting a scene to Model
|
||||
* and Material assets. It delegates asset-build duties to other components like
|
||||
* ModelAssetBuilderComponent and MaterialAssetBuilderComponent via export events.
|
||||
*/
|
||||
@@ -55,7 +55,7 @@ namespace AZ
|
||||
|
||||
AZStd::string_view m_relativeFileName;
|
||||
AZStd::string_view m_extension;
|
||||
const Uuid m_sourceUuid;
|
||||
const Uuid m_sourceUuid = Uuid::CreateNull();
|
||||
const DataStream::StreamType m_dataStreamType = DataStream::ST_BINARY;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
||||
#include <AzCore/IO/IOUtils.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -46,6 +47,12 @@ namespace AZ
|
||||
|
||||
AZStd::string ResolvePathReference(const AZStd::string& originatingSourceFilePath, const AZStd::string& referencedSourceFilePath)
|
||||
{
|
||||
// The IsAbsolute part prevents "second join parameter is an absolute path" warnings in StringFunc::Path::Join below
|
||||
if (referencedSourceFilePath.empty() || AZ::IO::PathView{referencedSourceFilePath}.IsAbsolute())
|
||||
{
|
||||
return referencedSourceFilePath;
|
||||
}
|
||||
|
||||
AZStd::string normalizedReferencedPath = referencedSourceFilePath;
|
||||
AzFramework::StringFunc::Path::Normalize(normalizedReferencedPath);
|
||||
|
||||
@@ -113,7 +120,7 @@ namespace AZ
|
||||
return results;
|
||||
}
|
||||
|
||||
Outcome<Data::AssetId> MakeAssetId(const AZStd::string& sourcePath, uint32_t productSubId)
|
||||
Outcome<Data::AssetId> MakeAssetId(const AZStd::string& sourcePath, uint32_t productSubId, TraceLevel reporting)
|
||||
{
|
||||
bool assetFound = false;
|
||||
AZ::Data::AssetInfo sourceInfo;
|
||||
@@ -122,7 +129,7 @@ namespace AZ
|
||||
|
||||
if (!assetFound)
|
||||
{
|
||||
AZ_Error("AssetUtils", false, "Could not find asset [%s]", sourcePath.c_str());
|
||||
AssetUtilsInternal::ReportIssue(reporting, AZStd::string::format("Could not find asset [%s]", sourcePath.c_str()).c_str());
|
||||
return AZ::Failure();
|
||||
}
|
||||
else
|
||||
@@ -131,10 +138,10 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
Outcome<Data::AssetId> MakeAssetId(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId)
|
||||
Outcome<Data::AssetId> MakeAssetId(const AZStd::string& originatingSourcePath, const AZStd::string& referencedSourceFilePath, uint32_t productSubId, TraceLevel reporting)
|
||||
{
|
||||
AZStd::string resolvedPath = ResolvePathReference(originatingSourcePath, referencedSourceFilePath);
|
||||
return MakeAssetId(resolvedPath, productSubId);
|
||||
return MakeAssetId(resolvedPath, productSubId, reporting);
|
||||
}
|
||||
} // namespace AssetUtils
|
||||
} // namespace RPI
|
||||
|
||||
@@ -14,14 +14,14 @@ namespace AZ
|
||||
{
|
||||
namespace ColorUtils
|
||||
{
|
||||
enum ColorSpace : uint32_t
|
||||
{
|
||||
LinearSRGB,
|
||||
SRGB
|
||||
};
|
||||
|
||||
AzToolsFramework::ColorEditorConfiguration GetLinearRgbEditorConfig()
|
||||
{
|
||||
enum ColorSpace : uint32_t
|
||||
{
|
||||
LinearSRGB,
|
||||
SRGB
|
||||
};
|
||||
|
||||
AzToolsFramework::ColorEditorConfiguration configuration;
|
||||
configuration.m_colorPickerDialogConfiguration = AzQtComponents::ColorPicker::Configuration::RGB;
|
||||
|
||||
@@ -59,6 +59,15 @@ namespace AZ
|
||||
return configuration;
|
||||
}
|
||||
|
||||
AzToolsFramework::ColorEditorConfiguration GetRgbEditorConfig()
|
||||
{
|
||||
AzToolsFramework::ColorEditorConfiguration configuration = GetLinearRgbEditorConfig();
|
||||
|
||||
configuration.m_propertyColorSpaceId = ColorSpace::SRGB;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
} // namespace ColorPropertyEditorConfigurations
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -137,7 +137,10 @@ namespace AZ
|
||||
}
|
||||
else if (!m_luaSourceFile.empty())
|
||||
{
|
||||
auto loadOutcome = RPI::AssetUtils::LoadAsset<ScriptAsset>(materialTypeSourceFilePath, m_luaSourceFile);
|
||||
// The sub ID for script assets must be explicit.
|
||||
// LUA source files output a compiled as well as an uncompiled asset, sub Ids of 1 and 2.
|
||||
auto loadOutcome =
|
||||
RPI::AssetUtils::LoadAsset<ScriptAsset>(materialTypeSourceFilePath, m_luaSourceFile, ScriptAsset::CompiledAssetSubId);
|
||||
if (!loadOutcome)
|
||||
{
|
||||
AZ_Error("LuaMaterialFunctorSourceData", false, "Could not load script file '%s'", m_luaSourceFile.c_str());
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <Atom/RPI.Edit/Common/AssetUtils.h>
|
||||
#include <Atom/RPI.Edit/Common/JsonFileLoadContext.h>
|
||||
#include <Atom/RPI.Edit/Common/JsonReportingHelper.h>
|
||||
#include <Atom/RPI.Edit/Common/JsonUtils.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Material/MaterialAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
|
||||
@@ -115,10 +116,11 @@ namespace AZ
|
||||
{
|
||||
m_properties = AZStd::move(newPropertyGroups);
|
||||
|
||||
AZ_Warning("MaterialSourceData", false,
|
||||
AZ_Warning(
|
||||
"MaterialSourceData", false,
|
||||
"This material is based on version '%u' of '%s', but the material type is now at version '%u'. "
|
||||
"Automatic updates are available. Consider updating the .material source file.",
|
||||
m_materialTypeVersion, m_materialType.c_str(), materialTypeSourceData.m_version);
|
||||
"Automatic updates are available. Consider updating the .material source file: '%s'.",
|
||||
m_materialTypeVersion, materialTypeFullPath.c_str(), materialTypeSourceData.m_version, materialSourceFilePath.data());
|
||||
}
|
||||
|
||||
m_materialTypeVersion = materialTypeSourceData.m_version;
|
||||
@@ -126,7 +128,8 @@ namespace AZ
|
||||
return changesWereApplied ? ApplyVersionUpdatesResult::UpdatesApplied : ApplyVersionUpdatesResult::NoUpdates;
|
||||
}
|
||||
|
||||
Outcome<Data::Asset<MaterialAsset> > MaterialSourceData::CreateMaterialAsset(Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const
|
||||
Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAsset(
|
||||
Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const
|
||||
{
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.SetElevateWarnings(elevateWarnings);
|
||||
@@ -172,66 +175,7 @@ namespace AZ
|
||||
materialAssetCreator.Begin(assetId, *parentMaterialAsset.GetValue().Get(), includeMaterialPropertyNames);
|
||||
}
|
||||
|
||||
for (auto& group : m_properties)
|
||||
{
|
||||
for (auto& property : group.second)
|
||||
{
|
||||
MaterialPropertyId propertyId{ group.first, property.first };
|
||||
if (!property.second.m_value.IsValid())
|
||||
{
|
||||
AZ_Warning("Material source data", false, "Source data for material property value is invalid.");
|
||||
}
|
||||
else
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex = materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId.GetFullName());
|
||||
if (propertyIndex.IsValid())
|
||||
{
|
||||
const MaterialPropertyDescriptor* propertyDescriptor = materialAssetCreator.m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex);
|
||||
switch (propertyDescriptor->GetDataType())
|
||||
{
|
||||
case MaterialPropertyDataType::Image:
|
||||
{
|
||||
Outcome<Data::Asset<ImageAsset>> imageAssetResult = MaterialUtils::GetImageAssetReference(materialSourceFilePath, property.second.m_value.GetValue<AZStd::string>());
|
||||
|
||||
if (imageAssetResult.IsSuccess())
|
||||
{
|
||||
auto& imageAsset = imageAssetResult.GetValue();
|
||||
// Load referenced images when load material
|
||||
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.second.m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MaterialPropertyDataType::Enum:
|
||||
{
|
||||
AZ::Name enumName = AZ::Name(property.second.m_value.GetValue<AZStd::string>());
|
||||
uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName);
|
||||
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
|
||||
{
|
||||
materialAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.second.m_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.ReportWarning("Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetFullName().GetStringView().data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath);
|
||||
|
||||
Data::Asset<MaterialAsset> material;
|
||||
if (materialAssetCreator.End(material))
|
||||
@@ -244,5 +188,181 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
Outcome<Data::Asset<MaterialAsset>> MaterialSourceData::CreateMaterialAssetFromSourceData(
|
||||
Data::AssetId assetId,
|
||||
AZStd::string_view materialSourceFilePath,
|
||||
bool elevateWarnings,
|
||||
bool includeMaterialPropertyNames,
|
||||
AZStd::unordered_set<AZStd::string>* sourceDependencies) const
|
||||
{
|
||||
const auto materialTypeSourcePath = AssetUtils::ResolvePathReference(materialSourceFilePath, m_materialType);
|
||||
const auto materialTypeAssetId = AssetUtils::MakeAssetId(materialTypeSourcePath, 0);
|
||||
if (!materialTypeAssetId.IsSuccess())
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "Failed to create material type asset ID: '%s'.", materialTypeSourcePath.c_str());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
MaterialTypeSourceData materialTypeSourceData;
|
||||
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(materialTypeSourcePath, materialTypeSourceData))
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "Failed to load MaterialTypeSourceData: '%s'.", materialTypeSourcePath.c_str());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
materialTypeSourceData.ResolveUvEnums();
|
||||
|
||||
const auto materialTypeAsset =
|
||||
materialTypeSourceData.CreateMaterialTypeAsset(materialTypeAssetId.GetValue(), materialTypeSourcePath, elevateWarnings);
|
||||
if (!materialTypeAsset.IsSuccess())
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "Failed to create material type asset from source data: '%s'.", materialTypeSourcePath.c_str());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
// Track all of the material and material type assets loaded while trying to create a material asset from source data. This will
|
||||
// be used for evaluating circular dependencies and returned for external monitoring or other use.
|
||||
AZStd::unordered_set<AZStd::string> dependencies;
|
||||
dependencies.insert(materialSourceFilePath);
|
||||
dependencies.insert(materialTypeSourcePath);
|
||||
|
||||
// Load and build a stack of MaterialSourceData from all of the parent materials in the hierarchy. Properties from the source
|
||||
// data will be applied in reverse to the asset creator.
|
||||
AZStd::vector<MaterialSourceData> parentSourceDataStack;
|
||||
|
||||
AZStd::string parentSourceRelPath = m_parentMaterial;
|
||||
AZStd::string parentSourceAbsPath = AssetUtils::ResolvePathReference(materialSourceFilePath, parentSourceRelPath);
|
||||
while (!parentSourceRelPath.empty())
|
||||
{
|
||||
if (!dependencies.insert(parentSourceAbsPath).second)
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "Detected circular dependency between materials: '%s' and '%s'.", materialSourceFilePath.data(), parentSourceAbsPath.c_str());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
MaterialSourceData parentSourceData;
|
||||
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(parentSourceAbsPath, parentSourceData))
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "Failed to load MaterialSourceData for parent material: '%s'.", parentSourceAbsPath.c_str());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
// Make sure that all materials in the hierarchy share the same material type
|
||||
const auto parentTypeAssetId = AssetUtils::MakeAssetId(parentSourceAbsPath, parentSourceData.m_materialType, 0);
|
||||
if (!parentTypeAssetId)
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "Parent material asset ID wasn't found: '%s'.", parentSourceAbsPath.c_str());
|
||||
return Failure();
|
||||
}
|
||||
|
||||
if (parentTypeAssetId.GetValue() != materialTypeAssetId.GetValue())
|
||||
{
|
||||
AZ_Error("MaterialSourceData", false, "This material and its parent material do not share the same material type.");
|
||||
return Failure();
|
||||
}
|
||||
|
||||
// Get the location of the next parent material and push the source data onto the stack
|
||||
parentSourceRelPath = parentSourceData.m_parentMaterial;
|
||||
parentSourceAbsPath = AssetUtils::ResolvePathReference(parentSourceAbsPath, parentSourceRelPath);
|
||||
parentSourceDataStack.emplace_back(AZStd::move(parentSourceData));
|
||||
}
|
||||
|
||||
// Create the material asset from all the previously loaded source data
|
||||
MaterialAssetCreator materialAssetCreator;
|
||||
materialAssetCreator.SetElevateWarnings(elevateWarnings);
|
||||
materialAssetCreator.Begin(assetId, *materialTypeAsset.GetValue().Get(), includeMaterialPropertyNames);
|
||||
|
||||
while (!parentSourceDataStack.empty())
|
||||
{
|
||||
parentSourceDataStack.back().ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath);
|
||||
parentSourceDataStack.pop_back();
|
||||
}
|
||||
|
||||
ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath);
|
||||
|
||||
Data::Asset<MaterialAsset> material;
|
||||
if (materialAssetCreator.End(material))
|
||||
{
|
||||
if (sourceDependencies)
|
||||
{
|
||||
sourceDependencies->insert(dependencies.begin(), dependencies.end());
|
||||
}
|
||||
|
||||
return Success(material);
|
||||
}
|
||||
|
||||
return Failure();
|
||||
}
|
||||
|
||||
void MaterialSourceData::ApplyPropertiesToAssetCreator(
|
||||
AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const
|
||||
{
|
||||
for (auto& group : m_properties)
|
||||
{
|
||||
for (auto& property : group.second)
|
||||
{
|
||||
MaterialPropertyId propertyId{ group.first, property.first };
|
||||
if (!property.second.m_value.IsValid())
|
||||
{
|
||||
materialAssetCreator.ReportWarning("Source data for material property value is invalid.");
|
||||
}
|
||||
else
|
||||
{
|
||||
MaterialPropertyIndex propertyIndex =
|
||||
materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId.GetFullName());
|
||||
if (propertyIndex.IsValid())
|
||||
{
|
||||
const MaterialPropertyDescriptor* propertyDescriptor =
|
||||
materialAssetCreator.m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex);
|
||||
switch (propertyDescriptor->GetDataType())
|
||||
{
|
||||
case MaterialPropertyDataType::Image:
|
||||
{
|
||||
Data::Asset<ImageAsset> imageAsset;
|
||||
|
||||
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
|
||||
imageAsset, materialSourceFilePath, property.second.m_value.GetValue<AZStd::string>());
|
||||
|
||||
if (result == MaterialUtils::GetImageAssetResult::Missing)
|
||||
{
|
||||
materialAssetCreator.ReportWarning(
|
||||
"Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(),
|
||||
property.second.m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
|
||||
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
|
||||
}
|
||||
break;
|
||||
case MaterialPropertyDataType::Enum:
|
||||
{
|
||||
AZ::Name enumName = AZ::Name(property.second.m_value.GetValue<AZStd::string>());
|
||||
uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName);
|
||||
if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue)
|
||||
{
|
||||
materialAssetCreator.ReportError(
|
||||
"Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.second.m_value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
materialAssetCreator.ReportWarning(
|
||||
"Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetFullName().GetStringView().data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -300,48 +300,6 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const
|
||||
{
|
||||
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is<uint32_t>())
|
||||
{
|
||||
const uint32_t index = propertyValue.GetValue<uint32_t>();
|
||||
if (index >= propertyDefinition.m_enumValues.size())
|
||||
{
|
||||
AZ_Error("Material source data", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
propertyValue = propertyDefinition.m_enumValues[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Image asset references must be converted from asset IDs to a relative source file path
|
||||
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image && propertyValue.Is<Data::Asset<ImageAsset>>())
|
||||
{
|
||||
const Data::Asset<ImageAsset>& imageAsset = propertyValue.GetValue<Data::Asset<ImageAsset>>();
|
||||
|
||||
Data::AssetInfo imageAssetInfo;
|
||||
if (imageAsset.GetId().IsValid())
|
||||
{
|
||||
bool result = false;
|
||||
AZStd::string rootFilePath;
|
||||
const AZStd::string platformName = ""; // Empty for default
|
||||
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetInfoById,
|
||||
imageAsset.GetId(), imageAsset.GetType(), platformName, imageAssetInfo, rootFilePath);
|
||||
if (!result)
|
||||
{
|
||||
AZ_Error("Material source data", false, "Image asset could not be found for property: '%s'.", propertyDefinition.m_name.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
propertyValue = imageAssetInfo.m_relativePath;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Outcome<Data::Asset<MaterialTypeAsset>> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const
|
||||
{
|
||||
MaterialTypeAssetCreator materialTypeAssetCreator;
|
||||
@@ -393,11 +351,12 @@ namespace AZ
|
||||
for (const ShaderVariantReferenceData& shaderRef : m_shaderCollection)
|
||||
{
|
||||
const auto& shaderFile = shaderRef.m_shaderFilePath;
|
||||
const auto& shaderAsset = AssetUtils::LoadAsset<ShaderAsset>(materialTypeSourceFilePath, shaderFile, 0);
|
||||
auto shaderAssetResult = AssetUtils::LoadAsset<ShaderAsset>(materialTypeSourceFilePath, shaderFile, 0);
|
||||
|
||||
if (shaderAsset)
|
||||
if (shaderAssetResult)
|
||||
{
|
||||
auto optionsLayout = shaderAsset.GetValue()->GetShaderOptionGroupLayout();
|
||||
auto shaderAsset = shaderAssetResult.GetValue();
|
||||
auto optionsLayout = shaderAsset->GetShaderOptionGroupLayout();
|
||||
ShaderOptionGroup options{ optionsLayout };
|
||||
for (auto& iter : shaderRef.m_shaderOptionValues)
|
||||
{
|
||||
@@ -408,12 +367,11 @@ namespace AZ
|
||||
}
|
||||
|
||||
materialTypeAssetCreator.AddShader(
|
||||
shaderAsset.GetValue(), options.GetShaderVariantId(),
|
||||
shaderRef.m_shaderTag.IsEmpty() ? Uuid::CreateRandom().ToString<AZ::Name>() : shaderRef.m_shaderTag
|
||||
);
|
||||
shaderAsset, options.GetShaderVariantId(),
|
||||
shaderRef.m_shaderTag.IsEmpty() ? Uuid::CreateRandom().ToString<AZ::Name>() : shaderRef.m_shaderTag);
|
||||
|
||||
// Gather UV names
|
||||
const ShaderInputContract& shaderInputContract = shaderAsset.GetValue()->GetInputContract();
|
||||
const ShaderInputContract& shaderInputContract = shaderAsset->GetInputContract();
|
||||
for (const ShaderInputContract::StreamChannelInfo& channel : shaderInputContract.m_streamChannels)
|
||||
{
|
||||
const RHI::ShaderSemantic& semantic = channel.m_semantic;
|
||||
@@ -493,15 +451,20 @@ namespace AZ
|
||||
{
|
||||
case MaterialPropertyDataType::Image:
|
||||
{
|
||||
Outcome<Data::Asset<ImageAsset>> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property.m_value.GetValue<AZStd::string>());
|
||||
Data::Asset<ImageAsset> imageAsset;
|
||||
|
||||
if (imageAssetResult.IsSuccess())
|
||||
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
|
||||
imageAsset, materialTypeSourceFilePath, property.m_value.GetValue<AZStd::string>());
|
||||
|
||||
if (result == MaterialUtils::GetImageAssetResult::Missing)
|
||||
{
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAssetResult.GetValue());
|
||||
materialTypeAssetCreator.ReportError(
|
||||
"Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(),
|
||||
property.m_value.GetValue<AZStd::string>().data());
|
||||
}
|
||||
else
|
||||
{
|
||||
materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.m_value.GetValue<AZStd::string>().data());
|
||||
materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -28,25 +28,36 @@ namespace AZ
|
||||
{
|
||||
namespace MaterialUtils
|
||||
{
|
||||
Outcome<Data::Asset<ImageAsset>> GetImageAssetReference(AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath)
|
||||
GetImageAssetResult GetImageAssetReference(Data::Asset<ImageAsset>& imageAsset, AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath)
|
||||
{
|
||||
imageAsset = {};
|
||||
|
||||
if (imageFilePath.empty())
|
||||
{
|
||||
// The image value was present but specified an empty string, meaning the texture asset should be explicitly cleared.
|
||||
return AZ::Success(Data::Asset<ImageAsset>());
|
||||
return GetImageAssetResult::Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
Outcome<Data::AssetId> imageAssetId = AssetUtils::MakeAssetId(materialSourceFilePath, imageFilePath, StreamingImageAsset::GetImageAssetSubId());
|
||||
// We use TraceLevel::None because fallback textures are available and we'll return GetImageAssetResult::Missing below in that case.
|
||||
// Callers of GetImageAssetReference will be responsible for logging warnings or errors as needed.
|
||||
|
||||
Outcome<Data::AssetId> imageAssetId = AssetUtils::MakeAssetId(materialSourceFilePath, imageFilePath, StreamingImageAsset::GetImageAssetSubId(), AssetUtils::TraceLevel::None);
|
||||
|
||||
if (!imageAssetId.IsSuccess())
|
||||
{
|
||||
return AZ::Failure();
|
||||
}
|
||||
else
|
||||
{
|
||||
Data::Asset<ImageAsset> unloadedImageAssetReference(imageAssetId.GetValue(), azrtti_typeid<StreamingImageAsset>(), imageFilePath);
|
||||
return AZ::Success(unloadedImageAssetReference);
|
||||
// When the AssetId cannot be found, we don't want to outright fail, because the runtime has mechanisms for displaying fallback textures which gives the
|
||||
// user a better recovery workflow. On the other hand we can't just provide an empty/invalid Asset<ImageAsset> because that would be interpreted as simply
|
||||
// no value was present and result in using no texture, and this would amount to a silent failure.
|
||||
// So we use a randomly generated (well except for the "BADA55E7" bit ;) UUID which the runtime and tools will interpret as a missing asset and represent
|
||||
// it as such.
|
||||
static const Uuid InvalidAssetPlaceholderId = "{BADA55E7-1A1D-4940-B655-9D08679BD62F}";
|
||||
imageAsset = Data::Asset<ImageAsset>{InvalidAssetPlaceholderId, azrtti_typeid<StreamingImageAsset>(), imageFilePath};
|
||||
return GetImageAssetResult::Missing;
|
||||
}
|
||||
|
||||
imageAsset = Data::Asset<ImageAsset>{imageAssetId.GetValue(), azrtti_typeid<StreamingImageAsset>(), imageFilePath};
|
||||
return GetImageAssetResult::Found;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace AZ
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Methods for all shader variant types
|
||||
|
||||
void ShaderVariantAssetCreator::SetBuildTimestamp(AZStd::sys_time_t buildTimestamp)
|
||||
void ShaderVariantAssetCreator::SetBuildTimestamp(AZ::u64 buildTimestamp)
|
||||
{
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#include <Atom/RPI.Public/Buffer/BufferSystemInterface.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
|
||||
AZ_DECLARE_BUDGET(RPI);
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -90,7 +91,7 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode Buffer::Init(BufferAsset& bufferAsset)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
RHI::ResultCode resultCode = RHI::ResultCode::Fail;
|
||||
|
||||
@@ -140,7 +141,7 @@ namespace AZ
|
||||
|
||||
if (bufferAsset.GetBuffer().size() > 0 && !initWithData)
|
||||
{
|
||||
AZ_TRACE_METHOD_NAME("Stream Upload");
|
||||
AZ_PROFILE_SCOPE(RPI, "Stream Upload");
|
||||
m_streamFence = RHI::Factory::Get().CreateFence();
|
||||
if (m_streamFence)
|
||||
{
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
|
||||
#include <Atom/RPI.Reflect/Buffer/BufferAssetView.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/std/parallel/lock.h>
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Debug/Timer.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
#include <AzCore/Jobs/Job.h>
|
||||
#include <AzCore/Task/TaskGraph.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <Atom_RPI_Traits_Platform.h>
|
||||
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
//Enables more detailed profiling descriptions within the culling system, but adds some performance overhead.
|
||||
//Enable this to more easily see which jobs are associated with which view.
|
||||
//#define AZ_CULL_PROFILE_VERBOSE
|
||||
//#define AZ_CULL_PROFILE_VERBOSE
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -43,6 +43,7 @@ namespace AZ
|
||||
AZ_CVAR(bool, r_CullInParallel, true, nullptr, ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(uint32_t, r_CullWorkPerBatch, 500, nullptr, ConsoleFunctorFlags::Null, "");
|
||||
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
void DebugDrawWorldCoordinateAxes(AuxGeomDraw* auxGeom)
|
||||
{
|
||||
auxGeom->DrawCylinder(Vector3(.5, .0, .0), Vector3(1, 0, 0), 0.02f, 1.0f, Colors::Red, AuxGeomDraw::DrawStyle::Solid, AuxGeomDraw::DepthTest::Off);
|
||||
@@ -198,6 +199,7 @@ namespace AZ
|
||||
AZ_Assert(false, "invalid frustum, cannot draw");
|
||||
}
|
||||
}
|
||||
#endif //AZ_CULL_DEBUG_ENABLED
|
||||
|
||||
CullingDebugContext::~CullingDebugContext()
|
||||
{
|
||||
@@ -265,89 +267,73 @@ namespace AZ
|
||||
return m_visScene->GetEntryCount();
|
||||
}
|
||||
|
||||
class AddObjectsToViewJob final
|
||||
: public Job
|
||||
|
||||
struct WorklistData
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(AddObjectsToViewJob, ThreadPoolAllocator, 0);
|
||||
|
||||
struct JobData
|
||||
{
|
||||
CullingDebugContext* m_debugCtx = nullptr;
|
||||
const Scene* m_scene = nullptr;
|
||||
View* m_view = nullptr;
|
||||
Frustum m_frustum;
|
||||
CullingDebugContext* m_debugCtx = nullptr;
|
||||
const Scene* m_scene = nullptr;
|
||||
View* m_view = nullptr;
|
||||
Frustum m_frustum;
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr;
|
||||
MaskedOcclusionCulling* m_maskedOcclusionCulling = nullptr;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
private:
|
||||
const AZStd::shared_ptr<JobData> m_jobData;
|
||||
CullingScene::WorkListType m_worklist;
|
||||
static AZStd::shared_ptr<WorklistData> MakeWorklistData(
|
||||
CullingDebugContext& debugCtx,
|
||||
const Scene& scene,
|
||||
View& view,
|
||||
Frustum& frustum,
|
||||
[[maybe_unused]] void* maskedOcclusionCulling)
|
||||
{
|
||||
AZStd::shared_ptr<WorklistData> worklistData = AZStd::make_shared<WorklistData>();
|
||||
worklistData->m_debugCtx = &debugCtx;
|
||||
worklistData->m_scene = &scene;
|
||||
worklistData->m_view = &view;
|
||||
worklistData->m_frustum = frustum;
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
worklistData->m_maskedOcclusionCulling = static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling);
|
||||
#endif
|
||||
return worklistData;
|
||||
}
|
||||
|
||||
constexpr size_t WorkListCapacity = 5;
|
||||
using WorkListType = AZStd::fixed_vector<AzFramework::IVisibilityScene::NodeData, WorkListCapacity>;
|
||||
|
||||
public:
|
||||
AddObjectsToViewJob(const AZStd::shared_ptr<AddObjectsToViewJob::JobData>& jobData, CullingScene::WorkListType& worklist)
|
||||
: Job(true, nullptr) //auto-deletes, no JobContext
|
||||
, m_jobData(jobData)
|
||||
, m_worklist(worklist)
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
|
||||
const AZStd::shared_ptr<WorklistData>& worklistData,
|
||||
AzFramework::VisibilityEntry* visibleEntry);
|
||||
#endif
|
||||
|
||||
static void ProcessWorklist(const AZStd::shared_ptr<WorklistData>& worklistData, const WorkListType& worklist)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
|
||||
|
||||
const View::UsageFlags viewFlags = worklistData->m_view->GetUsageFlags();
|
||||
const RHI::DrawListMask drawListMask = worklistData->m_view->GetDrawListMask();
|
||||
uint32_t numDrawPackets = 0;
|
||||
uint32_t numVisibleCullables = 0;
|
||||
|
||||
AZ_Assert(worklist.size() > 0, "Received empty worklist in ProcessWorklist");
|
||||
|
||||
for (const AzFramework::IVisibilityScene::NodeData& nodeData : worklist)
|
||||
{
|
||||
}
|
||||
|
||||
//work function
|
||||
void Process() override
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "AddObjectsToViewJob: Process");
|
||||
|
||||
const View::UsageFlags viewFlags = m_jobData->m_view->GetUsageFlags();
|
||||
const RHI::DrawListMask drawListMask = m_jobData->m_view->GetDrawListMask();
|
||||
uint32_t numDrawPackets = 0;
|
||||
uint32_t numVisibleCullables = 0;
|
||||
|
||||
for (const AzFramework::IVisibilityScene::NodeData& nodeData : m_worklist)
|
||||
{
|
||||
//If a node is entirely contained within the frustum, then we can skip the fine grained culling.
|
||||
bool nodeIsContainedInFrustum = ShapeIntersection::Contains(m_jobData->m_frustum, nodeData.m_bounds);
|
||||
//If a node is entirely contained within the frustum, then we can skip the fine grained culling.
|
||||
bool nodeIsContainedInFrustum =
|
||||
!worklistData->m_debugCtx->m_enableFrustumCulling ||
|
||||
ShapeIntersection::Contains(worklistData->m_frustum, nodeData.m_bounds);
|
||||
|
||||
#ifdef AZ_CULL_PROFILE_VERBOSE
|
||||
AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %d",
|
||||
m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? 1 : 0);
|
||||
AZ_PROFILE_SCOPE(RPI, "process node (view: %s, skip fine cull: %ds",
|
||||
worklistData->m_view->GetName().GetCStr(), nodeIsContainedInFrustum ? "true" : "false");
|
||||
#endif
|
||||
|
||||
if (nodeIsContainedInFrustum || !m_jobData->m_debugCtx->m_enableFrustumCulling)
|
||||
if (nodeIsContainedInFrustum)
|
||||
{
|
||||
//Add all objects within this node to the view, without any extra culling
|
||||
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
|
||||
{
|
||||
//Add all objects within this node to the view, without any extra culling
|
||||
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
|
||||
{
|
||||
{
|
||||
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
|
||||
{
|
||||
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
|
||||
|
||||
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
|
||||
c->m_cullData.m_hideFlags & viewFlags ||
|
||||
c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
|
||||
c->m_isHidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
||||
#endif
|
||||
{
|
||||
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view);
|
||||
++numVisibleCullables;
|
||||
c->m_isVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Do fine-grained culling before adding objects to the view
|
||||
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
|
||||
{
|
||||
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
|
||||
{
|
||||
@@ -355,160 +341,200 @@ namespace AZ
|
||||
|
||||
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
|
||||
c->m_cullData.m_hideFlags & viewFlags ||
|
||||
c->m_cullData.m_scene != m_jobData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
|
||||
c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
|
||||
c->m_isHidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IntersectResult res = ShapeIntersection::Classify(m_jobData->m_frustum, c->m_cullData.m_boundingSphere);
|
||||
if (res == IntersectResult::Exterior)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(m_jobData->m_frustum, c->m_cullData.m_boundingObb))
|
||||
{
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
if (TestOcclusionCulling(visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
||||
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
||||
#endif
|
||||
{
|
||||
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *m_jobData->m_view);
|
||||
++numVisibleCullables;
|
||||
c->m_isVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_jobData->m_debugCtx->m_debugDraw && (m_jobData->m_view->GetName() == m_jobData->m_debugCtx->m_currentViewSelectionName))
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
|
||||
|
||||
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(m_jobData->m_scene);
|
||||
if (auxGeomPtr)
|
||||
{
|
||||
//Draw the node bounds
|
||||
// "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum.
|
||||
// Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so
|
||||
// we have a few debug booleans to toggle which ones to draw.
|
||||
if (nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawFullyVisibleNodes)
|
||||
{
|
||||
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
|
||||
}
|
||||
else if (!nodeIsContainedInFrustum && m_jobData->m_debugCtx->m_drawPartiallyVisibleNodes)
|
||||
{
|
||||
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
|
||||
}
|
||||
|
||||
//Draw bounds on individual objects
|
||||
if (m_jobData->m_debugCtx->m_drawBoundingBoxes || m_jobData->m_debugCtx->m_drawBoundingSpheres || m_jobData->m_debugCtx->m_drawLodRadii)
|
||||
{
|
||||
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
|
||||
{
|
||||
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
|
||||
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
||||
++numVisibleCullables;
|
||||
c->m_isVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Do fine-grained culling before adding objects to the view
|
||||
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
|
||||
{
|
||||
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
|
||||
{
|
||||
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
|
||||
|
||||
if ((c->m_cullData.m_drawListMask & drawListMask).none() ||
|
||||
c->m_cullData.m_hideFlags & viewFlags ||
|
||||
c->m_cullData.m_scene != worklistData->m_scene || //[GFX_TODO][ATOM-13796] once the IVisibilitySystem supports multiple octree scenes, remove this
|
||||
c->m_isHidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IntersectResult res = ShapeIntersection::Classify(worklistData->m_frustum, c->m_cullData.m_boundingSphere);
|
||||
if (res == IntersectResult::Exterior)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (res == IntersectResult::Interior || ShapeIntersection::Overlaps(worklistData->m_frustum, c->m_cullData.m_boundingObb))
|
||||
{
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
if (TestOcclusionCulling(worklistData, visibleEntry) == MaskedOcclusionCulling::CullingResult::VISIBLE)
|
||||
#endif
|
||||
{
|
||||
numDrawPackets += AddLodDataToView(c->m_cullData.m_boundingSphere.GetCenter(), c->m_lodData, *worklistData->m_view);
|
||||
++numVisibleCullables;
|
||||
c->m_isVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
if (worklistData->m_debugCtx->m_debugDraw && (worklistData->m_view->GetName() == worklistData->m_debugCtx->m_currentViewSelectionName))
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "debug draw culling");
|
||||
|
||||
AuxGeomDrawPtr auxGeomPtr = AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(worklistData->m_scene);
|
||||
if (auxGeomPtr)
|
||||
{
|
||||
//Draw the node bounds
|
||||
// "Fully visible" nodes are nodes that are fully inside the frustum. "Partially visible" nodes intersect the edges of the frustum.
|
||||
// Since the nodes of an octree have lots of overlapping boxes with coplanar edges, it's easier to view these separately, so
|
||||
// we have a few debug booleans to toggle which ones to draw.
|
||||
if (nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawFullyVisibleNodes)
|
||||
{
|
||||
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Lime, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
|
||||
}
|
||||
else if (!nodeIsContainedInFrustum && worklistData->m_debugCtx->m_drawPartiallyVisibleNodes)
|
||||
{
|
||||
auxGeomPtr->DrawAabb(nodeData.m_bounds, Colors::Yellow, RPI::AuxGeomDraw::DrawStyle::Line, RPI::AuxGeomDraw::DepthTest::Off);
|
||||
}
|
||||
|
||||
//Draw bounds on individual objects
|
||||
if (worklistData->m_debugCtx->m_drawBoundingBoxes || worklistData->m_debugCtx->m_drawBoundingSpheres || worklistData->m_debugCtx->m_drawLodRadii)
|
||||
{
|
||||
for (AzFramework::VisibilityEntry* visibleEntry : nodeData.m_entries)
|
||||
{
|
||||
if (visibleEntry->m_typeFlags & AzFramework::VisibilityEntry::TYPE_RPI_Cullable)
|
||||
{
|
||||
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
|
||||
if (worklistData->m_debugCtx->m_drawBoundingBoxes)
|
||||
{
|
||||
Cullable* c = static_cast<Cullable*>(visibleEntry->m_userData);
|
||||
if (m_jobData->m_debugCtx->m_drawBoundingBoxes)
|
||||
{
|
||||
auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(),
|
||||
nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line);
|
||||
}
|
||||
auxGeomPtr->DrawObb(c->m_cullData.m_boundingObb, Matrix3x4::Identity(),
|
||||
nodeIsContainedInFrustum ? Colors::Lime : Colors::Yellow, AuxGeomDraw::DrawStyle::Line);
|
||||
}
|
||||
|
||||
if (m_jobData->m_debugCtx->m_drawBoundingSpheres)
|
||||
{
|
||||
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(),
|
||||
Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded);
|
||||
}
|
||||
if (worklistData->m_debugCtx->m_drawBoundingSpheres)
|
||||
{
|
||||
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(), c->m_cullData.m_boundingSphere.GetRadius(),
|
||||
Color(0.5f, 0.5f, 0.5f, 0.3f), AuxGeomDraw::DrawStyle::Shaded);
|
||||
}
|
||||
|
||||
if (m_jobData->m_debugCtx->m_drawLodRadii)
|
||||
{
|
||||
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(),
|
||||
c->m_lodData.m_lodSelectionRadius,
|
||||
Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded);
|
||||
}
|
||||
if (worklistData->m_debugCtx->m_drawLodRadii)
|
||||
{
|
||||
auxGeomPtr->DrawSphere(c->m_cullData.m_boundingSphere.GetCenter(),
|
||||
c->m_lodData.m_lodSelectionRadius,
|
||||
Color(1.0f, 0.5f, 0.0f, 0.3f), RPI::AuxGeomDraw::DrawStyle::Shaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_jobData->m_debugCtx->m_enableStats)
|
||||
{
|
||||
CullingDebugContext::CullStats& cullStats = m_jobData->m_debugCtx->GetCullStatsForView(m_jobData->m_view);
|
||||
|
||||
//no need for mutex here since these are all atomics
|
||||
cullStats.m_numVisibleDrawPackets += numDrawPackets;
|
||||
cullStats.m_numVisibleCullables += numVisibleCullables;
|
||||
++cullStats.m_numJobs;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
MaskedOcclusionCulling::CullingResult TestOcclusionCulling(AzFramework::VisibilityEntry* visibleEntry)
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
if (worklistData->m_debugCtx->m_enableStats)
|
||||
{
|
||||
if (!m_jobData->m_maskedOcclusionCulling)
|
||||
{
|
||||
return MaskedOcclusionCulling::CullingResult::VISIBLE;
|
||||
}
|
||||
CullingDebugContext::CullStats& cullStats = worklistData->m_debugCtx->GetCullStatsForView(worklistData->m_view);
|
||||
|
||||
if (visibleEntry->m_boundingVolume.Contains(m_jobData->m_view->GetCameraTransform().GetTranslation()))
|
||||
{
|
||||
// camera is inside bounding volume
|
||||
return MaskedOcclusionCulling::CullingResult::VISIBLE;
|
||||
}
|
||||
//no need for mutex here since these are all atomics
|
||||
cullStats.m_numVisibleDrawPackets += numDrawPackets;
|
||||
cullStats.m_numVisibleCullables += numVisibleCullables;
|
||||
++cullStats.m_numJobs;
|
||||
}
|
||||
#endif //AZ_CULL_DEBUG_ENABLED
|
||||
}
|
||||
|
||||
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
|
||||
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
static MaskedOcclusionCulling::CullingResult TestOcclusionCulling(
|
||||
const AZStd::shared_ptr<WorklistData>& worklistData,
|
||||
AzFramework::VisibilityEntry* visibleEntry)
|
||||
{
|
||||
if (!worklistData->m_maskedOcclusionCulling)
|
||||
{
|
||||
return MaskedOcclusionCulling::CullingResult::VISIBLE;
|
||||
}
|
||||
|
||||
// compute bounding volume corners
|
||||
Vector4 corners[8];
|
||||
corners[0] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
corners[1] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[2] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[3] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
corners[4] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
corners[5] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[6] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[7] = m_jobData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
#ifdef AZ_CULL_PROFILE_VERBOSE
|
||||
AZ_PROFILE_SCOPE(RPI, "TestOcclusionCulling");
|
||||
#endif
|
||||
|
||||
// find min clip-space depth and NDC min/max
|
||||
float minDepth = FLT_MAX;
|
||||
float ndcMinX = FLT_MAX;
|
||||
float ndcMinY = FLT_MAX;
|
||||
float ndcMaxX = -FLT_MAX;
|
||||
float ndcMaxY = -FLT_MAX;
|
||||
for (uint32_t index = 0; index < 8; ++index)
|
||||
{
|
||||
minDepth = AZStd::min(minDepth, corners[index].GetW());
|
||||
if (visibleEntry->m_boundingVolume.Contains(worklistData->m_view->GetCameraTransform().GetTranslation()))
|
||||
{
|
||||
// camera is inside bounding volume
|
||||
return MaskedOcclusionCulling::CullingResult::VISIBLE;
|
||||
}
|
||||
|
||||
// convert to NDC
|
||||
corners[index] /= corners[index].GetW();
|
||||
const Vector3& minBound = visibleEntry->m_boundingVolume.GetMin();
|
||||
const Vector3& maxBound = visibleEntry->m_boundingVolume.GetMax();
|
||||
|
||||
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
|
||||
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
|
||||
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
|
||||
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
|
||||
}
|
||||
// compute bounding volume corners
|
||||
Vector4 corners[8];
|
||||
corners[0] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
corners[1] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[2] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[3] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), minBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
corners[4] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
corners[5] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(minBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[6] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), maxBound.GetZ(), 1.0f);
|
||||
corners[7] = worklistData->m_view->GetWorldToClipMatrix() * Vector4(maxBound.GetX(), maxBound.GetY(), minBound.GetZ(), 1.0f);
|
||||
|
||||
// find min clip-space depth and NDC min/max
|
||||
float minDepth = FLT_MAX;
|
||||
float ndcMinX = FLT_MAX;
|
||||
float ndcMinY = FLT_MAX;
|
||||
float ndcMaxX = -FLT_MAX;
|
||||
float ndcMaxY = -FLT_MAX;
|
||||
for (uint32_t index = 0; index < 8; ++index)
|
||||
{
|
||||
minDepth = AZStd::min(minDepth, corners[index].GetW());
|
||||
if (minDepth < 0.00000001f)
|
||||
{
|
||||
return MaskedOcclusionCulling::VISIBLE;
|
||||
return MaskedOcclusionCulling::CullingResult::VISIBLE;
|
||||
}
|
||||
|
||||
// test against the occlusion buffer, which contains only the manually placed occlusion planes
|
||||
return m_jobData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
|
||||
|
||||
// convert to NDC
|
||||
corners[index] /= corners[index].GetW();
|
||||
|
||||
ndcMinX = AZStd::min(ndcMinX, corners[index].GetX());
|
||||
ndcMinY = AZStd::min(ndcMinY, corners[index].GetY());
|
||||
ndcMaxX = AZStd::max(ndcMaxX, corners[index].GetX());
|
||||
ndcMaxY = AZStd::max(ndcMaxY, corners[index].GetY());
|
||||
}
|
||||
|
||||
// test against the occlusion buffer, which contains only the manually placed occlusion planes
|
||||
return worklistData->m_maskedOcclusionCulling->TestRect(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, minDepth);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
void CullingScene::ProcessCullables(const Scene& scene, View& view, AZ::Job& parentJob)
|
||||
void CullingScene::ProcessCullablesCommon(
|
||||
const Scene& scene [[maybe_unused]],
|
||||
View& view,
|
||||
AZ::Frustum& frustum [[maybe_unused]],
|
||||
void*& maskedOcclusionCulling [[maybe_unused]])
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullables() - %s", view.GetName().GetCStr());
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesCommon() - %s", view.GetName().GetCStr());
|
||||
|
||||
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
|
||||
Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
if (m_debugCtx.m_freezeFrustums)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_debugCtx.m_frozenFrustumsMutex);
|
||||
@@ -533,10 +559,10 @@ namespace AZ
|
||||
CullingDebugContext::CullStats& cullStats = m_debugCtx.GetCullStatsForView(&view);
|
||||
cullStats.m_cameraViewToWorld = view.GetViewToWorldMatrix();
|
||||
}
|
||||
|
||||
#endif //AZ_CULL_DEBUG_ENABLED
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
// setup occlusion culling, if necessary
|
||||
MaskedOcclusionCulling* maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
|
||||
maskedOcclusionCulling = m_occlusionPlanes.empty() ? nullptr : view.GetMaskedOcclusionCulling();
|
||||
if (maskedOcclusionCulling)
|
||||
{
|
||||
// frustum cull occlusion planes
|
||||
@@ -578,23 +604,27 @@ namespace AZ
|
||||
static uint32_t indices[6] = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
// render into the occlusion buffer, specifying BACKFACE_NONE so it functions as a double-sided occluder
|
||||
maskedOcclusionCulling->RenderTriangles((float*)verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE);
|
||||
static_cast<MaskedOcclusionCulling*>(maskedOcclusionCulling)->RenderTriangles(verts, indices, 2, nullptr, MaskedOcclusionCulling::BACKFACE_NONE);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CullingScene::ProcessCullablesJobs(const Scene& scene, View& view, AZ::Job& parentJob)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesJobs() - %s", view.GetName().GetCStr());
|
||||
|
||||
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
|
||||
AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
|
||||
|
||||
void* maskedOcclusionCulling = nullptr;
|
||||
ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling);
|
||||
|
||||
WorkListType worklist;
|
||||
|
||||
AZStd::shared_ptr<AddObjectsToViewJob::JobData> jobData = AZStd::make_shared<AddObjectsToViewJob::JobData>();
|
||||
jobData->m_debugCtx = &m_debugCtx;
|
||||
jobData->m_scene = &scene;
|
||||
jobData->m_view = &view;
|
||||
jobData->m_frustum = frustum;
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
jobData->m_maskedOcclusionCulling = maskedOcclusionCulling;
|
||||
#endif
|
||||
AZStd::shared_ptr<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
|
||||
|
||||
auto nodeVisitorLambda = [jobData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
|
||||
auto nodeVisitorLambda = [worklistData, &parentJob, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()");
|
||||
AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries");
|
||||
@@ -606,8 +636,13 @@ namespace AZ
|
||||
|
||||
if (worklist.size() == worklist.capacity())
|
||||
{
|
||||
// capture worklistData & worklist by value
|
||||
auto processWorklist = [worklistData, worklist]()
|
||||
{
|
||||
ProcessWorklist(worklistData, worklist);
|
||||
};
|
||||
//Kick off a job to process the (full) worklist
|
||||
AddObjectsToViewJob* job = aznew AddObjectsToViewJob(jobData, worklist); //pool allocated (cheap), auto-deletes when job finishes
|
||||
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
|
||||
worklist.clear();
|
||||
parentJob.SetContinuation(job);
|
||||
job->Start();
|
||||
@@ -616,7 +651,7 @@ namespace AZ
|
||||
|
||||
if (m_debugCtx.m_enableFrustumCulling)
|
||||
{
|
||||
m_visScene->Enumerate(frustum, nodeVisitorLambda);
|
||||
m_visScene->Enumerate(frustum, nodeVisitorLambda);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -625,21 +660,76 @@ namespace AZ
|
||||
|
||||
if (worklist.size() > 0)
|
||||
{
|
||||
AZStd::shared_ptr<AddObjectsToViewJob::JobData> remainingJobData = AZStd::make_shared<AddObjectsToViewJob::JobData>();
|
||||
remainingJobData->m_debugCtx = &m_debugCtx;
|
||||
remainingJobData->m_scene = &scene;
|
||||
remainingJobData->m_view = &view;
|
||||
remainingJobData->m_frustum = frustum;
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
remainingJobData->m_maskedOcclusionCulling = maskedOcclusionCulling;
|
||||
#endif
|
||||
//Kick off a job to process any remaining workitems
|
||||
AddObjectsToViewJob* job = aznew AddObjectsToViewJob(remainingJobData, worklist); //pool allocated (cheap), auto-deletes when job finishes
|
||||
// capture worklistData & worklist by value
|
||||
auto processWorklist = [worklistData, worklist]()
|
||||
{
|
||||
ProcessWorklist(worklistData, worklist);
|
||||
};
|
||||
//Kick off a job to process the (full) worklist
|
||||
AZ::Job* job = AZ::CreateJobFunction(processWorklist, true);
|
||||
parentJob.SetContinuation(job);
|
||||
job->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void CullingScene::ProcessCullablesTG(const Scene& scene, View& view, AZ::TaskGraph& taskGraph)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene::ProcessCullablesTG() - %s", view.GetName().GetCStr());
|
||||
|
||||
const Matrix4x4& worldToClip = view.GetWorldToClipMatrix();
|
||||
AZ::Frustum frustum = Frustum::CreateFromMatrixColumnMajor(worldToClip);
|
||||
|
||||
void* maskedOcclusionCulling = nullptr;
|
||||
ProcessCullablesCommon(scene, view, frustum, maskedOcclusionCulling);
|
||||
|
||||
AZStd::unique_ptr<WorkListType> worklist = AZStd::make_unique<WorkListType>();
|
||||
|
||||
AZStd::shared_ptr<WorklistData> worklistData = MakeWorklistData(m_debugCtx, scene, view, frustum, maskedOcclusionCulling);
|
||||
static const AZ::TaskDescriptor descriptor{ "AZ::RPI::ProcessWorklist", "Graphics" };
|
||||
|
||||
auto nodeVisitorLambda = [worklistData, &taskGraph, &worklist](const AzFramework::IVisibilityScene::NodeData& nodeData) -> void
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "nodeVisitorLambda()");
|
||||
AZ_Assert(nodeData.m_entries.size() > 0, "should not get called with 0 entries");
|
||||
AZ_Assert(worklist->size() < worklist->capacity(), "we should always have room to push a node on the queue");
|
||||
|
||||
//Queue up a small list of work items (NodeData*) which will be pushed to a worker task once the queue is full.
|
||||
//This reduces the number of tasks in flight, reducing task-system overhead.
|
||||
worklist->emplace_back(AZStd::move(nodeData));
|
||||
|
||||
if (worklist->size() == worklist->capacity())
|
||||
{
|
||||
//Task takes ownership of the worklist unique ptr
|
||||
taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]()
|
||||
{
|
||||
ProcessWorklist(worklistData, *worklist.get());
|
||||
// allow worklist to go out of scope and be deleted
|
||||
});
|
||||
worklist = AZStd::make_unique<WorkListType>();
|
||||
}
|
||||
};
|
||||
|
||||
if (m_debugCtx.m_enableFrustumCulling)
|
||||
{
|
||||
m_visScene->Enumerate(frustum, nodeVisitorLambda);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_visScene->EnumerateNoCull(nodeVisitorLambda);
|
||||
}
|
||||
|
||||
if (worklist->size() > 0)
|
||||
{
|
||||
//Task takes ownership of the worklist unique ptr
|
||||
taskGraph.AddTask( descriptor, [worklistData, worklist = AZStd::move(worklist)]()
|
||||
{
|
||||
ProcessWorklist(worklistData, *worklist.get());
|
||||
// allow worklist to go out of scope and be deleted
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint32_t AddLodDataToView(const Vector3& pos, const Cullable::LodData& lodData, RPI::View& view)
|
||||
{
|
||||
#ifdef AZ_CULL_PROFILE_DETAILED
|
||||
@@ -702,6 +792,8 @@ namespace AZ
|
||||
AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", m_parentScene->GetName().GetCStr()));
|
||||
m_visScene = AZ::Interface<AzFramework::IVisibilitySystem>::Get()->CreateVisibilityScene(visSceneName);
|
||||
|
||||
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
|
||||
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
AZ_Assert(CountObjectsInScene() == 0, "The culling system should start with 0 entries in this scene.");
|
||||
#endif
|
||||
@@ -719,19 +811,35 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
|
||||
void CullingScene::BeginCullingTaskGraph(const AZStd::vector<ViewPtr>& views)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
|
||||
m_cullDataConcurrencyCheck.soft_lock();
|
||||
AZ::TaskGraph taskGraph;
|
||||
AZ::TaskDescriptor beginCullingDescriptor{"RPI_CullingScene_BeginCullingView", "Graphics"};
|
||||
for (auto& view : views)
|
||||
{
|
||||
taskGraph.AddTask(
|
||||
beginCullingDescriptor,
|
||||
[&view]()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCullingTaskGraph");
|
||||
view->BeginCulling();
|
||||
});
|
||||
}
|
||||
|
||||
m_debugCtx.ResetCullStats();
|
||||
m_debugCtx.m_numCullablesInScene = GetNumCullables();
|
||||
AZ::TaskGraphEvent waitForCompletion;
|
||||
taskGraph.Submit(&waitForCompletion);
|
||||
waitForCompletion.Wait();
|
||||
}
|
||||
|
||||
void CullingScene::BeginCullingJobs(const AZStd::vector<ViewPtr>& views)
|
||||
{
|
||||
AZ::JobCompletion beginCullingCompletion;
|
||||
|
||||
for (auto& view : views)
|
||||
{
|
||||
const auto cullingLambda = [&view]()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCullingJob");
|
||||
view->BeginCulling();
|
||||
};
|
||||
|
||||
@@ -741,7 +849,32 @@ namespace AZ
|
||||
}
|
||||
|
||||
beginCullingCompletion.StartAndWaitForCompletion();
|
||||
}
|
||||
|
||||
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CullingScene: BeginCulling");
|
||||
m_cullDataConcurrencyCheck.soft_lock();
|
||||
|
||||
m_debugCtx.ResetCullStats();
|
||||
m_debugCtx.m_numCullablesInScene = GetNumCullables();
|
||||
|
||||
m_taskGraphActive = AZ::Interface<AZ::TaskGraphActiveInterface>::Get();
|
||||
|
||||
if(views.size() == 1) // avoid job overhead when only 1 job
|
||||
{
|
||||
views[0]->BeginCulling();
|
||||
}
|
||||
else if (m_taskGraphActive && m_taskGraphActive->IsTaskGraphActive())
|
||||
{
|
||||
BeginCullingTaskGraph(views);
|
||||
}
|
||||
else
|
||||
{
|
||||
BeginCullingJobs(views);
|
||||
}
|
||||
|
||||
#ifdef AZ_CULL_DEBUG_ENABLED
|
||||
AuxGeomDrawPtr auxGeom;
|
||||
if (m_debugCtx.m_debugDraw)
|
||||
{
|
||||
@@ -774,6 +907,7 @@ namespace AZ
|
||||
m_debugCtx.m_frozenFrustums.clear();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CullingScene::EndCulling()
|
||||
|
||||
@@ -141,6 +141,7 @@ namespace AZ
|
||||
void DynamicDrawContext::InitVertexFormat(const AZStd::vector<VertexChannel>& vertexChannels)
|
||||
{
|
||||
AZ_Assert(!m_initialized, "Can't call InitVertexFormat after context was initialized (EndInit was called)");
|
||||
AZ_Assert(m_pipelineState, "Can't call InitVertexFormat before InitShader is called with a valid shader");
|
||||
|
||||
m_perVertexDataSize = 0;
|
||||
RHI::InputStreamLayoutBuilder layoutBuilder;
|
||||
@@ -150,7 +151,10 @@ namespace AZ
|
||||
bufferBuilder->Channel(channel.m_channel, channel.m_format);
|
||||
m_perVertexDataSize += RHI::GetFormatSize(channel.m_format);
|
||||
}
|
||||
m_pipelineState->InputStreamLayout() = layoutBuilder.End();
|
||||
if (m_pipelineState)
|
||||
{
|
||||
m_pipelineState->InputStreamLayout() = layoutBuilder.End();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicDrawContext::InitDrawListTag(RHI::DrawListTag drawListTag)
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
#include <Atom/RHI/Factory.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
|
||||
// Enable this define to debug output streaming image initialization and expanding process.
|
||||
//#define AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
||||
|
||||
AZ_DECLARE_BUDGET(RPI);
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
@@ -111,7 +112,7 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode StreamingImage::Init(StreamingImageAsset& imageAsset)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
Data::Instance<StreamingImagePool> pool;
|
||||
if (imageAsset.GetPoolAssetId().IsValid())
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Jobs/Job.h>
|
||||
|
||||
AZ_DECLARE_BUDGET(RPI);
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
@@ -34,7 +35,7 @@ namespace AZ
|
||||
|
||||
void StreamingImageController::AttachImage(StreamingImage* image)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
AZ_Assert(image, "Image must not be null");
|
||||
|
||||
@@ -69,7 +70,7 @@ namespace AZ
|
||||
|
||||
void StreamingImageController::Update()
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
AZStd::lock_guard<AZStd::mutex> lock(m_mutex);
|
||||
UpdateInternal(m_timestamp, m_contexts);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include <Atom/RHI/Factory.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
|
||||
namespace AZ
|
||||
@@ -43,7 +42,7 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode StreamingImagePool::Init(RHI::Device& device, StreamingImagePoolAsset& poolAsset)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
if (Validation::IsEnabled())
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
|
||||
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
#include <AtomCore/Utils/ScopedValue.h>
|
||||
|
||||
@@ -57,7 +56,7 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode Material::Init(MaterialAsset& materialAsset)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
ScopedValue isInitializing(&m_isInitializing, true, false);
|
||||
|
||||
@@ -234,7 +233,7 @@ namespace AZ
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnAssetReloaded %s", this, asset.GetHint().c_str());
|
||||
|
||||
Data::Asset<MaterialAsset> newMaterialAsset = { asset.GetAs<MaterialAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
Data::Asset<MaterialAsset> newMaterialAsset = Data::static_pointer_cast<MaterialAsset>(asset);
|
||||
|
||||
if (newMaterialAsset)
|
||||
{
|
||||
@@ -320,7 +319,7 @@ namespace AZ
|
||||
|
||||
bool Material::Compile()
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
if (NeedsCompile() && CanCompile())
|
||||
{
|
||||
@@ -610,7 +609,7 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
if (Data::Asset<StreamingImageAsset> streamingImageAsset = { imageAsset.GetAs<StreamingImageAsset>(), AZ::Data::AssetLoadBehavior::PreLoad })
|
||||
if (Data::Asset<StreamingImageAsset> streamingImageAsset = Data::static_pointer_cast<StreamingImageAsset>(imageAsset))
|
||||
{
|
||||
Data::Instance<Image> image = StreamingImage::FindOrCreate(streamingImageAsset);
|
||||
if (!image)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <Atom/RHI/Factory.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
#include <AzCore/Debug/Timer.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <Atom/RHI/Factory.h>
|
||||
#include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
|
||||
namespace AZ
|
||||
@@ -52,7 +51,7 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode ModelLod::Init(const Data::Asset<ModelLodAsset>& lodAsset, const Data::Asset<ModelAsset>& modelAsset)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
for (const ModelLodAsset::Mesh& mesh : lodAsset->GetMeshes())
|
||||
{
|
||||
@@ -389,7 +388,7 @@ namespace AZ
|
||||
const ModelLodAsset::Mesh::StreamBufferInfo& streamBufferInfo,
|
||||
Mesh& meshInstance)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
const Data::Asset<BufferAsset>& streamBufferAsset = streamBufferInfo.m_bufferAssetView.GetBufferAsset();
|
||||
const Data::Instance<Buffer>& streamBuffer = Buffer::FindOrCreate(streamBufferAsset);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <Atom/RPI.Public/Pass/FullscreenTrianglePass.h>
|
||||
#include <Atom/RPI.Public/Pass/PassUtils.h>
|
||||
#include <Atom/RPI.Public/RPIUtils.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Pass/FullscreenTrianglePassData.h>
|
||||
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
|
||||
@@ -46,16 +47,19 @@ namespace AZ
|
||||
|
||||
void FullscreenTrianglePass::OnShaderReinitialized(const Shader&)
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderReinitialized", this);
|
||||
LoadShader();
|
||||
}
|
||||
|
||||
void FullscreenTrianglePass::OnShaderAssetReinitialized(const Data::Asset<ShaderAsset>&)
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderAssetReinitialized", this);
|
||||
LoadShader();
|
||||
}
|
||||
|
||||
void FullscreenTrianglePass::OnShaderVariantReinitialized(const ShaderVariant&)
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::OnShaderVariantReinitialized", this);
|
||||
LoadShader();
|
||||
}
|
||||
|
||||
@@ -129,6 +133,8 @@ namespace AZ
|
||||
void FullscreenTrianglePass::InitializeInternal()
|
||||
{
|
||||
RenderPass::InitializeInternal();
|
||||
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->FullscreenTrianglePass::InitializeInternal", this);
|
||||
|
||||
// This draw item purposefully does not reference any geometry buffers.
|
||||
// Instead it's expected that the extended class uses a vertex shader
|
||||
@@ -136,6 +142,12 @@ namespace AZ
|
||||
RHI::DrawLinear draw = RHI::DrawLinear();
|
||||
draw.m_vertexCount = 3;
|
||||
|
||||
if (m_shader == nullptr)
|
||||
{
|
||||
AZ_Error("PassSystem", false, "[FullscreenTrianglePass]: Shader not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor;
|
||||
|
||||
// [GFX TODO][ATOM-872] The pass should be able to drive the shader variant
|
||||
|
||||
@@ -9,11 +9,15 @@
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
#include <AtomCore/std/containers/vector_set.h>
|
||||
|
||||
#include <Atom/RPI.Public/Pass/SlowClearPass.h>
|
||||
#include <Atom/RPI.Public/Pass/ParentPass.h>
|
||||
#include <Atom/RPI.Public/Pass/PassAttachment.h>
|
||||
#include <Atom/RPI.Public/Pass/PassDefines.h>
|
||||
#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
|
||||
#include <Atom/RPI.Public/RenderPipeline.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Pass/SlowClearPassData.h>
|
||||
#include <Atom/RPI.Reflect/Pass/PassName.h>
|
||||
#include <Atom/RPI.Reflect/Pass/PassRequest.h>
|
||||
|
||||
namespace AZ
|
||||
@@ -196,7 +200,7 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
// --- PassTemplate related functions ---
|
||||
// --- Child creation ---
|
||||
|
||||
void ParentPass::CreatePassesFromTemplate()
|
||||
{
|
||||
@@ -217,6 +221,49 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void ParentPass::CreateClearPassFromBinding(PassAttachmentBinding& binding, PassRequest& clearRequest)
|
||||
{
|
||||
if (binding.m_unifiedScopeDesc.m_loadStoreAction.m_loadAction == RHI::AttachmentLoadAction::Clear ||
|
||||
binding.m_unifiedScopeDesc.m_loadStoreAction.m_loadActionStencil == RHI::AttachmentLoadAction::Clear)
|
||||
{
|
||||
// Set the name of the child clear pass as well as the binding it's connected to
|
||||
clearRequest.m_passName = ConcatPassName(Name("Clear"), binding.m_name);
|
||||
clearRequest.m_connections[0].m_attachmentRef.m_attachment = binding.m_name;
|
||||
|
||||
// Set the pass clear value to the clear value of the attachment binding
|
||||
SlowClearPassData* clearData = static_cast<SlowClearPassData*>(clearRequest.m_passData.get());
|
||||
clearData->m_clearValue = binding.m_unifiedScopeDesc.m_loadStoreAction.m_clearValue;
|
||||
|
||||
// Create and add the pass
|
||||
Ptr<Pass> clearPass = PassSystemInterface::Get()->CreatePassFromRequest(&clearRequest);
|
||||
if (clearPass)
|
||||
{
|
||||
AddChild(clearPass);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ParentPass::CreateClearPassesFromBindings()
|
||||
{
|
||||
PassRequest clearRequest;
|
||||
clearRequest.m_templateName = Name("SlowClearPassTemplate");
|
||||
clearRequest.m_passData = AZStd::make_shared<SlowClearPassData>();
|
||||
clearRequest.m_connections.push_back();
|
||||
clearRequest.m_connections[0].m_localSlot = Name("ClearInputOutput");
|
||||
clearRequest.m_connections[0].m_attachmentRef.m_pass = Name("Parent");
|
||||
|
||||
for (uint32_t idx = 0; idx < GetInputCount(); ++idx)
|
||||
{
|
||||
CreateClearPassFromBinding(GetInputBinding(idx), clearRequest);
|
||||
}
|
||||
|
||||
for (uint32_t idx = 0; idx < GetInputOutputCount(); ++idx)
|
||||
{
|
||||
CreateClearPassFromBinding(GetInputOutputBinding(idx), clearRequest);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Pass behavior functions ---
|
||||
|
||||
void ParentPass::CreateChildPasses()
|
||||
@@ -229,6 +276,7 @@ namespace AZ
|
||||
m_flags.m_alreadyCreatedChildren = true;
|
||||
|
||||
RemoveChildren();
|
||||
CreateClearPassesFromBindings();
|
||||
CreatePassesFromTemplate();
|
||||
CreateChildPassesInternal();
|
||||
|
||||
|
||||
@@ -147,6 +147,11 @@ namespace AZ
|
||||
m_treeDepth = m_parent->m_treeDepth + 1;
|
||||
m_path = ConcatPassName(m_parent->m_path, m_name);
|
||||
m_flags.m_partOfHierarchy = m_parent->m_flags.m_partOfHierarchy;
|
||||
|
||||
if (m_state == PassState::Orphaned)
|
||||
{
|
||||
QueueForBuildAndInitialization();
|
||||
}
|
||||
}
|
||||
|
||||
void Pass::RemoveFromParent()
|
||||
@@ -154,7 +159,7 @@ namespace AZ
|
||||
AZ_RPI_PASS_ASSERT(m_parent != nullptr, "Trying to remove pass from parent but pointer to the parent pass is null.");
|
||||
m_parent->RemoveChild(Ptr<Pass>(this));
|
||||
m_queueState = PassQueueState::NoQueue;
|
||||
m_state = PassState::Idle;
|
||||
m_state = PassState::Orphaned;
|
||||
}
|
||||
|
||||
void Pass::OnOrphan()
|
||||
@@ -162,6 +167,8 @@ namespace AZ
|
||||
m_parent = nullptr;
|
||||
m_flags.m_partOfHierarchy = false;
|
||||
m_treeDepth = 0;
|
||||
m_queueState = PassQueueState::NoQueue;
|
||||
m_state = PassState::Orphaned;
|
||||
}
|
||||
|
||||
// --- Getters & Setters ---
|
||||
@@ -1281,6 +1288,7 @@ namespace AZ
|
||||
|
||||
void Pass::FrameBegin(FramePrepareParams params)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "Pass::FrameBegin() - %s", m_path.GetCStr());
|
||||
AZ_RPI_BREAK_ON_TARGET_PASS;
|
||||
|
||||
if (!IsEnabled())
|
||||
@@ -1303,7 +1311,10 @@ namespace AZ
|
||||
|
||||
// FrameBeginInternal needs to be the last function be called in FrameBegin because its implementation expects
|
||||
// all the attachments are imported to database (for example, ImageAttachmentPreview)
|
||||
FrameBeginInternal(params);
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "Pass::FrameBeginInternal()");
|
||||
FrameBeginInternal(params);
|
||||
}
|
||||
|
||||
// readback attachment with output state
|
||||
UpdateReadbackAttachment(params, false);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <Atom/RPI.Public/Pass/PassFilter.h>
|
||||
#include <Atom/RPI.Public/Pass/RasterPass.h>
|
||||
#include <Atom/RPI.Public/Pass/MSAAResolvePass.h>
|
||||
#include <Atom/RPI.Public/Pass/SlowClearPass.h>
|
||||
#include <Atom/RPI.Public/Pass/Specific/MSAAResolveFullScreenPass.h>
|
||||
#include <Atom/RPI.Public/Pass/Specific/EnvironmentCubeMapPass.h>
|
||||
#include <Atom/RPI.Public/Pass/Specific/RenderToTexturePass.h>
|
||||
@@ -60,6 +61,7 @@ namespace AZ
|
||||
{
|
||||
AddPassCreator(Name("ParentPass"), &ParentPass::Create);
|
||||
AddPassCreator(Name("RasterPass"), &RasterPass::Create);
|
||||
AddPassCreator(Name("SlowClearPass"), &SlowClearPass::Create);
|
||||
AddPassCreator(Name("CopyPass"), &CopyPass::Create);
|
||||
AddPassCreator(Name("FullScreenTriangle"), &FullscreenTrianglePass::Create);
|
||||
AddPassCreator(Name("ComputePass"), &ComputePass::Create);
|
||||
|
||||
@@ -90,13 +90,13 @@ namespace AZ
|
||||
return filter;
|
||||
}
|
||||
|
||||
void PassFilter::SetOwenrScene(const Scene* scene)
|
||||
void PassFilter::SetOwnerScene(const Scene* scene)
|
||||
{
|
||||
m_ownerScene = scene;
|
||||
UpdateFilterOptions();
|
||||
}
|
||||
|
||||
void PassFilter::SetOwenrRenderPipeline(const RenderPipeline* renderPipeline)
|
||||
void PassFilter::SetOwnerRenderPipeline(const RenderPipeline* renderPipeline)
|
||||
{
|
||||
m_ownerRenderPipeline = renderPipeline;
|
||||
UpdateFilterOptions();
|
||||
|
||||
@@ -294,7 +294,7 @@ namespace AZ
|
||||
void PassLibrary::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
|
||||
{
|
||||
// Handle pass asset reload
|
||||
Data::Asset<PassAsset> passAsset = { asset.GetAs<PassAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
Data::Asset<PassAsset> passAsset = Data::static_pointer_cast<PassAsset>(asset);
|
||||
if (passAsset && passAsset->GetPassTemplate())
|
||||
{
|
||||
LoadPassAsset(passAsset->GetPassTemplate()->m_name, passAsset, true);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
@@ -40,6 +39,7 @@
|
||||
#include <Atom/RPI.Reflect/Pass/PassTemplate.h>
|
||||
#include <Atom/RPI.Reflect/Pass/RasterPassData.h>
|
||||
#include <Atom/RPI.Reflect/Pass/RenderPassData.h>
|
||||
#include <Atom/RPI.Reflect/Pass/SlowClearPassData.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -68,6 +68,7 @@ namespace AZ
|
||||
PassSlot::Reflect(context);
|
||||
|
||||
PassData::Reflect(context);
|
||||
SlowClearPassData::Reflect(context);
|
||||
CopyPassData::Reflect(context);
|
||||
RenderPassData::Reflect(context);
|
||||
ComputePassData::Reflect(context);
|
||||
@@ -312,7 +313,6 @@ namespace AZ
|
||||
Pass::FramePrepareParams params{ &frameGraphBuilder };
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "Pass: FrameBegin");
|
||||
m_rootPass->FrameBegin(params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ namespace AZ
|
||||
|
||||
void RasterPass::UpdateDrawList()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "RasterPass::UpdateDrawList");
|
||||
// DrawLists from dynamic draw
|
||||
AZStd::vector<RHI::DrawListView> drawLists = DynamicDrawInterface::Get()->GetDrawListsForPass(this);
|
||||
|
||||
@@ -216,8 +217,6 @@ namespace AZ
|
||||
|
||||
void RasterPass::CompileResources(const RHI::FrameGraphCompileContext& context)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "RasterPass: CompileResources");
|
||||
|
||||
if (m_shaderResourceGroup == nullptr)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -276,7 +276,8 @@ namespace AZ
|
||||
{
|
||||
inputIndex = imageIndex;
|
||||
}
|
||||
const RHI::ImageView* imageView = context.GetImageView(attachment->GetAttachmentId(), binding.m_attachmentUsageIndex);
|
||||
const RHI::ImageView* imageView =
|
||||
context.GetImageView(attachment->GetAttachmentId(), binding.m_unifiedScopeDesc.GetImageViewDescriptor(), binding.m_scopeAttachmentUsage);
|
||||
|
||||
if (binding.m_shaderImageDimensionsNameIndex.HasName())
|
||||
{
|
||||
@@ -315,7 +316,7 @@ namespace AZ
|
||||
{
|
||||
inputIndex = bufferIndex;
|
||||
}
|
||||
const RHI::BufferView* bufferView = context.GetBufferView(attachment->GetAttachmentId(), binding.m_attachmentUsageIndex);
|
||||
const RHI::BufferView* bufferView = context.GetBufferView(attachment->GetAttachmentId(), binding.m_scopeAttachmentUsage);
|
||||
m_shaderResourceGroup->SetBufferView(RHI::ShaderInputBufferIndex(inputIndex), bufferView, arrayIndex);
|
||||
++bufferIndex;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Atom/RPI.Public/Pass/SlowClearPass.h>
|
||||
#include <Atom/RPI.Public/Pass/PassUtils.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Pass/SlowClearPassData.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
Ptr<SlowClearPass> SlowClearPass::Create(const PassDescriptor& descriptor)
|
||||
{
|
||||
Ptr<SlowClearPass> pass = aznew SlowClearPass(descriptor);
|
||||
return pass;
|
||||
}
|
||||
|
||||
SlowClearPass::SlowClearPass(const PassDescriptor& descriptor)
|
||||
: RenderPass(descriptor)
|
||||
{
|
||||
const SlowClearPassData* passData = PassUtils::GetPassData<SlowClearPassData>(descriptor);
|
||||
if (passData != nullptr)
|
||||
{
|
||||
m_clearValue = passData->m_clearValue;
|
||||
}
|
||||
}
|
||||
|
||||
void SlowClearPass::InitializeInternal()
|
||||
{
|
||||
RenderPass::InitializeInternal();
|
||||
|
||||
// Set clear value
|
||||
AZ_Assert(GetInputOutputCount() > 0, "SlowClearPass: Missing InputOutput binding!");
|
||||
RPI::PassAttachmentBinding& binding = GetInputOutputBinding(0);
|
||||
binding.m_unifiedScopeDesc.m_loadStoreAction.m_clearValue = m_clearValue;
|
||||
}
|
||||
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
@@ -231,7 +231,7 @@ namespace AZ
|
||||
|
||||
void ImageAttachmentPreviewPass::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
|
||||
{
|
||||
Data::Asset<ShaderAsset> shaderAsset = { asset.GetAs<ShaderAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
Data::Asset<ShaderAsset> shaderAsset = Data::static_pointer_cast<ShaderAsset>(asset);
|
||||
if (shaderAsset)
|
||||
{
|
||||
m_needsShaderLoad = true;
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
#include <Atom/RHI/Device.h>
|
||||
#include <Atom/RHI.Reflect/PlatformLimitsDescriptor.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Time/ITime.h>
|
||||
|
||||
#include <AzFramework/Asset/AssetSystemBus.h>
|
||||
|
||||
@@ -268,21 +268,18 @@ namespace AZ
|
||||
|
||||
AssetInitBus::Broadcast(&AssetInitBus::Events::PostLoadInit);
|
||||
|
||||
// Update tick time info
|
||||
FillTickTimeInfo();
|
||||
m_currentSimulationTime = GetCurrentTime();
|
||||
|
||||
for (auto& scene : m_scenes)
|
||||
{
|
||||
scene->Simulate(m_tickTime, m_simulationJobPolicy);
|
||||
scene->Simulate(m_simulationJobPolicy, m_currentSimulationTime);
|
||||
}
|
||||
}
|
||||
|
||||
void RPISystem::FillTickTimeInfo()
|
||||
float RPISystem::GetCurrentTime() const
|
||||
{
|
||||
AZ::TickRequestBus::BroadcastResult(m_tickTime.m_gameDeltaTime, &AZ::TickRequestBus::Events::GetTickDeltaTime);
|
||||
ScriptTimePoint currentTime;
|
||||
AZ::TickRequestBus::BroadcastResult(currentTime, &AZ::TickRequestBus::Events::GetTimeAtCurrentTick);
|
||||
m_tickTime.m_currentGameTime = static_cast<float>(currentTime.GetSeconds());
|
||||
const AZ::TimeUs currentSimulationTimeUs = AZ::GetRealElapsedTimeUs();
|
||||
return AZ::TimeUsToSeconds(currentSimulationTimeUs);
|
||||
}
|
||||
|
||||
void RPISystem::RenderTick()
|
||||
@@ -301,7 +298,7 @@ namespace AZ
|
||||
// [GFX TODO] We may parallel scenes' prepare render.
|
||||
for (auto& scenePtr : m_scenes)
|
||||
{
|
||||
scenePtr->PrepareRender(m_tickTime, m_prepareRenderJobPolicy);
|
||||
scenePtr->PrepareRender(m_prepareRenderJobPolicy, m_currentSimulationTime);
|
||||
}
|
||||
|
||||
m_rhiSystem.FrameUpdate(
|
||||
|
||||
@@ -178,6 +178,10 @@ namespace AZ
|
||||
pipelineViews.m_views.resize(1);
|
||||
}
|
||||
ViewPtr previousView = pipelineViews.m_views[0];
|
||||
if (view)
|
||||
{
|
||||
view->OnAddToRenderPipeline();
|
||||
}
|
||||
pipelineViews.m_views[0] = view;
|
||||
|
||||
if (previousView)
|
||||
@@ -238,6 +242,7 @@ namespace AZ
|
||||
pipelineViews.m_type = PipelineViewType::Transient;
|
||||
}
|
||||
view->SetPassesByDrawList(&pipelineViews.m_passesByDrawList);
|
||||
view->OnAddToRenderPipeline();
|
||||
pipelineViews.m_views.push_back(view);
|
||||
}
|
||||
}
|
||||
@@ -375,7 +380,7 @@ namespace AZ
|
||||
m_scene->RemoveRenderPipeline(m_nameId);
|
||||
}
|
||||
|
||||
void RenderPipeline::OnStartFrame([[maybe_unused]] const TickTimeInfo& tick)
|
||||
void RenderPipeline::OnStartFrame([[maybe_unused]] float time)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "RenderPipeline: OnStartFrame");
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
|
||||
#include <Atom/RPI.Public/View.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Debug/Profiler.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
#include <AzCore/Jobs/JobEmpty.h>
|
||||
@@ -44,6 +43,9 @@ namespace AZ
|
||||
{
|
||||
auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs();
|
||||
scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName());
|
||||
|
||||
// Set value for constants defined in SceneTimeSrg.azsli
|
||||
scene->m_timeInputIndex = scene->m_srg->FindShaderInputConstantIndex(Name{ "m_time" });
|
||||
}
|
||||
|
||||
scene->m_name = sceneDescriptor.m_nameId;
|
||||
@@ -111,7 +113,8 @@ namespace AZ
|
||||
{
|
||||
if (m_taskGraphActive)
|
||||
{
|
||||
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
|
||||
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
|
||||
m_simulationFinishedTGEvent.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -381,12 +384,14 @@ namespace AZ
|
||||
simulationTGDesc,
|
||||
[this, featureProcessor]()
|
||||
{
|
||||
featureProcessor->Simulate(m_simulatePacket);
|
||||
FeatureProcessor::SimulatePacket jobPacket = m_simulatePacket;
|
||||
jobPacket.m_parentJob = nullptr;
|
||||
featureProcessor->Simulate(jobPacket);
|
||||
});
|
||||
}
|
||||
simulationTG.Detach();
|
||||
m_simulationFinishedWorkActive = true;
|
||||
simulationTG.Submit(&m_simulationFinishedTGEvent);
|
||||
m_simulationFinishedTGEvent = AZStd::make_unique<TaskGraphEvent>();
|
||||
simulationTG.Submit(m_simulationFinishedTGEvent.get());
|
||||
}
|
||||
|
||||
void Scene::SimulateJobs()
|
||||
@@ -397,10 +402,11 @@ namespace AZ
|
||||
for (FeatureProcessorPtr& fp : m_featureProcessors)
|
||||
{
|
||||
FeatureProcessor* featureProcessor = fp.get();
|
||||
const auto jobLambda = [this, featureProcessor]()
|
||||
const auto jobLambda = [this, featureProcessor](AZ::Job& owner)
|
||||
{
|
||||
|
||||
featureProcessor->Simulate(m_simulatePacket);
|
||||
FeatureProcessor::SimulatePacket jobPacket = m_simulatePacket;
|
||||
jobPacket.m_parentJob = &owner;
|
||||
featureProcessor->Simulate(jobPacket);
|
||||
};
|
||||
|
||||
AZ::Job* simulationJob = AZ::CreateJobFunction(AZStd::move(jobLambda), true, nullptr); //auto-deletes
|
||||
@@ -410,16 +416,17 @@ namespace AZ
|
||||
//[GFX TODO]: the completion job should start here
|
||||
}
|
||||
|
||||
void Scene::Simulate([[maybe_unused]] const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy)
|
||||
void Scene::Simulate(RHI::JobPolicy jobPolicy, float simulationTime)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "Scene: Simulate");
|
||||
|
||||
m_simulationTime = tickInfo.m_currentGameTime;
|
||||
m_simulationTime = simulationTime;
|
||||
|
||||
// If previous simulation job wasn't done, wait for it to finish.
|
||||
if (m_taskGraphActive)
|
||||
{
|
||||
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
|
||||
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
|
||||
m_simulationFinishedTGEvent.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -449,17 +456,14 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::WaitTGEvent(AZ::TaskGraphEvent& completionTGEvent, AZStd::atomic_bool* workToWaitOn )
|
||||
void Scene::WaitAndCleanTGEvent(AZStd::unique_ptr<AZ::TaskGraphEvent>&& completionTGEvent)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanCompletionJob");
|
||||
if (!workToWaitOn || workToWaitOn->load())
|
||||
AZ_PROFILE_SCOPE(RPI, "Scene: WaitAndCleanTGEvent");
|
||||
if (completionTGEvent)
|
||||
{
|
||||
completionTGEvent.Wait();
|
||||
}
|
||||
if (workToWaitOn)
|
||||
{
|
||||
workToWaitOn->store(false);
|
||||
completionTGEvent->Wait();
|
||||
}
|
||||
// allow completionTGEvent to go out of scope and be deleted
|
||||
}
|
||||
|
||||
void Scene::WaitAndCleanCompletionJob(AZ::JobCompletion*& completionJob)
|
||||
@@ -483,11 +487,9 @@ namespace AZ
|
||||
{
|
||||
if (m_srg)
|
||||
{
|
||||
// Set value for constants defined in SceneTimeSrg.azsli
|
||||
RHI::ShaderInputConstantIndex timeIndex = m_srg->FindShaderInputConstantIndex(Name{ "m_time" });
|
||||
if (timeIndex.IsValid())
|
||||
if (m_timeInputIndex.IsValid())
|
||||
{
|
||||
m_srg->SetConstant(timeIndex, m_simulationTime);
|
||||
m_srg->SetConstant(m_timeInputIndex, m_simulationTime);
|
||||
}
|
||||
|
||||
// signal any handlers to update values for their partial scene srg
|
||||
@@ -499,12 +501,12 @@ namespace AZ
|
||||
|
||||
void Scene::CollectDrawPacketsTaskGraph()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
|
||||
AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsTaskGraph");
|
||||
AZ::TaskGraphEvent collectDrawPacketsTGEvent;
|
||||
static const AZ::TaskDescriptor collectDrawPacketsTGDesc{"RPI_Scene_PrepareRender_CollectDrawPackets", "Graphics"};
|
||||
|
||||
AZ::TaskGraph collectDrawPacketsTG;
|
||||
// Launch FeatureProcessor::Render() jobs
|
||||
|
||||
// Launch FeatureProcessor::Render() taskgraphs
|
||||
for (auto& fp : m_featureProcessors)
|
||||
{
|
||||
collectDrawPacketsTG.AddTask(
|
||||
@@ -518,34 +520,50 @@ namespace AZ
|
||||
collectDrawPacketsTG.Submit(&collectDrawPacketsTGEvent);
|
||||
|
||||
// Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs if m_parallelOctreeTraversal)
|
||||
bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
|
||||
const bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
|
||||
m_cullingScene->BeginCulling(m_renderPacket.m_views);
|
||||
AZ::JobCompletion processCullablesCompletion;
|
||||
for (ViewPtr& viewPtr : m_renderPacket.m_views)
|
||||
static const AZ::TaskDescriptor processCullablesDescriptor{"AZ::RPI::Scene::ProcessCullables", "Graphics"};
|
||||
AZ::TaskGraphEvent processCullablesTGEvent;
|
||||
AZ::TaskGraph processCullablesTG;
|
||||
if (parallelOctreeTraversal)
|
||||
{
|
||||
AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob)
|
||||
{
|
||||
m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
|
||||
},
|
||||
true, nullptr); //auto-deletes
|
||||
if (parallelOctreeTraversal)
|
||||
for (ViewPtr& viewPtr : m_renderPacket.m_views)
|
||||
{
|
||||
processCullablesJob->SetDependent(&processCullablesCompletion);
|
||||
processCullablesJob->Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
processCullablesJob->StartAndWaitForCompletion();
|
||||
processCullablesTG.AddTask(processCullablesDescriptor, [this, &viewPtr, &processCullablesTGEvent]()
|
||||
{
|
||||
AZ::TaskGraph subTaskGraph;
|
||||
m_cullingScene->ProcessCullablesTG(*this, *viewPtr, subTaskGraph);
|
||||
if (!subTaskGraph.IsEmpty())
|
||||
{
|
||||
subTaskGraph.Detach();
|
||||
subTaskGraph.Submit(&processCullablesTGEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ViewPtr& viewPtr : m_renderPacket.m_views)
|
||||
{
|
||||
m_cullingScene->ProcessCullablesTG(*this, *viewPtr, processCullablesTG);
|
||||
}
|
||||
}
|
||||
bool processCullablesHasWork = !processCullablesTG.IsEmpty();
|
||||
if (processCullablesHasWork)
|
||||
{
|
||||
processCullablesTG.Submit(&processCullablesTGEvent);
|
||||
}
|
||||
|
||||
WaitTGEvent(collectDrawPacketsTGEvent);
|
||||
processCullablesCompletion.StartAndWaitForCompletion();
|
||||
collectDrawPacketsTGEvent.Wait();
|
||||
if (processCullablesHasWork) // skip the wait if there is no work to do
|
||||
{
|
||||
processCullablesTGEvent.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::CollectDrawPacketsJobs()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "CollectDrawPackets");
|
||||
AZ_PROFILE_SCOPE(RPI, "CollectDrawPacketsJobs");
|
||||
AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion();
|
||||
|
||||
// Launch FeatureProcessor::Render() jobs
|
||||
@@ -562,15 +580,16 @@ namespace AZ
|
||||
}
|
||||
|
||||
// Launch CullingSystem::ProcessCullables() jobs (will run concurrently with FeatureProcessor::Render() jobs)
|
||||
const bool parallelOctreeTraversal = m_cullingScene->GetDebugContext().m_parallelOctreeTraversal;
|
||||
m_cullingScene->BeginCulling(m_renderPacket.m_views);
|
||||
for (ViewPtr& viewPtr : m_renderPacket.m_views)
|
||||
{
|
||||
AZ::Job* processCullablesJob = AZ::CreateJobFunction([this, &viewPtr](AZ::Job& thisJob)
|
||||
{
|
||||
m_cullingScene->ProcessCullables(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
|
||||
m_cullingScene->ProcessCullablesJobs(*this, *viewPtr, thisJob); // can't call directly because ProcessCullables needs a parent job
|
||||
},
|
||||
true, nullptr); //auto-deletes
|
||||
if (m_cullingScene->GetDebugContext().m_parallelOctreeTraversal)
|
||||
if (parallelOctreeTraversal)
|
||||
{
|
||||
processCullablesJob->SetDependent(collectDrawPacketsCompletion);
|
||||
processCullablesJob->Start();
|
||||
@@ -594,13 +613,13 @@ namespace AZ
|
||||
{
|
||||
finalizeDrawListsTG.AddTask(
|
||||
finalizeDrawListsTGDesc,
|
||||
[view]()
|
||||
[view, &finalizeDrawListsTGEvent]()
|
||||
{
|
||||
view->FinalizeDrawLists();
|
||||
view->FinalizeDrawListsTG(finalizeDrawListsTGEvent);
|
||||
});
|
||||
}
|
||||
finalizeDrawListsTG.Submit(&finalizeDrawListsTGEvent);
|
||||
WaitTGEvent(finalizeDrawListsTGEvent);
|
||||
finalizeDrawListsTGEvent.Wait();
|
||||
}
|
||||
|
||||
void Scene::FinalizeDrawListsJobs()
|
||||
@@ -608,9 +627,9 @@ namespace AZ
|
||||
AZ::JobCompletion* finalizeDrawListsCompletion = aznew AZ::JobCompletion();
|
||||
for (auto& view : m_renderPacket.m_views)
|
||||
{
|
||||
const auto finalizeDrawListsLambda = [view]()
|
||||
const auto finalizeDrawListsLambda = [view](AZ::Job& job)
|
||||
{
|
||||
view->FinalizeDrawLists();
|
||||
view->FinalizeDrawListsJob(&job);
|
||||
};
|
||||
|
||||
AZ::Job* finalizeDrawListsJob = AZ::CreateJobFunction(AZStd::move(finalizeDrawListsLambda), true, nullptr); //auto-deletes
|
||||
@@ -620,13 +639,14 @@ namespace AZ
|
||||
WaitAndCleanCompletionJob(finalizeDrawListsCompletion);
|
||||
}
|
||||
|
||||
void Scene::PrepareRender(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy)
|
||||
void Scene::PrepareRender(RHI::JobPolicy jobPolicy, float simulationTime)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "Scene: PrepareRender");
|
||||
|
||||
if (m_taskGraphActive)
|
||||
{
|
||||
WaitTGEvent(m_simulationFinishedTGEvent, &m_simulationFinishedWorkActive);
|
||||
WaitAndCleanTGEvent(AZStd::move(m_simulationFinishedTGEvent));
|
||||
m_simulationFinishedTGEvent.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -644,7 +664,7 @@ namespace AZ
|
||||
if (pipeline->NeedsRender())
|
||||
{
|
||||
activePipelines.push_back(pipeline);
|
||||
pipeline->OnStartFrame(tickInfo);
|
||||
pipeline->OnStartFrame(simulationTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -717,20 +737,19 @@ namespace AZ
|
||||
// Add dynamic draw data for all the views
|
||||
if (m_dynamicDrawSystem)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "DynamicDraw SubmitDrawData");
|
||||
m_dynamicDrawSystem->SubmitDrawData(this, m_renderPacket.m_views);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AZ_PROFILE_BEGIN(RPI, "FinalizeDrawLists");
|
||||
if (jobPolicy == RHI::JobPolicy::Serial)
|
||||
AZ_PROFILE_SCOPE(RPI, "FinalizeDrawLists");
|
||||
if (jobPolicy == RHI::JobPolicy::Serial ||
|
||||
m_renderPacket.m_views.size() <= 1) // FinalizeDrawListsX both immediately wait for the job to complete, skip job if only 1 job would be generated
|
||||
{
|
||||
for (auto& view : m_renderPacket.m_views)
|
||||
{
|
||||
view->FinalizeDrawLists();
|
||||
view->FinalizeDrawListsJob(nullptr);
|
||||
}
|
||||
AZ_PROFILE_END(RPI);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -742,7 +761,6 @@ namespace AZ
|
||||
{
|
||||
FinalizeDrawListsJobs();
|
||||
}
|
||||
AZ_PROFILE_END(RPI);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <Atom/RPI.Public/Shader/ShaderSystemInterface.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -96,8 +98,7 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode Shader::Init(ShaderAsset& shaderAsset)
|
||||
{
|
||||
Data::AssetBus::Handler::BusDisconnect();
|
||||
ShaderReloadNotificationBus::Handler::BusDisconnect();
|
||||
Data::AssetBus::MultiHandler::BusDisconnect();
|
||||
ShaderVariantFinderNotificationBus::Handler::BusDisconnect();
|
||||
|
||||
RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get();
|
||||
@@ -112,7 +113,8 @@ namespace AZ
|
||||
AZStd::unique_lock<decltype(m_variantCacheMutex)> lock(m_variantCacheMutex);
|
||||
m_shaderVariants.clear();
|
||||
}
|
||||
m_rootVariant.Init(Data::Asset<ShaderAsset>{&shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad}, shaderAsset.GetRootVariant(m_supervariantIndex), m_supervariantIndex);
|
||||
auto rootShaderVariantAsset = shaderAsset.GetRootVariant(m_supervariantIndex);
|
||||
m_rootVariant.Init(m_asset, rootShaderVariantAsset, m_supervariantIndex);
|
||||
|
||||
if (m_pipelineLibraryHandle.IsNull())
|
||||
{
|
||||
@@ -146,8 +148,8 @@ namespace AZ
|
||||
}
|
||||
|
||||
ShaderVariantFinderNotificationBus::Handler::BusConnect(m_asset.GetId());
|
||||
Data::AssetBus::Handler::BusConnect(m_asset.GetId());
|
||||
ShaderReloadNotificationBus::Handler::BusConnect(m_asset.GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(rootShaderVariantAsset.GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(m_asset.GetId());
|
||||
|
||||
return RHI::ResultCode::Success;
|
||||
}
|
||||
@@ -155,8 +157,7 @@ namespace AZ
|
||||
void Shader::Shutdown()
|
||||
{
|
||||
ShaderVariantFinderNotificationBus::Handler::BusDisconnect();
|
||||
Data::AssetBus::Handler::BusDisconnect();
|
||||
ShaderReloadNotificationBus::Handler::BusDisconnect();
|
||||
Data::AssetBus::MultiHandler::BusDisconnect();
|
||||
|
||||
if (m_pipelineLibraryHandle.IsValid())
|
||||
{
|
||||
@@ -181,14 +182,52 @@ namespace AZ
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Shader::OnAssetReloaded %s", this, asset.GetHint().c_str());
|
||||
|
||||
if (asset->GetId() == m_asset->GetId())
|
||||
if (asset.GetAs<ShaderVariantAsset>())
|
||||
{
|
||||
Data::Asset<ShaderAsset> newAsset = { asset.GetAs<ShaderAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
AZ_Assert(newAsset, "Reloaded ShaderAsset is null");
|
||||
m_reloadedRootShaderVariantAsset = Data::static_pointer_cast<ShaderVariantAsset>(asset);
|
||||
if (m_asset->m_buildTimestamp == m_reloadedRootShaderVariantAsset->GetBuildTimestamp())
|
||||
{
|
||||
Init(*m_asset.Get());
|
||||
ShaderReloadNotificationBus::Event(asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Init(*newAsset.Get());
|
||||
if (asset.GetAs<ShaderAsset>())
|
||||
{
|
||||
m_asset = Data::static_pointer_cast<ShaderAsset>(asset);
|
||||
if (!m_reloadedRootShaderVariantAsset.IsReady())
|
||||
{
|
||||
// Do nothing, as We should not re-initilize until the root shader variant asset has been reloaded.
|
||||
return;
|
||||
}
|
||||
AZ_Assert(m_asset->m_buildTimestamp == m_reloadedRootShaderVariantAsset->GetBuildTimestamp(),
|
||||
"shaderAsset '%s' timeStamp=%lld, but Root ShaderVariantAsset timeStamp=%lld", m_asset.GetHint().c_str(),
|
||||
m_asset->m_buildTimestamp, m_reloadedRootShaderVariantAsset->GetBuildTimestamp());
|
||||
m_asset->UpdateRootShaderVariantAsset(m_supervariantIndex, m_reloadedRootShaderVariantAsset);
|
||||
m_reloadedRootShaderVariantAsset = {}; // Clear the temporary reference.
|
||||
|
||||
if (ShaderReloadDebugTracker::IsEnabled())
|
||||
{
|
||||
auto makeTimeString = [](AZ::u64 timestamp, AZ::u64 now)
|
||||
{
|
||||
AZ::u64 elapsedMillis = now - timestamp;
|
||||
double elapsedSeconds = aznumeric_cast<double>(elapsedMillis / 1'000);
|
||||
AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds);
|
||||
return timeString;
|
||||
};
|
||||
|
||||
AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond();
|
||||
|
||||
const auto shaderVariantAsset = m_asset->GetRootVariant();
|
||||
ShaderReloadDebugTracker::Printf("{%p}->Shader::OnAssetReloaded for shader '%s' [build time %s] found variant '%s' [build time %s]", this,
|
||||
m_asset.GetHint().c_str(), makeTimeString(m_asset->m_buildTimestamp, now).c_str(),
|
||||
shaderVariantAsset.GetHint().c_str(), makeTimeString(shaderVariantAsset->GetBuildTimestamp(), now).c_str());
|
||||
}
|
||||
Init(*m_asset.Get());
|
||||
ShaderReloadNotificationBus::Event(asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this);
|
||||
}
|
||||
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -253,23 +292,6 @@ namespace AZ
|
||||
ShaderReloadNotificationBus::Event(m_asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, updatedVariant);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// ShaderReloadNotificationBus overrides...
|
||||
void Shader::OnShaderAssetReinitialized(const Data::Asset<ShaderAsset>& shaderAsset)
|
||||
{
|
||||
// When reloads occur, it's possible for old Asset objects to hang around and report reinitialization,
|
||||
// so we can reduce unnecessary reinitialization in that case.
|
||||
if (shaderAsset.Get() == m_asset.Get())
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Shader::OnShaderAssetReinitialized %s", this, shaderAsset.GetHint().c_str());
|
||||
|
||||
Init(*m_asset.Get());
|
||||
ShaderReloadNotificationBus::Event(shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this);
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
ConstPtr<RHI::PipelineLibraryData> Shader::LoadPipelineLibrary() const
|
||||
{
|
||||
@@ -320,6 +342,30 @@ namespace AZ
|
||||
}
|
||||
|
||||
const ShaderVariant& Shader::GetVariant(ShaderVariantStableId shaderVariantStableId)
|
||||
{
|
||||
const ShaderVariant& variant = GetVariantInternal(shaderVariantStableId);
|
||||
|
||||
if (ShaderReloadDebugTracker::IsEnabled())
|
||||
{
|
||||
auto makeTimeString = [](AZStd::sys_time_t timestamp, AZStd::sys_time_t now)
|
||||
{
|
||||
AZStd::sys_time_t elapsedMicroseconds = now - timestamp;
|
||||
double elapsedSeconds = aznumeric_cast<double>(elapsedMicroseconds / 1'000'000);
|
||||
AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds);
|
||||
return timeString;
|
||||
};
|
||||
|
||||
AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond();
|
||||
|
||||
ShaderReloadDebugTracker::Printf("{%p}->Shader::GetVariant for shader '%s' [build time %s] found variant '%s' [build time %s]", this,
|
||||
m_asset.GetHint().c_str(), makeTimeString(m_asset->GetBuildTimestamp(), now).c_str(),
|
||||
variant.GetShaderVariantAsset().GetHint().c_str(), makeTimeString(variant.GetShaderVariantAsset()->GetBuildTimestamp(), now).c_str());
|
||||
}
|
||||
|
||||
return variant;
|
||||
}
|
||||
|
||||
const ShaderVariant& Shader::GetVariantInternal(ShaderVariantStableId shaderVariantStableId)
|
||||
{
|
||||
if (!shaderVariantStableId.IsValid() || shaderVariantStableId == ShaderAsset::RootShaderVariantStableId)
|
||||
{
|
||||
@@ -336,7 +382,7 @@ namespace AZ
|
||||
// reloaded, but some (or all) shader variants haven't been built yet. Since we want to use the latest version of the
|
||||
// shader code, ignore the old variants and fall back to the newer root variant instead. There's no need to report a
|
||||
// warning here because m_asset->GetVariant below will report one.
|
||||
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
|
||||
if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp())
|
||||
{
|
||||
return findIt->second;
|
||||
}
|
||||
@@ -359,7 +405,7 @@ namespace AZ
|
||||
auto findIt = m_shaderVariants.find(shaderVariantStableId);
|
||||
if (findIt != m_shaderVariants.end())
|
||||
{
|
||||
if (findIt->second.GetBuildTimestamp() >= m_asset->GetShaderAssetBuildTimestamp())
|
||||
if (findIt->second.GetBuildTimestamp() >= m_asset->GetBuildTimestamp())
|
||||
{
|
||||
return findIt->second;
|
||||
}
|
||||
|
||||
@@ -7,24 +7,71 @@
|
||||
*/
|
||||
|
||||
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
|
||||
#include <AzCore/Module/Environment.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
{
|
||||
bool ShaderReloadDebugTracker::s_enabled = false;
|
||||
int ShaderReloadDebugTracker::s_indent = 0;
|
||||
namespace ShaderReloadDebugTrackerInternal
|
||||
{
|
||||
static constexpr char EnabledVariableName[] = "ShaderReloadDebugTracker enabled";
|
||||
static constexpr char IndentVariableName[] = "ShaderReloadDebugTracker indent";
|
||||
|
||||
static EnvironmentVariable<bool> s_enabled;
|
||||
static EnvironmentVariable<int> s_indent;
|
||||
}
|
||||
|
||||
void ShaderReloadDebugTracker::Init()
|
||||
{
|
||||
MakeReady();
|
||||
}
|
||||
|
||||
void ShaderReloadDebugTracker::Shutdown()
|
||||
{
|
||||
ShaderReloadDebugTrackerInternal::s_enabled.Reset();
|
||||
ShaderReloadDebugTrackerInternal::s_indent.Reset();
|
||||
}
|
||||
|
||||
void ShaderReloadDebugTracker::MakeReady()
|
||||
{
|
||||
if (!ShaderReloadDebugTrackerInternal::s_enabled.IsValid())
|
||||
{
|
||||
ShaderReloadDebugTrackerInternal::s_enabled = AZ::Environment::CreateVariable<bool>(AZ::Crc32(ShaderReloadDebugTrackerInternal::EnabledVariableName), false);
|
||||
ShaderReloadDebugTrackerInternal::s_indent = AZ::Environment::CreateVariable<int>(AZ::Crc32(ShaderReloadDebugTrackerInternal::IndentVariableName), 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShaderReloadDebugTracker::IsEnabled()
|
||||
{
|
||||
#ifdef AZ_ENABLE_SHADER_RELOAD_DEBUG_TRACKER
|
||||
MakeReady();
|
||||
|
||||
// Set this to true in the debugger to turn on hot reload tracing.
|
||||
// If needed, we could hook this up to a CVar.
|
||||
return s_enabled;
|
||||
return ShaderReloadDebugTrackerInternal::s_enabled.Get();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ShaderReloadDebugTracker::AddIndent()
|
||||
{
|
||||
MakeReady();
|
||||
ShaderReloadDebugTrackerInternal::s_indent.Get() += IndentSpaces;
|
||||
}
|
||||
|
||||
void ShaderReloadDebugTracker::RemoveIndent()
|
||||
{
|
||||
MakeReady();
|
||||
ShaderReloadDebugTrackerInternal::s_indent.Get() -= IndentSpaces;
|
||||
}
|
||||
|
||||
int ShaderReloadDebugTracker::GetIndent()
|
||||
{
|
||||
MakeReady();
|
||||
return ShaderReloadDebugTrackerInternal::s_indent.Get();
|
||||
}
|
||||
|
||||
ShaderReloadDebugTracker::ScopedSection::~ScopedSection()
|
||||
{
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
|
||||
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AtomCore/Instance/InstanceDatabase.h>
|
||||
|
||||
namespace AZ
|
||||
@@ -75,8 +74,6 @@ namespace AZ
|
||||
|
||||
RHI::ResultCode ShaderResourceGroup::Init(ShaderAsset& shaderAsset, const SupervariantIndex& supervariantIndex, const AZ::Name& srgName)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
|
||||
const auto& lay = shaderAsset.FindShaderResourceGroupLayout(srgName, supervariantIndex);
|
||||
m_layout = lay.get();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <Atom/RPI.Public/Shader/Shader.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroupPool.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderReloadDebugTracker.h>
|
||||
|
||||
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
|
||||
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
|
||||
@@ -86,10 +87,13 @@ namespace AZ
|
||||
};
|
||||
Data::InstanceDatabase<ShaderResourceGroupPool>::Create(azrtti_typeid<ShaderResourceGroupPool>(), handler, false);
|
||||
}
|
||||
|
||||
ShaderReloadDebugTracker::Init();
|
||||
}
|
||||
|
||||
void ShaderSystem::Shutdown()
|
||||
{
|
||||
ShaderReloadDebugTracker::Shutdown();
|
||||
Data::InstanceDatabase<Shader>::Destroy();
|
||||
Data::InstanceDatabase<ShaderResourceGroup>::Destroy();
|
||||
Data::InstanceDatabase<ShaderResourceGroupPool>::Destroy();
|
||||
|
||||
@@ -22,24 +22,20 @@ namespace AZ
|
||||
const Data::Asset<ShaderAsset>& shaderAsset,
|
||||
const Data::Asset<ShaderVariantAsset>& shaderVariantAsset,
|
||||
SupervariantIndex supervariantIndex)
|
||||
{
|
||||
{
|
||||
m_shaderAsset = shaderAsset;
|
||||
m_shaderVariantAsset = shaderVariantAsset;
|
||||
m_supervariantIndex = supervariantIndex;
|
||||
m_pipelineStateType = shaderAsset->GetPipelineStateType();
|
||||
m_pipelineLayoutDescriptor = shaderAsset->GetPipelineLayoutDescriptor(supervariantIndex);
|
||||
m_shaderVariantAsset = shaderVariantAsset;
|
||||
m_renderStates = &shaderAsset->GetRenderStates(supervariantIndex);
|
||||
m_supervariantIndex = supervariantIndex;
|
||||
|
||||
Data::AssetBus::MultiHandler::BusDisconnect();
|
||||
Data::AssetBus::MultiHandler::BusConnect(shaderAsset.GetId());
|
||||
Data::AssetBus::MultiHandler::BusConnect(shaderVariantAsset.GetId());
|
||||
|
||||
m_shaderAsset = shaderAsset;
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderVariant::~ShaderVariant()
|
||||
{
|
||||
Data::AssetBus::MultiHandler::BusDisconnect();
|
||||
|
||||
}
|
||||
|
||||
void ShaderVariant::ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const
|
||||
@@ -82,25 +78,5 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ShaderVariant::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->ShaderVariant::OnAssetReloaded %s", this, asset.GetHint().c_str());
|
||||
|
||||
if (asset.GetAs<ShaderVariantAsset>())
|
||||
{
|
||||
Data::Asset<ShaderVariantAsset> shaderVariantAsset = { asset.GetAs<ShaderVariantAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
Init(m_shaderAsset, shaderVariantAsset, m_supervariantIndex);
|
||||
ShaderReloadNotificationBus::Event(m_shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, *this);
|
||||
}
|
||||
|
||||
if (asset.GetAs<ShaderAsset>())
|
||||
{
|
||||
Data::Asset<ShaderAsset> shaderAsset = { asset.GetAs<ShaderAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
Init(shaderAsset, m_shaderVariantAsset, m_supervariantIndex);
|
||||
ShaderReloadNotificationBus::Event(m_shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, *this);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Math/MatrixUtils.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Jobs/JobCompletion.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
#include <AzCore/Task/TaskGraph.h>
|
||||
#include <Atom_RPI_Traits_Platform.h>
|
||||
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
@@ -47,18 +50,14 @@ namespace AZ
|
||||
{
|
||||
AZ_Assert(!name.IsEmpty(), "invalid name");
|
||||
|
||||
// Set default matrixes.
|
||||
// Set default matrices
|
||||
SetWorldToViewMatrix(AZ::Matrix4x4::CreateIdentity());
|
||||
AZ::Matrix4x4 viewToClipMatrix;
|
||||
AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::Constants::HalfPi, 1, 0.1f, 1000.f, true);
|
||||
SetViewToClipMatrix(viewToClipMatrix);
|
||||
|
||||
Data::Asset<ShaderAsset> viewSrgShaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs();
|
||||
TryCreateShaderResourceGroup();
|
||||
|
||||
if (viewSrgShaderAsset.IsReady())
|
||||
{
|
||||
m_shaderResourceGroup = ShaderResourceGroup::Create(viewSrgShaderAsset, RPISystemInterface::Get()->GetViewSrgLayout()->GetName());
|
||||
}
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
m_maskedOcclusionCulling = MaskedOcclusionCulling::Create();
|
||||
m_maskedOcclusionCulling->SetResolution(MaskedSoftwareOcclusionCullingWidth, MaskedSoftwareOcclusionCullingHeight);
|
||||
@@ -125,6 +124,7 @@ namespace AZ
|
||||
|
||||
m_worldToViewMatrix = worldToView;
|
||||
m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix;
|
||||
m_clipToWorldMatrix = m_worldToClipMatrix.GetInverseFull();
|
||||
|
||||
m_onWorldToViewMatrixChange.Signal(m_worldToViewMatrix);
|
||||
m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix);
|
||||
@@ -162,6 +162,7 @@ namespace AZ
|
||||
m_worldToViewMatrix = m_viewToWorldMatrix.GetInverseFast();
|
||||
|
||||
m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix;
|
||||
m_clipToWorldMatrix = m_worldToClipMatrix.GetInverseFull();
|
||||
|
||||
// Only signal an update when there is a change, otherwise this might block
|
||||
// user input from changing the value.
|
||||
@@ -177,6 +178,7 @@ namespace AZ
|
||||
m_viewToClipMatrix = viewToClip;
|
||||
|
||||
m_worldToClipMatrix = m_viewToClipMatrix * m_worldToViewMatrix;
|
||||
m_clipToWorldMatrix = m_worldToClipMatrix.GetInverseFull();
|
||||
|
||||
// Update z depth constant simultaneously
|
||||
// zNear -> n, zFar -> f
|
||||
@@ -217,6 +219,16 @@ namespace AZ
|
||||
return m_viewToWorldMatrix;
|
||||
}
|
||||
|
||||
AZ::Matrix3x4 View::GetWorldToViewMatrixAsMatrix3x4() const
|
||||
{
|
||||
return AZ::Matrix3x4::UnsafeCreateFromMatrix4x4(m_worldToViewMatrix);
|
||||
}
|
||||
|
||||
AZ::Matrix3x4 View::GetViewToWorldMatrixAsMatrix3x4() const
|
||||
{
|
||||
return AZ::Matrix3x4::UnsafeCreateFromMatrix4x4(m_viewToWorldMatrix);
|
||||
}
|
||||
|
||||
const AZ::Matrix4x4& View::GetViewToClipMatrix() const
|
||||
{
|
||||
return m_viewToClipMatrix;
|
||||
@@ -227,6 +239,11 @@ namespace AZ
|
||||
return m_worldToClipMatrix;
|
||||
}
|
||||
|
||||
const AZ::Matrix4x4& View::GetClipToWorldMatrix() const
|
||||
{
|
||||
return m_clipToWorldMatrix;
|
||||
}
|
||||
|
||||
bool View::HasDrawListTag(RHI::DrawListTag drawListTag)
|
||||
{
|
||||
return drawListTag.IsValid() && m_drawListMask[drawListTag.GetIndex()];
|
||||
@@ -237,24 +254,79 @@ namespace AZ
|
||||
return m_drawListContext.GetList(drawListTag);
|
||||
}
|
||||
|
||||
void View::FinalizeDrawLists()
|
||||
void View::FinalizeDrawListsTG(AZ::TaskGraphEvent& finalizeDrawListsTGEvent)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "View: FinalizeDrawLists");
|
||||
m_drawListContext.FinalizeLists();
|
||||
SortFinalizedDrawLists();
|
||||
SortFinalizedDrawListsTG(finalizeDrawListsTGEvent);
|
||||
}
|
||||
void View::FinalizeDrawListsJob(AZ::Job* parentJob)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "View: FinalizeDrawLists");
|
||||
m_drawListContext.FinalizeLists();
|
||||
SortFinalizedDrawListsJob(parentJob);
|
||||
}
|
||||
|
||||
void View::SortFinalizedDrawLists()
|
||||
void View::SortFinalizedDrawListsTG(AZ::TaskGraphEvent& finalizeDrawListsTGEvent)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "View: SortFinalizedDrawLists");
|
||||
RHI::DrawListsByTag& drawListsByTag = m_drawListContext.GetMergedDrawListsByTag();
|
||||
|
||||
AZ::TaskGraph drawListSortTG;
|
||||
AZ::TaskDescriptor drawListSortTGDescriptor{"RPI_View_SortFinalizedDrawLists", "Graphics"};
|
||||
for (size_t idx = 0; idx < drawListsByTag.size(); ++idx)
|
||||
{
|
||||
if (drawListsByTag[idx].size() > 1)
|
||||
{
|
||||
SortDrawList(drawListsByTag[idx], RHI::DrawListTag(idx));
|
||||
drawListSortTG.AddTask(drawListSortTGDescriptor, [this, &drawListsByTag, idx]()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "View: SortDrawList Task");
|
||||
SortDrawList(drawListsByTag[idx], RHI::DrawListTag(idx));
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!drawListSortTG.IsEmpty())
|
||||
{
|
||||
drawListSortTG.Detach();
|
||||
drawListSortTG.Submit(&finalizeDrawListsTGEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void View::SortFinalizedDrawListsJob(AZ::Job* parentJob)
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "View: SortFinalizedDrawLists");
|
||||
RHI::DrawListsByTag& drawListsByTag = m_drawListContext.GetMergedDrawListsByTag();
|
||||
|
||||
AZ::JobCompletion jobCompletion;
|
||||
for (size_t idx = 0; idx < drawListsByTag.size(); ++idx)
|
||||
{
|
||||
if (drawListsByTag[idx].size() > 1)
|
||||
{
|
||||
auto jobLambda = [this, &drawListsByTag, idx]()
|
||||
{
|
||||
AZ_PROFILE_SCOPE(RPI, "View: SortDrawList Job");
|
||||
SortDrawList(drawListsByTag[idx], RHI::DrawListTag(idx));
|
||||
};
|
||||
Job* jobSortDrawList = aznew JobFunction<decltype(jobLambda)>(jobLambda, true, nullptr); // Auto-deletes
|
||||
if (parentJob)
|
||||
{
|
||||
parentJob->StartAsChild(jobSortDrawList);
|
||||
}
|
||||
else
|
||||
{
|
||||
jobSortDrawList->SetDependent(&jobCompletion);
|
||||
jobSortDrawList->Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parentJob)
|
||||
{
|
||||
parentJob->WaitForChildren();
|
||||
}
|
||||
else
|
||||
{
|
||||
jobCompletion.StartAndWaitForCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
void View::SortDrawList(RHI::DrawList& drawList, RHI::DrawListTag tag)
|
||||
@@ -263,12 +335,12 @@ namespace AZ
|
||||
passWithDrawListTag->SortDrawList(drawList);
|
||||
}
|
||||
|
||||
void View::ConnectWorldToViewMatrixChangedHandler(View::MatrixChangedEvent::Handler& handler)
|
||||
void View::ConnectWorldToViewMatrixChangedHandler(MatrixChangedEvent::Handler& handler)
|
||||
{
|
||||
handler.Connect(m_onWorldToViewMatrixChange);
|
||||
}
|
||||
|
||||
void View::ConnectWorldToClipMatrixChangedHandler(View::MatrixChangedEvent::Handler& handler)
|
||||
void View::ConnectWorldToClipMatrixChangedHandler(MatrixChangedEvent::Handler& handler)
|
||||
{
|
||||
handler.Connect(m_onWorldToClipMatrixChange);
|
||||
}
|
||||
@@ -361,16 +433,19 @@ namespace AZ
|
||||
{
|
||||
if (m_clipSpaceOffset.IsZero())
|
||||
{
|
||||
Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix;
|
||||
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull());
|
||||
if (m_shaderResourceGroup)
|
||||
{
|
||||
Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix;
|
||||
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Offset the current and previous frame clip matricies
|
||||
// Offset the current and previous frame clip matrices
|
||||
Matrix4x4 offsetViewToClipMatrix = m_viewToClipMatrix;
|
||||
offsetViewToClipMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX());
|
||||
offsetViewToClipMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY());
|
||||
@@ -379,27 +454,33 @@ namespace AZ
|
||||
offsetViewToClipPrevMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX());
|
||||
offsetViewToClipPrevMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY());
|
||||
|
||||
// Build other matricies dependent on the view to clip matricies
|
||||
// Build other matrices dependent on the view to clip matrices
|
||||
Matrix4x4 offsetWorldToClipMatrix = offsetViewToClipMatrix * m_worldToViewMatrix;
|
||||
Matrix4x4 offsetWorldToClipPrevMatrix = offsetViewToClipPrevMatrix * m_worldToViewPrevMatrix;
|
||||
|
||||
Matrix4x4 offsetClipToViewMatrix = offsetViewToClipMatrix.GetInverseFull();
|
||||
Matrix4x4 offsetClipToWorldMatrix = m_viewToWorldMatrix * offsetClipToViewMatrix;
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull());
|
||||
|
||||
if (m_shaderResourceGroup)
|
||||
{
|
||||
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull());
|
||||
}
|
||||
}
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position);
|
||||
m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull());
|
||||
m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ);
|
||||
m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants);
|
||||
if (m_shaderResourceGroup)
|
||||
{
|
||||
m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position);
|
||||
m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix);
|
||||
m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull());
|
||||
m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ);
|
||||
m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants);
|
||||
|
||||
m_shaderResourceGroup->Compile();
|
||||
m_shaderResourceGroup->Compile();
|
||||
}
|
||||
|
||||
m_viewToClipPrevMatrix = m_viewToClipMatrix;
|
||||
m_worldToViewPrevMatrix = m_worldToViewMatrix;
|
||||
@@ -410,6 +491,7 @@ namespace AZ
|
||||
void View::BeginCulling()
|
||||
{
|
||||
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
|
||||
AZ_PROFILE_SCOPE(RPI, "View: ClearMaskedOcclusionBuffer");
|
||||
m_maskedOcclusionCulling->ClearBuffer();
|
||||
#endif
|
||||
}
|
||||
@@ -418,5 +500,30 @@ namespace AZ
|
||||
{
|
||||
return m_maskedOcclusionCulling;
|
||||
}
|
||||
|
||||
void View::TryCreateShaderResourceGroup()
|
||||
{
|
||||
if (!m_shaderResourceGroup)
|
||||
{
|
||||
if (auto rpiSystemInterface = RPISystemInterface::Get())
|
||||
{
|
||||
if (Data::Asset<ShaderAsset> viewSrgShaderAsset = rpiSystemInterface->GetCommonShaderAssetForSrgs();
|
||||
viewSrgShaderAsset.IsReady())
|
||||
{
|
||||
m_shaderResourceGroup =
|
||||
ShaderResourceGroup::Create(viewSrgShaderAsset, rpiSystemInterface->GetViewSrgLayout()->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void View::OnAddToRenderPipeline()
|
||||
{
|
||||
TryCreateShaderResourceGroup();
|
||||
if (!m_shaderResourceGroup)
|
||||
{
|
||||
AZ_Warning("RPI::View", false, "Shader Resource Group failed to initialize");
|
||||
}
|
||||
}
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -36,12 +36,12 @@ namespace AZ
|
||||
AzFramework::WindowNotificationBus::Handler::BusConnect(nativeWindow);
|
||||
AzFramework::ViewportRequestBus::Handler::BusConnect(id);
|
||||
|
||||
m_onProjectionMatrixChangedHandler = ViewportContext::MatrixChangedEvent::Handler([this](const AZ::Matrix4x4& matrix)
|
||||
m_onProjectionMatrixChangedHandler = MatrixChangedEvent::Handler([this](const AZ::Matrix4x4& matrix)
|
||||
{
|
||||
m_projectionMatrixChangedEvent.Signal(matrix);
|
||||
});
|
||||
|
||||
m_onViewMatrixChangedHandler = ViewportContext::MatrixChangedEvent::Handler([this](const AZ::Matrix4x4& matrix)
|
||||
m_onViewMatrixChangedHandler = MatrixChangedEvent::Handler([this](const AZ::Matrix4x4& matrix)
|
||||
{
|
||||
m_viewMatrixChangedEvent.Signal(matrix);
|
||||
});
|
||||
@@ -203,6 +203,11 @@ namespace AZ
|
||||
return GetDefaultView()->GetWorldToViewMatrix();
|
||||
}
|
||||
|
||||
AZ::Matrix3x4 ViewportContext::GetCameraViewMatrixAsMatrix3x4() const
|
||||
{
|
||||
return GetDefaultView()->GetWorldToViewMatrixAsMatrix3x4();
|
||||
}
|
||||
|
||||
void ViewportContext::SetCameraViewMatrix(const AZ::Matrix4x4& matrix)
|
||||
{
|
||||
GetDefaultView()->SetWorldToViewMatrix(matrix);
|
||||
|
||||
@@ -176,6 +176,20 @@ namespace AZ
|
||||
return {};
|
||||
}
|
||||
|
||||
ViewportContextPtr ViewportContextManager::GetViewportContextByScene(const Scene* scene) const
|
||||
{
|
||||
AZStd::lock_guard lock(m_containerMutex);
|
||||
for (const auto& viewportData : m_viewportContexts)
|
||||
{
|
||||
ViewportContextPtr viewportContext = viewportData.second.context.lock();
|
||||
if (viewportContext && viewportContext->GetRenderScene().get() == scene)
|
||||
{
|
||||
return viewportContext;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ViewportContextManager::RenameViewportContext(ViewportContextPtr viewportContext, const Name& newContextName)
|
||||
{
|
||||
auto currentAssignedViewportContext = GetViewportContextByName(newContextName);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
*/
|
||||
|
||||
#include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
|
||||
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzFramework/Asset/AssetSystemBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -40,5 +43,56 @@ namespace AZ
|
||||
|
||||
return loadResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Data::AssetId StreamingImageAssetHandler::AssetMissingInCatalog(const Data::Asset<Data::AssetData>& asset)
|
||||
{
|
||||
// Find out if the asset is missing completely, or just still processing
|
||||
// and escalate the asset to the top of the list
|
||||
AzFramework::AssetSystem::AssetStatus missingAssetStatus;
|
||||
AzFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
missingAssetStatus, &AzFramework::AssetSystem::AssetSystemRequests::GetAssetStatusById, asset.GetId().m_guid);
|
||||
|
||||
// Determine which fallback image to use
|
||||
const char* relativePath = DefaultImageAssetPaths::DefaultFallback;
|
||||
|
||||
bool useDebugFallbackImages = true;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->GetObject(useDebugFallbackImages, "/O3DE/Atom/RPI/UseDebugFallbackImages");
|
||||
}
|
||||
|
||||
if (useDebugFallbackImages)
|
||||
{
|
||||
switch (missingAssetStatus)
|
||||
{
|
||||
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Queued:
|
||||
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiling:
|
||||
relativePath = DefaultImageAssetPaths::Processing;
|
||||
break;
|
||||
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Failed:
|
||||
relativePath = DefaultImageAssetPaths::ProcessingFailed;
|
||||
break;
|
||||
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Missing:
|
||||
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Unknown:
|
||||
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiled:
|
||||
relativePath = DefaultImageAssetPaths::Missing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the fallback image has been processed
|
||||
AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown;
|
||||
AzFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, relativePath);
|
||||
|
||||
// Return the asset id of the fallback image
|
||||
Data::AssetId assetId{};
|
||||
bool autoRegisterIfNotFound = false;
|
||||
Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetId, &Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, relativePath,
|
||||
azrtti_typeid<AZ::RPI::StreamingImageAsset>(), autoRegisterIfNotFound);
|
||||
|
||||
return assetId;
|
||||
}
|
||||
} // namespace RPI
|
||||
} // namespace AZ
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Math/Vector4.h>
|
||||
#include <AzCore/Math/Color.h>
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace RPI
|
||||
@@ -123,7 +121,7 @@ namespace AZ
|
||||
|
||||
void LuaMaterialFunctor::Process(RuntimeContext& context)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
InitScriptContext();
|
||||
|
||||
@@ -141,7 +139,7 @@ namespace AZ
|
||||
|
||||
void LuaMaterialFunctor::Process(EditorContext& context)
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
InitScriptContext();
|
||||
|
||||
|
||||
@@ -226,10 +226,12 @@ namespace AZ
|
||||
|
||||
if (changesWereApplied)
|
||||
{
|
||||
AZ_Warning("MaterialAsset", false,
|
||||
AZ_Warning(
|
||||
"MaterialAsset", false,
|
||||
"This material is based on version '%u' of %s, but the material type is now at version '%u'. "
|
||||
"Automatic updates are available. Consider updating the .material source file.",
|
||||
originalVersion, m_materialTypeAsset.ToString<AZStd::string>().c_str(), m_materialTypeAsset->GetVersion());
|
||||
"Automatic updates are available. Consider updating the .material source file for '%s'.",
|
||||
originalVersion, m_materialTypeAsset.ToString<AZStd::string>().c_str(), m_materialTypeAsset->GetVersion(),
|
||||
GetId().ToString<AZStd::string>().c_str());
|
||||
}
|
||||
|
||||
m_materialTypeVersion = m_materialTypeAsset->GetVersion();
|
||||
@@ -237,7 +239,7 @@ namespace AZ
|
||||
|
||||
void MaterialAsset::ReinitializeMaterialTypeAsset(Data::Asset<Data::AssetData> asset)
|
||||
{
|
||||
Data::Asset<MaterialTypeAsset> newMaterialTypeAsset = { asset.GetAs<MaterialTypeAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
Data::Asset<MaterialTypeAsset> newMaterialTypeAsset = Data::static_pointer_cast<MaterialTypeAsset>(asset);
|
||||
|
||||
if (newMaterialTypeAsset)
|
||||
{
|
||||
|
||||
@@ -118,12 +118,16 @@ namespace AZ
|
||||
else if (value.is<Data::Asset<Data::AssetData>>())
|
||||
{
|
||||
result.m_value = Data::Asset<RPI::ImageAsset>(
|
||||
AZStd::any_cast<Data::Asset<Data::AssetData>>(value).GetId(), azrtti_typeid<RPI::StreamingImageAsset>());
|
||||
AZStd::any_cast<Data::Asset<Data::AssetData>>(value).GetId(),
|
||||
azrtti_typeid<RPI::StreamingImageAsset>(),
|
||||
AZStd::any_cast<Data::Asset<Data::AssetData>>(value).GetHint());
|
||||
}
|
||||
else if (value.is<Data::Asset<StreamingImageAsset>>())
|
||||
{
|
||||
result.m_value = Data::Asset<RPI::ImageAsset>(
|
||||
AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetId(), azrtti_typeid<RPI::StreamingImageAsset>());
|
||||
AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetId(),
|
||||
azrtti_typeid<RPI::StreamingImageAsset>(),
|
||||
AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetHint());
|
||||
}
|
||||
else if (value.is<Data::Asset<ImageAsset>>())
|
||||
{
|
||||
|
||||
@@ -188,6 +188,10 @@ namespace AZ
|
||||
void MaterialTypeAsset::SetReady()
|
||||
{
|
||||
m_status = AssetStatus::Ready;
|
||||
|
||||
// If this was created dynamically using MaterialTypeAssetCreator (which is what calls SetReady()),
|
||||
// we need to connect to the AssetBus for reloads.
|
||||
PostLoadInit();
|
||||
}
|
||||
|
||||
bool MaterialTypeAsset::PostLoadInit()
|
||||
|
||||
@@ -126,8 +126,8 @@ namespace AZ
|
||||
}
|
||||
|
||||
ShaderCollection::Item::Item()
|
||||
: m_renderStatesOverlay(RHI::GetInvalidRenderStates())
|
||||
{
|
||||
m_renderStatesOverlay = RHI::GetInvalidRenderStates();
|
||||
}
|
||||
|
||||
ShaderCollection::Item& ShaderCollection::operator[](size_t i)
|
||||
@@ -156,7 +156,8 @@ namespace AZ
|
||||
}
|
||||
|
||||
ShaderCollection::Item::Item(const Data::Asset<ShaderAsset>& shaderAsset, const AZ::Name& shaderTag, ShaderVariantId variantId)
|
||||
: m_shaderAsset(shaderAsset)
|
||||
: m_renderStatesOverlay(RHI::GetInvalidRenderStates())
|
||||
, m_shaderAsset(shaderAsset)
|
||||
, m_shaderVariantId(variantId)
|
||||
, m_shaderTag(shaderTag)
|
||||
, m_shaderOptionGroup(shaderAsset->GetShaderOptionGroupLayout(), variantId)
|
||||
@@ -164,7 +165,8 @@ namespace AZ
|
||||
}
|
||||
|
||||
ShaderCollection::Item::Item(Data::Asset<ShaderAsset>&& shaderAsset, const AZ::Name& shaderTag, ShaderVariantId variantId)
|
||||
: m_shaderAsset(AZStd::move(shaderAsset))
|
||||
: m_renderStatesOverlay(RHI::GetInvalidRenderStates())
|
||||
, m_shaderAsset(AZStd::move(shaderAsset))
|
||||
, m_shaderVariantId(variantId)
|
||||
, m_shaderTag(shaderTag)
|
||||
, m_shaderOptionGroup(shaderAsset->GetShaderOptionGroupLayout(), variantId)
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
|
||||
#include <Atom/RPI.Reflect/Model/ModelKdTree.h>
|
||||
#include <AzCore/Asset/AssetSerializer.h>
|
||||
#include <AzCore/Debug/EventTrace.h>
|
||||
#include <AzCore/Jobs/JobFunction.h>
|
||||
#include <AzCore/Math/IntersectSegment.h>
|
||||
#include <AzCore/std/limits.h>
|
||||
@@ -137,7 +136,7 @@ namespace AZ
|
||||
// For runtime approach is to do this during asset processing and serialized spatial information alongside with mesh model assets
|
||||
const auto jobLambda = [&]() -> void
|
||||
{
|
||||
AZ_TRACE_METHOD();
|
||||
AZ_PROFILE_FUNCTION(RPI);
|
||||
|
||||
AZStd::unique_ptr<ModelKdTree> tree = AZStd::make_unique<ModelKdTree>();
|
||||
tree->Build(this);
|
||||
@@ -201,23 +200,11 @@ namespace AZ
|
||||
AZ::Vector3& normal) const
|
||||
{
|
||||
const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView();
|
||||
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
|
||||
const BufferAssetView* positionBufferView = mesh.GetSemanticBufferAssetView(m_positionName);
|
||||
|
||||
// find position semantic
|
||||
const ModelLodAsset::Mesh::StreamBufferInfo* positionBuffer = nullptr;
|
||||
|
||||
for (const ModelLodAsset::Mesh::StreamBufferInfo& bufferInfo : streamBufferList)
|
||||
if (positionBufferView && positionBufferView->GetBufferAsset().Get())
|
||||
{
|
||||
if (bufferInfo.m_semantic.m_name == m_positionName)
|
||||
{
|
||||
positionBuffer = &bufferInfo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (positionBuffer && positionBuffer->m_bufferAssetView.GetBufferAsset().Get())
|
||||
{
|
||||
BufferAsset* bufferAssetViewPtr = positionBuffer->m_bufferAssetView.GetBufferAsset().Get();
|
||||
BufferAsset* bufferAssetViewPtr = positionBufferView->GetBufferAsset().Get();
|
||||
BufferAsset* indexAssetViewPtr = indexBufferView.GetBufferAsset().Get();
|
||||
|
||||
if (!bufferAssetViewPtr || !indexAssetViewPtr)
|
||||
@@ -225,7 +212,7 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::BufferViewDescriptor positionBufferViewDesc = bufferAssetViewPtr->GetBufferViewDescriptor();
|
||||
RHI::BufferViewDescriptor positionBufferViewDesc = positionBufferView->GetBufferViewDescriptor();
|
||||
AZStd::array_view<uint8_t> positionRawBuffer = bufferAssetViewPtr->GetBuffer();
|
||||
|
||||
const uint32_t positionElementSize = positionBufferViewDesc.m_elementSize;
|
||||
@@ -234,22 +221,28 @@ namespace AZ
|
||||
// Position is 3 floats
|
||||
if (positionElementSize != sizeof(float) * 3)
|
||||
{
|
||||
AZ_Warning("ModelAsset", false, "unsupported mesh posiiton format, only full 3 floats per vertex are supported at the moment");
|
||||
AZ_Warning(
|
||||
"ModelAsset", false, "unsupported mesh posiiton format, only full 3 floats per vertex are supported at the moment");
|
||||
return false;
|
||||
}
|
||||
|
||||
RHI::BufferViewDescriptor indexBufferViewDesc = indexBufferView.GetBufferViewDescriptor();
|
||||
AZStd::array_view<uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer();
|
||||
RHI::BufferViewDescriptor indexRawDesc = indexAssetViewPtr->GetBufferViewDescriptor();
|
||||
|
||||
bool anyHit = false;
|
||||
|
||||
const AZ::Vector3 rayEnd = rayStart + rayDir;
|
||||
AZ::Vector3 a, b, c;
|
||||
AZ::Vector3 intersectionNormal;
|
||||
|
||||
bool anyHit = false;
|
||||
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
|
||||
const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(indexRawBuffer.data());
|
||||
for (uint32_t indexIter = 0; indexIter <= indexRawDesc.m_elementCount - 3; indexIter += 3, indexPtr += 3)
|
||||
|
||||
const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(
|
||||
indexRawBuffer.data() + (indexBufferViewDesc.m_elementOffset * indexBufferViewDesc.m_elementSize));
|
||||
const float* positionPtr = reinterpret_cast<const float*>(
|
||||
positionRawBuffer.data() + (positionBufferViewDesc.m_elementOffset * positionBufferViewDesc.m_elementSize));
|
||||
|
||||
constexpr int StepSize = 3; // number of values per vertex (x, y, z)
|
||||
for (uint32_t indexIter = 0; indexIter < indexBufferViewDesc.m_elementCount; indexIter += StepSize, indexPtr += StepSize)
|
||||
{
|
||||
AZ::u32 index0 = indexPtr[0];
|
||||
AZ::u32 index1 = indexPtr[1];
|
||||
@@ -261,17 +254,17 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
const float* p = reinterpret_cast<const float*>(&positionRawBuffer[index0 * positionElementSize]);
|
||||
a.Set(const_cast<float*>(p)); // faster than AZ::Vector3 c-tor
|
||||
|
||||
p = reinterpret_cast<const float*>(&positionRawBuffer[index1 * positionElementSize]);
|
||||
b.Set(const_cast<float*>(p));
|
||||
|
||||
p = reinterpret_cast<const float*>(&positionRawBuffer[index2 * positionElementSize]);
|
||||
c.Set(const_cast<float*>(p));
|
||||
// faster than AZ::Vector3 c-tor
|
||||
const float* aRef = &positionPtr[index0 * StepSize];
|
||||
a.Set(aRef);
|
||||
const float* bRef = &positionPtr[index1 * StepSize];
|
||||
b.Set(bRef);
|
||||
const float* cRef = &positionPtr[index2 * StepSize];
|
||||
c.Set(cRef);
|
||||
|
||||
float currentDistanceNormalized;
|
||||
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, intersectionNormal, currentDistanceNormalized))
|
||||
if (AZ::Intersect::IntersectSegmentTriangleCCW(
|
||||
rayStart, rayEnd, a, b, c, intersectionNormal, currentDistanceNormalized))
|
||||
{
|
||||
anyHit = true;
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace AZ
|
||||
->Field("pipelineStateType", &ShaderAsset::m_pipelineStateType)
|
||||
->Field("shaderOptionGroupLayout", &ShaderAsset::m_shaderOptionGroupLayout)
|
||||
->Field("drawListName", &ShaderAsset::m_drawListName)
|
||||
->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_shaderAssetBuildTimestamp)
|
||||
->Field("shaderAssetBuildTimestamp", &ShaderAsset::m_buildTimestamp)
|
||||
->Field("perAPIShaderData", &ShaderAsset::m_perAPIShaderData)
|
||||
;
|
||||
}
|
||||
@@ -108,7 +108,6 @@ namespace AZ
|
||||
|
||||
ShaderAsset::~ShaderAsset()
|
||||
{
|
||||
Data::AssetBus::Handler::BusDisconnect();
|
||||
ShaderVariantFinderNotificationBus::Handler::BusDisconnect();
|
||||
AssetInitBus::Handler::BusDisconnect();
|
||||
}
|
||||
@@ -134,11 +133,11 @@ namespace AZ
|
||||
return m_drawListName;
|
||||
}
|
||||
|
||||
AZStd::sys_time_t ShaderAsset::GetShaderAssetBuildTimestamp() const
|
||||
AZStd::sys_time_t ShaderAsset::GetBuildTimestamp() const
|
||||
{
|
||||
return m_shaderAssetBuildTimestamp;
|
||||
return m_buildTimestamp;
|
||||
}
|
||||
|
||||
|
||||
void ShaderAsset::SetReady()
|
||||
{
|
||||
m_status = AssetStatus::Ready;
|
||||
@@ -256,7 +255,7 @@ namespace AZ
|
||||
}
|
||||
return GetRootVariant(supervariantIndex);
|
||||
}
|
||||
else if (variant->GetBuildTimestamp() >= m_shaderAssetBuildTimestamp)
|
||||
else if (variant->GetBuildTimestamp() >= m_buildTimestamp)
|
||||
{
|
||||
return variant;
|
||||
}
|
||||
@@ -570,46 +569,16 @@ namespace AZ
|
||||
|
||||
bool ShaderAsset::PostLoadInit()
|
||||
{
|
||||
// Once the ShaderAsset is loaded, it is necessary to listen for changes in the Root Variant Asset.
|
||||
Data::AssetBus::Handler::BusConnect(GetRootVariant().GetId());
|
||||
ShaderVariantFinderNotificationBus::Handler::BusConnect(GetId());
|
||||
|
||||
AssetInitBus::Handler::BusDisconnect();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderAsset::ReinitializeRootShaderVariant(Data::Asset<Data::AssetData> asset)
|
||||
{
|
||||
Data::Asset<ShaderVariantAsset> shaderVariantAsset = { asset.GetAs<ShaderVariantAsset>(), AZ::Data::AssetLoadBehavior::PreLoad };
|
||||
AZ_Assert(shaderVariantAsset->GetStableId() == RootShaderVariantStableId, "Was expecting to update the root variant");
|
||||
SupervariantIndex supervariantIndex = GetSupervariantIndexFromAssetId(asset.GetId());
|
||||
GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = asset;
|
||||
ShaderReloadNotificationBus::Event(GetId(), &ShaderReloadNotificationBus::Events::OnShaderAssetReinitialized, Data::Asset<ShaderAsset>{ this, AZ::Data::AssetLoadBehavior::PreLoad } );
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// AssetBus overrides...
|
||||
void ShaderAsset::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
|
||||
|
||||
void ShaderAsset::UpdateRootShaderVariantAsset(SupervariantIndex supervariantIndex, Data::Asset<ShaderVariantAsset> newRootVariant)
|
||||
{
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->ShaderAsset::OnAssetReloaded %s", this, asset.GetHint().c_str());
|
||||
ReinitializeRootShaderVariant(asset);
|
||||
GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = newRootVariant;
|
||||
}
|
||||
void ShaderAsset::OnAssetReady(Data::Asset<Data::AssetData> asset)
|
||||
{
|
||||
// We have to listen to OnAssetReady, OnAssetReloaded isn't enough, because of the following scenario:
|
||||
// The user changes a .shader file, which causes the AP to rebuild the ShaderAsset and root ShaderVariantAsset.
|
||||
// 1) Thread A creates the new ShaderAsset, loads it, and gets the old ShaderVariantAsset.
|
||||
// 2) Thread B creates the new ShaderVariantAsset, loads it, and calls OnAssetReloaded.
|
||||
// 3) Main thread calls ShaderAsset::PostLoadInit which connects to the AssetBus but it's too late to receive OnAssetReloaded,
|
||||
// so it continues using the old ShaderVariantAsset instead of the new one.
|
||||
// The OnAssetReady bus function is called automatically whenever a connection to AssetBus is made, so listening to this gives
|
||||
// us the opportunity to assign the appropriate ShaderVariantAsset.
|
||||
|
||||
ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->ShaderAsset::OnAssetReady %s", this, asset.GetHint().c_str());
|
||||
ReinitializeRootShaderVariant(asset);
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
/// ShaderVariantFinderNotificationBus overrides
|
||||
@@ -628,7 +597,6 @@ namespace AZ
|
||||
m_shaderVariantTree = shaderVariantTreeAsset;
|
||||
}
|
||||
lock.unlock();
|
||||
ShaderReloadNotificationBus::Event(GetId(), &ShaderReloadNotificationBus::Events::OnShaderAssetReinitialized, Data::Asset<ShaderAsset>{ this, AZ::Data::AssetLoadBehavior::PreLoad });
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace AZ
|
||||
{
|
||||
if (ValidateIsReady())
|
||||
{
|
||||
m_asset->m_shaderAssetBuildTimestamp = shaderAssetBuildTimestamp;
|
||||
m_asset->m_buildTimestamp = shaderAssetBuildTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ namespace AZ
|
||||
m_asset->m_pipelineStateType = sourceShaderAsset.m_pipelineStateType;
|
||||
m_asset->m_drawListName = sourceShaderAsset.m_drawListName;
|
||||
m_asset->m_shaderOptionGroupLayout = sourceShaderAsset.m_shaderOptionGroupLayout;
|
||||
m_asset->m_shaderAssetBuildTimestamp = sourceShaderAsset.m_shaderAssetBuildTimestamp;
|
||||
m_asset->m_buildTimestamp = sourceShaderAsset.m_buildTimestamp;
|
||||
|
||||
// copy root variant assets
|
||||
for (auto& perAPIShaderData : sourceShaderAsset.m_perAPIShaderData)
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::sys_time_t ShaderVariantAsset::GetBuildTimestamp() const
|
||||
AZ::u64 ShaderVariantAsset::GetBuildTimestamp() const
|
||||
{
|
||||
return m_buildTimestamp;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user