Integrating up through commit 90f050496

This commit is contained in:
alexpete
2021-04-07 14:03:29 -07:00
parent 8f2ed080a9
commit c2cbd430fe
2694 changed files with 285622 additions and 176874 deletions
@@ -11,32 +11,26 @@
*/
#include <Material/EditorMaterialComponentExporter.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <AzQtComponents/Components/Widgets/BrowseEdit.h>
#include <Material/EditorMaterialComponentUtil.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzQtComponents/Components/Widgets/BrowseEdit.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QApplication>
#include <QTableWidget>
#include <QHeaderView>
#include <QFileDialog>
#include <QCheckBox>
#include <QComboBox>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QHBoxLayout>
#include <QTableWidget>
#include <QVBoxLayout>
AZ_POP_DISABLE_WARNING
@@ -102,28 +96,6 @@ namespace AZ
return exportPath;
}
// Returns message text based on the item state
QString GetExportItemStatusMessage(const ExportItem& exportItem)
{
QFileInfo fileInfo(exportItem.m_exportPath.c_str());
if (!exportItem.m_enabled)
{
return QString("Do not generate a new material.");
}
if (fileInfo == QFileInfo())
{
return QString("A valid material file path is required.");
}
if (fileInfo.exists())
{
return QString("\"%1\" will be replaced in the designated folder.").arg(fileInfo.fileName());
}
return QString("\"%1\" will be generated in the designated folder.").arg(fileInfo.fileName());
}
bool OpenExportDialog(ExportItemsContainer& exportItems)
{
QWidget* activeWindow = nullptr;
@@ -133,11 +105,10 @@ namespace AZ
QDialog dialog(activeWindow);
dialog.setWindowTitle("Generate Source Materials");
const QStringList headerLabels = { "Enable", "Material Slot", "Material Filename", "Status" };
const int EnableColumn = 0;
const int MaterialColumn = 1;
const int FileColumn = 2;
const int StatusColumn = 3;
const QStringList headerLabels = { "Material Slot", "Material Filename", "Overwrite" };
const int MaterialSlotColumn = 0;
const int MaterialFileColumn = 1;
const int OverwriteFileColumn = 2;
// Create a table widget that will be filled with all of the data and options for each exported material
QTableWidget* tableWidget = new QTableWidget(&dialog);
@@ -153,8 +124,10 @@ namespace AZ
tableWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
// Force the table to stretch its header to fill the entire width of the dialog
tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
tableWidget->horizontalHeader()->setStretchLastSection(true);
tableWidget->horizontalHeader()->setSectionResizeMode(MaterialSlotColumn, QHeaderView::ResizeToContents);
tableWidget->horizontalHeader()->setSectionResizeMode(MaterialFileColumn, QHeaderView::Stretch);
tableWidget->horizontalHeader()->setSectionResizeMode(OverwriteFileColumn, QHeaderView::ResizeToContents);
tableWidget->horizontalHeader()->setStretchLastSection(false);
// Hide row numbers
tableWidget->verticalHeader()->setVisible(false);
@@ -166,46 +139,55 @@ namespace AZ
// Configuring initial settings based on whether or not the target file already exists
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData();
exportItem.m_exists = fileInfo.exists();
exportItem.m_overwrite = false;
// Populate the table with data for every column
tableWidget->setItem(row, EnableColumn, new QTableWidgetItem(exportItem.m_enabled));
tableWidget->setItem(row, MaterialColumn, new QTableWidgetItem(GetLabelByAssetId(exportItem.m_assetId).c_str()));
tableWidget->setItem(row, FileColumn, new QTableWidgetItem(fileInfo.fileName()));
tableWidget->setItem(row, MaterialSlotColumn, new QTableWidgetItem());
tableWidget->setItem(row, MaterialFileColumn, new QTableWidgetItem());
tableWidget->setItem(row, OverwriteFileColumn, new QTableWidgetItem());
// Create a check box for toggling the enabled state of this item
QWidget* enableCheckBoxParent = new QWidget(tableWidget);
QCheckBox* enableCheckBox = new QCheckBox(enableCheckBoxParent);
enableCheckBox->setChecked(exportItem.m_enabled);
// Center checkbox in cell
QHBoxLayout* enableCheckBoxLayout = new QHBoxLayout(enableCheckBoxParent);
enableCheckBoxLayout->setAlignment(Qt::AlignCenter);
enableCheckBoxLayout->addWidget(enableCheckBox);
enableCheckBoxParent->setLayout(enableCheckBoxLayout);
tableWidget->setCellWidget(row, EnableColumn, enableCheckBoxParent);
QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget);
materialSlotCheckBox->setChecked(exportItem.m_enabled);
materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_assetId).c_str());
tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox);
// Create a file picker widget for selecting the save path for the exported material
AzQtComponents::BrowseEdit* fileWidget = new AzQtComponents::BrowseEdit(tableWidget);
fileWidget->setLineEditReadOnly(true);
fileWidget->setClearButtonEnabled(false);
fileWidget->setText(fileInfo.fileName());
tableWidget->setCellWidget(row, FileColumn, fileWidget);
AzQtComponents::BrowseEdit* materialFileWidget = new AzQtComponents::BrowseEdit(tableWidget);
materialFileWidget->setLineEditReadOnly(true);
materialFileWidget->setClearButtonEnabled(false);
materialFileWidget->setEnabled(exportItem.m_enabled);
materialFileWidget->setText(fileInfo.fileName());
tableWidget->setCellWidget(row, MaterialFileColumn, materialFileWidget);
// The status widget will be used to inform the user of issues and outcomes from selected settings
QLabel* statusWidget = new QLabel(tableWidget);
statusWidget->setText(GetExportItemStatusMessage(exportItem));
tableWidget->setCellWidget(row, StatusColumn, statusWidget);
// Create a check box for toggling the overwrite state of this item
QWidget* overwriteCheckBoxContainer = new QWidget(tableWidget);
QCheckBox* overwriteCheckBox = new QCheckBox(overwriteCheckBoxContainer);
overwriteCheckBox->setChecked(exportItem.m_overwrite);
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
overwriteCheckBoxContainer->setLayout(new QHBoxLayout(overwriteCheckBoxContainer));
overwriteCheckBoxContainer->layout()->addWidget(overwriteCheckBox);
overwriteCheckBoxContainer->layout()->setAlignment(Qt::AlignCenter);
overwriteCheckBoxContainer->layout()->setContentsMargins(0, 0, 0, 0);
tableWidget->setCellWidget(row, OverwriteFileColumn, overwriteCheckBoxContainer);
// Whenever the selection is updated, automatically apply the change to the export item
QObject::connect(enableCheckBox, &QCheckBox::stateChanged, enableCheckBox, [&dialog, &exportItem, enableCheckBox, fileWidget, statusWidget]([[maybe_unused]] int state) {
exportItem.m_enabled = enableCheckBox->isChecked();
fileWidget->setEnabled(exportItem.m_enabled);
statusWidget->setText(GetExportItemStatusMessage(exportItem));
QObject::connect(materialSlotCheckBox, &QCheckBox::stateChanged, materialSlotCheckBox, [&]([[maybe_unused]] int state) {
exportItem.m_enabled = materialSlotCheckBox->isChecked();
materialFileWidget->setEnabled(exportItem.m_enabled);
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
});
// Whenever the overwrite check box is updated, automatically apply the change to the export item
QObject::connect(overwriteCheckBox, &QCheckBox::stateChanged, overwriteCheckBox, [&]([[maybe_unused]] int state) {
exportItem.m_overwrite = overwriteCheckBox->isChecked();
});
// Whenever the browse button is clicked, open a save file dialog in the same location as the current export file setting
QObject::connect(fileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, fileWidget, [&dialog, &exportItem, enableCheckBox, fileWidget, statusWidget]() {
QObject::connect(materialFileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, materialFileWidget, [&]() {
QFileInfo fileInfo = QFileDialog::getSaveFileName(&dialog,
QString("Select Material Filename"),
exportItem.m_exportPath.c_str(),
@@ -213,21 +195,24 @@ namespace AZ
nullptr,
QFileDialog::DontConfirmOverwrite);
if (fileInfo != QFileInfo())
// Only update the export data if a valid path and filename was selected
if (!fileInfo.absoluteFilePath().isEmpty())
{
// Only update the export data if a valid path and filename was selected
exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData();
exportItem.m_exists = fileInfo.exists();
exportItem.m_overwrite = fileInfo.exists();
\
// Update the controls to display the new state
fileWidget->setText(fileInfo.fileName());
statusWidget->setText(GetExportItemStatusMessage(exportItem));
materialFileWidget->setText(fileInfo.fileName());
overwriteCheckBox->setChecked(exportItem.m_overwrite);
overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists);
}
});
++row;
}
tableWidget->sortItems(MaterialColumn);
tableWidget->sortItems(MaterialSlotColumn);
// Create the bottom row of the dialog with action buttons for exporting or canceling the operation
QWidget* buttonRow = new QWidget(&dialog);
@@ -245,7 +230,8 @@ namespace AZ
buttonLayout->addWidget(cancelButton);
// Create a heading label for the top of the dialog
QLabel* labelWidget = new QLabel("Select the material slots that you want to generate new source materials for. Edit the material file name and location using the file picker.", &dialog);
QLabel* labelWidget = new QLabel("\nSelect the material slots that you want to generate new source materials for. Edit the material file name and location using the file picker.\n", &dialog);
labelWidget->setWordWrap(true);
QVBoxLayout* dialogLayout = new QVBoxLayout(&dialog);
dialogLayout->addWidget(labelWidget);
@@ -257,7 +243,7 @@ namespace AZ
// Temporarily settng fixed size because dialog.show/exec invokes WindowDecorationWrapper::showEvent.
// This forces the dialog to be centered and sized based on the layout of content.
// Resizing the dialog after show will not be centered and moving the dialog programatically doesn't m0ve the custmk frame.
dialog.setFixedSize(1000, 200);
dialog.setFixedSize(500, 200);
dialog.show();
// Removing fixed size to allow drag resizing
@@ -275,78 +261,25 @@ namespace AZ
return false;
}
// Load the originating product asset from which the new source has set will be generated
auto materialAssetOutcome = AZ::RPI::AssetUtils::LoadAsset<AZ::RPI::MaterialAsset>(exportItem.m_assetId);
if (!materialAssetOutcome)
if (exportItem.m_exists && !exportItem.m_overwrite)
{
AZ_Error("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load initial material asset while attempting to export: %s", exportItem.m_exportPath.c_str());
return false;
}
AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset = materialAssetOutcome.GetValue();
AZ::Data::Asset<AZ::RPI::MaterialTypeAsset> materialTypeAsset = materialAsset->GetMaterialTypeAsset();
// We need a valid path to the material type source data because it's required for to get the property layout and assign to the new material
const AZStd::string& materialTypePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(materialTypeAsset.GetId());
if (materialTypePath.empty())
{
AZ_Error("AZ::Render::EditorMaterialComponentExporter", false, "Failed to locate source material type asset while attempting to export: %s", exportItem.m_exportPath.c_str());
return false;
}
// Getting the source info for the material type file to make sure that it exists
// We also need to watch folder to generate a relative asset path for the material type
bool result = false;
AZ::Data::AssetInfo info;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, materialTypePath.c_str(), info, watchFolder);
if (!result)
{
AZ_Error("AZ::Render::EditorMaterialComponentExporter", false, "Failed to get source file info and asset path while attempting to export: %s", exportItem.m_exportPath.c_str());
return false;
}
// At this point, we should be ready to attempt to load the material type data
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(materialTypePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material type source data: %s", materialTypePath.c_str());
return false;
}
AZ::RPI::MaterialTypeSourceData materialTypeSourceData = materialTypeOutcome.GetValue();
// Construct the material source data object that will be exported
AZ::RPI::MaterialSourceData exportData;
exportData.m_propertyLayoutVersion = materialTypeSourceData.m_propertyLayout.m_version;
// Converting the absolute material type app to an asset relative path
exportData.m_materialType = materialTypePath;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::Bus::Events::MakePathRelative, exportData.m_materialType, watchFolder.c_str());
// Copy all of the properties from the material asset to the source data that will be exported
result = true;
materialTypeSourceData.EnumerateProperties([&materialAsset, &materialTypeSourceData, &exportData, &exportItem, &result](const AZStd::string& groupNameId, const AZStd::string& propertyNameId, const auto& propertyDefinition) {
const AZ::RPI::MaterialPropertyId propertyId(groupNameId, propertyNameId);
const AZ::RPI::MaterialPropertyIndex propertyIndex = materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName());
AZ::RPI::MaterialPropertyValue propertyValue = materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
if (!materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
{
AZ_Error("AZ::Render::EditorMaterialComponentExporter", false, "Failed to export: %s", exportItem.m_exportPath.c_str());
result = false;
return false;
}
if (propertyDefinition.m_value == propertyValue)
{
return true;
}
exportData.m_properties[groupNameId][propertyDefinition.m_nameId].m_value = propertyValue;
return true;
});
}
return result && AZ::RPI::JsonUtils::SaveObjectToFile(exportItem.m_exportPath, exportData);
EditorMaterialComponentUtil::MaterialEditData editData;
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_assetId, editData))
{
AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data.");
return false;
}
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.m_exportPath, editData))
{
AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to save material data.");
return false;
}
return true;
}
} // namespace EditorMaterialComponentExporter
} // namespace Render
@@ -32,6 +32,8 @@ namespace AZ
struct ExportItem
{
bool m_enabled = true;
bool m_exists = false;
bool m_overwrite = false;
AZ::Data::AssetId m_assetId;
AZStd::string m_exportPath;
};
@@ -11,32 +11,39 @@
*/
#include <Material/EditorMaterialComponentInspector.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
#include <Atom/RPI.Edit/Material/MaterialFunctorSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzQtComponents/Components/Widgets/Text.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/Thumbnails/ThumbnailWidget.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QApplication>
#include <QDialog>
#include <QPushButton>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
AZ_POP_DISABLE_WARNING
namespace AZ
@@ -45,8 +52,11 @@ namespace AZ
{
namespace EditorMaterialComponentInspector
{
MaterialPropertyInspector::MaterialPropertyInspector(const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback, QWidget* parent)
MaterialPropertyInspector::MaterialPropertyInspector(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback,
QWidget* parent)
: AtomToolsFramework::InspectorWidget(parent)
, m_slotName(slotName)
, m_materialAssetId(assetId)
, m_propertyChangedCallback(propertyChangedCallback)
{
@@ -59,65 +69,25 @@ namespace AZ
bool MaterialPropertyInspector::LoadMaterial()
{
if (!m_materialAssetId.IsValid())
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(m_materialAssetId, m_editData))
{
AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Attempted to load material data for invalid asset id.");
AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Failed to load material data.");
return false;
}
// Load the originating product asset from which the new source has set will be generated
auto materialAssetOutcome = AZ::RPI::AssetUtils::LoadAsset<AZ::RPI::MaterialAsset>(m_materialAssetId);
if (!materialAssetOutcome)
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Failed to load material asset: %s", m_materialAssetId.ToString<AZStd::string>().c_str());
return false;
}
m_materialAsset = materialAssetOutcome.GetValue();
m_materialTypeAsset = m_materialAsset->GetMaterialTypeAsset();
m_parentMaterialAsset = {};
// The material instance is still needed for functor execution
m_materialInstance = AZ::RPI::Material::Create(m_materialAsset);
m_materialInstance = AZ::RPI::Material::Create(m_editData.m_materialAsset);
if (!m_materialInstance)
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material instance could not be created.");
return false;
}
// We need a valid path to the material type source data because it's required for to get the property layout and assign to the new material
const AZStd::string& materialTypePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_materialTypeAsset.GetId());
if (materialTypePath.empty())
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Failed to locate source material type asset: %s", m_materialAssetId.ToString<AZStd::string>().c_str());
return false;
}
// Getting the source info for the material type file to make sure that it exists
// We also need to watch folder to generate a relative asset path for the material type
bool result = false;
AZ::Data::AssetInfo info;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, materialTypePath.c_str(), info, watchFolder);
if (!result)
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Failed to get source file info and asset path: %s", materialTypePath.c_str());
return false;
}
// At this point, we should be ready to attempt to load the material type data
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(materialTypePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Failed to load material type source data: %s", materialTypePath.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.GetValue();
// Get a list of all the editor functors to be used for property editor states
auto propertyLayout = m_materialAsset->GetMaterialPropertiesLayout();
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(materialTypePath, propertyLayout);
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData)
auto propertyLayout = m_editData.m_materialAsset->GetMaterialPropertiesLayout();
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext =
AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout);
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_editData.m_materialTypeSourceData.m_materialFunctorSourceData)
{
AZ::RPI::MaterialFunctorSourceData::FunctorResult createResult = functorData->CreateFunctor(editorContext);
@@ -131,7 +101,7 @@ namespace AZ
}
else
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material functors were not created: '%s'.", materialTypePath.c_str());
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material functors were not created: '%s'.", m_editData.m_materialTypeSourcePath.c_str());
}
}
@@ -153,40 +123,61 @@ namespace AZ
const AZStd::string& groupNameId = "Details";
const AZStd::string& groupDisplayName = "Details";
const AZStd::string& groupDescription = "";
auto& group = m_groups[groupNameId];
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset;
propertyConfig.m_id = "details.materialType";
propertyConfig.m_nameId = "materialType";
propertyConfig.m_displayName = "Material Type";
propertyConfig.m_description = propertyConfig.m_displayName;
propertyConfig.m_defaultValue = AZStd::any(m_materialAsset->GetMaterialTypeAsset());
propertyConfig.m_originalValue = propertyConfig.m_defaultValue;
propertyConfig.m_parentValue = propertyConfig.m_defaultValue;
propertyConfig.m_readOnly = true;
group.m_properties.emplace_back(propertyConfig);
auto propertyGroupContainer = new QWidget(this);
propertyGroupContainer->setLayout(new QHBoxLayout());
propertyConfig = {};
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset;
propertyConfig.m_id = "details.parentMaterial";
propertyConfig.m_nameId = "parentMaterial";
propertyConfig.m_displayName = "Parent Material";
propertyConfig.m_description = propertyConfig.m_displayName;
propertyConfig.m_defaultValue = AZStd::any(m_parentMaterialAsset);
propertyConfig.m_originalValue = propertyConfig.m_defaultValue;
propertyConfig.m_parentValue = propertyConfig.m_defaultValue;
propertyConfig.m_readOnly = true;
group.m_properties.emplace_back(propertyConfig);
AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey =
MAKE_TKEY(AzToolsFramework::AssetBrowser::ProductThumbnailKey, m_editData.m_materialAssetId);
auto thumbnailWidget = new AzToolsFramework::Thumbnailer::ThumbnailWidget(this);
thumbnailWidget->setFixedSize(QSize(120, 120));
thumbnailWidget->setVisible(true);
thumbnailWidget->SetThumbnailKey(thumbnailKey, AzToolsFramework::Thumbnailer::ThumbnailContext::DefaultContext);
propertyGroupContainer->layout()->addWidget(thumbnailWidget);
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(&group, &group, group.TYPEINFO_Uuid(), this, this,
[this](const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target) {
AZ_UNUSED(source);
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(target);
return property && AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue);
});
AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget);
auto materialInfoWidget = new QLabel(this);
QSizePolicy sizePolicy1(QSizePolicy::Ignored, QSizePolicy::Preferred);
sizePolicy1.setHorizontalStretch(0);
sizePolicy1.setVerticalStretch(0);
sizePolicy1.setHeightForWidth(materialInfoWidget->sizePolicy().hasHeightForWidth());
materialInfoWidget->setSizePolicy(sizePolicy1);
materialInfoWidget->setMinimumSize(QSize(0, 0));
materialInfoWidget->setMaximumSize(QSize(16777215, 16777215));
materialInfoWidget->setTextFormat(Qt::AutoText);
materialInfoWidget->setScaledContents(false);
materialInfoWidget->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignTop);
materialInfoWidget->setWordWrap(true);
QFileInfo materialFileInfo(AZ::RPI::AssetUtils::GetProductPathByAssetId(m_editData.m_materialAsset.GetId()).c_str());
QFileInfo materialSourceFileInfo(m_editData.m_materialSourcePath.c_str());
QFileInfo materialTypeSourceFileInfo(m_editData.m_materialTypeSourcePath.c_str());
QFileInfo materialParentSourceFileInfo(AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_editData.m_materialParentAsset.GetId()).c_str());
QString materialInfo;
materialInfo += tr("<table>");
materialInfo += tr("<tr><td><b>Material Slot&emsp;</b></td><td>%1</td></tr>").arg(m_slotName.c_str());
if (!materialFileInfo.fileName().isEmpty())
{
materialInfo += tr("<tr><td><b>Material&emsp;</b></td><td>%1</td></tr>").arg(materialFileInfo.fileName());
}
if (!materialTypeSourceFileInfo.fileName().isEmpty())
{
materialInfo += tr("<tr><td><b>Material Type&emsp;</b></td><td>%1</td></tr>").arg(materialTypeSourceFileInfo.fileName());
}
if (!materialSourceFileInfo.fileName().isEmpty())
{
materialInfo += tr("<tr><td><b>Material Source&emsp;</b></td><td>%1</td></tr>").arg(materialSourceFileInfo.fileName());
}
if (!materialParentSourceFileInfo.fileName().isEmpty())
{
materialInfo += tr("<tr><td><b>Material Parent&emsp;</b></td><td>%1</td></tr>").arg(materialParentSourceFileInfo.fileName());
}
materialInfo += tr("</table>");
materialInfoWidget->setText(materialInfo);
propertyGroupContainer->layout()->addWidget(materialInfoWidget);
AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupContainer);
}
void MaterialPropertyInspector::AddUvNamesGroup()
@@ -196,7 +187,7 @@ namespace AZ
const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model.";
auto& group = m_groups[groupNameId];
const RPI::MaterialUvNameMap& uvNameMap = m_materialAsset->GetMaterialTypeAsset()->GetUvNameMap();
const RPI::MaterialUvNameMap& uvNameMap = m_editData.m_materialAsset->GetMaterialTypeAsset()->GetUvNameMap();
group.m_properties.reserve(uvNameMap.size());
for (const RPI::UvNamePair& uvNamePair : uvNameMap)
@@ -237,14 +228,14 @@ namespace AZ
AddUvNamesGroup();
// Copy all of the properties from the material asset to the source data that will be exported
for (const auto& groupDefinition : m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder())
for (const auto& groupDefinition : m_editData.m_materialTypeSourceData.GetGroupDefinitionsInDisplayOrder())
{
const AZStd::string& groupNameId = groupDefinition.m_nameId;
const AZStd::string& groupDisplayName = !groupDefinition.m_displayName.empty() ? groupDefinition.m_displayName : groupNameId;
const AZStd::string& groupDescription = !groupDefinition.m_description.empty() ? groupDefinition.m_description : groupDisplayName;
auto& group = m_groups[groupNameId];
const auto& propertyLayout = m_materialTypeSourceData.m_propertyLayout;
const auto& propertyLayout = m_editData.m_materialTypeSourceData.m_propertyLayout;
const auto& propertyListItr = propertyLayout.m_properties.find(groupNameId);
if (propertyListItr != propertyLayout.m_properties.end())
{
@@ -255,10 +246,10 @@ namespace AZ
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition);
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupNameId, propertyDefinition.m_nameId).GetFullName();
const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
group.m_properties.emplace_back(propertyConfig);
}
}
@@ -283,7 +274,7 @@ namespace AZ
{
if (m_propertyChangedCallback)
{
m_propertyChangedCallback(m_materialPropertyOverrideMap);
m_propertyChangedCallback(m_editData.m_materialPropertyOverrideMap);
}
}
@@ -365,15 +356,15 @@ namespace AZ
void MaterialPropertyInspector::SetOverrides(const MaterialPropertyOverrideMap& propertyOverrideMap)
{
m_materialPropertyOverrideMap = propertyOverrideMap;
m_editData.m_materialPropertyOverrideMap = propertyOverrideMap;
for (auto& group : m_groups)
{
for (auto& property : group.second.m_properties)
{
const AtomToolsFramework::DynamicPropertyConfig& propertyConfig = property.GetConfig();
const auto overrideItr = m_materialPropertyOverrideMap.find(propertyConfig.m_id);
const auto& editValue = overrideItr != m_materialPropertyOverrideMap.end() ? overrideItr->second : propertyConfig.m_originalValue;
const auto overrideItr = m_editData.m_materialPropertyOverrideMap.find(propertyConfig.m_id);
const auto& editValue = overrideItr != m_editData.m_materialPropertyOverrideMap.end() ? overrideItr->second : propertyConfig.m_originalValue;
// This first converts to an acceptable runtime type in case the value came from script
const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
@@ -400,6 +391,82 @@ namespace AZ
RebuildAll();
}
bool MaterialPropertyInspector::SaveMaterial() const
{
const QString defaultPath = AtomToolsFramework::GetUniqueFileInfo(
QString(AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@")) +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "Materials" +
AZ_CORRECT_FILESYSTEM_SEPARATOR + "untitled." +
AZ::RPI::MaterialSourceData::Extension).absoluteFilePath();
const QString saveFilePath = AtomToolsFramework::GetSaveFileInfo(defaultPath).absoluteFilePath();
if (saveFilePath.isEmpty())
{
return false;
}
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(saveFilePath.toUtf8().constData(), m_editData))
{
AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Failed to save material data.");
return false;
}
return true;
}
bool MaterialPropertyInspector::SaveMaterialToSource() const
{
const QString saveFilePath = AtomToolsFramework::GetSaveFileInfo(m_editData.m_materialSourcePath.c_str()).absoluteFilePath();
if (saveFilePath.isEmpty())
{
return false;
}
if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(saveFilePath.toUtf8().constData(), m_editData))
{
AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Failed to save material data.");
return false;
}
return true;
}
bool MaterialPropertyInspector::HasMaterialSource() const
{
return !m_editData.m_materialSourcePath.empty() &&
AZ::StringFunc::Path::IsExtension(m_editData.m_materialSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
}
bool MaterialPropertyInspector::HasMaterialParentSource() const
{
return !m_editData.m_materialParentSourcePath.empty() &&
AZ::StringFunc::Path::IsExtension(
m_editData.m_materialParentSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
}
void MaterialPropertyInspector::OpenMaterialSourceInEditor() const
{
if (HasMaterialSource())
{
EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, m_editData.m_materialSourcePath);
}
}
void MaterialPropertyInspector::OpenMaterialParentSourceInEditor() const
{
if (HasMaterialParentSource())
{
EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, m_editData.m_materialParentSourcePath);
}
}
const EditorMaterialComponentUtil::MaterialEditData& MaterialPropertyInspector::GetEditData() const
{
return m_editData;
}
void MaterialPropertyInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
// For some reason the reflected property editor notifications are not symmetrical
@@ -422,7 +489,7 @@ namespace AZ
{
if (m_activeProperty == property)
{
m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
UpdateMaterialInstanceProperty(*m_activeProperty);
RunPropertyChangedCallback();
}
@@ -438,7 +505,7 @@ namespace AZ
{
if (m_activeProperty == property)
{
m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
UpdateMaterialInstanceProperty(*m_activeProperty);
RunPropertyChangedCallback();
RunEditorMaterialFunctors();
@@ -447,7 +514,9 @@ namespace AZ
}
}
bool OpenInspectorDialog(const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap, PropertyChangedCallback propertyChangedCallback)
bool OpenInspectorDialog(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap,
PropertyChangedCallback propertyChangedCallback)
{
QWidget* activeWindow = nullptr;
AzToolsFramework::EditorWindowRequestBus::BroadcastResult(activeWindow, &AzToolsFramework::EditorWindowRequests::GetAppMainWindow);
@@ -456,7 +525,7 @@ namespace AZ
QDialog dialog(activeWindow);
dialog.setWindowTitle("Material Inspector");
MaterialPropertyInspector* inspector = new MaterialPropertyInspector(assetId, propertyChangedCallback, &dialog);
MaterialPropertyInspector* inspector = new MaterialPropertyInspector(slotName, assetId, propertyChangedCallback, &dialog);
if (!inspector->LoadMaterial())
{
return false;
@@ -465,48 +534,47 @@ namespace AZ
inspector->Populate();
inspector->SetOverrides(propertyOverrideMap);
// Create the bottom row of the dialog with action buttons for exporting or canceling the operation
QWidget* buttonRow = new QWidget(&dialog);
buttonRow->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// Create the menu bottom row with actions for exporting or canceling the operation
QToolButton* menuButton = new QToolButton(&dialog);
menuButton->setAutoRaise(true);
menuButton->setIcon(QIcon(":/Cards/img/UI20/Cards/menu_ico.svg"));
menuButton->setVisible(true);
QObject::connect(menuButton, &QToolButton::clicked, &dialog, [&]() {
QAction* action = nullptr;
QPushButton* revertButton = new QPushButton("Revert", buttonRow);
QObject::connect(revertButton, &QPushButton::clicked, revertButton, [inspector, propertyOverrideMap] {
inspector->SetOverrides(propertyOverrideMap);
});
QMenu menu(&dialog);
action = menu.addAction("Clear Overrides", [&] { inspector->SetOverrides(MaterialPropertyOverrideMap()); });
action = menu.addAction("Revert Changes", [&] { inspector->SetOverrides(propertyOverrideMap); });
QPushButton* clearButton = new QPushButton("Clear", buttonRow);
QObject::connect(clearButton, &QPushButton::clicked, clearButton, [inspector] {
inspector->SetOverrides(MaterialPropertyOverrideMap());
});
menu.addSeparator();
action = menu.addAction("Confirm Changes", [&] { dialog.accept(); });
action = menu.addAction("Cancel Changes", [&] { dialog.reject(); });
QPushButton* confirmButton = new QPushButton("Confirm", buttonRow);
QObject::connect(confirmButton, &QPushButton::clicked, confirmButton, [&dialog] {
dialog.accept();
});
menu.addSeparator();
action = menu.addAction("Save Material", [&] { inspector->SaveMaterial(); });
action = menu.addAction("Save Material To Source", [&] { inspector->SaveMaterialToSource(); });
action->setEnabled(inspector->HasMaterialSource());
QPushButton* cancelButton = new QPushButton("Cancel", buttonRow);
QObject::connect(cancelButton, &QPushButton::clicked, cancelButton, [inspector, propertyOverrideMap, &dialog] {
inspector->SetOverrides(propertyOverrideMap);
dialog.reject();
});
menu.addSeparator();
action = menu.addAction("Open Source Material In Editor", [&] { inspector->OpenMaterialSourceInEditor(); });
action->setEnabled(inspector->HasMaterialSource());
action = menu.addAction("Open Parent Material In Editor", [&] { inspector->OpenMaterialParentSourceInEditor(); });
action->setEnabled(inspector->HasMaterialParentSource());
menu.exec(QCursor::pos());
});
QHBoxLayout* buttonLayout = new QHBoxLayout(buttonRow);
buttonLayout->addStretch();
buttonLayout->addWidget(revertButton);
buttonLayout->addWidget(clearButton);
buttonLayout->addWidget(confirmButton);
buttonLayout->addWidget(cancelButton);
QObject::connect(&dialog, &QDialog::rejected, &dialog, [&] { inspector->SetOverrides(propertyOverrideMap); });
QVBoxLayout* dialogLayout = new QVBoxLayout(&dialog);
dialogLayout->addWidget(menuButton);
dialogLayout->addWidget(inspector);
dialogLayout->addWidget(buttonRow);
dialog.setLayout(dialogLayout);
// Forcing the initial dialog size to accomodate typical content.
// Temporarily settng fixed size because dialog.show/exec invokes WindowDecorationWrapper::showEvent.
// This forces the dialog to be centered and sized based on the layout of content.
// Resizing the dialog after show will not be centered and moving the dialog programatically doesn't m0ve the custmk frame.
dialog.setFixedSize(300, 600);
dialog.setFixedSize(500, 800);
dialog.show();
// Removing fixed size to allow drag resizing
@@ -13,19 +13,15 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Asset/AssetCommon.h>
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
#include <AtomToolsFramework/Inspector/InspectorWidget.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/function/function_base.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h>
#include <Material/EditorMaterialComponentUtil.h>
#endif
namespace AZ
@@ -44,7 +40,9 @@ namespace AZ
public:
AZ_CLASS_ALLOCATOR(MaterialPropertyInspector, AZ::SystemAllocator, 0);
explicit MaterialPropertyInspector(const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback, QWidget* parent = nullptr);
explicit MaterialPropertyInspector(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback,
QWidget* parent = nullptr);
~MaterialPropertyInspector() override;
bool LoadMaterial();
@@ -56,6 +54,14 @@ namespace AZ
void SetOverrides(const MaterialPropertyOverrideMap& propertyOverrideMap);
bool SaveMaterial() const;
bool SaveMaterialToSource() const;
bool HasMaterialSource() const;
bool HasMaterialParentSource() const;
void OpenMaterialSourceInEditor() const;
void OpenMaterialParentSourceInEditor() const;
const EditorMaterialComponentUtil::MaterialEditData& GetEditData() const;
private:
// AzToolsFramework::IPropertyEditorNotify overrides...
@@ -76,20 +82,19 @@ namespace AZ
// Tracking the property that is actively being edited in the inspector
const AtomToolsFramework::DynamicProperty* m_activeProperty = {};
AZStd::string m_slotName;
AZ::Data::AssetId m_materialAssetId = {};
MaterialPropertyOverrideMap m_materialPropertyOverrideMap = {};
EditorMaterialComponentUtil::MaterialEditData m_editData;
PropertyChangedCallback m_propertyChangedCallback = {};
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset = {};
AZ::Data::Asset<AZ::RPI::MaterialTypeAsset> m_materialTypeAsset = {};
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_parentMaterialAsset = {};
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance = {};
AZ::RPI::MaterialTypeSourceData m_materialTypeSourceData;
AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors = {};
AZ::RPI::MaterialPropertyFlags m_dirtyPropertyFlags = {};
AZStd::unordered_map<AZStd::string, AtomToolsFramework::DynamicPropertyGroup> m_groups = {};
};
bool OpenInspectorDialog(const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap, PropertyChangedCallback propertyChangedCallback);
bool OpenInspectorDialog(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap,
PropertyChangedCallback propertyChangedCallback);
} // namespace EditorMaterialComponentInspector
} // namespace Render
} // namespace AZ
@@ -242,7 +242,7 @@ namespace AZ
if (m_materialAsset.GetId().IsValid())
{
if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_propertyOverrides, applyPropertyChangedCallback))
if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), m_materialAsset.GetId(), m_propertyOverrides, applyPropertyChangedCallback))
{
OnMaterialChanged();
}
@@ -0,0 +1,176 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Material/EditorMaterialComponentUtil.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Common/JsonUtils.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AZ
{
namespace Render
{
namespace EditorMaterialComponentUtil
{
bool LoadMaterialEditDataFromAssetId(const AZ::Data::AssetId& assetId, MaterialEditData& editData)
{
editData = MaterialEditData();
if (!assetId.IsValid())
{
AZ_Warning("AZ::Render::EditorMaterialComponentUtil", false, "Attempted to load material data for invalid asset id.");
return false;
}
editData.m_materialAssetId = assetId;
// Load the originating product asset from which the new source has set will be generated
auto materialAssetOutcome = AZ::RPI::AssetUtils::LoadAsset<AZ::RPI::MaterialAsset>(editData.m_materialAssetId);
if (!materialAssetOutcome)
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to load material asset: %s", editData.m_materialAssetId.ToString<AZStd::string>().c_str());
return false;
}
editData.m_materialAsset = materialAssetOutcome.GetValue();
editData.m_materialTypeAsset = editData.m_materialAsset->GetMaterialTypeAsset();
editData.m_materialParentAsset = {};
editData.m_materialSourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(editData.m_materialAsset.GetId());
if (AzFramework::StringFunc::Path::IsExtension(
editData.m_materialSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(editData.m_materialSourcePath, editData.m_materialSourceData))
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Material source data could not be loaded: '%s'.", editData.m_materialSourcePath.c_str());
return false;
}
}
if (!editData.m_materialSourceData.m_parentMaterial.empty())
{
// There is a parent for this material
auto parentMaterialResult = AZ::RPI::AssetUtils::LoadAsset<AZ::RPI::MaterialAsset>(editData.m_materialSourcePath, editData.m_materialSourceData.m_parentMaterial);
if (!parentMaterialResult)
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Parent material asset could not be loaded: '%s'.", editData.m_materialSourceData.m_parentMaterial.c_str());
return false;
}
editData.m_materialParentAsset = parentMaterialResult.GetValue();
editData.m_materialParentSourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(editData.m_materialParentAsset.GetId());
}
// We need a valid path to the material type source data to get the property layout and assign to the new material
editData.m_materialTypeSourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(editData.m_materialTypeAsset.GetId());
if (editData.m_materialTypeSourcePath.empty())
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to locate source material type asset: %s", editData.m_materialAssetId.ToString<AZStd::string>().c_str());
return false;
}
// Load the material type source data
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(editData.m_materialTypeSourcePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to load material type source data: %s", editData.m_materialTypeSourcePath.c_str());
return false;
}
editData.m_materialTypeSourceData = materialTypeOutcome.GetValue();
return true;
}
bool SaveSourceMaterialFromEditData(const AZStd::string& path, const MaterialEditData& editData)
{
// Getting the source info for the material type file to make sure that it exists
// We also need to watch folder to generate a relative asset path for the material type
bool result = false;
AZ::Data::AssetInfo info;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
editData.m_materialTypeSourcePath.c_str(), info, watchFolder);
if (!result)
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to get source file info and asset path while attempting to export: %s", path.c_str());
return false;
}
// Construct the material source data object that will be exported
AZ::RPI::MaterialSourceData exportData;
exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.m_propertyLayout.m_version;
// Converting absolute material paths to asset relative paths
exportData.m_materialType = editData.m_materialTypeSourcePath;
AzFramework::ApplicationRequests::Bus::Broadcast(
&AzFramework::ApplicationRequests::Bus::Events::MakePathRelative, exportData.m_materialType, watchFolder.c_str());
exportData.m_parentMaterial = editData.m_materialParentSourcePath;
AzFramework::ApplicationRequests::Bus::Broadcast(
&AzFramework::ApplicationRequests::Bus::Events::MakePathRelative, exportData.m_parentMaterial, watchFolder.c_str());
// Copy all of the properties from the material asset to the source data that will be exported
result = true;
editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupNameId, const AZStd::string& propertyNameId, const auto& propertyDefinition) {
const AZ::RPI::MaterialPropertyId propertyId(groupNameId, propertyNameId);
const AZ::RPI::MaterialPropertyIndex propertyIndex =
editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName());
AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition.m_value;
if (editData.m_materialParentAsset.IsReady())
{
propertyValueDefault = editData.m_materialParentAsset->GetPropertyValues()[propertyIndex.GetIndex()];
}
// Check for and apply any property overrides before saving property values
auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId.GetFullName());
if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end())
{
propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second);
}
if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str());
result = false;
return false;
}
// Don't export values if they are the same as the material type or parent
if (propertyValueDefault == propertyValue)
{
return true;
}
exportData.m_properties[groupNameId][propertyDefinition.m_nameId].m_value = propertyValue;
return true;
});
return result && AZ::RPI::JsonUtils::SaveObjectToFile(path, exportData);
}
} // namespace EditorMaterialComponentUtil
} // namespace Render
} // namespace AZ
//#include <AtomLyIntegration/CommonFeatures/moc_EditorMaterialComponentUtil.cpp>
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/string/string.h>
namespace AZ
{
namespace Render
{
namespace EditorMaterialComponentUtil
{
struct MaterialEditData
{
AZ::Data::AssetId m_materialAssetId = {};
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset = {};
AZ::Data::Asset<AZ::RPI::MaterialTypeAsset> m_materialTypeAsset = {};
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialParentAsset = {};
AZ::RPI::MaterialSourceData m_materialSourceData;
AZ::RPI::MaterialTypeSourceData m_materialTypeSourceData;
AZStd::string m_materialSourcePath;
AZStd::string m_materialTypeSourcePath;
AZStd::string m_materialParentSourcePath;
MaterialPropertyOverrideMap m_materialPropertyOverrideMap = {};
};
bool LoadMaterialEditDataFromAssetId(const AZ::Data::AssetId& assetId, MaterialEditData& editData);
bool SaveSourceMaterialFromEditData(const AZStd::string& path, const MaterialEditData& editData);
} // namespace EditorMaterialComponentUtil
} // namespace Render
} // namespace AZ
@@ -25,7 +25,7 @@
#include <AtomToolsFramework/Util/Util.h>
#include <Material/Thumbnails/MaterialThumbnail.h>
#include <Material/MaterialThumbnail.h>
// Disables warning messages triggered by the Qt library
// 4251: class needs to have dll-interface to be used by clients of class
@@ -95,8 +95,6 @@ namespace AZ
{
AzFramework::TargetManagerClient::Bus::Handler::BusConnect();
EditorMaterialSystemComponentRequestBus::Handler::BusConnect();
m_materialPreviewerFactory = AZStd::make_unique <LyIntegration::MaterialPreviewerFactory>();
AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler::BusConnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorMenuNotificationBus::Handler::BusConnect();
@@ -108,11 +106,9 @@ namespace AZ
{
AzFramework::TargetManagerClient::Bus::Handler::BusDisconnect();
EditorMaterialSystemComponentRequestBus::Handler::BusDisconnect();
AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler::BusDisconnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorMenuNotificationBus::Handler::BusDisconnect();
m_materialPreviewerFactory.reset();
TeardownThumbnails();
@@ -157,16 +153,11 @@ namespace AZ
}
}
const AzToolsFramework::AssetBrowser::PreviewerFactory* EditorMaterialSystemComponent::GetPreviewerFactory(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const
{
return m_materialPreviewerFactory->IsEntrySupported(entry) ? m_materialPreviewerFactory.get() : nullptr;
}
void EditorMaterialSystemComponent::OnApplicationAboutToStop()
{
TeardownThumbnails();
}
void EditorMaterialSystemComponent::OnPopulateToolMenuItems()
{
if (!m_openMaterialEditorAction)
@@ -198,7 +189,7 @@ namespace AZ
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider,
MAKE_TCACHE(MaterialThumbnailCache),
MAKE_TCACHE(Thumbnails::MaterialThumbnailCache),
ThumbnailContext::DefaultContext);
}
@@ -208,7 +199,7 @@ namespace AZ
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::UnregisterThumbnailProvider,
MaterialThumbnailCache::ProviderName,
Thumbnails::MaterialThumbnailCache::ProviderName,
ThumbnailContext::DefaultContext);
}
@@ -16,16 +16,12 @@
#include <AzFramework/Application/Application.h>
#include <AzFramework/TargetManagement/TargetManagementAPI.h>
#include <AzToolsFramework/AssetBrowser/Previewer/PreviewerBus.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h>
#include <Material/Preview/MaterialPreviewerFactory.h>
#include <Material/Thumbnails/MaterialThumbnail.h>
namespace AZ
{
namespace Render
@@ -35,7 +31,6 @@ namespace AZ
: public AZ::Component
, private EditorMaterialSystemComponentRequestBus::Handler
, private AzFramework::TargetManagerClient::Bus::Handler
, private AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler
, private AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
, public AzToolsFramework::EditorMenuNotificationBus::Handler
@@ -63,10 +58,7 @@ namespace AZ
//! AzFramework::TargetManagerClient::Bus::Handler overrides...
void TargetJoinedNetwork(AzFramework::TargetInfo info) override;
void TargetLeftNetwork(AzFramework::TargetInfo info) override;
// AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler overrides...
const AzToolsFramework::AssetBrowser::PreviewerFactory* GetPreviewerFactory(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const override;
// AzFramework::ApplicationLifecycleEvents overrides...
void OnApplicationAboutToStop() override;
@@ -83,8 +75,6 @@ namespace AZ
// Material Editor target for interprocess communication with MaterialEditor
AzFramework::TargetInfo m_materialEditorTarget;
AZStd::unique_ptr<LyIntegration::MaterialPreviewerFactory> m_materialPreviewerFactory;
QAction* m_openMaterialEditorAction = nullptr;
};
} // namespace Render
@@ -67,6 +67,11 @@ namespace AZ
incompatible.push_back(AZ_CRC("MaterialProviderService", 0x64849a6b));
}
void MaterialComponentController::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("MaterialReceiverService", 0x0d1a6a74));
}
MaterialComponentController::MaterialComponentController(const MaterialComponentConfig& config)
: m_configuration(config)
{
@@ -38,6 +38,7 @@ namespace AZ
static void Reflect(ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
MaterialComponentController() = default;
MaterialComponentController(const MaterialComponentConfig& config);
@@ -0,0 +1,116 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <QtConcurrent/QtConcurrent>
#include <Source/Material/MaterialThumbnail.h>
#include <Source/Thumbnails/ThumbnailUtils.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
const int MaterialThumbnailSize = 200;
//////////////////////////////////////////////////////////////////////////
// MaterialThumbnail
//////////////////////////////////////////////////////////////////////////
MaterialThumbnail::MaterialThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, int thumbnailSize)
: Thumbnail(key, thumbnailSize)
{
m_assetId = GetAssetId(key, RPI::MaterialAsset::RTTI_Type());
if (!m_assetId.IsValid())
{
AZ_Error("MaterialThumbnail", false, "Failed to find matching assetId for the thumbnailKey.");
m_state = State::Failed;
return;
}
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler::BusConnect(key);
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
}
void MaterialThumbnail::LoadThread()
{
AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::QueueEvent(
RPI::MaterialAsset::RTTI_Type(),
&AzToolsFramework::Thumbnailer::ThumbnailerRendererRequests::RenderThumbnail,
m_key,
m_thumbnailSize);
// wait for response from thumbnail renderer
m_renderWait.acquire();
}
MaterialThumbnail::~MaterialThumbnail()
{
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler::BusDisconnect();
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
}
void MaterialThumbnail::ThumbnailRendered(QPixmap& thumbnailImage)
{
m_pixmap = thumbnailImage;
m_renderWait.release();
}
void MaterialThumbnail::ThumbnailFailedToRender()
{
m_state = State::Failed;
m_renderWait.release();
}
void MaterialThumbnail::OnCatalogAssetChanged([[maybe_unused]] const AZ::Data::AssetId& assetId)
{
if (m_assetId == assetId &&
m_state == State::Ready)
{
m_state = State::Unloaded;
Load();
}
}
//////////////////////////////////////////////////////////////////////////
// MaterialThumbnailCache
//////////////////////////////////////////////////////////////////////////
MaterialThumbnailCache::MaterialThumbnailCache()
: ThumbnailCache<MaterialThumbnail>()
{
}
MaterialThumbnailCache::~MaterialThumbnailCache() = default;
int MaterialThumbnailCache::GetPriority() const
{
// Material thumbnails override default source thumbnails, so carry higher priority
return 1;
}
const char* MaterialThumbnailCache::GetProviderName() const
{
return ProviderName;
}
bool MaterialThumbnailCache::IsSupportedThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) const
{
return
GetAssetId(key, RPI::MaterialAsset::RTTI_Type()).IsValid() &&
// in case it's a source fbx, it will contain both material and model products
// model thumbnails are handled by MeshThumbnail
!GetAssetId(key, RPI::ModelAsset::RTTI_Type()).IsValid();
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
#include <Material/moc_MaterialThumbnail.cpp>
@@ -0,0 +1,77 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <Thumbnails/Rendering/CommonThumbnailRenderer.h>
#endif
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
/**
* Custom material or model thumbnail that detects when an asset changes and updates the thumbnail
*/
class MaterialThumbnail
: public AzToolsFramework::Thumbnailer::Thumbnail
, public AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
{
Q_OBJECT
public:
MaterialThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, int thumbnailSize);
~MaterialThumbnail() override;
//! AzToolsFramework::ThumbnailerRendererNotificationBus::Handler overrides...
void ThumbnailRendered(QPixmap& thumbnailImage) override;
void ThumbnailFailedToRender() override;
protected:
void LoadThread() override;
private:
// AzFramework::AssetCatalogEventBus::Handler interface overrides...
void OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) override;
AZStd::binary_semaphore m_renderWait;
Data::AssetId m_assetId;
};
/**
* Cache configuration for large material thumbnails
*/
class MaterialThumbnailCache
: public AzToolsFramework::Thumbnailer::ThumbnailCache<MaterialThumbnail>
{
public:
MaterialThumbnailCache();
~MaterialThumbnailCache() override;
int GetPriority() const override;
const char* GetProviderName() const override;
static constexpr const char* ProviderName = "Material Thumbnails";
protected:
bool IsSupportedThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) const override;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -1,150 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/IO/FileIO.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzCore/Math/MatrixUtils.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <Source/Material/Preview/MaterialPreviewer.h>
// Disables warning messages triggered by the Qt library
// 4251: class needs to have dll-interface to be used by clients of class
// 4800: forcing value to bool 'true' or 'false' (performance warning)
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <Source/Material/Preview/ui_MaterialPreviewer.h>
#include <QString>
#include <QResizeEvent>
AZ_POP_DISABLE_WARNING
namespace AZ
{
namespace LyIntegration
{
static constexpr int CharWidth = 6;
MaterialPreviewer::MaterialPreviewer(QWidget* parent)
: Previewer(parent)
, m_ui(new Ui::MaterialPreviewerClass())
{
m_ui->setupUi(this);
}
MaterialPreviewer::~MaterialPreviewer()
{
}
void MaterialPreviewer::Display(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
using namespace AzToolsFramework::AssetBrowser;
if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Source)
{
const auto source = azrtti_cast<const SourceAssetBrowserEntry*>(entry);
if (source->GetChildCount() > 0)
{
const auto product = azrtti_cast<const ProductAssetBrowserEntry*>(source->GetChild(0));
if (product)
{
DisplayInternal(product);
}
}
}
else if (entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product)
{
const auto product = azrtti_cast<const ProductAssetBrowserEntry*>(entry);
DisplayInternal(product);
}
}
const QString& MaterialPreviewer::GetName() const
{
return m_name;
}
void MaterialPreviewer::resizeEvent([[maybe_unused]] QResizeEvent* event)
{
m_ui->m_materialPreviewWidget->setMaximumHeight(m_ui->m_materialPreviewWidget->width());
UpdateFileInfo();
}
void MaterialPreviewer::DisplayInternal(const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry* product)
{
using namespace AzToolsFramework;
using namespace Thumbnailer;
if (product->GetAssetId() == m_assetId)
{
return;
}
m_assetId = product->GetAssetId();
m_fileInfo = QString::fromUtf8(product->GetParent()->GetName().c_str());
bool result = false;
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetType assetType;
AZStd::string rootFilePath;
const AZStd::string platformName = ""; // Empty for default
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetInfoById,
m_assetId, assetType, platformName, assetInfo, rootFilePath);
if (!result)
{
return;
}
AZStd::string fullSourcePath = AZStd::string::format("%s/%s", rootFilePath.c_str(), assetInfo.m_relativePath.c_str());
SharedThumbnailKey thumbnailKey = MAKE_TKEY(AzToolsFramework::AssetBrowser::SourceThumbnailKey, fullSourcePath.c_str());
m_ui->m_materialPreviewWidget->SetThumbnailKey(thumbnailKey, Thumbnailer::ThumbnailContext::DefaultContext);
UpdateFileInfo();
}
void MaterialPreviewer::UpdateFileInfo() const
{
m_ui->m_fileInfoLabel->setText(WordWrap(m_fileInfo, m_ui->m_fileInfoLabel->width() / CharWidth));
}
QString MaterialPreviewer::WordWrap(const QString& string, int maxLength)
{
QString result;
int length = 0;
for (const QChar& c : string)
{
if (c == '\n')
{
length = 0;
}
else if (length > maxLength)
{
result.append('\n');
length = 0;
}
else
{
length++;
}
result.append(c);
}
return result;
}
} // namespace LyIntegration
} // namespace AZ
#include <Source/Material/Preview/moc_MaterialPreviewer.cpp>
@@ -1,77 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/AssetBrowser/Previewer/Previewer.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QWidget>
#include <QScopedPointer>
AZ_POP_DISABLE_WARNING
#endif
namespace Ui
{
class MaterialPreviewerClass;
}
namespace AzToolsFramework
{
namespace AssetBrowser
{
class ProductAssetBrowserEntry;
class SourceAssetBrowserEntry;
class AssetBrowserEntry;
}
}
class QResizeEvent;
namespace AZ
{
namespace LyIntegration
{
class MaterialPreviewer final
: public AzToolsFramework::AssetBrowser::Previewer
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(MaterialPreviewer, AZ::SystemAllocator, 0);
explicit MaterialPreviewer(QWidget* parent = nullptr);
~MaterialPreviewer();
// AzToolsFramework::AssetBrowser::Previewer overrides...
void Clear() const override {}
void Display(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) override;
const QString& GetName() const override;
protected:
void resizeEvent(QResizeEvent* event) override;
private:
void DisplayInternal(const AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry* product);
void UpdateFileInfo() const;
// QLabel word wrap does not break long words such as filenames, so manual word wrap needed
static QString WordWrap(const QString& string, int maxLength);
QScopedPointer<Ui::MaterialPreviewerClass> m_ui;
Data::AssetId m_assetId;
QString m_fileInfo;
QString m_name = "MaterialPreviewer";
};
} // namespace LyIntegration
} // namespace AZ
@@ -1,128 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MaterialPreviewerClass</class>
<widget class="QWidget" name="MaterialPreviewerClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>266</width>
<height>399</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>100</height>
</size>
</property>
<property name="windowTitle">
<string>Preview</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="AzToolsFramework::Thumbnailer::ThumbnailWidget" name="m_materialPreviewWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="m_fileInfoLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>File Info</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzToolsFramework::Thumbnailer::ThumbnailWidget</class>
<extends>QWidget</extends>
<header>AzToolsFramework/Thumbnails/ThumbnailWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -1,52 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Source/Material/Preview/MaterialPreviewer.h>
#include <Source/Material/Preview/MaterialPreviewerFactory.h>
namespace AZ
{
namespace LyIntegration
{
AzToolsFramework::AssetBrowser::Previewer* MaterialPreviewerFactory::CreatePreviewer(QWidget* parent) const
{
return new MaterialPreviewer(parent);
}
bool MaterialPreviewerFactory::IsEntrySupported(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const
{
using namespace AzToolsFramework::AssetBrowser;
switch (entry->GetEntryType())
{
case AssetBrowserEntry::AssetEntryType::Source:
{
const auto source = azrtti_cast<const SourceAssetBrowserEntry*>(entry);
return source->GetPrimaryAssetType() == RPI::MaterialAsset::RTTI_Type();
}
case AssetBrowserEntry::AssetEntryType::Product:
const auto product = azrtti_cast<const ProductAssetBrowserEntry*>(entry);
return product->GetAssetType() == RPI::MaterialAsset::RTTI_Type();
}
return false;
}
const QString& MaterialPreviewerFactory::GetName() const
{
return m_name;
}
} // namespace LyIntegration
} // namespace AZ
@@ -1,43 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/AssetBrowser/Previewer/PreviewerFactory.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QString>
AZ_POP_DISABLE_WARNING
namespace AZ
{
namespace LyIntegration
{
class MaterialPreviewerFactory final
: public AzToolsFramework::AssetBrowser::PreviewerFactory
{
public:
AZ_CLASS_ALLOCATOR(MaterialPreviewerFactory, AZ::SystemAllocator, 0);
MaterialPreviewerFactory() = default;
~MaterialPreviewerFactory() = default;
// AzToolsFramework::AssetBrowser::PreviewerFactory overrides...
AzToolsFramework::AssetBrowser::Previewer* CreatePreviewer(QWidget* parent = nullptr) const override;
bool IsEntrySupported(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const override;
const QString& GetName() const override;
private:
QString m_name = "MaterialPreviewer";
};
} // namespace LyIntegration
} // namespace AZ
@@ -1,132 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <QPixmap>
#include <QtConcurrent/QtConcurrent>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Source/Material/Thumbnails/MaterialThumbnail.h>
namespace AZ
{
namespace LyIntegration
{
using namespace AzToolsFramework::Thumbnailer;
using namespace AzToolsFramework::AssetBrowser;
const int MaterialThumbnailSize = 200;
//////////////////////////////////////////////////////////////////////////
// MaterialThumbnail
//////////////////////////////////////////////////////////////////////////
MaterialThumbnail::MaterialThumbnail(SharedThumbnailKey key, int thumbnailSize)
: Thumbnail(key, thumbnailSize)
{
const SourceThumbnailKey* sourceThumbnailKey = azrtti_cast<const SourceThumbnailKey*>(m_key.data());
if (!sourceThumbnailKey)
{
AZ_Error("MaterialThumbnail", sourceThumbnailKey, "Incorrect key type, excpected SourceThumbnailKey");
m_state = State::Failed;
return;
}
Data::AssetInfo assetInfo;
AZStd::string watchFolder;
bool hasResult = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(hasResult, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath,
sourceThumbnailKey->GetFileName().c_str(), assetInfo, watchFolder);
if (!hasResult)
{
AZ_Error("MaterialThumbnail", hasResult, "AssetInfo for %s could not be found", sourceThumbnailKey->GetFileName().c_str());
m_state = State::Failed;
return;
}
m_assetType = AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid();
m_assetId = assetInfo.m_assetId;
ThumbnailerRendererNotificationBus::Handler::BusConnect(m_assetId);
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
}
void MaterialThumbnail::LoadThread()
{
ThumbnailerRendererRequestBus::QueueEvent(m_assetType, &ThumbnailerRendererRequests::RenderThumbnail, m_assetId, m_thumbnailSize);
// wait for response from thumbnail renderer
m_renderWait.acquire();
}
MaterialThumbnail::~MaterialThumbnail()
{
ThumbnailerRendererNotificationBus::Handler::BusDisconnect(m_assetId);
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
}
void MaterialThumbnail::ThumbnailRendered(QPixmap& thumbnailImage)
{
m_pixmap = thumbnailImage;
m_renderWait.release();
}
void MaterialThumbnail::ThumbnailFailedToRender()
{
m_state = State::Failed;
m_renderWait.release();
}
void MaterialThumbnail::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
if (m_assetId == assetId &&
m_state == State::Ready)
{
m_state = State::Unloaded;
Load();
}
}
//////////////////////////////////////////////////////////////////////////
// MaterialThumbnailCache
//////////////////////////////////////////////////////////////////////////
MaterialThumbnailCache::MaterialThumbnailCache()
: ThumbnailCache<MaterialThumbnail, SourceKeyHash, SourceKeyEqual>()
, m_renderer(AZStd::make_unique<MaterialThumbnailRenderer>())
{
}
MaterialThumbnailCache::~MaterialThumbnailCache() = default;
int MaterialThumbnailCache::GetPriority() const
{
// Material thumbnails override default source thumbnails, so carry higher priority
return 1;
}
const char* MaterialThumbnailCache::GetProviderName() const
{
return ProviderName;
}
bool MaterialThumbnailCache::IsSupportedThumbnail(SharedThumbnailKey key) const
{
auto sourceKey = azrtti_cast<const AzToolsFramework::AssetBrowser::SourceThumbnailKey*>(key.data());
return sourceKey && sourceKey->GetExtension() == ".material";
}
} // namespace LyIntegration
} // namespace AZ
#include <Source/Material/Thumbnails/moc_MaterialThumbnail.cpp>
@@ -1,82 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/parallel/binary_semaphore.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <Material/Thumbnails/MaterialThumbnailRenderer.h>
#endif
namespace AZ
{
namespace LyIntegration
{
/**
* Custom material thumbnail that detects when a material asset changes and updates the thumbnail
*/
class MaterialThumbnail
: public AzToolsFramework::Thumbnailer::Thumbnail
, public AzToolsFramework::ThumbnailerRendererNotificationBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
{
Q_OBJECT
public:
MaterialThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, int thumbnailSize);
~MaterialThumbnail() override;
//! AzToolsFramework::ThumbnailerRendererNotificationBus::Handler overrides...
void ThumbnailRendered(QPixmap& thumbnailImage) override;
void ThumbnailFailedToRender() override;
protected:
void LoadThread() override;
private:
// AzFramework::AssetCatalogEventBus::Handler interface overrides...
void OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) override;
Data::AssetId m_assetId;
Data::AssetType m_assetType;
AZStd::binary_semaphore m_renderWait;
};
/**
* Cache configuration for large material thumbnails
*/
class MaterialThumbnailCache
: public AzToolsFramework::Thumbnailer::ThumbnailCache<MaterialThumbnail, AzToolsFramework::AssetBrowser::SourceKeyHash, AzToolsFramework::AssetBrowser::SourceKeyEqual>
{
public:
MaterialThumbnailCache();
~MaterialThumbnailCache() override;
int GetPriority() const override;
const char* GetProviderName() const override;
static constexpr const char* ProviderName = "Material Thumbnails";
protected:
bool IsSupportedThumbnail(AzToolsFramework::SharedThumbnailKey key) const override;
private:
AZStd::unique_ptr<MaterialThumbnailRenderer> m_renderer;
};
} // namespace LyIntegration
} // namespace AZ
@@ -1,359 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzCore/Math/MatrixUtils.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RPI.Public/Pass/Specific/SwapChainPass.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h>
#include <Atom/Feature/CoreLights/DirectionalLightFeatureProcessorInterface.h>
#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
#include <Mesh/MeshComponent.h>
#include <Material/MaterialComponent.h>
#include <Atom/RPI.Public/Material/Material.h>
#include <Source/Material/Thumbnails/MaterialThumbnailRenderer.h>
#include "Atom/Feature/Utils/FrameCaptureBus.h"
#include "Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h"
namespace AZ
{
namespace LyIntegration
{
using namespace AzToolsFramework::Thumbnailer;
static constexpr const char* ModelPath = "materialeditor/viewportmodels/quadsphere.azmodel";
static constexpr const char* LightingPresetPath = "lightingpresets/default.lightingpreset.azasset";
static constexpr float AspectRatio = 1.0f;
static constexpr float NearDist = 0.1f;
static constexpr float FarDist = 100.0f;
MaterialThumbnailRenderer::MaterialThumbnailRenderer()
{
m_entityContext = AZStd::make_unique<AzFramework::EntityContext>();
m_entityContext->InitContext();
Data::AssetType materialAssetType = AzTypeInfo<RPI::MaterialAsset>::Uuid();
ThumbnailerRendererRequestBus::Handler::BusConnect(materialAssetType);
SystemTickBus::Handler::BusConnect();
m_shouldPullNextAsset = true;
}
MaterialThumbnailRenderer::~MaterialThumbnailRenderer()
{
ThumbnailerRendererRequestBus::Handler::BusDisconnect();
SystemTickBus::Handler::BusDisconnect();
AZ::Data::AssetBus::Handler::BusDisconnect();
Render::MaterialComponentNotificationBus::Handler::BusDisconnect();
m_materialAssetToRender.Release();
if (m_initialized)
{
Render::FrameCaptureNotificationBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
if (m_modelEntity)
{
AzFramework::EntityContextRequestBus::Event(m_entityContext->GetContextId(),
&AzFramework::EntityContextRequestBus::Events::DestroyEntity, m_modelEntity);
m_modelEntity = nullptr;
}
m_frameworkScene->UnsetSubsystem<RPI::Scene>();
m_scene->Deactivate();
m_scene->RemoveRenderPipeline(m_renderPipeline->GetId());
RPI::RPISystemInterface::Get()->UnregisterScene(m_scene);
bool sceneRemovedSuccessfully = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(sceneRemovedSuccessfully, &AzFramework::SceneSystemRequests::RemoveScene, m_sceneName);
m_scene = nullptr;
m_renderPipeline = nullptr;
}
}
bool MaterialThumbnailRenderer::Installed() const
{
return true;
}
void MaterialThumbnailRenderer::OnSystemTick()
{
ThumbnailerRendererRequestBus::ExecuteQueuedEvents();
}
void MaterialThumbnailRenderer::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
m_deltaTime = deltaTime;
m_simulateTime = time.GetSeconds();
if (m_shouldPullNextAsset && !m_assetIdQueue.empty())
{
m_shouldPullNextAsset = false;
const auto assetId = m_assetIdQueue.front();
m_assetIdQueue.pop();
m_materialAssetToRender.Release();
AZ::Data::AssetBus::Handler::BusDisconnect();
if (assetId.IsValid())
{
m_materialAssetToRender.Create(assetId);
m_materialAssetToRender.QueueLoad();
AZ::Data::AssetBus::Handler::BusConnect(assetId);
}
}
else if (m_readyToCapture)
{
m_renderPipeline->AddToRenderTickOnce();
RPI::AttachmentReadback::CallbackFunction readbackCallback = [&](const RPI::AttachmentReadback::ReadbackResult& result)
{
uchar* data = result.m_dataBuffer.get()->data();
QImage image(data, result.m_imageDescriptor.m_size.m_width, result.m_imageDescriptor.m_size.m_height, QImage::Format_RGBA8888);
QPixmap pixmap;
pixmap.convertFromImage(image);
ThumbnailerRendererNotificationBus::Event(m_materialAssetToRender.GetId(),
&ThumbnailerRendererNotifications::ThumbnailRendered, pixmap);
};
Render::FrameCaptureNotificationBus::Handler::BusConnect();
bool startedCapture = false;
Render::FrameCaptureRequestBus::BroadcastResult(
startedCapture,
&Render::FrameCaptureRequestBus::Events::CapturePassAttachmentWithCallback,
m_passHierarchy, AZStd::string("Output"), readbackCallback);
// Reset the capture flag the capture requst was successful. Otherwise try capture it again next tick.
if (startedCapture)
{
m_readyToCapture = false;
}
}
}
void MaterialThumbnailRenderer::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
m_materialAssetToRender = asset;
AZ::Data::AssetBus::Handler::BusDisconnect();
Render::MaterialComponentRequestBus::Event(m_modelEntity->GetId(),
&Render::MaterialComponentRequestBus::Events::SetDefaultMaterialOverride,
m_materialAssetToRender.GetId());
// listen to material override finished notification.
Render::MaterialComponentNotificationBus::Handler::BusConnect(m_modelEntity->GetId());
}
void MaterialThumbnailRenderer::OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetCanceled(asset.GetId());
}
void MaterialThumbnailRenderer::OnAssetCanceled([[maybe_unused]] AZ::Data::AssetId assetId)
{
m_readyToCapture = false;
m_shouldPullNextAsset = true;
m_materialAssetToRender.Release();
AZ::Data::AssetBus::Handler::BusDisconnect();
}
void MaterialThumbnailRenderer::OnCaptureFinished([[maybe_unused]] Render::FrameCaptureResult result, [[maybe_unused]] const AZStd::string& info)
{
m_shouldPullNextAsset = true;
m_renderPipeline->RemoveFromRenderTick();
Render::FrameCaptureNotificationBus::Handler::BusDisconnect();
if (m_assetIdQueue.empty())
{
TickBus::Handler::BusDisconnect();
}
}
void MaterialThumbnailRenderer::Init()
{
using namespace Data;
// Create and register a scene with minimum required feature processors
RPI::SceneDescriptor sceneDesc;
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::TransformServiceFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::MeshFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PointLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SpotLightFeatureProcessor");
// There is currently a bug where having multiple DirectionalLightFeatureProcessors active can result in shadow flickering [ATOM-13568]
// as well as continually rebuilding MeshDrawPackets [ATOM-13633]. Lets just disable the directional light FP for now.
// Possibly re-enable with [GFX TODO][ATOM-13639]
// sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DirectionalLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DiskLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::CapsuleLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::QuadLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DecalTextureArrayFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::ImageBasedLightFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PostProcessFeatureProcessor");
sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SkyBoxFeatureProcessor");
m_scene = RPI::Scene::CreateScene(sceneDesc);
// Setup scene srg modification callback (to push per-frame values to the shaders)
RPI::ShaderResourceGroupCallback callback = [this](RPI::ShaderResourceGroup* srg)
{
if (srg == nullptr)
{
return;
}
bool needCompile = false;
RHI::ShaderInputConstantIndex timeIndex = srg->FindShaderInputConstantIndex(Name{ "m_time" });
if (timeIndex.IsValid())
{
srg->SetConstant(timeIndex, (float)m_simulateTime);
needCompile = true;
}
RHI::ShaderInputConstantIndex deltaTimeIndex = srg->FindShaderInputConstantIndex(Name{ "m_deltaTime" });
if (deltaTimeIndex.IsValid())
{
srg->SetConstant(deltaTimeIndex, m_deltaTime);
needCompile = true;
}
if (needCompile)
{
srg->Compile();
}
};
m_scene->SetShaderResourceGroupCallback(callback);
// Bind m_defaultScene to the GameEntityContext's AzFramework::Scene
Outcome<AzFramework::Scene*, AZStd::string> createSceneOutcome;
AzFramework::SceneSystemRequestBus::BroadcastResult(createSceneOutcome, &AzFramework::SceneSystemRequests::CreateScene,
m_sceneName);
AZ_Assert(createSceneOutcome, createSceneOutcome.GetError().c_str()); // This should never happen unless scene creation has changed.
createSceneOutcome.GetValue()->SetSubsystem(m_scene.get());
m_frameworkScene = createSceneOutcome.GetValue();
m_frameworkScene->SetSubsystem(m_scene.get());
m_entityContext->InitContext();
bool success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(success, &AzFramework::SceneSystemRequests::SetSceneForEntityContextId,
m_entityContext->GetContextId(), m_frameworkScene);
AZ_Assert(success, "Unable to set entity context on AzFramework::Scene: %s", m_sceneName.c_str());
// Create a render pipeline from the specified asset for the window context and add the pipeline to the scene
RPI::RenderPipelineDescriptor pipelineDesc;
pipelineDesc.m_mainViewTagName = "MainCamera";
pipelineDesc.m_name = m_pipelineName;
pipelineDesc.m_rootPassTemplate = "MainPipelineRenderToTexture";
// We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue
// [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost
pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
m_renderPipeline = RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc);
m_scene->AddRenderPipeline(m_renderPipeline);
m_scene->Activate();
RPI::RPISystemInterface::Get()->RegisterScene(m_scene);
m_passHierarchy.push_back(m_pipelineName);
m_passHierarchy.push_back("CopyToSwapChain");
// Connect camera to pipeline's default view after camera entity activated
Name viewName = Name("MainCamera");
m_view = RPI::View::CreateView(viewName, RPI::View::UsageCamera);
m_transform = Transform::CreateFromQuaternionAndTranslation(Quaternion::CreateIdentity(), Vector3::CreateZero());
m_view->SetCameraTransform(Matrix3x4::CreateFromTransform(m_transform));
Matrix4x4 viewToClipMatrix;
MakePerspectiveFovMatrixRH(viewToClipMatrix,
Constants::HalfPi,
AspectRatio,
NearDist,
FarDist, true);
m_view->SetViewToClipMatrix(viewToClipMatrix);
m_renderPipeline->SetDefaultView(m_view);
// Create lighting preset
m_lightingPresetAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::AnyAsset>(LightingPresetPath);
if (m_lightingPresetAsset.IsReady())
{
auto preset = m_lightingPresetAsset->GetDataAs<AZ::Render::LightingPreset>();
if (preset)
{
auto iblFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::ImageBasedLightFeatureProcessorInterface>();
auto postProcessFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::PostProcessFeatureProcessorInterface>();
auto exposureControlSettingInterface = postProcessFeatureProcessor->GetOrCreateSettingsInterface(AZ::EntityId())->GetOrCreateExposureControlSettingsInterface();
auto directionalLightFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DirectionalLightFeatureProcessorInterface>();
auto skyboxFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
skyboxFeatureProcessor->Enable(true);
skyboxFeatureProcessor->SetSkyboxMode(AZ::Render::SkyBoxMode::Cubemap);
Camera::Configuration cameraConfig;
cameraConfig.m_fovRadians = Constants::HalfPi;
cameraConfig.m_nearClipDistance = NearDist;
cameraConfig.m_farClipDistance = FarDist;
cameraConfig.m_frustumWidth = 100.0f;
cameraConfig.m_frustumHeight = 100.0f;
AZStd::vector<AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle> lightHandles;
preset->ApplyLightingPreset(
iblFeatureProcessor,
skyboxFeatureProcessor,
exposureControlSettingInterface,
directionalLightFeatureProcessor,
cameraConfig,
lightHandles);
}
}
// Create preview model
AzFramework::EntityContextRequestBus::EventResult(m_modelEntity, m_entityContext->GetContextId(),
&AzFramework::EntityContextRequestBus::Events::CreateEntity, "PreviewModel");
m_modelEntity->CreateComponent(Render::MeshComponentTypeId);
m_modelEntity->CreateComponent(Render::MaterialComponentTypeId);
m_modelEntity->CreateComponent(azrtti_typeid<AzFramework::TransformComponent>());
m_modelEntity->Init();
m_modelEntity->Activate();
TransformBus::Event(m_modelEntity->GetId(), &TransformBus::Events::SetLocalTM,
Transform::CreateTranslation(Vector3(0, 0.8f, -0.5f)));
Render::MeshComponentRequestBus::Event(m_modelEntity->GetId(), &Render::MeshComponentRequestBus::Events::SetModelAssetPath, ModelPath);
}
void MaterialThumbnailRenderer::RenderThumbnail(Data::AssetId assetId, [[maybe_unused]] int thumbnailSize)
{
if (!m_initialized)
{
Init();
m_initialized = true;
}
if (m_assetIdQueue.empty())
{
m_shouldPullNextAsset = true;
TickBus::Handler::BusConnect();
}
m_assetIdQueue.emplace(assetId);
}
void MaterialThumbnailRenderer::OnMaterialsUpdated([[maybe_unused]] const Render::MaterialAssignmentMap& materials)
{
m_readyToCapture = true;
Render::MaterialComponentNotificationBus::Handler::BusDisconnect();
}
} // namespace LyIntegration
} // namespace AZ
@@ -1,112 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzFramework/Scene/Scene.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <Atom/RPI.Public/Base.h>
#include <Atom/Feature/Utils/FrameCaptureBus.h>
#include <Atom/Feature/Utils/LightingPreset.h>
// Disables warning messages triggered by the Qt library
// 4251: class needs to have dll-interface to be used by clients of class
// 4800: forcing value to bool 'true' or 'false' (performance warning)
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <QPixmap>
AZ_POP_DISABLE_WARNING
namespace AZ
{
namespace LyIntegration
{
//! Provides custom rendering of material thumbnails
class MaterialThumbnailRenderer
: public AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::Handler
, public SystemTickBus::Handler
, public TickBus::Handler
, public AZ::Data::AssetBus::Handler
, public Render::FrameCaptureNotificationBus::Handler
, public Render::MaterialComponentNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(MaterialThumbnailRenderer, AZ::SystemAllocator, 0)
MaterialThumbnailRenderer();
~MaterialThumbnailRenderer();
bool Installed() const override;
//! ThumbnailerRendererRequestsBus::Handler interface overrides...
void RenderThumbnail(AZ::Data::AssetId assetId, int thumbnailSize) override;
//! SystemTickBus::Handler interface overrides...
void OnSystemTick() override;
//! AZ::TickBus::Handler interface overrides...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// AZ::Data::AssetBus::Handler
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetCanceled(AZ::Data::AssetId assetId) override;
//! Render::FrameCaptureNotificationBus::Handler overrides...
void OnCaptureFinished(Render::FrameCaptureResult result, const AZStd::string& info) override;
private:
// MaterialComponentNotificationBus::Handler overrides...
void OnMaterialsUpdated(const Render::MaterialAssignmentMap& materials) override;
void Init();
RPI::ScenePtr m_scene;
AZStd::string m_sceneName = "Material Thumbnail Scene";
AZStd::string m_pipelineName = "Material Thumbnail Pipeline";
AzFramework::Scene* m_frameworkScene = nullptr;
RPI::RenderPipelinePtr m_renderPipeline;
AZStd::unique_ptr<AzFramework::EntityContext> m_entityContext;
AZStd::vector<AZStd::string> m_passHierarchy;
AZ::Data::Asset<AZ::RPI::AnyAsset> m_lightingPresetAsset;
RPI::ViewPtr m_view = nullptr;
Entity* m_modelEntity = nullptr;
Transform m_transform;
//! Ready to process next request, this value is accessed from different threads.
AZStd::atomic<bool> m_shouldPullNextAsset;
//! Is renderer initialized. Initialization is only performed once the first thumbnail request is submitted.
bool m_initialized = false;
//! It takes an extra frame to load a mesh and apply material, this variable is set to true once we are ready to render pipeline to texture.
bool m_readyToCapture = false;
//! Incoming thumbnail requests are appended to this queue and processed one at a time in OnTick function.
AZStd::queue<Data::AssetId> m_assetIdQueue;
//! Current material asset being rendered.
Data::Asset<RPI::MaterialAsset> m_materialAssetToRender;
double m_simulateTime = 0.0f;
float m_deltaTime = 0.0f;
};
} // namespace LyIntegration
} // namespace AZ