Files
o3de/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/ImageAssetProducer.cpp
T
Esteban Papp 1f9b284de2 Merge branch 'development' into cmake/SPEC-7179
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h
#	Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h
#	Code/Editor/Plugins/EditorCommon/stdafx.cpp
#	Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h
#	Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h
#	Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h
#	Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h
#	Code/Tools/AssetProcessor/native/precompiled.h
#	Code/Tools/Standalone/StandaloneTools_precompiled.h
#	Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h
#	Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h
#	Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h
#	Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake
#	Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h
#	Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h
#	Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp
#	Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake
#	Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h
#	Gems/Blast/Code/Source/StdAfx.cpp
#	Gems/Camera/Code/Source/Camera_precompiled.h
#	Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h
#	Gems/FastNoise/Code/Source/FastNoise_precompiled.h
#	Gems/Gestures/Code/Source/Gestures_precompiled.h
#	Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h
#	Gems/GraphCanvas/Code/precompiled.h
#	Gems/ImGui/Code/Source/ImGui_precompiled.h
#	Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h
#	Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h
#	Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp
#	Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h
#	Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h
#	Gems/LyShine/Code/Source/LyShine_precompiled.h
#	Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h
#	Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h
#	Gems/Maestro/Code/Source/Maestro_precompiled.h
#	Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h
#	Gems/Metastream/Code/Source/Metastream_precompiled.h
#	Gems/Microphone/Code/Source/Microphone_precompiled.h
#	Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h
#	Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h
#	Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h
#	Gems/PhysX/Code/Source/PhysX_precompiled.h
#	Gems/PhysX/Code/physx_unsupported_files.cmake
#	Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h
#	Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h
#	Gems/ScriptCanvas/Code/Editor/precompiled.h
#	Gems/ScriptCanvas/Code/Source/precompiled.h
#	Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h
#	Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h
#	Gems/ScriptEvents/Code/Source/precompiled.h
#	Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp
#	Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h
#	Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h
#	Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h
#	Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h
#	Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h
#	Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h
#	Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h
#	Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h
#	Gems/Twitch/Code/Source/Twitch_precompiled.h
#	Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
2021-07-16 15:42:37 -07:00

