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
@@ -16,6 +16,7 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -33,6 +34,7 @@ namespace AZ
}
EditorCommonFeaturesSystemComponent::EditorCommonFeaturesSystemComponent() = default;
EditorCommonFeaturesSystemComponent::~EditorCommonFeaturesSystemComponent() = default;
//! Main system component for the Atom Common Feature Gem's editor/tools module.
@@ -84,49 +86,67 @@ namespace AZ
void EditorCommonFeaturesSystemComponent::Activate()
{
m_renderer = AZStd::make_unique<AZ::LyIntegration::Thumbnails::CommonThumbnailRenderer>();
m_previewerFactory = AZStd::make_unique <LyIntegration::CommonPreviewerFactory>();
m_skinnedMeshDebugDisplay = AZStd::make_unique<SkinnedMeshDebugDisplay>();
AzToolsFramework::EditorLevelNotificationBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler::BusConnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
}
void EditorCommonFeaturesSystemComponent::Deactivate()
{
AzToolsFramework::EditorLevelNotificationBus::Handler::BusDisconnect();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler::BusDisconnect();
m_skinnedMeshDebugDisplay.reset();
m_previewerFactory.reset();
m_renderer.reset();
}
void EditorCommonFeaturesSystemComponent::OnNewLevelCreated()
{
AZ::Data::AssetCatalogRequestBus::BroadcastResult(m_levelDefaultSliceAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, m_atomLevelDefaultAssetPath.c_str(), azrtti_typeid<AZ::SliceAsset>(), false);
bool isPrefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (m_levelDefaultSliceAssetId.IsValid())
if (!isPrefabSystemEnabled)
{
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().GetAsset<AZ::SliceAsset>(
m_levelDefaultSliceAssetId,
AZ::Data::AssetLoadBehavior::Default);
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
m_levelDefaultSliceAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, m_atomLevelDefaultAssetPath.c_str(),
azrtti_typeid<AZ::SliceAsset>(), false);
asset.BlockUntilLoadComplete();
if (asset)
if (m_levelDefaultSliceAssetId.IsValid())
{
AZ::Vector3 cameraPosition = AZ::Vector3::CreateZero();
bool activeCameraFound = false;
Camera::EditorCameraRequestBus::BroadcastResult(activeCameraFound, &Camera::EditorCameraRequestBus::Events::GetActiveCameraPosition, cameraPosition);
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().GetAsset<AZ::SliceAsset>(
m_levelDefaultSliceAssetId, AZ::Data::AssetLoadBehavior::Default);
if (activeCameraFound)
asset.BlockUntilLoadComplete();
if (asset)
{
AZ::Transform worldTransform = AZ::Transform::CreateTranslation(cameraPosition);
AZ::Vector3 cameraPosition = AZ::Vector3::CreateZero();
bool activeCameraFound = false;
Camera::EditorCameraRequestBus::BroadcastResult(
activeCameraFound, &Camera::EditorCameraRequestBus::Events::GetActiveCameraPosition, cameraPosition);
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusConnect();
if (IEditor* editor = GetLegacyEditor();
!editor->IsUndoSuspended())
if (activeCameraFound)
{
editor->SuspendUndo();
}
AZ::Transform worldTransform = AZ::Transform::CreateTranslation(cameraPosition);
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice, asset, worldTransform);
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusConnect();
if (IEditor* editor = GetLegacyEditor(); !editor->IsUndoSuspended())
{
editor->SuspendUndo();
}
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::InstantiateEditorSlice, asset,
worldTransform);
}
}
}
}
@@ -165,7 +185,6 @@ namespace AZ
}
}
void EditorCommonFeaturesSystemComponent::OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& /*ticket*/)
{
if (m_levelDefaultSliceAssetId == sliceAssetId)
@@ -175,5 +194,16 @@ namespace AZ
AZ_Warning("EditorCommonFeaturesSystemComponent", false, "Failed to instantiate default Atom environment slice.");
}
}
const AzToolsFramework::AssetBrowser::PreviewerFactory* EditorCommonFeaturesSystemComponent::GetPreviewerFactory(
const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const
{
return m_previewerFactory->IsEntrySupported(entry) ? m_previewerFactory.get() : nullptr;
}
void EditorCommonFeaturesSystemComponent::OnApplicationAboutToStop()
{
m_renderer.reset();
}
} // namespace Render
} // namespace AZ
@@ -13,8 +13,12 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzFramework/Application/Application.h>
#include <AzToolsFramework/API/EditorLevelNotificationBus.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/AssetBrowser/Previewer/PreviewerBus.h>
#include <Thumbnails/Rendering/CommonThumbnailRenderer.h>
#include <Source/Thumbnails/Preview/CommonPreviewerFactory.h>
namespace AZ
{
@@ -27,6 +31,8 @@ namespace AZ
: public AZ::Component
, public AzToolsFramework::EditorLevelNotificationBus::Handler
, public AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler
, public AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler
, public AzFramework::ApplicationLifecycleEvents::Bus::Handler
{
public:
AZ_COMPONENT(EditorCommonFeaturesSystemComponent, "{D73D77CF-D5AF-428B-909B-324E96D3DEF5}");
@@ -54,12 +60,21 @@ namespace AZ
void OnSliceInstantiated(const AZ::Data::AssetId&, AZ::SliceComponent::SliceInstanceAddress&, const AzFramework::SliceInstantiationTicket&) override;
void OnSliceInstantiationFailed(const AZ::Data::AssetId&, const AzFramework::SliceInstantiationTicket&) override;
// AzToolsFramework::AssetBrowser::PreviewerRequestBus::Handler overrides...
const AzToolsFramework::AssetBrowser::PreviewerFactory* GetPreviewerFactory(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const override;
// AzFramework::ApplicationLifecycleEvents overrides...
void OnApplicationAboutToStop() override;
private:
AZStd::unique_ptr<SkinnedMeshDebugDisplay> m_skinnedMeshDebugDisplay;
AZ::Data::AssetId m_levelDefaultSliceAssetId;
AZStd::string m_atomLevelDefaultAssetPath{ "LevelAssets/default.slice" };
float m_envProbeHeight{ 200.0f };
AZStd::unique_ptr<AZ::LyIntegration::Thumbnails::CommonThumbnailRenderer> m_renderer;
AZStd::unique_ptr<LyIntegration::CommonPreviewerFactory> m_previewerFactory;
};
} // namespace Render
} // namespace AZ
@@ -83,16 +83,21 @@ namespace AZ
m_entityId = entityId;
m_dirty = true;
RPI::ScenePtr scene = RPI::RPISystemInterface::Get()->GetDefaultScene();
if (scene)
{
AZ::RPI::SceneNotificationBus::Handler::BusConnect(scene->GetId());
}
GridComponentRequestBus::Handler::BusConnect(m_entityId);
AZ::TransformNotificationBus::Handler::BusConnect(m_entityId);
AZ::TickBus::Handler::BusConnect();
}
void GridComponentController::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
GridComponentRequestBus::Handler::BusDisconnect();
AZ::RPI::SceneNotificationBus::Handler::BusDisconnect();
m_entityId = EntityId(EntityId::InvalidEntityId);
}
@@ -171,11 +176,8 @@ namespace AZ
return m_configuration.m_secondaryColor;
}
void GridComponentController::OnTick(float deltaTime, AZ::ScriptTimePoint time)
void GridComponentController::OnBeginPrepareRender()
{
AZ_UNUSED(time);
AZ_UNUSED(deltaTime);
auto* auxGeomFP = AZ::RPI::Scene::GetFeatureProcessorForEntity<AZ::RPI::AuxGeomFeatureProcessorInterface>(m_entityId);
if (auto auxGeom = auxGeomFP->GetDrawQueue())
{
@@ -13,10 +13,10 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AtomLyIntegration/CommonFeatures/Grid/GridComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Grid/GridComponentConfig.h>
#include <Atom/RPI.Public/SceneBus.h>
namespace AZ
{
@@ -25,8 +25,8 @@ namespace AZ
//! Controls behavior and rendering of a wireframe grid
class GridComponentController final
: public GridComponentRequestBus::Handler
, public AZ::TickBus::Handler
, public AZ::TransformNotificationBus::Handler
, public AZ::RPI::SceneNotificationBus::Handler
{
public:
friend class EditorGridComponent;
@@ -64,12 +64,12 @@ namespace AZ
void SetSecondaryColor(const AZ::Color& gridSecondaryColor) override;
AZ::Color GetSecondaryColor() const override;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//! AZ::TransformNotificationBus::Handler overrides ...
void OnTransformChanged(const Transform& local, const Transform& world) override;
// AZ::RPI::SceneNotificationBus::Handler overrides ...
void OnBeginPrepareRender() override;
void BuildGrid();
EntityId m_entityId;
@@ -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,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,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
@@ -198,5 +198,14 @@ namespace AZ
AzToolsFramework::Refresh_EntireTree);
}
AZ::u32 EditorMeshComponent::OnConfigurationChanged()
{
// temp variable is needed to hold reference to m_modelAsset while it's being loaded.
// Otherwise it gets released in Deactivate function, and instantly re-activating the component
// places it in a bad state, which happens in OnConfigurationChanged base function.
// This is a bug with AssetManager [LYN-2249]
auto temp = m_controller.m_configuration.m_modelAsset;
return BaseClass::OnConfigurationChanged();
}
} // namespace Render
} // namespace AZ
@@ -64,6 +64,8 @@ namespace AZ
// MeshComponentNotificationBus overrides ...
void OnModelReady(const Data::Asset<RPI::ModelAsset>& modelAsset, const Data::Instance<RPI::Model>& model) override;
AZ::u32 OnConfigurationChanged() override;
AZ::Crc32 AddEditorMaterialComponent();
bool HasEditorMaterialComponent() const;
AZ::u32 GetEditorMaterialComponentVisibility() const;
@@ -0,0 +1,101 @@
/*
* 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/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <Source/Mesh/EditorMeshSystemComponent.h>
#include <Source/Mesh/MeshThumbnail.h>
namespace AZ
{
namespace Render
{
//! Main system component for the Atom Common Feature Gem's editor/tools module.
void EditorMeshSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<EditorMeshSystemComponent, AZ::Component>()
->Version(0);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<EditorMeshSystemComponent>("EditorMeshSystemComponent", "System component that sets up necessary logic related to EditorMeshComponent..")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
void EditorMeshSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("EditorMeshSystem"));
}
void EditorMeshSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("EditorMeshSystem"));
}
void EditorMeshSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("ThumbnailerService"));
}
void EditorMeshSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
AZ_UNUSED(dependent);
}
void EditorMeshSystemComponent::Activate()
{
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
SetupThumbnails();
}
void EditorMeshSystemComponent::Deactivate()
{
TeardownThumbnails();
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
}
void EditorMeshSystemComponent::OnApplicationAboutToStop()
{
TeardownThumbnails();
}
void EditorMeshSystemComponent::SetupThumbnails()
{
using namespace AzToolsFramework::Thumbnailer;
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider,
MAKE_TCACHE(Thumbnails::MeshThumbnailCache),
ThumbnailContext::DefaultContext);
}
void EditorMeshSystemComponent::TeardownThumbnails()
{
using namespace AzToolsFramework::Thumbnailer;
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::UnregisterThumbnailProvider,
Thumbnails::MeshThumbnailCache::ProviderName,
ThumbnailContext::DefaultContext);
}
} // namespace Render
} // namespace AZ
@@ -0,0 +1,49 @@
/*
* 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/Component/Component.h>
#include <AzFramework/Application/Application.h>
namespace AZ
{
namespace Render
{
//! System component that sets up necessary logic related to EditorMeshComponent.
class EditorMeshSystemComponent
: public AZ::Component
, private AzFramework::ApplicationLifecycleEvents::Bus::Handler
{
public:
AZ_COMPONENT(EditorMeshSystemComponent, "{4D332E3D-C4FC-410B-A915-8E234CBDD4EC}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
// AZ::Component interface overrides...
void Activate() override;
void Deactivate() override;
private:
// AzFramework::ApplicationLifecycleEvents overrides...
void OnApplicationAboutToStop() override;
void SetupThumbnails();
void TeardownThumbnails();
};
} // namespace Render
} // namespace AZ
@@ -124,7 +124,6 @@ namespace AZ
void MeshComponentController::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("MaterialProviderService", 0x64849a6b));
dependent.push_back(AZ_CRC("TransformService", 0x8ee22c50));
}
@@ -181,12 +180,14 @@ namespace AZ
m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(m_entityId);
AZ_Error("MeshComponentController", m_meshFeatureProcessor, "Unable to find a MeshFeatureProcessorInterface on the entityId.");
RegisterModel();
MeshComponentRequestBus::Handler::BusConnect(m_entityId);
TransformNotificationBus::Handler::BusConnect(m_entityId);
MaterialReceiverRequestBus::Handler::BusConnect(m_entityId);
MaterialComponentNotificationBus::Handler::BusConnect(m_entityId);
AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId);
//Buses must be connected before RegisterModel in case requests are made as a result of HandleModelChange
RegisterModel();
}
void MeshComponentController::Deactivate()
@@ -196,6 +197,7 @@ namespace AZ
TransformNotificationBus::Handler::BusDisconnect();
MaterialReceiverRequestBus::Handler::BusDisconnect();
MaterialComponentNotificationBus::Handler::BusDisconnect();
UnregisterModel();
m_meshFeatureProcessor = nullptr;
@@ -229,7 +231,8 @@ namespace AZ
AZStd::unordered_set<AZ::Name> MeshComponentController::GetModelUvNames() const
{
return GetModel()->GetUvNames();
const Data::Instance<RPI::Model> model = GetModel();
return model ? model->GetUvNames() : AZStd::unordered_set<AZ::Name>();
}
void MeshComponentController::OnMaterialsUpdated([[maybe_unused]] const MaterialAssignmentMap& materials)
@@ -263,16 +266,16 @@ namespace AZ
m_meshHandle = m_meshFeatureProcessor->AcquireMesh(m_configuration.m_modelAsset, materials);
m_meshFeatureProcessor->ConnectModelChangeEventHandler(m_meshHandle, m_changeEventHandler);
// [GFX TODO] This should happen automatically. m_changeEventHandler should be passed to AcquireMesh
// If the model instance or asset already exists, announce a model change to let others know it's loaded.
HandleModelChange(m_meshFeatureProcessor->GetModel(m_meshHandle));
const AZ::Transform& transform = m_transformInterface ? m_transformInterface->GetWorldTM() : Transform::Identity();
m_meshFeatureProcessor->SetTransform(m_meshHandle, transform);
m_meshFeatureProcessor->SetSortKey(m_meshHandle, m_configuration.m_sortKey);
m_meshFeatureProcessor->SetLodOverride(m_meshHandle, m_configuration.m_lodOverride);
m_meshFeatureProcessor->SetExcludeFromReflectionCubeMaps(m_meshHandle, m_configuration.m_excludeFromReflectionCubeMaps);
m_meshFeatureProcessor->SetUseForwardPassIblSpecular(m_meshHandle, m_configuration.m_useForwardPassIblSpecular);
// [GFX TODO] This should happen automatically. m_changeEventHandler should be passed to AcquireMesh
// If the model instance or asset already exists, announce a model change to let others know it's loaded.
HandleModelChange(m_meshFeatureProcessor->GetModel(m_meshHandle));
}
}
@@ -0,0 +1,113 @@
/*
* 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 <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <QtConcurrent/QtConcurrent>
#include <Source/Mesh/MeshThumbnail.h>
#include <Source/Thumbnails/ThumbnailUtils.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
const int MeshThumbnailSize = 200;
//////////////////////////////////////////////////////////////////////////
// MeshThumbnail
//////////////////////////////////////////////////////////////////////////
MeshThumbnail::MeshThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, int thumbnailSize)
: Thumbnail(key, thumbnailSize)
{
m_assetId = GetAssetId(key, RPI::ModelAsset::RTTI_Type());
if (!m_assetId.IsValid())
{
AZ_Error("MeshThumbnail", 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 MeshThumbnail::LoadThread()
{
AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::QueueEvent(
RPI::ModelAsset::RTTI_Type(),
&AzToolsFramework::Thumbnailer::ThumbnailerRendererRequests::RenderThumbnail,
m_key,
m_thumbnailSize);
// wait for response from thumbnail renderer
m_renderWait.acquire();
}
MeshThumbnail::~MeshThumbnail()
{
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler::BusDisconnect();
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
}
void MeshThumbnail::ThumbnailRendered(QPixmap& thumbnailImage)
{
m_pixmap = thumbnailImage;
m_renderWait.release();
}
void MeshThumbnail::ThumbnailFailedToRender()
{
m_state = State::Failed;
m_renderWait.release();
}
void MeshThumbnail::OnCatalogAssetChanged([[maybe_unused]] const AZ::Data::AssetId& assetId)
{
if (m_assetId == assetId &&
m_state == State::Ready)
{
m_state = State::Unloaded;
Load();
}
}
//////////////////////////////////////////////////////////////////////////
// MeshThumbnailCache
//////////////////////////////////////////////////////////////////////////
MeshThumbnailCache::MeshThumbnailCache()
: ThumbnailCache<MeshThumbnail>()
{
}
MeshThumbnailCache::~MeshThumbnailCache() = default;
int MeshThumbnailCache::GetPriority() const
{
// Material thumbnails override default source thumbnails, so carry higher priority
return 1;
}
const char* MeshThumbnailCache::GetProviderName() const
{
return ProviderName;
}
bool MeshThumbnailCache::IsSupportedThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) const
{
return GetAssetId(key, RPI::ModelAsset::RTTI_Type()).IsValid();
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
#include <Mesh/moc_MeshThumbnail.cpp>
@@ -0,0 +1,76 @@
/*
* 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>
#endif
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
/**
* Custom material or model thumbnail that detects when an asset changes and updates the thumbnail
*/
class MeshThumbnail
: public AzToolsFramework::Thumbnailer::Thumbnail
, public AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
{
Q_OBJECT
public:
MeshThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, int thumbnailSize);
~MeshThumbnail() 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 MeshThumbnailCache
: public AzToolsFramework::Thumbnailer::ThumbnailCache<MeshThumbnail>
{
public:
MeshThumbnailCache();
~MeshThumbnailCache() override;
int GetPriority() const override;
const char* GetProviderName() const override;
static constexpr const char* ProviderName = "Mesh Thumbnails";
protected:
bool IsSupportedThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey key) const override;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -55,6 +55,7 @@
#include <Material/EditorMaterialComponent.h>
#include <Material/EditorMaterialSystemComponent.h>
#include <Mesh/EditorMeshComponent.h>
#include <Mesh/EditorMeshSystemComponent.h>
#include <ReflectionProbe/EditorReflectionProbeComponent.h>
#include <PostProcess/EditorPostFxLayerComponent.h>
#include <PostProcess/Bloom/EditorBloomComponent.h>
@@ -131,6 +132,7 @@ namespace AZ
EditorImageBasedLightComponent::CreateDescriptor(),
EditorMaterialComponent::CreateDescriptor(),
EditorMaterialSystemComponent::CreateDescriptor(),
EditorMeshSystemComponent::CreateDescriptor(),
EditorMeshComponent::CreateDescriptor(),
EditorPhysicalSkyComponent::CreateDescriptor(),
EditorPointLightComponent::CreateDescriptor(),
@@ -153,6 +155,7 @@ namespace AZ
azrtti_typeid<AtomLyIntegrationCommonFeaturesSystemComponent>(),
#ifdef ATOMLYINTEGRATION_FEATURE_COMMON_EDITOR
azrtti_typeid<EditorMaterialSystemComponent>(),
azrtti_typeid<EditorMeshSystemComponent>(),
azrtti_typeid<EditorCommonFeaturesSystemComponent>(),
azrtti_typeid<EditorPostFxSystemComponent>(),
#endif
@@ -13,7 +13,7 @@
#include <AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationComponentConfig.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <Atom/Feature/ACES/Aces.h>
#include <ACES/Aces.h>
namespace AZ
{
@@ -14,7 +14,7 @@
#include <Atom/RPI.Public/Scene.h>
#include <Atom/Feature/ACES/Aces.h>
#include <ACES/Aces.h>
#include <PostProcess/LookModification/LookModificationComponentController.h>
@@ -18,7 +18,7 @@
#include <AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationBus.h>
#include <AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationComponentConfig.h>
#include <Atom/Feature/ACES/Aces.h>
#include <ACES/Aces.h>
#include <Atom/Feature/PostProcess/LookModification/LookModificationSettingsInterface.h>
#include <Atom/Feature/PostProcess/PostProcessSettingsInterface.h>
#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
@@ -0,0 +1,80 @@
/*
* 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 <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <Source/Thumbnails/Preview/CommonPreviewer.h>
#include <Source/Thumbnails/ThumbnailUtils.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/Thumbnails/Preview/ui_CommonPreviewer.h>
#include <QString>
#include <QResizeEvent>
AZ_POP_DISABLE_WARNING
namespace AZ
{
namespace LyIntegration
{
static constexpr int CharWidth = 6;
CommonPreviewer::CommonPreviewer(QWidget* parent)
: Previewer(parent)
, m_ui(new Ui::CommonPreviewerClass())
{
m_ui->setupUi(this);
}
CommonPreviewer::~CommonPreviewer()
{
}
void CommonPreviewer::Display(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry)
{
using namespace AzToolsFramework::AssetBrowser;
using namespace AzToolsFramework::Thumbnailer;
auto thumbnailKey = entry->GetThumbnailKey();
m_ui->m_previewWidget->SetThumbnailKey(thumbnailKey, ThumbnailContext::DefaultContext);
m_fileInfo = QString::fromUtf8(entry->GetName().c_str());
UpdateFileInfo();
}
const QString& CommonPreviewer::GetName() const
{
return m_name;
}
void CommonPreviewer::resizeEvent([[maybe_unused]] QResizeEvent* event)
{
m_ui->m_previewWidget->setMaximumHeight(m_ui->m_previewWidget->width());
UpdateFileInfo();
}
void CommonPreviewer::UpdateFileInfo() const
{
m_ui->m_fileInfoLabel->setText(Thumbnails::WordWrap(m_fileInfo, m_ui->m_fileInfoLabel->width() / CharWidth));
}
} // namespace LyIntegration
} // namespace AZ
#include <Source/Thumbnails/Preview/moc_CommonPreviewer.cpp>
@@ -24,7 +24,7 @@ AZ_POP_DISABLE_WARNING
namespace Ui
{
class MaterialPreviewerClass;
class CommonPreviewerClass;
}
namespace AzToolsFramework
@@ -43,15 +43,15 @@ namespace AZ
{
namespace LyIntegration
{
class MaterialPreviewer final
class CommonPreviewer final
: public AzToolsFramework::AssetBrowser::Previewer
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(MaterialPreviewer, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR(CommonPreviewer, AZ::SystemAllocator, 0);
explicit MaterialPreviewer(QWidget* parent = nullptr);
~MaterialPreviewer();
explicit CommonPreviewer(QWidget* parent = nullptr);
~CommonPreviewer();
// AzToolsFramework::AssetBrowser::Previewer overrides...
void Clear() const override {}
@@ -62,16 +62,11 @@ namespace AZ
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;
QScopedPointer<Ui::CommonPreviewerClass> m_ui;
QString m_fileInfo;
QString m_name = "MaterialPreviewer";
QString m_name = "CommonPreviewer";
};
} // namespace LyIntegration
} // namespace AZ
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MaterialPreviewerClass</class>
<widget class="QWidget" name="MaterialPreviewerClass">
<class>CommonPreviewerClass</class>
<widget class="QWidget" name="CommonPreviewerClass">
<property name="geometry">
<rect>
<x>0</x>
@@ -27,7 +27,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="AzToolsFramework::Thumbnailer::ThumbnailWidget" name="m_materialPreviewWidget" native="true">
<widget class="AzToolsFramework::Thumbnailer::ThumbnailWidget" name="m_previewWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -0,0 +1,41 @@
/*
* 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.Reflect/Model/ModelAsset.h>
#include <Source/Thumbnails/Preview/CommonPreviewer.h>
#include <Source/Thumbnails/Preview/CommonPreviewerFactory.h>
#include <Source/Thumbnails/ThumbnailUtils.h>
namespace AZ
{
namespace LyIntegration
{
AzToolsFramework::AssetBrowser::Previewer* CommonPreviewerFactory::CreatePreviewer(QWidget* parent) const
{
return new CommonPreviewer(parent);
}
bool CommonPreviewerFactory::IsEntrySupported(const AzToolsFramework::AssetBrowser::AssetBrowserEntry* entry) const
{
return
Thumbnails::GetAssetId(entry->GetThumbnailKey(), RPI::MaterialAsset::RTTI_Type()).IsValid() ||
Thumbnails::GetAssetId(entry->GetThumbnailKey(), RPI::ModelAsset::RTTI_Type()).IsValid();
}
const QString& CommonPreviewerFactory::GetName() const
{
return m_name;
}
} // namespace LyIntegration
} // namespace AZ
@@ -22,14 +22,14 @@ namespace AZ
{
namespace LyIntegration
{
class MaterialPreviewerFactory final
class CommonPreviewerFactory final
: public AzToolsFramework::AssetBrowser::PreviewerFactory
{
public:
AZ_CLASS_ALLOCATOR(MaterialPreviewerFactory, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR(CommonPreviewerFactory, AZ::SystemAllocator, 0);
MaterialPreviewerFactory() = default;
~MaterialPreviewerFactory() = default;
CommonPreviewerFactory() = default;
~CommonPreviewerFactory() = default;
// AzToolsFramework::AssetBrowser::PreviewerFactory overrides...
AzToolsFramework::AssetBrowser::Previewer* CreatePreviewer(QWidget* parent = nullptr) const override;
@@ -37,7 +37,7 @@ namespace AZ
const QString& GetName() const override;
private:
QString m_name = "MaterialPreviewer";
QString m_name = "CommonPreviewer";
};
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,95 @@
/*
* 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 <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <Thumbnails/Rendering/CommonThumbnailRenderer.h>
#include <Thumbnails/Rendering/ThumbnailRendererData.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/FindThumbnailToRenderStep.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/ReleaseResourcesStep.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/WaitForAssetsToLoadStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
CommonThumbnailRenderer::CommonThumbnailRenderer()
: m_data(new ThumbnailRendererData)
{
// CommonThumbnailRenderer supports both models and materials, but we connect on materialAssetType
// since MaterialOrModelThumbnail dispatches event on materialAssetType address too
AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler::BusConnect(RPI::MaterialAsset::RTTI_Type());
AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler::BusConnect(RPI::ModelAsset::RTTI_Type());
SystemTickBus::Handler::BusConnect();
m_steps[Step::Initialize] = AZStd::make_shared<InitializeStep>(this);
m_steps[Step::FindThumbnailToRender] = AZStd::make_shared<FindThumbnailToRenderStep>(this);
m_steps[Step::WaitForAssetsToLoad] = AZStd::make_shared<WaitForAssetsToLoadStep>(this);
m_steps[Step::Capture] = AZStd::make_shared<CaptureStep>(this);
m_steps[Step::ReleaseResources] = AZStd::make_shared<ReleaseResourcesStep>(this);
}
CommonThumbnailRenderer::~CommonThumbnailRenderer()
{
if (m_currentStep != Step::None)
{
CommonThumbnailRenderer::SetStep(Step::ReleaseResources);
}
AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler::BusDisconnect();
SystemTickBus::Handler::BusDisconnect();
}
void CommonThumbnailRenderer::SetStep(Step step)
{
if (m_currentStep != Step::None)
{
m_steps[m_currentStep]->Stop();
}
m_currentStep = step;
m_steps[m_currentStep]->Start();
}
Step CommonThumbnailRenderer::GetStep() const
{
return m_currentStep;
}
bool CommonThumbnailRenderer::Installed() const
{
return true;
}
void CommonThumbnailRenderer::OnSystemTick()
{
AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::ExecuteQueuedEvents();
}
AZStd::shared_ptr<ThumbnailRendererData> CommonThumbnailRenderer::GetData() const
{
return m_data;
}
void CommonThumbnailRenderer::RenderThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, [[maybe_unused]] int thumbnailSize)
{
m_data->m_thumbnailQueue.push(thumbnailKey);
if (m_currentStep == Step::None)
{
SetStep(Step::Initialize);
}
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,66 @@
/*
* 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 <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <Thumbnails/Rendering/ThumbnailRendererContext.h>
#include <Thumbnails/Rendering/ThumbnailRendererData.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
{
namespace Thumbnails
{
class ThumbnailRendererStep;
//! Provides custom rendering of material and model thumbnails
class CommonThumbnailRenderer
: private AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler
, private SystemTickBus::Handler
, public ThumbnailRendererContext
{
public:
AZ_CLASS_ALLOCATOR(CommonThumbnailRenderer, AZ::SystemAllocator, 0)
CommonThumbnailRenderer();
~CommonThumbnailRenderer();
//! ThumbnailRendererContext overrides...
void SetStep(Step step) override;
Step GetStep() const override;
AZStd::shared_ptr<ThumbnailRendererData> GetData() const override;
private:
//! ThumbnailerRendererRequestsBus::Handler interface overrides...
void RenderThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, int thumbnailSize) override;
bool Installed() const override;
//! SystemTickBus::Handler interface overrides...
void OnSystemTick() override;
AZStd::unordered_map<Step, AZStd::shared_ptr<ThumbnailRendererStep>> m_steps;
Step m_currentStep = Step::None;
AZStd::shared_ptr<ThumbnailRendererData> m_data;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,46 @@
/*
* 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/std/smart_ptr/shared_ptr.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/ThumbnailRendererStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
struct ThumbnailRendererData;
enum class Step
{
None,
Initialize,
FindThumbnailToRender,
WaitForAssetsToLoad,
Capture,
ReleaseResources
};
//! An interface for ThumbnailRendererSteps to communicate with thumbnail renderer
class ThumbnailRendererContext
{
public:
virtual void SetStep(Step step) = 0;
virtual Step GetStep() const = 0;
virtual AZStd::shared_ptr<ThumbnailRendererData> GetData() const = 0;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,75 @@
/*
* 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/RPI.Reflect/Model/ModelAsset.h"
#include <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Reflect/System/AnyAsset.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <Thumbnails/Thumbnail.h>
namespace AzFramework
{
class Scene;
}
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
//! ThumbnailRendererData encapsulates all data used by thumbnail renderer and caches assets
struct ThumbnailRendererData final
{
static constexpr const char* LightingPresetPath = "lightingpresets/default.lightingpreset.azasset";
static constexpr const char* DefaultModelPath = "materialeditor/viewportmodels/quadsphere.azmodel";
static constexpr const char* DefaultMaterialPath = "materials/basic_grey.azmaterial";
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;
RPI::ViewPtr m_view = nullptr;
Entity* m_modelEntity = nullptr;
double m_simulateTime = 0.0f;
float m_deltaTime = 0.0f;
//! Incoming thumbnail requests are appended to this queue and processed one at a time in OnTick function.
AZStd::queue<AzToolsFramework::Thumbnailer::SharedThumbnailKey> m_thumbnailQueue;
//! Current thumbnail key being rendered.
AzToolsFramework::Thumbnailer::SharedThumbnailKey m_thumbnailKeyRendered;
Data::Asset<RPI::AnyAsset> m_lightingPresetAsset;
Data::Asset<RPI::ModelAsset> m_defaultModelAsset;
//! Model asset about to be rendered
Data::Asset<RPI::ModelAsset> m_modelAsset;
Data::Asset<RPI::MaterialAsset> m_defaultMaterialAsset;
//! Material asset about to be rendered
Data::Asset<RPI::MaterialAsset> m_materialAsset;
AZStd::unordered_set<Data::AssetId> m_assetsToLoad;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,121 @@
/*
* 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 <Atom/Feature/Utils/FrameCaptureBus.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/View.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <Thumbnails/Rendering/ThumbnailRendererContext.h>
#include <Thumbnails/Rendering/ThumbnailRendererData.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.h>
#include <AzCore/Math/MatrixUtils.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
CaptureStep::CaptureStep(ThumbnailRendererContext* context)
: ThumbnailRendererStep(context)
{
}
void CaptureStep::Start()
{
Render::MaterialComponentRequestBus::Event(
m_context->GetData()->m_modelEntity->GetId(),
&Render::MaterialComponentRequestBus::Events::SetDefaultMaterialOverride,
m_context->GetData()->m_materialAsset.GetId());
Render::MeshComponentRequestBus::Event(
m_context->GetData()->m_modelEntity->GetId(),
&Render::MeshComponentRequestBus::Events::SetModelAsset,
m_context->GetData()->m_modelAsset);
RepositionCamera();
m_readyToCapture = true;
m_ticksToCapture = 1;
TickBus::Handler::BusConnect();
}
void CaptureStep::Stop()
{
m_context->GetData()->m_renderPipeline->RemoveFromRenderTick();
TickBus::Handler::BusDisconnect();
Render::FrameCaptureNotificationBus::Handler::BusDisconnect();
}
void CaptureStep::RepositionCamera() const
{
// Get bounding sphere of the model asset and estimate how far the camera needs to be see all of it
const Aabb& aabb = m_context->GetData()->m_modelAsset->GetAabb();
Vector3 modelCenter;
float radius;
aabb.GetAsSphere(modelCenter, radius);
float distance = StartingDistanceMultiplier *
GetMax(GetMax(aabb.GetExtents().GetX(), aabb.GetExtents().GetY()), aabb.GetExtents().GetZ()) +
DepthNear;
const Quaternion cameraRotation = Quaternion::CreateFromAxisAngle(Vector3::CreateAxisZ(), StartingRotationAngle);
Vector3 cameraPosition(modelCenter.GetX(), modelCenter.GetY() - distance, modelCenter.GetZ());
cameraPosition = cameraRotation.TransformVector(cameraPosition);
auto cameraTransform = Transform::CreateFromQuaternionAndTranslation(cameraRotation, cameraPosition);
m_context->GetData()->m_view->SetCameraTransform(Matrix3x4::CreateFromTransform(cameraTransform));
}
void CaptureStep::OnTick(float deltaTime, ScriptTimePoint time)
{
m_context->GetData()->m_deltaTime = deltaTime;
m_context->GetData()->m_simulateTime = time.GetSeconds();
if (m_readyToCapture && m_ticksToCapture-- <= 0)
{
m_context->GetData()->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);
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Event(
m_context->GetData()->m_thumbnailKeyRendered,
&AzToolsFramework::Thumbnailer::ThumbnailerRendererNotifications::ThumbnailRendered,
pixmap);
};
Render::FrameCaptureNotificationBus::Handler::BusConnect();
bool startedCapture = false;
Render::FrameCaptureRequestBus::BroadcastResult(
startedCapture,
&Render::FrameCaptureRequestBus::Events::CapturePassAttachmentWithCallback,
m_context->GetData()->m_passHierarchy, AZStd::string("Output"), readbackCallback);
// Reset the capture flag if the capture request was successful. Otherwise try capture it again next tick.
if (startedCapture)
{
m_readyToCapture = false;
}
}
}
void CaptureStep::OnCaptureFinished([[maybe_unused]] Render::FrameCaptureResult result, [[maybe_unused]] const AZStd::string& info)
{
m_context->SetStep(Step::FindThumbnailToRender);
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,59 @@
/*
* 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/Utils/FrameCaptureBus.h>
#include <AzCore/Component/TickBus.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/ThumbnailRendererStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
//! CaptureStep renders a thumbnail to a pixmap and notifies MaterialOrModelThumbnail once finished
class CaptureStep
: public ThumbnailRendererStep
, private TickBus::Handler
, private Render::FrameCaptureNotificationBus::Handler
{
public:
CaptureStep(ThumbnailRendererContext* context);
void Start() override;
void Stop() override;
private:
//! Places the camera so that the entire model is visible
void RepositionCamera() const;
//! AZ::TickBus::Handler interface overrides...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//! Render::FrameCaptureNotificationBus::Handler overrides...
void OnCaptureFinished(Render::FrameCaptureResult result, const AZStd::string& info) override;
static constexpr float DepthNear = 0.01f;
static constexpr float StartingDistanceMultiplier = 1.75f;
static constexpr float StartingRotationAngle = Constants::QuarterPi / 2.0f;
//! This flag is needed to wait one frame after each frame capture to reset FrameCaptureSystemComponent
bool m_readyToCapture = true;
//! This is necessary to suspend capture to allow a frame for Material and Mesh components to assign materials
int m_ticksToCapture = 0;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,83 @@
/*
* 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 <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Thumbnails/ThumbnailUtils.h>
#include <Thumbnails/Rendering/ThumbnailRendererContext.h>
#include <Thumbnails/Rendering/ThumbnailRendererData.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/FindThumbnailToRenderStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
FindThumbnailToRenderStep::FindThumbnailToRenderStep(ThumbnailRendererContext* context)
: ThumbnailRendererStep(context)
{
}
void FindThumbnailToRenderStep::Start()
{
TickBus::Handler::BusConnect();
}
void FindThumbnailToRenderStep::Stop()
{
TickBus::Handler::BusDisconnect();
}
void FindThumbnailToRenderStep::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] ScriptTimePoint time)
{
PickNextThumbnail();
}
void FindThumbnailToRenderStep::PickNextThumbnail()
{
if (!m_context->GetData()->m_thumbnailQueue.empty())
{
// pop the next thumbnailkey to be rendered from the queue
m_context->GetData()->m_thumbnailKeyRendered = m_context->GetData()->m_thumbnailQueue.front();
m_context->GetData()->m_thumbnailQueue.pop();
// Find whether thumbnailkey contains a material asset or set a default material
m_context->GetData()->m_materialAsset = m_context->GetData()->m_defaultMaterialAsset;
Data::AssetId materialAssetId = GetAssetId(m_context->GetData()->m_thumbnailKeyRendered, RPI::MaterialAsset::RTTI_Type());
if (materialAssetId.IsValid())
{
if (m_context->GetData()->m_assetsToLoad.emplace(materialAssetId).second)
{
m_context->GetData()->m_materialAsset.Create(materialAssetId);
m_context->GetData()->m_materialAsset.QueueLoad();
}
}
// Find whether thumbnailkey contains a model asset or set a default model
m_context->GetData()->m_modelAsset = m_context->GetData()->m_defaultModelAsset;
Data::AssetId modelAssetId = GetAssetId(m_context->GetData()->m_thumbnailKeyRendered, RPI::ModelAsset::RTTI_Type());
if (modelAssetId.IsValid())
{
if (m_context->GetData()->m_assetsToLoad.emplace(modelAssetId).second)
{
m_context->GetData()->m_modelAsset.Create(modelAssetId);
m_context->GetData()->m_modelAsset.QueueLoad();
}
}
m_context->SetStep(Step::WaitForAssetsToLoad);
}
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,44 @@
/*
* 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 <Thumbnails/Rendering/ThumbnailRendererSteps/ThumbnailRendererStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
//! FindThumbnailToRenderStep checks whether there are any new thumbnails that need to be rendered every tick
class FindThumbnailToRenderStep
: public ThumbnailRendererStep
, private TickBus::Handler
{
public:
FindThumbnailToRenderStep(ThumbnailRendererContext* context);
void Start() override;
void Stop() override;
private:
//! AZ::TickBus::Handler interface overrides...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
void PickNextThumbnail();
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,224 @@
/*
* 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 <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h>
#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
#include <Atom/Feature/SkyBox/SkyBoxFeatureProcessorInterface.h>
#include <Atom/Feature/Utils/LightingPreset.h>
#include <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/Scene.h>
#include <Atom/RPI.Public/View.h>
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Atom/RPI.Reflect/System/RenderPipelineDescriptor.h>
#include <Atom/RPI.Reflect/System/SceneDescriptor.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentConstants.h>
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentConstants.h>
#include <AzCore/Math/MatrixUtils.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Thumbnails/Rendering/ThumbnailRendererData.h>
#include <Thumbnails/Rendering/ThumbnailRendererContext.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
InitializeStep::InitializeStep(ThumbnailRendererContext* context)
: ThumbnailRendererStep(context)
{
}
void InitializeStep::Start()
{
auto data = m_context->GetData();
data->m_entityContext = AZStd::make_unique<AzFramework::EntityContext>();
data->m_entityContext->InitContext();
// 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");
data->m_scene = RPI::Scene::CreateScene(sceneDesc);
// Setup scene srg modification callback (to push per-frame values to the shaders)
RPI::ShaderResourceGroupCallback callback = [data](RPI::ShaderResourceGroup* srg)
{
if (srg == nullptr)
{
return;
}
bool needCompile = false;
RHI::ShaderInputConstantIndex timeIndex = srg->FindShaderInputConstantIndex(Name{ "m_time" });
if (timeIndex.IsValid())
{
srg->SetConstant(timeIndex, aznumeric_cast<float>(data->m_simulateTime));
needCompile = true;
}
RHI::ShaderInputConstantIndex deltaTimeIndex = srg->FindShaderInputConstantIndex(Name{ "m_deltaTime" });
if (deltaTimeIndex.IsValid())
{
srg->SetConstant(deltaTimeIndex, data->m_deltaTime);
needCompile = true;
}
if (needCompile)
{
srg->Compile();
}
};
data->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,
data->m_sceneName);
AZ_Assert(createSceneOutcome, createSceneOutcome.GetError().c_str()); // This should never happen unless scene creation has changed.
createSceneOutcome.GetValue()->SetSubsystem(data->m_scene.get());
data->m_frameworkScene = createSceneOutcome.GetValue();
data->m_frameworkScene->SetSubsystem(data->m_scene.get());
bool success = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(
success,
&AzFramework::SceneSystemRequests::SetSceneForEntityContextId,
data->m_entityContext->GetContextId(),
data->m_frameworkScene);
AZ_Assert(success, "Unable to set entity context on AzFramework::Scene: %s", data->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 = data->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;
data->m_renderPipeline = RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc);
data->m_scene->AddRenderPipeline(data->m_renderPipeline);
data->m_scene->Activate();
RPI::RPISystemInterface::Get()->RegisterScene(data->m_scene);
data->m_passHierarchy.push_back(data->m_pipelineName);
data->m_passHierarchy.push_back("CopyToSwapChain");
// Connect camera to pipeline's default view after camera entity activated
Name viewName = Name("MainCamera");
data->m_view = RPI::View::CreateView(viewName, RPI::View::UsageCamera);
Matrix4x4 viewToClipMatrix;
MakePerspectiveFovMatrixRH(viewToClipMatrix,
Constants::QuarterPi,
AspectRatio,
NearDist,
FarDist, true);
data->m_view->SetViewToClipMatrix(viewToClipMatrix);
data->m_renderPipeline->SetDefaultView(data->m_view);
// Create lighting preset
data->m_lightingPresetAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::AnyAsset>(LightingPresetPath);
if (data->m_lightingPresetAsset.IsReady())
{
auto preset = data->m_lightingPresetAsset->GetDataAs<Render::LightingPreset>();
if (preset)
{
auto iblFeatureProcessor = data->m_scene->GetFeatureProcessor<Render::ImageBasedLightFeatureProcessorInterface>();
auto postProcessFeatureProcessor = data->m_scene->GetFeatureProcessor<Render::PostProcessFeatureProcessorInterface>();
auto exposureControlSettingInterface = postProcessFeatureProcessor->GetOrCreateSettingsInterface(EntityId())->GetOrCreateExposureControlSettingsInterface();
auto directionalLightFeatureProcessor = data->m_scene->GetFeatureProcessor<Render::DirectionalLightFeatureProcessorInterface>();
auto skyboxFeatureProcessor = data->m_scene->GetFeatureProcessor<Render::SkyBoxFeatureProcessorInterface>();
skyboxFeatureProcessor->Enable(true);
skyboxFeatureProcessor->SetSkyboxMode(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<Render::DirectionalLightFeatureProcessorInterface::LightHandle> lightHandles;
preset->ApplyLightingPreset(
iblFeatureProcessor,
skyboxFeatureProcessor,
exposureControlSettingInterface,
directionalLightFeatureProcessor,
cameraConfig,
lightHandles);
}
}
// Create preview model
AzFramework::EntityContextRequestBus::EventResult(data->m_modelEntity, data->m_entityContext->GetContextId(),
&AzFramework::EntityContextRequestBus::Events::CreateEntity, "ThumbnailPreviewModel");
data->m_modelEntity->CreateComponent(Render::MeshComponentTypeId);
data->m_modelEntity->CreateComponent(Render::MaterialComponentTypeId);
data->m_modelEntity->CreateComponent(azrtti_typeid<AzFramework::TransformComponent>());
data->m_modelEntity->Init();
data->m_modelEntity->Activate();
// preload default model
Data::AssetId defaultModelAssetId;
Data::AssetCatalogRequestBus::BroadcastResult(
defaultModelAssetId,
&Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
m_context->GetData()->DefaultModelPath,
RPI::ModelAsset::RTTI_Type(),
false);
if (m_context->GetData()->m_assetsToLoad.emplace(defaultModelAssetId).second)
{
data->m_defaultModelAsset.Create(defaultModelAssetId);
data->m_defaultModelAsset.QueueLoad();
}
// preload default material
Data::AssetId defaultMaterialAssetId;
Data::AssetCatalogRequestBus::BroadcastResult(
defaultMaterialAssetId,
&Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
m_context->GetData()->DefaultMaterialPath,
RPI::MaterialAsset::RTTI_Type(),
false);
if (m_context->GetData()->m_assetsToLoad.emplace(defaultMaterialAssetId).second)
{
data->m_defaultMaterialAsset.Create(defaultMaterialAssetId);
data->m_defaultMaterialAsset.QueueLoad();
}
m_context->SetStep(Step::FindThumbnailToRender);
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,42 @@
/*
* 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 <Thumbnails/Rendering/ThumbnailRendererSteps/ThumbnailRendererStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
//! InitializeStep sets up RPI system and scene and prepares it for rendering thumbnail entities
//! This step is only called once when CommonThumbnailRenderer begins rendering its first thumbnail
class InitializeStep
: public ThumbnailRendererStep
{
public:
InitializeStep(ThumbnailRendererContext* context);
void Start() override;
private:
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;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,62 @@
/*
* 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 <Atom/RPI.Public/RenderPipeline.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Atom/RPI.Public/Scene.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Scene/SceneSystemBus.h>
#include <Thumbnails/Rendering/ThumbnailRendererContext.h>
#include <Thumbnails/Rendering/ThumbnailRendererData.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/ReleaseResourcesStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
ReleaseResourcesStep::ReleaseResourcesStep(ThumbnailRendererContext* context)
: ThumbnailRendererStep(context)
{
}
void ReleaseResourcesStep::Start()
{
m_context->GetData()->m_defaultMaterialAsset.Release();
m_context->GetData()->m_defaultModelAsset.Release();
m_context->GetData()->m_materialAsset.Release();
m_context->GetData()->m_modelAsset.Release();
if (m_context->GetData()->m_modelEntity)
{
AzFramework::EntityContextRequestBus::Event(m_context->GetData()->m_entityContext->GetContextId(),
&AzFramework::EntityContextRequestBus::Events::DestroyEntity, m_context->GetData()->m_modelEntity);
m_context->GetData()->m_modelEntity = nullptr;
}
m_context->GetData()->m_frameworkScene->UnsetSubsystem<RPI::Scene>();
m_context->GetData()->m_scene->Deactivate();
m_context->GetData()->m_scene->RemoveRenderPipeline(m_context->GetData()->m_renderPipeline->GetId());
RPI::RPISystemInterface::Get()->UnregisterScene(m_context->GetData()->m_scene);
bool sceneRemovedSuccessfully = false;
AzFramework::SceneSystemRequestBus::BroadcastResult(
sceneRemovedSuccessfully,
&AzFramework::SceneSystemRequests::RemoveScene,
m_context->GetData()->m_sceneName);
m_context->GetData()->m_scene = nullptr;
m_context->GetData()->m_renderPipeline = nullptr;
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,34 @@
/*
* 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 <Thumbnails/Rendering/ThumbnailRendererSteps/ThumbnailRendererStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
class ReleaseResourcesStep
: public ThumbnailRendererStep
{
public:
ReleaseResourcesStep(ThumbnailRendererContext* context);
void Start() override;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,41 @@
/*
* 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
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
class ThumbnailRendererContext;
//! ThumbnailRendererStep decouples CommonThumbnailRenderer logic into easy-to-understand and debug pieces
class ThumbnailRendererStep
{
public:
explicit ThumbnailRendererStep(ThumbnailRendererContext* context) : m_context(context) {}
virtual ~ThumbnailRendererStep() = default;
//! Start is called when step begins execution
virtual void Start() {}
//! Stop is called when step ends execution
virtual void Stop() {}
protected:
ThumbnailRendererContext* m_context;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,105 @@
/*
* 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 "Thumbnails/ThumbnailerBus.h"
#include <Thumbnails/Rendering/ThumbnailRendererData.h>
#include <Thumbnails/Rendering/ThumbnailRendererContext.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.h>
#include <Thumbnails/Rendering/ThumbnailRendererSteps/WaitForAssetsToLoadStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
WaitForAssetsToLoadStep::WaitForAssetsToLoadStep(ThumbnailRendererContext* context)
: ThumbnailRendererStep(context)
{
}
void WaitForAssetsToLoadStep::Start()
{
LoadNextAsset();
}
void WaitForAssetsToLoadStep::Stop()
{
Data::AssetBus::Handler::BusDisconnect();
TickBus::Handler::BusDisconnect();
m_context->GetData()->m_assetsToLoad.clear();
}
void WaitForAssetsToLoadStep::LoadNextAsset()
{
if (m_context->GetData()->m_assetsToLoad.empty())
{
// When all assets are loaded, render the thumbnail itself
m_context->SetStep(Step::Capture);
}
else
{
// Pick the the next asset and wait until its ready
const auto assetIdIt = m_context->GetData()->m_assetsToLoad.begin();
m_context->GetData()->m_assetsToLoad.erase(assetIdIt);
m_assetId = *assetIdIt;
Data::AssetBus::Handler::BusConnect(m_assetId);
// If asset is already loaded, then AssetEvents will call OnAssetReady instantly and we don't need to wait this time
if (Data::AssetBus::Handler::BusIsConnected())
{
TickBus::Handler::BusConnect();
m_timeRemainingS = TimeOutS;
}
}
}
void WaitForAssetsToLoadStep::OnAssetReady([[maybe_unused]] Data::Asset<Data::AssetData> asset)
{
Data::AssetBus::Handler::BusDisconnect();
LoadNextAsset();
}
void WaitForAssetsToLoadStep::OnAssetError([[maybe_unused]] Data::Asset<Data::AssetData> asset)
{
Data::AssetBus::Handler::BusDisconnect();
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Event(
m_context->GetData()->m_thumbnailKeyRendered,
&AzToolsFramework::Thumbnailer::ThumbnailerRendererNotifications::ThumbnailFailedToRender);
m_context->SetStep(Step::FindThumbnailToRender);
}
void WaitForAssetsToLoadStep::OnAssetCanceled([[maybe_unused]] Data::AssetId assetId)
{
Data::AssetBus::Handler::BusDisconnect();
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Event(
m_context->GetData()->m_thumbnailKeyRendered,
&AzToolsFramework::Thumbnailer::ThumbnailerRendererNotifications::ThumbnailFailedToRender);
m_context->SetStep(Step::FindThumbnailToRender);
}
void WaitForAssetsToLoadStep::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
m_timeRemainingS -= deltaTime;
if (m_timeRemainingS < 0)
{
auto assetIdStr = m_assetId.ToString<AZStd::string>();
AZ_Warning("CommonThumbnailRenderer", false, "Timed out waiting for asset %s to load.", assetIdStr.c_str());
AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Event(
m_context->GetData()->m_thumbnailKeyRendered,
&AzToolsFramework::Thumbnailer::ThumbnailerRendererNotifications::ThumbnailFailedToRender);
m_context->SetStep(Step::FindThumbnailToRender);
}
}
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,54 @@
/*
* 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 <Thumbnails/Rendering/ThumbnailRendererSteps/ThumbnailRendererStep.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
//! WaitForAssetsToLoadStep pauses further rendering until all assets used for rendering a thumbnail have been loaded
class WaitForAssetsToLoadStep
: public ThumbnailRendererStep
, private Data::AssetBus::Handler
, private TickBus::Handler
{
public:
WaitForAssetsToLoadStep(ThumbnailRendererContext* context);
void Start() override;
void Stop() override;
private:
void LoadNextAsset();
// AZ::Data::AssetBus::Handler
void OnAssetReady(Data::Asset<Data::AssetData> asset) override;
void OnAssetError(Data::Asset<Data::AssetData> asset) override;
void OnAssetCanceled(Data::AssetId assetId) override;
//! AZ::TickBus::Handler interface overrides...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
static constexpr float TimeOutS = 3.0f;
Data::AssetId m_assetId;
float m_timeRemainingS = 0;
};
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,90 @@
/*
* 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 <API/EditorAssetSystemAPI.h>
#include <AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Thumbnails/ThumbnailUtils.h>
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
Data::AssetId GetAssetId(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, const Data::AssetType& assetType)
{
static const Data::AssetId invalidAssetId;
// if it's a source thumbnail key, find first product with a matching asset type
auto sourceKey = azrtti_cast<const AzToolsFramework::AssetBrowser::SourceThumbnailKey*>(key.data());
if (sourceKey)
{
bool foundIt = false;
AZStd::vector<Data::AssetInfo> productsAssetInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundIt, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID, sourceKey->GetSourceUuid(), productsAssetInfo);
if (!foundIt)
{
return invalidAssetId;
}
auto assetInfoIt = AZStd::find_if(productsAssetInfo.begin(), productsAssetInfo.end(),
[&assetType](const Data::AssetInfo& assetInfo)
{
return assetInfo.m_assetType == assetType;
});
if (assetInfoIt == productsAssetInfo.end())
{
return invalidAssetId;
}
return assetInfoIt->m_assetId;
}
// if it's a product thumbnail key just return its assetId
auto productKey = azrtti_cast<const AzToolsFramework::AssetBrowser::ProductThumbnailKey*>(key.data());
if (productKey && productKey->GetAssetType() == assetType)
{
return productKey->GetAssetId();
}
return invalidAssetId;
}
QString 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 Thumbnails
} // namespace LyIntegration
} // namespace AZ
@@ -0,0 +1,34 @@
/*
* 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>
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#endif
namespace AZ
{
namespace LyIntegration
{
namespace Thumbnails
{
//! Get assetId by assetType that belongs to either source or product thumbnail key
Data::AssetId GetAssetId(AzToolsFramework::Thumbnailer::SharedThumbnailKey key, const Data::AssetType& assetType);
//! Word wrap function for previewer QLabel, since by default it does not break long words such as filenames, so manual word wrap needed
QString WordWrap(const QString& string, int maxLength);
} // namespace Thumbnails
} // namespace LyIntegration
} // namespace AZ