Updated Material Editor to use the available default fallback images to visually indicate a missing texture.

Material Editor also warns the user when saving a material that is populated with fallback image references.
Factored out the path strings for the default images to ImateSystemInterface.h.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
santorac
2021-11-10 19:28:42 -08:00
parent e40b226374
commit f27203a5fa
10 changed files with 113 additions and 27 deletions
@@ -28,7 +28,19 @@ namespace AZ
namespace MaterialUtils
{
Outcome<Data::Asset<ImageAsset>> GetImageAssetReference(AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath);
enum class GetImageAssetResult
{
Empty, //! No image was actually requested, the path was empty
Found, //! The requested asset was found
Missing, //! The requested asset was not found, and a placeholder asset was used instead
MissingNoFallback //! The requested asset was not found, and a placeholder asset was not found either
};
//! Finds an ImageAsset referenced by a material file (or a placeholder)
//! @param imageAsset the resulting ImageAsset
//! @param materialSourceFilePath the full path to a material source file that is referenfing an image file
//! @param imageFilePath the path to an image source file, which could be relative to the asset root or relative to the material file
GetImageAssetResult GetImageAssetReference(Data::Asset<ImageAsset>& imageAsset, AZStd::string_view materialSourceFilePath, const AZStd::string imageFilePath);
//! Resolve an enum to a uint32_t given its name and definition array (in MaterialPropertyDescriptor).
//! @param propertyDescriptor it contains the definition of all enum names in an array.
@@ -29,6 +29,14 @@ namespace AZ
Count
};
namespace DefaultImageAssetPaths
{
static constexpr char DefaultFallback[] = "textures/defaults/defaultfallback.png.streamingimage";
static constexpr char Processing[] = "textures/defaults/processing.png.streamingimage";
static constexpr char ProcessingFailed[] = "textures/defaults/processingfailed.png.streamingimage";
static constexpr char Missing[] = "textures/defaults/missing.png.streamingimage";
}
class ImageSystemInterface
{
public:
@@ -317,22 +317,20 @@ namespace AZ
{
case MaterialPropertyDataType::Image:
{
Outcome<Data::Asset<ImageAsset>> imageAssetResult = MaterialUtils::GetImageAssetReference(
materialSourceFilePath, property.second.m_value.GetValue<AZStd::string>());
Data::Asset<ImageAsset> imageAsset;
if (imageAssetResult.IsSuccess())
{
auto& imageAsset = imageAssetResult.GetValue();
// Load referenced images when load material
imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad);
materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
}
else
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
imageAsset, materialSourceFilePath, property.second.m_value.GetValue<AZStd::string>());
if (result == MaterialUtils::GetImageAssetResult::Missing || result == MaterialUtils::GetImageAssetResult::MissingNoFallback)
{
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:
@@ -493,12 +493,13 @@ namespace AZ
{
case MaterialPropertyDataType::Image:
{
auto imageAssetResult = MaterialUtils::GetImageAssetReference(
materialTypeSourceFilePath, property.m_value.GetValue<AZStd::string>());
Data::Asset<ImageAsset> imageAsset;
if (imageAssetResult)
MaterialUtils::GetImageAssetResult result = MaterialUtils::GetImageAssetReference(
imageAsset, materialTypeSourceFilePath, property.m_value.GetValue<AZStd::string>());
if (result == MaterialUtils::GetImageAssetResult::Empty || result == MaterialUtils::GetImageAssetResult::Found)
{
auto imageAsset = imageAssetResult.GetValue();
materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset);
}
else
@@ -28,25 +28,37 @@ 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());
if (!imageAssetId.IsSuccess())
{
return AZ::Failure();
}
else
{
Data::Asset<ImageAsset> unloadedImageAssetReference(imageAssetId.GetValue(), azrtti_typeid<StreamingImageAsset>(), imageFilePath);
return AZ::Success(unloadedImageAssetReference);
constexpr static char ErrorMissingTexture[] = "textures/defaults/missing.png";
imageAssetId = AssetUtils::MakeAssetId(ErrorMissingTexture, StreamingImageAsset::GetImageAssetSubId());
if (imageAssetId.IsSuccess())
{
imageAsset = Data::Asset<ImageAsset>{imageAssetId.GetValue(), azrtti_typeid<StreamingImageAsset>(), imageFilePath};
return GetImageAssetResult::Missing;
}
else
{
return GetImageAssetResult::MissingNoFallback;
}
}
imageAsset = Data::Asset<ImageAsset>{imageAssetId.GetValue(), azrtti_typeid<StreamingImageAsset>(), imageFilePath};
return GetImageAssetResult::Found;
}
}
@@ -7,6 +7,7 @@
*/
#include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzFramework/Asset/AssetSystemBus.h>
@@ -53,7 +54,7 @@ namespace AZ
// Generate asset info to use to register the fallback asset with the asset catalog
Data::AssetInfo assetInfo;
assetInfo.m_relativePath = "textures/defaults/defaultfallback.png.streamingimage";
assetInfo.m_relativePath = DefaultImageAssetPaths::DefaultFallback;
assetInfo.m_assetType = azrtti_typeid<StreamingImageAsset>();
bool useDebugFallbackImages = true;
@@ -68,15 +69,15 @@ namespace AZ
{
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Queued:
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiling:
assetInfo.m_relativePath = "textures/defaults/processing.png.streamingimage";
assetInfo.m_relativePath = DefaultImageAssetPaths::Processing;
break;
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Failed:
assetInfo.m_relativePath = "textures/defaults/processingfailed.png.streamingimage";
assetInfo.m_relativePath = DefaultImageAssetPaths::ProcessingFailed;
break;
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Missing:
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Unknown:
case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiled:
assetInfo.m_relativePath = "textures/defaults/missing.png.streamingimage";
assetInfo.m_relativePath = DefaultImageAssetPaths::Missing;
break;
}
}
@@ -73,6 +73,9 @@ namespace AtomToolsFramework
//! Can the document be saved
virtual bool IsSavable() const = 0;
//! Get a list of warnings about the data that would be good to know before saving
virtual AZStd::vector<AZStd::string> GetDataWarnings() const { return {}; }
//! Returns true if there are reversible modifications to the document
virtual bool CanUndo() const = 0;
@@ -365,6 +365,24 @@ namespace AtomToolsFramework
return false;
}
AZStd::vector<AZStd::string> dataWarnings;
AtomToolsDocumentRequestBus::EventResult(dataWarnings, documentId, &AtomToolsDocumentRequestBus::Events::GetDataWarnings);
if (!dataWarnings.empty())
{
AZStd::string allWarnings;
AzFramework::StringFunc::Join(allWarnings, dataWarnings.begin(), dataWarnings.end(), "\n");
auto result = QMessageBox::warning(
QApplication::activeWindow(), QString("Data Warnings"),
QString("Are you sure you want to save with the following data warnings? \n\n%1").arg(allWarnings.c_str()),
QMessageBox::StandardButton::Yes, QMessageBox::StandardButton::No);
if (result == QMessageBox::StandardButton::No)
{
return false;
}
}
AtomToolsFramework::TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
bool result = false;
@@ -12,6 +12,7 @@
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
@@ -467,6 +468,37 @@ namespace MaterialEditor
{
return AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
}
AZStd::vector<AZStd::string> MaterialDocument::GetDataWarnings() const
{
AZStd::vector<AZStd::string> warnings;
for (auto& [propertyName, dynamicProperty] : m_properties)
{
AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(dynamicProperty.GetValue());
if (propertyValue.Is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
{
auto isSameAsset = [&propertyValue](const char* path)
{
AZ::Data::AssetId assetId = propertyValue.GetValue<AZ::Data::Asset<AZ::RPI::ImageAsset>>().GetId();
AZ::Data::AssetId otherAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(otherAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, path, AZ::Data::AssetType{}, false);
return assetId == otherAssetId;
};
if (isSameAsset(AZ::RPI::DefaultImageAssetPaths::DefaultFallback) ||
isSameAsset(AZ::RPI::DefaultImageAssetPaths::Missing) ||
isSameAsset(AZ::RPI::DefaultImageAssetPaths::Processing) ||
isSameAsset(AZ::RPI::DefaultImageAssetPaths::ProcessingFailed)
)
{
warnings.push_back(AZStd::string::format("%s is using a placeholder image asset.", propertyName.GetCStr()));
}
}
}
return warnings;
}
bool MaterialDocument::CanUndo() const
{
@@ -55,6 +55,7 @@ namespace MaterialEditor
bool IsOpen() const override;
bool IsModified() const override;
bool IsSavable() const override;
AZStd::vector<AZStd::string> GetDataWarnings() const override;
bool CanUndo() const override;
bool CanRedo() const override;
bool Undo() override;