Files
o3de/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp
T
galibzon 6763e2a3ac Shaders changes require two or more change cycles before updating (#5142)
* Shaders changes require two or more change cycles before updating

This fixes the problem described in the title.

Consolidated the responsibility to update the root shader variant
asset into the Shader() class. It was unnecessarily spread across
Shader(), ShaderVariant() and ShaderAsset().
In particular OnAssetReloaded now makes a temporary copy of the root
ShaderVariantAsset and updates the ShaderAsset with such reference
only when OnAssetReloaded() is called on behalf of the ShaderAsset.

Signed-off-by: galibzon <66021303+galibzon@users.noreply.github.com>
2021-11-02 16:21:26 -05:00

482 lines
26 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 <ReflectionProbe/EditorReflectionProbeComponent.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzCore/IO/SystemFile.h>
#include <Atom/RPI.Reflect/Image/StreamingImagePoolAsset.h>
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <Atom/Utils/DdsFile.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Component/Entity.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QApplication>
#include <QMessageBox>
#include <QProgressDialog>
AZ_POP_DISABLE_WARNING
namespace AZ
{
namespace Render
{
void EditorReflectionProbeComponent::Reflect(AZ::ReflectContext* context)
{
BaseClass::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorReflectionProbeComponent, BaseClass>()
->Version(2, ConvertToEditorRenderComponentAdapter<1>)
->Field("useBakedCubemap", &EditorReflectionProbeComponent::m_useBakedCubemap)
->Field("bakedCubeMapQualityLevel", &EditorReflectionProbeComponent::m_bakedCubeMapQualityLevel)
->Field("bakedCubeMapRelativePath", &EditorReflectionProbeComponent::m_bakedCubeMapRelativePath)
->Field("authoredCubeMapAsset", &EditorReflectionProbeComponent::m_authoredCubeMapAsset)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorReflectionProbeComponent>(
"Reflection Probe", "The ReflectionProbe component captures an IBL specular reflection at a specific position in the level")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Atom")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Component_Placeholder.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/reflection-probe/")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo<RPI::ModelAsset>::Uuid())
->ClassElement(AZ::Edit::ClassElements::Group, "Cubemap Bake")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->UIElement(AZ::Edit::UIHandlers::Button, "Bake Reflection Probe", "Bake Reflection Probe")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
->Attribute(AZ::Edit::Attributes::ButtonText, "Bake Reflection Probe")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorReflectionProbeComponent::BakeReflectionProbe)
->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting)
->ClassElement(AZ::Edit::ClassElements::Group, "Cubemap")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorReflectionProbeComponent::m_useBakedCubemap, "Use Baked Cubemap", "Selects between a cubemap that captures the environment at location in the scene or a preauthored cubemap")
->Attribute(AZ::Edit::Attributes::ChangeValidate, &EditorReflectionProbeComponent::OnUseBakedCubemapValidate)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorReflectionProbeComponent::OnUseBakedCubemapChanged)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorReflectionProbeComponent::m_bakedCubeMapQualityLevel, "Baked Cubemap Quality", "Resolution of the baked cubemap")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting)
->EnumAttribute(BakedCubeMapQualityLevel::VeryLow, "Very Low")
->EnumAttribute(BakedCubeMapQualityLevel::Low, "Low")
->EnumAttribute(BakedCubeMapQualityLevel::Medium, "Medium")
->EnumAttribute(BakedCubeMapQualityLevel::High, "High")
->EnumAttribute(BakedCubeMapQualityLevel::VeryHigh, "Very High")
->DataElement(AZ::Edit::UIHandlers::MultiLineEdit, &EditorReflectionProbeComponent::m_bakedCubeMapRelativePath, "Baked Cubemap Path", "Baked Cubemap Path")
->Attribute(AZ::Edit::Attributes::ReadOnly, true)
->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorReflectionProbeComponent::m_authoredCubeMapAsset, "Cubemap file", "Authored Cubemap file")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetAuthoredCubemapVisibilitySetting)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorReflectionProbeComponent::OnAuthoredCubemapChanged)
;
editContext->Class<ReflectionProbeComponentController>(
"ReflectionProbeComponentController", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &ReflectionProbeComponentController::m_configuration, "Configuration", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
;
editContext->Class<ReflectionProbeComponentConfig>(
"ReflectionProbeComponentConfig", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->ClassElement(AZ::Edit::ClassElements::Group, "Inner Extents")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &ReflectionProbeComponentConfig::m_innerHeight, "Height", "Height of the reflection probe inner volume")
->Attribute(AZ::Edit::Attributes::Min, 0.f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &ReflectionProbeComponentConfig::m_innerLength, "Length", "Length of the reflection probe inner volume")
->Attribute(AZ::Edit::Attributes::Min, 0.f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &ReflectionProbeComponentConfig::m_innerWidth, "Width", "Width of the reflection probe inner volume")
->Attribute(AZ::Edit::Attributes::Min, 0.f)
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->ClassElement(AZ::Edit::ClassElements::Group, "Settings")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::CheckBox, &ReflectionProbeComponentConfig::m_useParallaxCorrection, "Parallax Correction", "Correct the reflection to adjust for the offset from the capture position")
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
->DataElement(AZ::Edit::UIHandlers::CheckBox, &ReflectionProbeComponentConfig::m_showVisualization, "Show Visualization", "Show the reflection probe visualization sphere")
->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
;
}
}
if (auto behaviorContext = azrtti_cast<BehaviorContext*>(context))
{
behaviorContext->EBus<EditorReflectionProbeBus>("EditorReflectionProbeBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "render")
->Event("BakeReflectionProbe", &EditorReflectionProbeInterface::BakeReflectionProbe)
;
behaviorContext->ConstantProperty("EditorReflectionProbeComponentTypeId", BehaviorConstant(Uuid(EditorReflectionProbeComponentTypeId)))
->Attribute(AZ::Script::Attributes::Module, "render")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
}
}
EditorReflectionProbeComponent::EditorReflectionProbeComponent(const ReflectionProbeComponentConfig& config)
: BaseClass(config)
{
}
void EditorReflectionProbeComponent::Activate()
{
BaseClass::Activate();
AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId());
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(GetEntityId());
EditorReflectionProbeBus::Handler::BusConnect(GetEntityId());
AZ::TickBus::Handler::BusConnect();
ReflectionProbeComponentConfig& configuration = m_controller.m_configuration;
// update UI cubemap path display
m_bakedCubeMapRelativePath = configuration.m_bakedCubeMapRelativePath;
AZ::u64 entityId = (AZ::u64)GetEntityId();
configuration.m_entityId = entityId;
}
void EditorReflectionProbeComponent::Deactivate()
{
EditorReflectionProbeBus::Handler::BusDisconnect(GetEntityId());
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect();
AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
BaseClass::Deactivate();
}
void EditorReflectionProbeComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
if (!m_controller.m_featureProcessor)
{
return;
}
if (m_controller.m_configuration.m_useBakedCubemap)
{
AZStd::string cubeMapRelativePath = m_controller.m_configuration.m_bakedCubeMapRelativePath + ".streamingimage";
Data::Asset<RPI::StreamingImageAsset> cubeMapAsset;
CubeMapAssetNotificationType notificationType = CubeMapAssetNotificationType::None;
if (m_controller.m_featureProcessor->CheckCubeMapAssetNotification(cubeMapRelativePath, cubeMapAsset, notificationType))
{
// a cubemap bake is in progress for this entity component
if (notificationType == CubeMapAssetNotificationType::Ready)
{
// bake is complete, update configuration with the new baked cubemap asset
m_controller.m_configuration.m_bakedCubeMapAsset = cubeMapAsset;
// refresh the currently rendered cubemap
m_controller.UpdateCubeMap();
// update the UI
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
}
else if (notificationType == CubeMapAssetNotificationType::Error)
{
// cubemap bake failed
QMessageBox::information(
QApplication::activeWindow(),
"Reflection Probe",
"Reflection Probe cubemap failed to bake, please check the Asset Processor for more information.",
QMessageBox::Ok);
// clear relative path, this will allow the user to retry
m_controller.m_configuration.m_bakedCubeMapRelativePath.clear();
// update the UI
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
}
}
}
}
void EditorReflectionProbeComponent::DisplayEntityViewport([[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
{
// only draw the bounds if selected
if (!IsSelected())
{
return;
}
AZ::Vector3 position = AZ::Vector3::CreateZero();
AZ::TransformBus::EventResult(position, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation);
AZ::Quaternion rotationQuaternion = AZ::Quaternion::CreateIdentity();
AZ::TransformBus::EventResult(rotationQuaternion, GetEntityId(), &AZ::TransformBus::Events::GetWorldRotationQuaternion);
AZ::Matrix3x3 rotationMatrix = AZ::Matrix3x3::CreateFromQuaternion(rotationQuaternion);
float scale = 1.0f;
AZ::TransformBus::EventResult(scale, GetEntityId(), &AZ::TransformBus::Events::GetLocalUniformScale);
// draw AABB at probe position using the inner dimensions
Color color(0.0f, 0.0f, 1.0f, 1.0f);
debugDisplay.SetColor(color);
ReflectionProbeComponentConfig& configuration = m_controller.m_configuration;
AZ::Vector3 innerExtents(configuration.m_innerWidth, configuration.m_innerLength, configuration.m_innerHeight);
innerExtents *= scale;
debugDisplay.DrawWireOBB(position, rotationMatrix.GetBasisX(), rotationMatrix.GetBasisY(), rotationMatrix.GetBasisZ(), innerExtents / 2.0f);
}
AZ::Aabb EditorReflectionProbeComponent::GetEditorSelectionBoundsViewport([[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo)
{
return m_controller.GetWorldBounds();
}
bool EditorReflectionProbeComponent::SupportsEditorRayIntersect()
{
return false;
}
AZ::Outcome<void, AZStd::string> EditorReflectionProbeComponent::OnUseBakedCubemapValidate([[maybe_unused]] void* newValue, [[maybe_unused]] const AZ::Uuid& valueType)
{
if (!m_controller.m_featureProcessor)
{
return AZ::Failure(AZStd::string("This Reflection Probe entity is hidden, it must be visible in order to change the cubemap type."));
}
return AZ::Success();
}
AZ::u32 EditorReflectionProbeComponent::OnUseBakedCubemapChanged()
{
// save setting to the configuration
m_controller.m_configuration.m_useBakedCubemap = m_useBakedCubemap;
// refresh currently displayed cubemap
m_controller.UpdateCubeMap();
return AZ::Edit::PropertyRefreshLevels::EntireTree;
}
AZ::u32 EditorReflectionProbeComponent::OnAuthoredCubemapChanged()
{
// save the selected authored asset to the configuration
m_controller.m_configuration.m_authoredCubeMapAsset = m_authoredCubeMapAsset;
// refresh currently displayed cubemap
m_controller.UpdateCubeMap();
return AZ::Edit::PropertyRefreshLevels::None;
}
AZ::u32 EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting()
{
// controls specific to baked cubemaps call this to determine their visibility
// they are visible when the mode is set to baked, otherwise hidden
return m_useBakedCubemap ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::u32 EditorReflectionProbeComponent::GetAuthoredCubemapVisibilitySetting()
{
// controls specific to authored cubemaps call this to determine their visibility
// they are hidden when the mode is set to baked, otherwise visible
return m_useBakedCubemap ? AZ::Edit::PropertyVisibility::Hide : AZ::Edit::PropertyVisibility::Show;
}
AZ::u32 EditorReflectionProbeComponent::BakeReflectionProbe()
{
if (!m_useBakedCubemap)
{
AZ_Assert(false, "BakeReflectionProbe() called on a Reflection Probe set to use an authored cubemap");
return AZ::Edit::PropertyRefreshLevels::None;
}
if (m_bakeInProgress)
{
return AZ::Edit::PropertyRefreshLevels::None;
}
// retrieve entity visibility
bool isHidden = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
isHidden,
GetEntityId(),
&AzToolsFramework::EditorEntityInfoRequestBus::Events::IsHidden);
// the entity must be visible in order to bake
if (isHidden)
{
QMessageBox::information(
QApplication::activeWindow(),
"Reflection Probe",
"This Reflection Probe entity is hidden, it must be visible in order to bake the cubemap.",
QMessageBox::Ok);
return AZ::Edit::PropertyRefreshLevels::None;
}
char projectPath[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@projectroot@", projectPath, AZ_MAX_PATH_LEN);
// retrieve the source cubemap path from the configuration
// we need to make sure to use the same source cubemap for each bake
AZStd::string cubeMapRelativePath = m_controller.m_configuration.m_bakedCubeMapRelativePath;
AZStd::string cubeMapFullPath;
if (!cubeMapRelativePath.empty())
{
// test to see if the cubemap file is actually there, if it was removed we need to
// generate a new filename, otherwise it will cause an error in the asset system
AzFramework::StringFunc::Path::Join(projectPath, cubeMapRelativePath.c_str(), cubeMapFullPath, true, true);
if (!AZ::IO::FileIOBase::GetInstance()->Exists(cubeMapFullPath.c_str()))
{
// clear it to force the generation of a new filename
cubeMapRelativePath.clear();
}
// if the quality level changed we need to generate a new filename
if (m_controller.m_configuration.m_bakedCubeMapQualityLevel != m_bakedCubeMapQualityLevel)
{
cubeMapRelativePath.clear();
}
}
// build a new cubemap path if necessary
if (cubeMapRelativePath.empty())
{
// the file name is a combination of the entity name, a UUID, and the filemask
Entity* entity = GetEntity();
AZ_Assert(entity, "ReflectionProbe entity is null");
AZ::Uuid uuid = AZ::Uuid::CreateRandom();
AZStd::string uuidString;
uuid.ToString(uuidString);
// determine the filemask suffix from the cubemap quality level setting
AZStd::string fileSuffix = BakedCubeMapFileSuffixes[aznumeric_cast<uint32_t>(m_bakedCubeMapQualityLevel)];
cubeMapRelativePath = "ReflectionProbes/" + entity->GetName() + "_" + uuidString + fileSuffix;
// replace any invalid filename characters
auto invalidCharacters = [](char letter)
{
return
letter == ':' || letter == '"' || letter == '\'' ||
letter == '{' || letter == '}' ||
letter == '<' || letter == '>';
};
AZStd::replace_if(cubeMapRelativePath.begin(), cubeMapRelativePath.end(), invalidCharacters, '_');
// build the full source path
AzFramework::StringFunc::Path::Join(projectPath, cubeMapRelativePath.c_str(), cubeMapFullPath, true, true);
}
// make sure the folder is created
AZStd::string reflectionProbeFolder;
AzFramework::StringFunc::Path::GetFolderPath(cubeMapFullPath.data(), reflectionProbeFolder);
AZ::IO::SystemFile::CreateDir(reflectionProbeFolder.c_str());
// check out the file in source control
bool checkedOutSuccessfully = false;
using ApplicationBus = AzToolsFramework::ToolsApplicationRequestBus;
ApplicationBus::BroadcastResult(
checkedOutSuccessfully,
&ApplicationBus::Events::RequestEditForFileBlocking,
cubeMapFullPath.c_str(),
"Checking out for edit...",
ApplicationBus::Events::RequestEditProgressCallback());
if (!checkedOutSuccessfully)
{
AZ_Error("ReflectionProbe", false, "Failed to write \"%s\", source control checkout failed", cubeMapFullPath.c_str());
}
// save the relative source path in the configuration
AzToolsFramework::ScopedUndoBatch undoBatch("Cubemap path changed.");
m_controller.m_configuration.m_bakedCubeMapRelativePath = cubeMapRelativePath;
m_controller.m_configuration.m_bakedCubeMapQualityLevel = m_bakedCubeMapQualityLevel;
SetDirty();
// update UI cubemap path display
m_bakedCubeMapRelativePath = cubeMapRelativePath;
// callback from the EnvironmentCubeMapPass when the cubemap render is complete
BuildCubeMapCallback buildCubeMapCallback = [=](uint8_t* const* cubeMapFaceTextureData, const RHI::Format cubeMapTextureFormat)
{
// write the cubemap data to the .dds file
WriteOutputFile(cubeMapFullPath.c_str(), cubeMapFaceTextureData, cubeMapTextureFormat);
m_bakeInProgress = false;
};
// initiate the cubemap bake, this will invoke the buildCubeMapCallback when the cubemap data is ready
m_bakeInProgress = true;
AZStd::string cubeMapRelativeAssetPath = cubeMapRelativePath + ".streamingimage";
m_controller.BakeReflectionProbe(buildCubeMapCallback, cubeMapRelativeAssetPath);
// show a dialog box letting the user know the probe is baking
QProgressDialog bakeDialog;
bakeDialog.setWindowFlags(bakeDialog.windowFlags() & ~Qt::WindowCloseButtonHint);
bakeDialog.setLabelText("Baking Reflection Probe...");
bakeDialog.setWindowModality(Qt::WindowModal);
bakeDialog.setMaximumSize(QSize(256, 96));
bakeDialog.setMinimum(0);
bakeDialog.setMaximum(0);
bakeDialog.setMinimumDuration(0);
bakeDialog.setAutoClose(false);
bakeDialog.setCancelButton(nullptr);
bakeDialog.show();
// display until finished or canceled
while (m_bakeInProgress)
{
if (bakeDialog.wasCanceled())
{
m_bakeInProgress = false;
break;
}
QApplication::processEvents();
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
}
return AZ::Edit::PropertyRefreshLevels::ValuesOnly;
}
void EditorReflectionProbeComponent::WriteOutputFile(AZStd::string filePath, uint8_t* const* cubeMapTextureData, const RHI::Format cubeMapTextureFormat)
{
static const u32 CubeMapFaceSize = 1024;
static const u32 NumCubeMapFaces = 6;
u32 bytesPerTexel = RHI::GetFormatSize(cubeMapTextureFormat);
u32 bytesPerCubeMapFace = CubeMapFaceSize * CubeMapFaceSize * bytesPerTexel;
AZStd::vector<uint8_t> buffer;
buffer.resize_no_construct(bytesPerCubeMapFace * NumCubeMapFaces);
for (AZ::u32 i = 0; i < NumCubeMapFaces; ++i)
{
memcpy(buffer.data() + (i * bytesPerCubeMapFace), cubeMapTextureData[i], bytesPerCubeMapFace);
}
DdsFile::DdsFileData ddsFileData;
ddsFileData.m_size.m_width = CubeMapFaceSize;
ddsFileData.m_size.m_height = CubeMapFaceSize;
ddsFileData.m_format = cubeMapTextureFormat;
ddsFileData.m_isCubemap = true;
ddsFileData.m_buffer = &buffer;
auto outcome = AZ::DdsFile::WriteFile(filePath, ddsFileData);
if (!outcome)
{
AZ_Warning("WriteDds", false, outcome.GetError().m_message.c_str());
}
}
} // namespace Render
} // namespace AZ