Files
o3de/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageBuilderComponent.cpp
T
Qing Tao c9ee1f7871 ATOM-17132 Image builder failed to build some images after preset was fixed (#6900)
The issue was because the preset reload was only happened in CreateJobs but not ProcessJobs. But these two functions might be called from different AssetBuilder. The fix is to add preset reload for Processjobs too.
There was another change to add debug device name for streaming image.

Signed-off-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com>
2022-01-14 13:31:13 -08:00

420 lines
18 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <ImageBuilderComponent.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/Debug/Trace.h>
#include <BuilderSettings/BuilderSettingManager.h>
#include <BuilderSettings/CubemapSettings.h>
#include <ImageLoader/ImageLoaders.h>
#include <Processing/ImageAssetProducer.h>
#include <Processing/ImageConvert.h>
#include <Processing/ImageToProcess.h>
#include <Processing/PixelFormatInfo.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <QFile>
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <Atom/RPI.Reflect/Asset/AssetHandler.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h>
#include <Atom/RPI.Reflect/Image/ImageMipChainAsset.h>
namespace ImageProcessingAtom
{
BuilderPluginComponent::BuilderPluginComponent()
{
// AZ Components should only initialize their members to null and empty in constructor
// after construction, they may be deserialized from file.
}
BuilderPluginComponent::~BuilderPluginComponent()
{
}
void BuilderPluginComponent::Init()
{
}
void BuilderPluginComponent::Activate()
{
//load qt plugins for some image file formats support
AzQtComponents::PrepareQtPaths();
// create and initialize BuilderSettingManager once since it's will be used for image conversion
BuilderSettingManager::CreateInstance();
auto outcome = BuilderSettingManager::Instance()->LoadConfig();
AZ_Error("Image Processing", outcome.IsSuccess(), "Failed to load Atom image builder settings.");
if (!outcome.IsSuccess())
{
return;
}
// Activate is where you'd perform registration with other objects and systems.
// Since we want to register our builder, we do that here:
AssetBuilderSDK::AssetBuilderDesc builderDescriptor;
builderDescriptor.m_name = "Atom Image Builder";
for (int i = 0; i < s_TotalSupportedImageExtensions; i++)
{
builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(s_SupportedImageExtensions[i], AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
}
builderDescriptor.m_busId = azrtti_typeid<ImageBuilderWorker>();
builderDescriptor.m_createJobFunction = AZStd::bind(&ImageBuilderWorker::CreateJobs, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_processJobFunction = AZStd::bind(&ImageBuilderWorker::ProcessJob, &m_imageBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
builderDescriptor.m_version = 27; // [ATOM-16958]
builderDescriptor.m_analysisFingerprint = ImageProcessingAtom::BuilderSettingManager::Instance()->GetAnalysisFingerprint();
m_imageBuilder.BusConnect(builderDescriptor.m_busId);
AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDescriptor);
m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::ImageMipChainAssetHandler>());
m_assetHandlers.emplace_back(AZ::RPI::MakeAssetHandler<AZ::RPI::StreamingImageAssetHandler>());
ImageProcessingRequestBus::Handler::BusConnect();
ImageBuilderRequestBus::Handler::BusConnect();
}
void BuilderPluginComponent::Deactivate()
{
ImageProcessingRequestBus::Handler::BusDisconnect();
ImageBuilderRequestBus::Handler::BusDisconnect();
m_imageBuilder.BusDisconnect();
BuilderSettingManager::DestroyInstance();
CPixelFormats::DestroyInstance();
}
void BuilderPluginComponent::Reflect(AZ::ReflectContext* context)
{
// components also get Reflect called automatically
// this is your opportunity to perform static reflection or type registration of any types you want the serializer to know about
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<BuilderPluginComponent, AZ::Component>()
->Version(0)
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }))
;
}
BuilderSettingManager::Reflect(context);
BuilderSettings::Reflect(context);
MultiplatformPresetSettings::Reflect(context);
PresetSettings::Reflect(context);
CubemapSettings::Reflect(context);
MipmapSettings::Reflect(context);
TextureSettings::Reflect(context);
}
void BuilderPluginComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ImagerBuilderPluginService", 0x6dc0db6e));
}
void BuilderPluginComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ImagerBuilderPluginService", 0x6dc0db6e));
}
IImageObjectPtr BuilderPluginComponent::LoadImage(const AZStd::string& filePath)
{
return IImageObjectPtr(LoadImageFromFile(filePath));
}
IImageObjectPtr BuilderPluginComponent::LoadImagePreview(const AZStd::string& filePath)
{
IImageObjectPtr image(LoadImageFromFile(filePath));
if (image)
{
ImageToProcess imageToProcess(image);
imageToProcess.ConvertFormat(ePixelFormat_R8G8B8A8);
return imageToProcess.Get();
}
return image;
}
IImageObjectPtr BuilderPluginComponent::CreateImage(
AZ::u32 width,
AZ::u32 height,
AZ::u32 maxMipCount,
EPixelFormat pixelFormat)
{
IImageObjectPtr image(IImageObject::CreateImage(width, height, maxMipCount, pixelFormat));
return image;
}
AZStd::vector<AssetBuilderSDK::JobProduct> BuilderPluginComponent::ConvertImageObject(
IImageObjectPtr imageObject,
const AZStd::string& presetName,
const AZStd::string& platformName,
const AZStd::string& outputDir,
const AZ::Data::AssetId& sourceAssetId,
const AZStd::string& sourceAssetName)
{
AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
AZStd::string_view presetFilePath;
const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(PresetName(presetName), platformName, &presetFilePath);
if (preset == nullptr)
{
AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str());
return outProducts;
}
AZStd::unique_ptr<ImageConvertProcessDescriptor> desc = AZStd::make_unique<ImageConvertProcessDescriptor>();
TextureSettings& textureSettings = desc->m_textureSetting;
textureSettings.m_preset = preset->m_name;
desc->m_inputImage = imageObject;
desc->m_presetSetting = *preset;
desc->m_isPreview = false;
desc->m_platform = platformName;
desc->m_filePath = presetFilePath;
desc->m_isStreaming = BuilderSettingManager::Instance()->GetBuilderSetting(platformName)->m_enableStreaming;
desc->m_imageName = sourceAssetName;
desc->m_outputFolder = outputDir;
desc->m_sourceAssetId = sourceAssetId;
// Create an image convert process
ImageConvertProcess process(AZStd::move(desc));
process.ProcessAll();
bool result = process.IsSucceed();
if (result)
{
process.GetAppendOutputProducts(outProducts);
}
return outProducts;
}
bool BuilderPluginComponent::DoesSupportPlatform(const AZStd::string& platformId)
{
return ImageProcessingAtom::BuilderSettingManager::Instance()->DoesSupportPlatform(platformId);
}
bool BuilderPluginComponent::IsPresetFormatSquarePow2(const AZStd::string& presetName, const AZStd::string& platformName)
{
AZStd::string_view filePath;
const PresetSettings* preset = BuilderSettingManager::Instance()->GetPreset(PresetName(presetName), platformName, &filePath);
if (preset == nullptr)
{
AZ_Assert(false, "Cannot find preset with name %s.", presetName.c_str());
return false;
}
const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo(preset->m_pixelFormat);
return info->bSquarePow2;
}
void ImageBuilderWorker::ShutDown()
{
// it is important to note that this will be called on a different thread than your process job thread
m_isShuttingDown = true;
}
PresetName GetImagePreset(const AZStd::string& imageFileFullPath)
{
// first let preset from asset info
TextureSettings textureSettings;
AZStd::string settingFilePath = imageFileFullPath + TextureSettings::ExtensionName;
TextureSettings::LoadTextureSetting(settingFilePath, textureSettings);
if (!textureSettings.m_preset.IsEmpty())
{
return textureSettings.m_preset;
}
return BuilderSettingManager::Instance()->GetSuggestedPreset(imageFileFullPath);
}
void HandlePresetDependency(PresetName presetName, AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceDependencyList)
{
// Reload preset if it was changed
ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetName);
auto presetSettings = BuilderSettingManager::Instance()->GetPreset(presetName, /*default platform*/"");
AssetBuilderSDK::SourceFileDependency sourceFileDependency;
sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute;
// Need to watch any possibe preset paths
AZStd::vector<AZStd::string> possiblePresetPaths = BuilderSettingManager::Instance()->GetPossiblePresetPaths(presetName);
for (const auto& path:possiblePresetPaths)
{
sourceFileDependency.m_sourceFileDependencyPath = path;
sourceDependencyList.push_back(sourceFileDependency);
}
if (presetSettings)
{
// handle special case here
// Cubemap setting may reference some other presets
if (presetSettings->m_cubemapSetting)
{
if (presetSettings->m_cubemapSetting->m_generateIBLDiffuse && !presetSettings->m_cubemapSetting->m_iblDiffusePreset.IsEmpty())
{
HandlePresetDependency(presetSettings->m_cubemapSetting->m_iblDiffusePreset, sourceDependencyList);
}
if (presetSettings->m_cubemapSetting->m_generateIBLSpecular && !presetSettings->m_cubemapSetting->m_iblSpecularPreset.IsEmpty())
{
HandlePresetDependency(presetSettings->m_cubemapSetting->m_iblSpecularPreset, sourceDependencyList);
}
}
}
}
void ReloadPresetIfNeeded(PresetName presetName)
{
// Reload preset if it was changed
ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetName);
auto presetSettings = BuilderSettingManager::Instance()->GetPreset(presetName, /*default platform*/"");
if (presetSettings)
{
// handle special case here
// Cubemap setting may reference some other presets
if (presetSettings->m_cubemapSetting)
{
if (presetSettings->m_cubemapSetting->m_generateIBLDiffuse && !presetSettings->m_cubemapSetting->m_iblDiffusePreset.IsEmpty())
{
ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetSettings->m_cubemapSetting->m_iblDiffusePreset);
}
if (presetSettings->m_cubemapSetting->m_generateIBLSpecular && !presetSettings->m_cubemapSetting->m_iblSpecularPreset.IsEmpty())
{
ImageProcessingAtom::BuilderSettingManager::Instance()->ReloadPreset(presetSettings->m_cubemapSetting->m_iblSpecularPreset);
}
}
}
}
// this happens early on in the file scanning pass
// this function should consistently always create the same jobs, and should do no checking whether the job is up to date or not - just be consistent.
void ImageBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
{
if (m_isShuttingDown)
{
response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown;
return;
}
// Full path of the image file
AZStd::string fullPath;
AzFramework::StringFunc::Path::Join(request.m_watchFolder.data(), request.m_sourceFile.data(), fullPath, true, true);
// Get the extension of the file
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(request.m_sourceFile.c_str(), ext, false);
AZStd::to_upper(ext.begin(), ext.end());
// We process the same file for all platforms
for (const AssetBuilderSDK::PlatformInfo& platformInfo : request.m_enabledPlatforms)
{
if (ImageProcessingAtom::BuilderSettingManager::Instance()->DoesSupportPlatform(platformInfo.m_identifier))
{
AssetBuilderSDK::JobDescriptor descriptor;
descriptor.m_jobKey = "Image Compile: " + ext;
descriptor.SetPlatformIdentifier(platformInfo.m_identifier.c_str());
descriptor.m_critical = false;
descriptor.m_additionalFingerprintInfo = "";
response.m_createJobOutputs.push_back(descriptor);
}
}
// add source dependency for .assetinfo file
AssetBuilderSDK::SourceFileDependency sourceFileDependency;
sourceFileDependency.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute;
sourceFileDependency.m_sourceFileDependencyPath = fullPath + TextureSettings::ExtensionName;
response.m_sourceFileDependencyList.push_back(sourceFileDependency);
// add source dependencies for .preset files
// Get the preset for this file
auto presetName = GetImagePreset(fullPath.c_str());
HandlePresetDependency(presetName, response.m_sourceFileDependencyList);
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
return;
}
// later on, this function will be called for jobs that actually need doing.
// the request will contain the CreateJobResponse you constructed earlier, including any keys and values you placed into the hash table
void ImageBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response)
{
// Before we begin, let's make sure we are not meant to abort.
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
AZStd::vector<AZStd::string> productFilepaths;
bool imageProcessingSuccessful = false;
bool needConversion = true;
// Do conversion and get exported file's path
if (needConversion)
{
// Handles preset changes
auto presetName = GetImagePreset(request.m_fullPath);
ReloadPresetIfNeeded(presetName);
AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Performing image conversion: %s\n", request.m_fullPath.c_str());
ImageConvertProcess* process = CreateImageConvertProcess(request.m_fullPath, request.m_tempDirPath,
request.m_jobDescription.GetPlatformIdentifier(), response.m_outputProducts);
if (process != nullptr)
{
//the process can be stopped if the job is cancelled or the worker is shutting down
while (!process->IsFinished() && !m_isShuttingDown && !jobCancelListener.IsCancelled())
{
process->UpdateProcess();
}
//get process result
imageProcessingSuccessful = process->IsSucceed();
if (imageProcessingSuccessful)
{
process->GetAppendOutputProducts(response.m_outputProducts);
}
delete process;
}
else
{
imageProcessingSuccessful = false;
}
}
if (imageProcessingSuccessful)
{
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
else
{
if (m_isShuttingDown)
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
}
else if (jobCancelListener.IsCancelled())
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled was requested for job %s.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled;
}
else
{
AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Unexpected error during processing job %s.\n", request.m_fullPath.c_str());
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed;
}
}
}
} // namespace ImageProcessingAtom