278 lines
12 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 <Processing/ImageAssetProducer.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/PixelFormatInfo.h>
#include <Processing/Utils.h>
#include <Processing/ImageFlags.h>
#include <Atom/RHI.Reflect/Format.h>
#include <Atom/RHI.Reflect/ImageSubresource.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
#include <Atom/RPI.Reflect/Image/ImageAsset.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace ImageProcessingAtom
{
using namespace AZ;
namespace
{
// Trying to fit as many as mips to 64k memory for one mip chain
const uint32_t s_mininumMipBlockSize = 64 * 1024;
}
ImageAssetProducer::ImageAssetProducer(
const IImageObjectPtr imageObject,
AZStd::string_view saveFolder,
const Data::AssetId& sourceAssetId,
AZStd::string_view fileName,
uint8_t numResidentMips,
uint32_t subId)
: m_imageObject(imageObject)
, m_productFolder(saveFolder)
, m_sourceAssetId(sourceAssetId)
, m_fileName(fileName)
, m_numResidentMips(numResidentMips)
, m_subId(subId)
{
AZ_Assert(imageObject, "Input imageObject can't be empty");
AZ_Assert(sourceAssetId.IsValid(), "The source asset Id is not valid");
AZ_Assert(saveFolder.size() != 0, "saveFolder shouldn't be empty");
AZ_Assert(fileName.size() != 0, "fileName shouldn't be empty");
}
AZStd::string ImageAssetProducer::GenerateAssetFullPath(ImageAssetType assetType, uint32_t assetSubId)
{
// Note: m_fileName is the filename contains original extension to avoid file name collision if there are files with same name but with different extension.
// For example, test.jpg's image asset full path will be outputFolder/test.jpg.streamingimage.
// And test.png's image asset full path will be outputFolder/test.png.streamingimage.
AZStd::string fileName;
if (assetType == ImageAssetType::MipChain)
{
fileName = AZStd::string::format("%s.%d.%s", m_fileName.c_str(), assetSubId, RPI::ImageMipChainAsset::Extension);
}
else if (assetType == ImageAssetType::Image)
{
fileName = AZStd::string::format("%s.%s", m_fileName.c_str(), RPI::StreamingImageAsset::Extension);
}
else
{
AZ_Assert(false, "Unknown image asset type");
}
AZStd::string outPath;
AzFramework::StringFunc::Path::Join(m_productFolder.c_str(), fileName.c_str(), outPath, true, true);
return outPath;
}
const AZStd::vector<AssetBuilderSDK::JobProduct>& ImageAssetProducer::GetJobProducts() const
{
return m_jobProducts;
}
bool ImageAssetProducer::BuildImageAssets()
{
Data::AssetId imageAssetId(m_sourceAssetId.m_guid, m_subId);
AssetBuilderSDK::JobProduct product;
RPI::StreamingImageAssetCreator builder;
builder.Begin(imageAssetId);
int32_t arraySize = m_imageObject->HasImageFlags(EIF_Cubemap) ? 6 : 1;
uint32_t imageWidth = m_imageObject->GetWidth(0);
// The current cubemap faces are vertically aligned in the same buffer of image object. So the height should be divided by the array size.
uint32_t imageHeight = m_imageObject->GetHeight(0) / arraySize;
RHI::Format format = Utils::PixelFormatToRHIFormat(m_imageObject->GetPixelFormat(), m_imageObject->HasImageFlags(EIF_SRGBRead));
RHI::ImageBindFlags bindFlag = RHI::ImageBindFlags::ShaderRead;
RHI::ImageDescriptor imageDesc = RHI::ImageDescriptor::Create2DArray(bindFlag, imageWidth, imageHeight, arraySize, format);
imageDesc.m_mipLevels = m_imageObject->GetMipCount();
if (m_imageObject->HasImageFlags(EIF_Cubemap))
{
imageDesc.m_isCubemap = true;
}
builder.SetImageDescriptor(imageDesc);
// Set ImageViewDescriptor for cubemap. The regular 2d images can use the default one.
if (m_imageObject->HasImageFlags(EIF_Cubemap))
{
builder.SetImageViewDescriptor(RHI::ImageViewDescriptor::CreateCubemap());
}
// Build mip chain assets.
// Start from smallest mips so the mip chain asset for the lowest resolutions may contain more high level mips
uint32_t lastMip = imageDesc.m_mipLevels;
uint32_t totalSize = 0;
AZStd::vector<Data::Asset<RPI::ImageMipChainAsset>> mipChains;
uint32_t subid = m_subId + 1;
bool saveProduct = false; // used to skip exporting the tail mip chain to job product
// Merge m_residentMipCount amount of mips into a single mip chain, and add it to the StreamingImageAsset's tail mip chain
if (m_numResidentMips != 0)
{
const uint32_t boundedResidentMipCount = AZStd::min(static_cast<uint32_t>(m_numResidentMips), lastMip);
const uint32_t residentStartMip = lastMip - boundedResidentMipCount;
// Build the StreamingImageAsset's tail mip chain
Data::Asset<RPI::ImageMipChainAsset> chainAsset;
const bool isSuccess = BuildMipChainAsset(Data::AssetId(m_sourceAssetId.m_guid, subid), residentStartMip, boundedResidentMipCount, chainAsset, saveProduct);
if (!isSuccess)
{
AZ_Warning("Image Processing", false, "Failed to build the StreamingImageAsset's m_tailMipChain");
return false;
}
mipChains.emplace_back(chainAsset);
// Set the state for the remaining mips
saveProduct = true;
lastMip = residentStartMip;
subid++;
}
// Create MipChainAssets for the remaining mips
for (int32_t mip = lastMip - 1; mip >= 0; mip--)
{
totalSize += m_imageObject->GetMipBufSize(mip);
// Trying to fit as many as mips to 64k memory for one mip chain
if (mip == 0 || totalSize + m_imageObject->GetMipBufSize(mip - 1) > s_mininumMipBlockSize)
{
// Build a chain
Data::Asset<RPI::ImageMipChainAsset> chainAsset;
bool isSuccess = BuildMipChainAsset(Data::AssetId(m_sourceAssetId.m_guid, subid), mip, lastMip - mip, chainAsset, saveProduct);
saveProduct = true;
if (!isSuccess)
{
AZ_Warning("Image Processing", false, "Failed to build mip chain asset");
return false;
}
mipChains.emplace_back(chainAsset);
subid++;
lastMip = mip;
totalSize = 0;
}
}
// Set the builder's state to non-streaming if it only has a single mip chain, which will be stored in the
// StreamingImage's tail mip chain
if (mipChains.size() == 1)
{
builder.SetFlags(AZ::RPI::StreamingImageFlags::NotStreamable);
}
// Add mip chains to the builder from mip level 0 to highest
for (auto it = mipChains.rbegin(); it != mipChains.rend(); it++)
{
builder.AddMipChainAsset(*it->GetAs<RPI::ImageMipChainAsset>());
// Add all the mip chain assets as dependencies except the tail mip chain since its embedded in the StreamingImageAsset
if (it->Get() != mipChains.begin()->Get())
{
// Use PreLoad for mipchain assets for now.
// [GFX TODO] [ATOM-14467] Remove unnecessary code in StreamingImage::OnAssetReloaded when runtime switching dependency load behavior is supported
product.m_dependencies.push_back(AssetBuilderSDK::ProductDependency(it->GetId(), AZ::Data::ProductDependencyInfo::CreateFlags(AZ::Data::AssetLoadBehavior::PreLoad)));
}
}
product.m_dependenciesHandled = true; // We've output the dependencies immediately above so it's OK to tell the AP we've handled dependencies
bool result = false;
Data::Asset<RPI::StreamingImageAsset> imageAsset;
if (builder.End(imageAsset))
{
AZStd::string destPath = GenerateAssetFullPath(ImageAssetType::Image, imageAsset.GetId().m_subId);
result = AZ::Utils::SaveObjectToFile(destPath, AZ::DataStream::ST_BINARY, imageAsset.GetData(), imageAsset.GetData()->GetType(), nullptr);
if (!result)
{
AZ_Warning("Image Processing", false, "Failed to save image asset to file %s", destPath.c_str());
}
else
{
product.m_productAssetType = imageAsset.GetData()->GetType();
product.m_productSubID = imageAsset.GetId().m_subId;
product.m_productFileName = destPath;
// The StreamingImageAsset is added to end of product list in purpose.
// This is in case a new mip chain file is generated, for example when updating the original image's resolution,
// the mip chain asset hasn't be registered by the AssetCatalog when processing the asset change notification for
// StreamingImageAsset reload and it leads to an unknown asset error.
// The Asset system can be modified to solve the problem so the order doesn't matter.
// The task is tracked in ATOM-242
m_jobProducts.push_back(AZStd::move(product));
}
}
return result;
}
bool ImageAssetProducer::BuildMipChainAsset(const Data::AssetId& chainAssetId, uint32_t startMip, uint32_t mipLevels,
Data::Asset<RPI::ImageMipChainAsset>& outAsset, bool saveAsProduct)
{
RPI::ImageMipChainAssetCreator builder;
uint32_t arraySize = m_imageObject->HasImageFlags(EIF_Cubemap) ? 6 : 1;
builder.Begin(chainAssetId, mipLevels, arraySize);
for (uint32_t mip = startMip; mip < startMip + mipLevels; mip++)
{
uint8_t* mipBuffer;
uint32_t pitch;
m_imageObject->GetImagePointer(mip, mipBuffer, pitch);
RHI::Format format = Utils::PixelFormatToRHIFormat(m_imageObject->GetPixelFormat(), m_imageObject->HasImageFlags(EIF_SRGBRead));
RHI::ImageSubresourceLayout layout = RHI::GetImageSubresourceLayout(RHI::Size(m_imageObject->GetWidth(mip), m_imageObject->GetHeight(mip) / arraySize, 1), format);
builder.BeginMip(layout);
for (uint32_t arrayIndex = 0; arrayIndex < arraySize; ++arrayIndex)
{
builder.AddSubImage(mipBuffer + arrayIndex * layout.m_bytesPerImage, layout.m_bytesPerImage);
}
builder.EndMip();
}
bool result = false;
if (builder.End(outAsset))
{
AZStd::string destPath = GenerateAssetFullPath(ImageAssetType::MipChain, outAsset.GetId().m_subId);
if (saveAsProduct)
{
result = AZ::Utils::SaveObjectToFile(destPath, AZ::DataStream::ST_BINARY, outAsset.GetData(), outAsset.GetData()->GetType(), nullptr);
if (result)
{
// Save job product
AssetBuilderSDK::JobProduct product;
product.m_productAssetType = outAsset.GetData()->GetType();
product.m_productSubID = outAsset.GetId().m_subId;
product.m_productFileName = destPath;
m_jobProducts.emplace_back(AZStd::move(product));
}
}
else
{
result = true;
}
}
return result;
}
} // namespace ImageProcessingAtom