Convert TextureAtlas gem/builder to use Atom (#853)

* Convert TextureAtlas gem/builder to use Atom
* Convert image markup to use Atom image
This commit is contained in:
michabr
2021-05-25 14:05:03 -07:00
committed by GitHub
parent 82b4b83256
commit 713a3fd885
16 changed files with 252 additions and 258 deletions
@@ -12,7 +12,7 @@
# Extracted from Editor.xml
set(GEM_DEPENDENCIES
Gem::Maestro.Editor
Gem::TextureAtlas
Gem::TextureAtlas.Editor
Gem::LmbrCentral.Editor
Gem::LyShine.Editor
Gem::HttpRequestor
+4 -4
View File
@@ -86,7 +86,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
Gem::LyShine.Static
Legacy::CryCommon
Gem::LmbrCentral
Gem::TextureAtlas
Gem::TextureAtlas.Editor
Gem::AtomToolsFramework.Static
Gem::AtomToolsFramework.Editor
${additional_dependencies}
@@ -118,10 +118,10 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
AZ::AssetBuilderSDK
Gem::LyShine.Editor.Static
Gem::LmbrCentral
Gem::TextureAtlas
Gem::TextureAtlas.Editor
RUNTIME_DEPENDENCIES
Gem::LmbrCentral.Editor
Gem::TextureAtlas
Gem::TextureAtlas.Editor
)
endif()
@@ -176,7 +176,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
Legacy::CryCommon
AZ::AssetBuilderSDK
Gem::LmbrCentral
Gem::TextureAtlas
Gem::TextureAtlas.Editor
Gem::LyShine.Editor.Static
)
ly_add_googletest(
+22 -61
View File
@@ -40,6 +40,7 @@
#include "RenderGraph.h"
#include <AtomLyIntegration/AtomFont/FFont.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
namespace
{
@@ -1076,7 +1077,7 @@ UiTextComponent::InlineImage::InlineImage(const AZStd::string& texturePathname,
{
m_filepath = texturePathname;
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePath, m_filepath);
m_texture = nullptr;
m_texture.reset();
m_size = AZ::Vector2(0.0f, 0.0f);
m_vAlign = vAlign;
m_yOffset = yOffset;
@@ -1094,24 +1095,11 @@ UiTextComponent::InlineImage::InlineImage(const AZStd::string& texturePathname,
else
{
// Load the texture
uint32 loadTextureFlags = (FT_USAGE_ALLOWREADSRGB | FT_DONT_STREAM);
ITexture* texture = gEnv->pRenderer->EF_LoadTexture(texturePathname.c_str(), loadTextureFlags);
if (!texture || !texture->IsTextureLoaded())
m_texture = CDraw2d::LoadTexture(m_filepath);
if (m_texture)
{
gEnv->pSystem->Warning(
VALIDATOR_MODULE_SHINE,
VALIDATOR_WARNING,
VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
texturePathname.c_str(),
"No texture file found for image: %s. "
"NOTE: File must be in current project or a gem.",
texturePathname.c_str());
}
else
{
m_texture = texture;
m_size = AZ::Vector2(static_cast<float>(m_texture->GetWidth()), static_cast<float>(m_texture->GetHeight()));
AZ::RHI::Size size = m_texture->GetDescriptor().m_size;
m_size = AZ::Vector2(size.m_width, size.m_height);
}
}
@@ -1127,17 +1115,6 @@ UiTextComponent::InlineImage::InlineImage(const AZStd::string& texturePathname,
////////////////////////////////////////////////////////////////////////////////////////////////////
UiTextComponent::InlineImage::~InlineImage()
{
// In order to avoid the texture being deleted while there are still commands on the render
// thread command queue that use it, we queue a command to delete the texture onto the
// command queue.
if (m_texture && !m_atlas)
{
SResourceAsync* pInfo = new SResourceAsync();
pInfo->eClassName = eRCN_Texture;
pInfo->pResource = m_texture;
gEnv->pRenderer->ReleaseResourceAsync(pInfo);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1148,13 +1125,6 @@ bool UiTextComponent::InlineImage::OnAtlasLoaded(const TextureAtlasNamespace::Te
m_coordinates = atlas->GetAtlasCoordinates(m_filepath);
if (m_coordinates.GetWidth() > 0)
{
if (m_texture)
{
SResourceAsync* pInfo = new SResourceAsync();
pInfo->eClassName = eRCN_Texture;
pInfo->pResource = m_texture;
gEnv->pRenderer->ReleaseResourceAsync(pInfo);
}
m_atlas = atlas;
m_texture = m_atlas->GetTexture();
return true;
@@ -1177,25 +1147,7 @@ bool UiTextComponent::InlineImage::OnAtlasUnloaded(const TextureAtlasNamespace::
else
{
// Load the texture
uint32 loadTextureFlags = (FT_USAGE_ALLOWREADSRGB | FT_DONT_STREAM);
ITexture* texture = gEnv->pRenderer->EF_LoadTexture(m_filepath.c_str(), loadTextureFlags);
if (!texture || !texture->IsTextureLoaded())
{
gEnv->pSystem->Warning(
VALIDATOR_MODULE_SHINE,
VALIDATOR_WARNING,
VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
m_filepath.c_str(),
"No texture file found for image: %s. "
"NOTE: File must be in current project or a gem.",
m_filepath.c_str());
m_texture = nullptr;
}
else
{
m_texture = texture;
}
m_texture = CDraw2d::LoadTexture(m_filepath);
}
return true;
}
@@ -1869,7 +1821,7 @@ void UiTextComponent::Render(LyShine::IRenderGraph* renderGraph)
UiTransformInterface::RectPointsArray rectPoints;
GetTextBoundingBoxPrivate(GetDrawBatchLines(), m_selectionStart, m_selectionEnd, rectPoints);
ITexture* whiteTexture = gEnv->pRenderer->GetWhiteTexture();
auto systemImage = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::White);
bool isClampTextureMode = true;
uint32 packedColor = (m_textSelectionColor.GetA8() << 24) | (m_textSelectionColor.GetR8() << 16) | (m_textSelectionColor.GetG8() << 8) | m_textSelectionColor.GetB8();
@@ -1878,7 +1830,12 @@ void UiTextComponent::Render(LyShine::IRenderGraph* renderGraph)
{
IRenderer::DynUiPrimitive* primitive = renderGraph->GetDynamicQuadPrimitive(rect.pt, packedColor);
primitive->m_next = nullptr;
renderGraph->AddPrimitive(primitive, whiteTexture, isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
if (lyRenderGraph)
{
lyRenderGraph->AddPrimitiveAtom(primitive, systemImage, isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
}
}
}
@@ -1887,21 +1844,25 @@ void UiTextComponent::Render(LyShine::IRenderGraph* renderGraph)
{
for (auto batch : m_renderCache.m_imageBatches)
{
ITexture* texture = batch->m_texture;
AZ::Data::Instance<AZ::RPI::Image> texture = batch->m_texture;
// If the fade value has changed we need to update the alpha values in the vertex colors but we do
// not want to touch or recompute the RGB values
if (batch->m_cachedPrimitive.m_vertices[0].color.a != finalAlphaByte)
{
for (int i=0; i < 4; ++i)
for (int i = 0; i < 4; ++i)
{
batch->m_cachedPrimitive.m_vertices[i].color.a = finalAlphaByte;
}
}
bool isClampTextureMode = true;
renderGraph->AddPrimitive(&batch->m_cachedPrimitive, texture,
isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
LyShine::RenderGraph* lyRenderGraph = dynamic_cast<LyShine::RenderGraph*>(renderGraph);
if (lyRenderGraph)
{
lyRenderGraph->AddPrimitiveAtom(&batch->m_cachedPrimitive, texture,
isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
}
}
}
+6 -5
View File
@@ -21,13 +21,14 @@
#include <LyShine/Bus/UiLayoutCellDefaultBus.h>
#include <LyShine/Bus/UiElementBus.h>
#include <LyShine/Bus/UiCanvasBus.h>
#include <LyShine/Bus/UiAnimateEntityBus.h>
#include <LyShine/UiAssetTypes.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/set.h>
#include <LyShine/Bus/UiAnimateEntityBus.h>
#include <LyShine/UiAssetTypes.h>
#include <Atom/RPI.Reflect/Image/Image.h>
// Only needed for internal unit-testing
#include <LyShine.h>
@@ -91,7 +92,7 @@ public: //types
bool OnAtlasLoaded(const TextureAtlasNamespace::TextureAtlas* atlas);
bool OnAtlasUnloaded(const TextureAtlasNamespace::TextureAtlas* atlas);
ITexture* m_texture;
AZ::Data::Instance<AZ::RPI::Image> m_texture;
AZ::Vector2 m_size;
VAlign m_vAlign;
float m_yOffset;
@@ -616,8 +617,8 @@ private: // types
struct RenderCacheImageBatch
{
ITexture* m_texture;
IRenderer::DynUiPrimitive m_cachedPrimitive;
AZ::Data::Instance<AZ::RPI::Image> m_texture;
IRenderer::DynUiPrimitive m_cachedPrimitive;
};
struct RenderCacheData
+43 -1
View File
@@ -10,7 +10,7 @@
#
ly_add_target(
NAME TextureAtlas ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAME TextureAtlas.Static STATIC
NAMESPACE Gem
FILES_CMAKE
textureatlas_files.cmake
@@ -21,4 +21,46 @@ ly_add_target(
PRIVATE
Legacy::CryCommon
AZ::AzFramework
PUBLIC
Gem::Atom_RPI.Public
AZ::AtomCore
)
ly_add_target(
NAME TextureAtlas ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
NAMESPACE Gem
FILES_CMAKE
textureatlas_module_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Legacy::CryCommon
Gem::TextureAtlas.Static
)
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME TextureAtlas.Editor GEM_MODULE
NAMESPACE Gem
FILES_CMAKE
textureatlas_module_files.cmake
textureatlas_builder_files.cmake
COMPILE_DEFINITIONS
PRIVATE
TEXTUREATLAS_EDITOR
INCLUDE_DIRECTORIES
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Legacy::CryCommon
AZ::AzCore
AZ::AzFramework
AZ::AssetBuilderSDK
Gem::TextureAtlas.Static
Gem::ImageProcessingAtom.Headers
)
endif()
@@ -16,7 +16,8 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Asset/AssetCommon.h>
class ITexture;
#include <Atom/RPI.Reflect/Image/Image.h>
#include <AtomCore/Instance/Instance.h>
namespace TextureAtlasNamespace
{
@@ -77,9 +78,9 @@ namespace TextureAtlasNamespace
//! Retrieve a coordinate set from the Atlas by its handle
virtual AtlasCoordinates GetAtlasCoordinates(const AZStd::string& handle) const = 0;
//! Links this atlas to an image pointer
virtual void SetTexture(ITexture* image) = 0;
virtual void SetTexture(AZ::Data::Instance<AZ::RPI::Image> image) = 0;
//! Returns the image linked to this atlas
virtual ITexture* GetTexture() const = 0;
virtual AZ::Data::Instance<AZ::RPI::Image> GetTexture() const = 0;
//! Returns the width of the atlas
virtual int GetWidth() const = 0;
//! Returns the height of the atlas
@@ -10,7 +10,6 @@
*
*/
#include "ImageProcessing_precompiled.h"
#include "AtlasBuilderComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
@@ -10,7 +10,6 @@
*
*/
#include "ImageProcessing_precompiled.h"
#include "AtlasBuilderWorker.h"
#include <AzCore/Math/MathIntrinsics.h>
@@ -19,22 +18,17 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/sort.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/regex.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <ImageProcessing/ImageObject.h>
#include <Processing/ImageConvert.h>
#include <Processing/ImageConvertJob.h>
#include <ImageLoader/ImageLoaders.h>
#include <Processing/ImageObjectImpl.h>
#include <Processing/PixelFormatInfo.h>
#include <BuilderSettings/PresetSettings.h>
#include <BuilderSettings/TextureSettings.h>
#include <BuilderSettings/BuilderSettingManager.h>
#include <Atom/ImageProcessing/ImageObject.h>
#include <Atom/ImageProcessing/ImageProcessingBus.h>
#include <Atom/ImageProcessing/PixelFormats.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <qimage.h>
#include <QString>
@@ -44,13 +38,13 @@
namespace TextureAtlasBuilder
{
//! Counts leading zeros
uint32 CountLeadingZeros32(uint32 x)
uint32_t CountLeadingZeros32(uint32_t x)
{
return x == 0 ? 32 : az_clz_u32(x);
}
//! Integer log2
uint32 IntegerLog2(uint32 x)
uint32_t IntegerLog2(uint32_t x)
{
return 31 - CountLeadingZeros32(x);
}
@@ -113,13 +107,24 @@ namespace TextureAtlasBuilder
{
bool resolved = false;
// Get full path by appending the relative path to the watch directory
AZStd::string fullPath = watchDirectory;
fullPath.append("/");
fullPath.append(relativePath);
if (relativePath[0] == '@')
{
// Get full path by resolving the alias at the front of the path
char resolvedPath[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath(relativePath.c_str(), resolvedPath, AZ_MAX_PATH_LEN);
resolvedFullPathOut = resolvedPath;
resolved = true;
}
else
{
// Get full path by appending the relative path to the watch directory
AZStd::string fullPath = watchDirectory;
fullPath.append("/");
fullPath.append(relativePath);
// Resolve to canonical path (remove "./" and "../")
resolved = GetCanonicalPathFromFullPath(fullPath, resolvedFullPathOut);
// Resolve to canonical path (remove "./" and "../")
resolved = GetCanonicalPathFromFullPath(fullPath, resolvedFullPathOut);
}
return resolved;
}
@@ -140,23 +145,6 @@ namespace TextureAtlasBuilder
return result;
}
const ImageProcessing::PresetSettings* GetImageProcessPresetSettings(const AZStd::string& presetName, const AZStd::string& platformIdentifier)
{
// Get the specified presetId
AZ::Uuid presetId = ImageProcessing::BuilderSettingManager::Instance()->GetPresetIdFromName(presetName);
if (presetId.IsNull())
{
AZ_Error("Texture Editor", false, "Texture Preset %s has no associated UUID.", presetName.c_str());
return nullptr;
}
// Get the preset settings for the platform this job is building for
const ImageProcessing::PresetSettings* presetSettings = ImageProcessing::BuilderSettingManager::Instance()->GetPreset(
presetId, platformIdentifier);
return presetSettings;
}
// Reflect the input parameters
void AtlasBuilderInput::Reflect(AZ::ReflectContext* context)
{
@@ -474,7 +462,7 @@ namespace TextureAtlasBuilder
{
AZStd::string ext;
AzFramework::StringFunc::Path::GetExtension(candidates[i].c_str(), ext, false);
if (ImageProcessing::IsExtensionSupported(ext.c_str()) && ext != "dds")
if (ext != "dds")
{
bool duplicate = false;
for (size_t j = 0; j < paths.size() && !duplicate; ++j)
@@ -589,7 +577,7 @@ namespace TextureAtlasBuilder
{
AddFolderContents(paths, child, valid);
}
else if (ImageProcessing::IsExtensionSupported(ext.c_str()) && ext != "dds")
else if (ext != "dds")
{
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, child);
bool duplicate = false;
@@ -652,7 +640,11 @@ namespace TextureAtlasBuilder
// We process the same file for all platforms
for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms)
{
if (ImageProcessing::BuilderSettingManager::Instance()->DoesSupportPlatform(info.m_identifier))
bool doesSupportPlatform = false;
ImageProcessingAtom::ImageBuilderRequestBus::BroadcastResult(doesSupportPlatform,
&ImageProcessingAtom::ImageBuilderRequests::DoesSupportPlatform,
info.m_identifier);
if (doesSupportPlatform)
{
AssetBuilderSDK::JobDescriptor descriptor = GetJobDescriptor(request.m_sourceFile, input);
descriptor.SetPlatformIdentifier(info.m_identifier.c_str());
@@ -707,12 +699,8 @@ namespace TextureAtlasBuilder
// Before we begin, let's make sure we are not meant to abort.
AssetBuilderSDK::JobCancelListener jobCancelListener(request.m_jobId);
AZStd::vector<AZStd::string> productFilepaths;
const AZStd::string path = request.m_fullPath;
bool imageProcessingSuccessful = false;
// read in settings/filepaths
AtlasBuilderInput input;
input.m_forceSquare = AzFramework::StringFunc::ToBool(request.m_jobDescription.m_jobParameters.find(AZ_CRC("forceSquare"))->second.c_str());
@@ -752,43 +740,37 @@ namespace TextureAtlasBuilder
// Default to the TextureAtlas preset which is currently set to use compression for all platforms except for iOS.
// Currently the only fully supported compression for iOS is PVRTC which requires the texture to be square and a power of 2.
// Due to this limitation, we default to using no compression for iOS until ASTC is fully supported
const AZStd::string defaultPresetName = "TextureAtlas";
const AZStd::string defaultPresetName = "UserInterface_Compressed";
input.m_presetName = defaultPresetName;
}
// Get a preset to use for the output image
const ImageProcessing::PresetSettings* preset = GetImageProcessPresetSettings(input.m_presetName, request.m_platformInfo.m_identifier);
if (preset)
bool isFormatSquarePow2 = false;
ImageProcessingAtom::ImageBuilderRequestBus::BroadcastResult(isFormatSquarePow2,
&ImageProcessingAtom::ImageBuilderRequests::IsPresetFormatSquarePow2,
input.m_presetName, request.m_platformInfo.m_identifier);
if (isFormatSquarePow2)
{
// Check the preset's pixel format requirements
const ImageProcessing::PixelFormatInfo* pixelFormatInfo = ImageProcessing::CPixelFormats::GetInstance().GetPixelFormatInfo(preset->m_pixelFormat);
if (pixelFormatInfo && pixelFormatInfo->bSquarePow2)
{
// Override the user config settings to force square and power of 2.
// Otherwise the image conversion process will stretch the image to satisfy these requirements
input.m_forceSquare = true;
input.m_forcePowerOf2 = true;
}
}
else
{
AZ_Error("AtlasBuilder", false, "Could not find a preset setting for the output image.");
return;
// Override the user config settings to force square and power of 2.
// Otherwise the image conversion process will stretch the image to satisfy these requirements
input.m_forceSquare = true;
input.m_forcePowerOf2 = true;
}
// Read in images
AZStd::vector<ImageProcessing::IImageObjectPtr> images;
AZStd::vector<ImageProcessingAtom::IImageObjectPtr> images;
AZ::u64 totalArea = 0;
int maxArea = input.m_maxDimension * input.m_maxDimension;
bool sizeFailure = false;
for (int i = 0; i < input.m_filePaths.size() && !jobCancelListener.IsCancelled(); ++i)
{
ImageProcessing::IImageObject* inputImage = ImageProcessing::LoadImageFromFile(input.m_filePaths[i]);
ImageProcessingAtom::IImageObjectPtr inputImage;
ImageProcessingAtom::ImageProcessingRequestBus::BroadcastResult(inputImage, &ImageProcessingAtom::ImageProcessingRequests::LoadImage, input.m_filePaths[i]);
// Check if we were able to load the image
if (inputImage)
{
ImageProcessing::IImageObjectPtr image = ImageProcessing::IImageObjectPtr(inputImage);
images.push_back(image);
images.push_back(inputImage);
totalArea += inputImage->GetWidth(0) * inputImage->GetHeight(0);
}
else
@@ -837,8 +819,13 @@ namespace TextureAtlasBuilder
// Add white texture if we need to
if (input.m_includeWhiteTexture)
{
ImageProcessing::IImageObjectPtr texture(ImageProcessing::IImageObject::CreateImage(
cellSize, cellSize, 1, ImageProcessing::EPixelFormat::ePixelFormat_R8G8B8A8));
ImageProcessingAtom::IImageObjectPtr texture;
ImageProcessingAtom::ImageBuilderRequestBus::BroadcastResult(texture,
&ImageProcessingAtom::ImageBuilderRequests::CreateImage,
aznumeric_cast<AZ::u32>(cellSize),
aznumeric_cast<AZ::u32>(cellSize),
1,
ImageProcessingAtom::EPixelFormat::ePixelFormat_R8G8B8A8);
// Make the texture white
texture->ClearColor(1, 1, 1, 1);
@@ -897,8 +884,8 @@ namespace TextureAtlasBuilder
}
if (input.m_forcePowerOf2)
{
resultWidth = aznumeric_cast<int>(pow(2, 1 + IntegerLog2(static_cast<uint32>(resultWidth - 1))));
resultHeight = aznumeric_cast<int>(pow(2, 1 + IntegerLog2(static_cast<uint32>(resultHeight - 1))));
resultWidth = aznumeric_cast<int>(pow(2, 1 + IntegerLog2(static_cast<uint32_t>(resultWidth - 1))));
resultHeight = aznumeric_cast<int>(pow(2, 1 + IntegerLog2(static_cast<uint32_t>(resultHeight - 1))));
}
else
{
@@ -918,8 +905,13 @@ namespace TextureAtlasBuilder
}
// Process texture sheet
ImageProcessing::IImageObjectPtr outImage(ImageProcessing::IImageObject::CreateImage(
resultWidth, resultHeight, 1, ImageProcessing::EPixelFormat::ePixelFormat_R8G8B8A8));
ImageProcessingAtom::IImageObjectPtr outImage;
ImageProcessingAtom::ImageBuilderRequestBus::BroadcastResult(outImage,
&ImageProcessingAtom::ImageBuilderRequests::CreateImage,
aznumeric_cast<AZ::u32>(resultWidth),
aznumeric_cast<AZ::u32>(resultHeight),
1,
ImageProcessingAtom::EPixelFormat::ePixelFormat_R8G8B8A8);
// Clear the sheet
outImage->ClearColor(input.m_unusedColor.GetR(), input.m_unusedColor.GetG(), input.m_unusedColor.GetB(), input.m_unusedColor.GetA());
@@ -1010,54 +1002,21 @@ namespace TextureAtlasBuilder
// Output texture sheet
AZStd::string imageFileName, imageOutputPath;
AzFramework::StringFunc::Path::GetFileName(request.m_sourceFile.c_str(), imageFileName);
imageFileName += ".dds";
imageFileName += ".texatlas";
AzFramework::StringFunc::Path::Join(
request.m_tempDirPath.c_str(), imageFileName.c_str(), imageOutputPath, true, true);
// Let the ImageProcessor do the rest of the work.
ImageProcessing::TextureSettings textureSettings;
textureSettings.m_preset = preset->m_uuid;
AZStd::vector<AssetBuilderSDK::JobProduct> outProducts;
ImageProcessingAtom::ImageBuilderRequestBus::BroadcastResult(outProducts,
&ImageProcessingAtom::ImageBuilderRequests::ConvertImageObject,
outImage,
input.m_presetName,
request.m_platformInfo.m_identifier,
imageOutputPath,
request.m_sourceFileUUID,
request.m_sourceFile);
// Mipmaps for the texture atlas would require more work than the Image Processor does. This is because if we
// let the Image Processor make mipmaps, it might bleed the textures in the atlas together.
textureSettings.m_enableMipmap = false;
// Check if the ImageBuilder wants to enable streaming
bool isStreaming = ImageProcessing::BuilderSettingManager::Instance()
->GetBuilderSetting(request.m_platformInfo.m_identifier)
->m_enableStreaming;
bool canOverridePreset = false;
ImageProcessing::ImageConvertProcess* process =
new ImageProcessing::ImageConvertProcess(outImage,
textureSettings,
*preset,
false,
isStreaming,
canOverridePreset,
imageOutputPath,
request.m_platformInfo.m_identifier);
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();
process->GetAppendOutputFilePaths(productFilepaths);
delete process;
}
else
{
imageProcessingSuccessful = false;
}
if (imageProcessingSuccessful)
if (!outProducts.empty())
{
TextureAtlasNamespace::TextureAtlasRequestBus::Broadcast(
&TextureAtlasNamespace::TextureAtlasRequests::SaveAtlasToFile, outputPath, output, resultWidth, resultHeight);
@@ -1067,27 +1026,23 @@ namespace TextureAtlasBuilder
// The Image Processing Gem can produce multiple output files under certain
// circumstances, but the texture atlas is not expected to produce such output
if (productFilepaths.size() > 1)
if (outProducts.size() > 1)
{
AZ_Error("AtlasBuilder", false, "Image processing resulted in multiple output files. Texture atlas is expected to produce one output.");
response.m_outputProducts.clear();
return;
}
if (productFilepaths.size() > 0)
{
response.m_outputProducts.push_back(AssetBuilderSDK::JobProduct(productFilepaths[0]));
response.m_outputProducts.back().m_productAssetType = azrtti_typeid<TextureAtlasNamespace::TextureAtlasAsset>();
response.m_outputProducts.back().m_productSubID = 1;
response.m_outputProducts.push_back(outProducts[0]);
// The texatlasidx file is a data file that indicates where the original parts are inside the atlas,
// and this would usually imply that it refers to its dds file in some way or needs it to function.
// The texatlasidx file should be the one that depends on the DDS because it's possible to use the DDS
// without the texatlasid, but not the other way around
AZ::Data::AssetId productAssetId(request.m_sourceFileUUID, response.m_outputProducts.back().m_productSubID);
response.m_outputProducts[static_cast<int>(Product::TexatlasidxProduct)].m_dependencies.push_back(AssetBuilderSDK::ProductDependency(productAssetId, 0));
response.m_outputProducts[static_cast<int>(Product::TexatlasidxProduct)].m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies
// The texatlasidx file is a data file that indicates where the original parts are inside the atlas,
// and this would usually imply that it refers to its dds file in some way or needs it to function.
// The texatlasidx file should be the one that depends on the DDS because its possible to use the DDS
// without the texatlasid, but not the other way around
AZ::Data::AssetId productAssetId(request.m_sourceFileUUID, response.m_outputProducts.back().m_productSubID);
response.m_outputProducts[static_cast<int>(Product::TexatlasidxProduct)].m_dependencies.push_back(AssetBuilderSDK::ProductDependency(productAssetId, 0));
response.m_outputProducts[static_cast<int>(Product::TexatlasidxProduct)].m_dependenciesHandled = true; // We've populated the dependencies immediately above so it's OK to tell the AP we've handled dependencies
}
response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success;
}
}
@@ -1315,7 +1270,7 @@ namespace TextureAtlasBuilder
if (powerOfTwo)
{
// Starting dimension needs to be rounded up to the nearest power of two
dimension = aznumeric_cast<int>(pow(2, 1 + IntegerLog2(static_cast<uint32>(dimension - 1))));
dimension = aznumeric_cast<int>(pow(2, 1 + IntegerLog2(static_cast<uint32_t>(dimension - 1))));
}
AZStd::vector<AtlasCoordinates> track;
@@ -1363,7 +1318,7 @@ namespace TextureAtlasBuilder
if (powerOfTwo)
{
// Starting dimension needs to be rounded up to the nearest power of two
minWidth = aznumeric_cast<AZ::u32>(pow(2, 1 + IntegerLog2(static_cast<uint32>(minWidth - 1))));
minWidth = aznumeric_cast<AZ::u32>(pow(2, 1 + IntegerLog2(static_cast<uint32_t>(minWidth - 1))));
}
// Round min width up to the nearest compression unit
@@ -1400,7 +1355,7 @@ namespace TextureAtlasBuilder
// Find the height of the solution
for (int i = 0; i < track.size(); ++i)
{
uint32 bottom = static_cast<uint32>(AZStd::max(0, track[i].GetBottom()));
uint32_t bottom = static_cast<uint32_t>(AZStd::max(0, track[i].GetBottom()));
if (height < bottom)
{
height = bottom;
@@ -1411,7 +1366,7 @@ namespace TextureAtlasBuilder
if (powerOfTwo)
{
// Starting dimensions need to be rounded up to the nearest power of two
height = aznumeric_cast<AZ::u32>(pow(2, 1 + IntegerLog2(static_cast<uint32>(height - 1))));
height = aznumeric_cast<AZ::u32>(pow(2, 1 + IntegerLog2(static_cast<uint32_t>(height - 1))));
}
AZ::u32 resultArea = height * width;
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Math/Color.h>
#include <AssetBuilderSDK/AssetBuilderSDK.h>
#include <AssetBuilderSDK/AssetBuilderBusses.h>
#include <qimage.h>
@@ -110,7 +111,7 @@ namespace TextureAtlasBuilder
enum class Product
{
TexatlasidxProduct = 0,
DdsProduct = 1
StreamingImageProduct = 1
};
//! An asset builder for texture atlases
@@ -120,14 +120,14 @@ namespace TextureAtlasNamespace
}
// Links this atlas to an image pointer
void TextureAtlasImpl::SetTexture(ITexture* image)
void TextureAtlasImpl::SetTexture(AZ::Data::Instance<AZ::RPI::Image> image)
{
// We don't need to delete the old value because the pointer is handled elsewhere
m_image = image;
}
// Returns the image linked to this atlas
ITexture* TextureAtlasImpl::GetTexture() const
AZ::Data::Instance<AZ::RPI::Image> TextureAtlasImpl::GetTexture() const
{
return m_image;
}
@@ -20,7 +20,8 @@
#include "TextureAtlas/TextureAtlas.h"
#include "TextureAtlas/TextureAtlasBus.h"
#include <ITexture.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include <AtomCore/Instance/Instance.h>
namespace TextureAtlasNamespace
{
@@ -61,10 +62,10 @@ namespace TextureAtlasNamespace
AtlasCoordinates GetAtlasCoordinates(const AZStd::string& handle) const override;
//! Links this atlas to an image pointer
void SetTexture(ITexture* image) override;
void SetTexture(AZ::Data::Instance<AZ::RPI::Image> image) override;
//! Returns the image linked to this atlas
ITexture* GetTexture() const override;
AZ::Data::Instance<AZ::RPI::Image> GetTexture() const override;
//! Replaces the mappings of this Texture Atlas Object, with the source's mappings
void OverwriteMappings(TextureAtlasImpl* source);
@@ -80,7 +81,7 @@ namespace TextureAtlasNamespace
private:
AZStd::unordered_map<AZStd::string, AtlasCoordinates, hash_case_insensitive, equal_to_case_insensitive> m_data;
ITexture* m_image;
AZ::Data::Instance<AZ::RPI::Image> m_image;
int m_width;
int m_height;
};
@@ -16,6 +16,10 @@
#include "TextureAtlasSystemComponent.h"
#ifdef TEXTUREATLAS_EDITOR
#include "Editor/AtlasBuilderComponent.h"
#endif
#include <IGem.h>
namespace TextureAtlasNamespace
@@ -33,6 +37,9 @@ namespace TextureAtlasNamespace
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
TextureAtlasSystemComponent::CreateDescriptor(),
#ifdef TEXTUREATLAS_EDITOR
TextureAtlasBuilder::AtlasBuilderComponent::CreateDescriptor(), //builder component for texture atlas
#endif
});
}
@@ -22,7 +22,25 @@
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <IRenderer.h>
#include <Atom/RPI.Public/Image/StreamingImage.h>
namespace
{
AZ::Data::Instance<AZ::RPI::Image> LoadAtlasImage(const AZStd::string& imagePath)
{
// The file may not be in the AssetCatalog at this point if it is still processing or doesn't exist on disk.
// Use GenerateAssetIdTEMP instead of GetAssetIdByPath so that it will return a valid AssetId anyways
AZ::Data::AssetId streamingImageAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
streamingImageAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GenerateAssetIdTEMP,
imagePath.c_str());
streamingImageAssetId.m_subId = AZ::RPI::StreamingImageAsset::GetImageAssetSubId();
auto streamingImageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::RPI::StreamingImageAsset>(streamingImageAssetId, AZ::Data::AssetLoadBehavior::PreLoad);
AZ::Data::Instance<AZ::RPI::Image> image = AZ::RPI::StreamingImage::FindOrCreate(streamingImageAsset);
return image;
}
}
namespace TextureAtlasNamespace
{
@@ -95,27 +113,15 @@ namespace TextureAtlasNamespace
// We reload the image here to prevent stuttering in the editor
if (iterator->second.m_atlas && iterator->second.m_atlas->GetTexture())
{
SResourceAsync* pInfo = new SResourceAsync();
pInfo->eClassName = eRCN_Texture;
pInfo->pResource = iterator->second.m_atlas->GetTexture();
// ToDo: Update to work with Atom? LYN-3680
// ???->ReleaseResourceAsync(pInfo);
iterator->second.m_atlas->GetTexture().reset();
}
// Reload Texture
AZStd::string imagePath = iterator->second.m_path.substr(0, iterator->second.m_path.find_last_of('.'));
imagePath.append(".dds");
// ToDo: Update to work with Atom? LYN-3680
// uint32 loadTextureFlags = (FT_USAGE_ALLOWREADSRGB | FT_DONT_STREAM);
ITexture* texture = nullptr;
if (!texture || !texture->IsTextureLoaded())
AZStd::string imagePath = iterator->second.m_path;
AZ::Data::Instance<AZ::RPI::Image> texture = LoadAtlasImage(imagePath);
if (!texture)
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_UNKNOWN,
VALIDATOR_WARNING,
VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
imagePath.c_str(),
"No texture file found for texture atlas: %s. "
AZ_Error("TextureAtlasSystemComponent",
false,
"Failed to find or create an image instance for texture atlas '%s'"
"NOTE: File must be in current project or a gem.",
imagePath.c_str());
TextureAtlas* temp = iterator->second.m_atlas;
@@ -123,6 +129,7 @@ namespace TextureAtlasNamespace
TextureAtlasNotificationBus::Broadcast(&TextureAtlasNotifications::OnAtlasUnloaded, temp);
return;
}
iterator->second.m_atlas->SetTexture(texture);
TextureAtlasNotificationBus::Broadcast(&TextureAtlasNotifications::OnAtlasReloaded, iterator->second.m_atlas);
break;
@@ -186,30 +193,23 @@ namespace TextureAtlasNamespace
delete[] buffer;
if (loadedAtlas)
{
// Get the image path based on the atlas path
// Convert to image path based on the atlas path
AZStd::string imagePath = path;
AzFramework::StringFunc::Path::ReplaceExtension(imagePath, "dds");
// Load the image in
// ToDo: Update to work with Atom? LYN-3680
// uint32 loadTextureFlags = (FT_USAGE_ALLOWREADSRGB | FT_DONT_STREAM);
ITexture* texture = nullptr;
if (!texture || !texture->IsTextureLoaded())
AzFramework::StringFunc::Path::ReplaceExtension(imagePath, "texatlas");
AZ::Data::Instance<AZ::RPI::Image> texture = LoadAtlasImage(imagePath);
if (!texture)
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_UNKNOWN,
VALIDATOR_WARNING,
VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
imagePath.c_str(),
"No texture file found for texture atlas: %s. "
AZ_Error("TextureAtlasSystemComponent",
false,
"Failed to find or create an image instance for texture atlas '%s'"
"NOTE: File must be in current project or a gem.",
imagePath.c_str());
path.c_str());
delete loadedAtlas;
return nullptr;
}
else
{
texture->SetFilter(FILTER_LINEAR);
// Add the atlas to the list
AtlasInfo info(loadedAtlas, assetPath);
++info.m_refs;
@@ -241,11 +241,7 @@ namespace TextureAtlasNamespace
// Tell the renderer to release the texture.
if (temp.m_atlas && temp.m_atlas->GetTexture())
{
SResourceAsync* pInfo = new SResourceAsync();
pInfo->eClassName = eRCN_Texture;
pInfo->pResource = temp.m_atlas->GetTexture();
// ToDo: Update to work with Atom? LYN-3680
// ???->ReleaseResourceAsync(pInfo);
temp.m_atlas->GetTexture().reset();
}
// Delete the atlas
SAFE_DELETE(temp.m_atlas);
@@ -0,0 +1,17 @@
#
# 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.
#
set(FILES
Source/Editor/AtlasBuilderComponent.h
Source/Editor/AtlasBuilderComponent.cpp
Source/Editor/AtlasBuilderWorker.h
Source/Editor/AtlasBuilderWorker.cpp
)
@@ -15,7 +15,6 @@ set(FILES
Include/TextureAtlas/TextureAtlasBus.h
Include/TextureAtlas/TextureAtlasNotificationBus.h
Include/TextureAtlas/TextureAtlas.h
Source/TextureAtlasModule.cpp
Source/TextureAtlasSystemComponent.cpp
Source/TextureAtlasSystemComponent.h
Source/TextureAtlasImpl.h
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
Source/TextureAtlasModule.cpp
)