From 0fa3472f24f5a0063ef6157a005a47114734f517 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Mar 2026 11:40:43 +0100 Subject: [PATCH] Refactor Texture Data function to be reusable --- Source/Editor/Cooker/Steps/CookAssetsStep.cpp | 53 +--------------- .../Windows/Assets/CubeTextureWindow.cs | 14 ++++- Source/Editor/Windows/Assets/TextureWindow.cs | 14 ++++- .../Engine/ContentImporters/ImportTexture.cpp | 46 ++------------ .../Engine/Tools/TextureTool/TextureTool.cpp | 61 +++++++++++++++++++ Source/Engine/Tools/TextureTool/TextureTool.h | 3 + 6 files changed, 94 insertions(+), 97 deletions(-) diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index d260496bc..a85973964 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -742,57 +742,8 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data) { auto chunk = New(); data.InitData.Header.Chunks[mipIndex] = chunk; - - if (header.Format == PixelFormat::Basis) - { - // Store as-is - int32 maxDataSize = 0; - for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) - { - auto& mipData = textureData->Items[arrayIndex].Mips[mipIndex]; - maxDataSize = Math::Max(maxDataSize, mipData.Data.Length()); - } - chunk->Data.Allocate(maxDataSize * textureData->GetArraySize()); - for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) - { - auto& mipData = textureData->Items[arrayIndex].Mips[mipIndex]; - byte* dst = chunk->Data.Get() + maxDataSize * arrayIndex; - Platform::MemoryCopy(dst, mipData.Data.Get(), mipData.Data.Length()); - Platform::MemoryClear(dst + mipData.Data.Length(), maxDataSize - mipData.Data.Length()); - } - continue; - } - - // Calculate the texture data storage layout - uint32 rowPitch, slicePitch; - const int32 mipWidth = Math::Max(1, textureData->Width >> mipIndex); - const int32 mipHeight = Math::Max(1, textureData->Height >> mipIndex); - RenderTools::ComputePitch(textureData->Format, mipWidth, mipHeight, rowPitch, slicePitch); - chunk->Data.Allocate(slicePitch * textureData->GetArraySize()); - - // Copy array slices into mip data (sequential) - for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) - { - auto& mipData = textureData->Items[arrayIndex].Mips[mipIndex]; - byte* src = mipData.Data.Get(); - byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex); - - // Faster path if source and destination data layout matches - if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch) - { - Platform::MemoryCopy(dst, src, slicePitch); - } - else - { - const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch); - for (uint32 line = 0; line < mipData.Lines; line++) - { - Platform::MemoryCopy(dst, src, copyRowSize); - src += mipData.RowPitch; - dst += rowPitch; - } - } - } + if (TextureTool::WriteTextureData(chunk->Data, *textureData, mipIndex)) + return true; } // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks) diff --git a/Source/Editor/Windows/Assets/CubeTextureWindow.cs b/Source/Editor/Windows/Assets/CubeTextureWindow.cs index c4d7ab053..c82cd561a 100644 --- a/Source/Editor/Windows/Assets/CubeTextureWindow.cs +++ b/Source/Editor/Windows/Assets/CubeTextureWindow.cs @@ -46,7 +46,9 @@ namespace FlaxEditor.Windows.Assets var texture = window.Asset; var group = layout.Group("General"); - group.Label("Format: " + texture.Format); + var textureFormat = texture.Format; + var gpuFormat = texture.Texture?.Format ?? textureFormat; + group.Label(textureFormat != gpuFormat && gpuFormat != PixelFormat.Unknown ? $"Format: {textureFormat} ({gpuFormat})" : $"Format: {textureFormat}"); group.Label(string.Format("Size: {0}x{1}", texture.Width, texture.Height)).AddCopyContextMenu(); group.Label("Mip levels: " + texture.MipLevels); group.Label("Memory usage: " + Utilities.Utils.FormatBytesCount(texture.TotalMemoryUsage)); @@ -117,7 +119,7 @@ namespace FlaxEditor.Windows.Assets private readonly CustomEditorPresenter _propertiesEditor; private readonly PropertiesProxy _properties; - private bool _isWaitingForLoad; + private bool _isWaitingForLoad, _isWaitingForTexture; /// public CubeTextureWindow(Editor editor, AssetItem item) @@ -193,8 +195,8 @@ namespace FlaxEditor.Windows.Assets // Check if need to load if (_isWaitingForLoad && _asset.IsLoaded) { - // Clear flag _isWaitingForLoad = false; + _isWaitingForTexture = true; // Init properties and parameters proxy _properties.OnLoad(this); @@ -203,6 +205,12 @@ namespace FlaxEditor.Windows.Assets // Setup ClearEditedFlag(); } + if (_isWaitingForTexture && _asset.Texture.IsAllocated) + { + // Refresh properties to display GPU texture info + _isWaitingForTexture = false; + _propertiesEditor.BuildLayout(); + } } /// diff --git a/Source/Editor/Windows/Assets/TextureWindow.cs b/Source/Editor/Windows/Assets/TextureWindow.cs index 3977d562a..dcdd6e750 100644 --- a/Source/Editor/Windows/Assets/TextureWindow.cs +++ b/Source/Editor/Windows/Assets/TextureWindow.cs @@ -221,7 +221,7 @@ namespace FlaxEditor.Windows.Assets private readonly SplitPanel _split; private readonly TexturePreview _preview; private readonly ToolStripButton _saveButton; - private bool _isWaitingForLoad; + private bool _isWaitingForLoad, _isWaitingForTexture; /// public TextureWindow(Editor editor, AssetItem item) @@ -328,8 +328,8 @@ namespace FlaxEditor.Windows.Assets // Check if need to load if (_isWaitingForLoad && _asset.IsLoaded) { - // Clear flag _isWaitingForLoad = false; + _isWaitingForTexture = true; // Init properties and parameters proxy foreach (var child in _tabs.Children) @@ -344,6 +344,16 @@ namespace FlaxEditor.Windows.Assets // Setup ClearEditedFlag(); } + if (_isWaitingForTexture && _asset.Texture.IsAllocated) + { + // Refresh properties to display GPU texture info + _isWaitingForTexture = false; + foreach (var child in _tabs.Children) + { + if (child is Tab tab && tab.Proxy != null) + tab.Presenter.BuildLayout(); + } + } } /// diff --git a/Source/Engine/ContentImporters/ImportTexture.cpp b/Source/Engine/ContentImporters/ImportTexture.cpp index ba0f6421f..a53aabd94 100644 --- a/Source/Engine/ContentImporters/ImportTexture.cpp +++ b/Source/Engine/ContentImporters/ImportTexture.cpp @@ -184,48 +184,12 @@ CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const Textu } // Save mip maps - if (!isCubeMap) + for (int32 mipIndex = 0; mipIndex < textureHeader.MipLevels; mipIndex++) { - for (int32 mipIndex = 0; mipIndex < textureHeader.MipLevels; mipIndex++) - { - auto mipData = textureData.GetData(0, mipIndex); - - if (context.AllocateChunk(mipIndex)) - return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[mipIndex]->Data.Copy(mipData->Data.Get(), static_cast(mipData->DepthPitch)); - } - } - else - { - // Allocate memory for a temporary buffer - const uint32 imageSize = textureData.GetData(0, 0)->DepthPitch * 6; - MemoryWriteStream imageData(imageSize); - - // Copy cube sides for every mip into separate chunks - for (int32 mipLevelIndex = 0; mipLevelIndex < textureHeader.MipLevels; mipLevelIndex++) - { - // Write array slices to the stream - imageData.SetPosition(0); - for (int32 cubeFaceIndex = 0; cubeFaceIndex < 6; cubeFaceIndex++) - { - // Get image - const auto image = textureData.GetData(cubeFaceIndex, mipLevelIndex); - if (image == nullptr) - { - LOG(Warning, "Cannot create cube texture '{0}'. Missing image slice.", context.InputPath); - return CreateAssetResult::Error; - } - ASSERT(image->DepthPitch < MAX_int32); - - // Copy data - imageData.WriteBytes(image->Data.Get(), image->Data.Length()); - } - - // Copy mip - if (context.AllocateChunk(mipLevelIndex)) - return CreateAssetResult::CannotAllocateChunk; - context.Data.Header.Chunks[mipLevelIndex]->Data.Copy(imageData.GetHandle(), imageData.GetPosition()); - } + if (context.AllocateChunk(mipIndex)) + return CreateAssetResult::CannotAllocateChunk; + if (TextureTool::WriteTextureData(context.Data.Header.Chunks[mipIndex]->Data, textureData, mipIndex)) + return CreateAssetResult::Error; } #if IMPORT_TEXTURE_CACHE_OPTIONS diff --git a/Source/Engine/Tools/TextureTool/TextureTool.cpp b/Source/Engine/Tools/TextureTool/TextureTool.cpp index 7bb00e290..a9503a993 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.cpp +++ b/Source/Engine/Tools/TextureTool/TextureTool.cpp @@ -14,6 +14,7 @@ #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/PixelFormatSampler.h" +#include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" @@ -526,6 +527,66 @@ PixelFormat TextureTool::ToPixelFormat(TextureFormatType format, int32 width, in } } +#if USE_EDITOR + +bool TextureTool::WriteTextureData(BytesContainer& result, const TextureData& textureData, int32 mipIndex) +{ + if (textureData.Format == PixelFormat::Basis) + { + // Store as-is, each slice is stored in a separate block with the same size + int32 maxDataSize = 0; + for (int32 arrayIndex = 0; arrayIndex < textureData.Items.Count(); arrayIndex++) + { + auto& mipData = textureData.Items[arrayIndex].Mips[mipIndex]; + maxDataSize = Math::Max(maxDataSize, mipData.Data.Length()); + } + result.Allocate(maxDataSize * textureData.GetArraySize()); + for (int32 arrayIndex = 0; arrayIndex < textureData.Items.Count(); arrayIndex++) + { + auto& mipData = textureData.Items[arrayIndex].Mips[mipIndex]; + byte* dst = result.Get() + maxDataSize * arrayIndex; + Platform::MemoryCopy(dst, mipData.Data.Get(), mipData.Data.Length()); + Platform::MemoryClear(dst + mipData.Data.Length(), maxDataSize - mipData.Data.Length()); + } + return false; + } + + // Calculate the texture data storage layout + uint32 rowPitch, slicePitch; + const int32 mipWidth = Math::Max(1, textureData.Width >> mipIndex); + const int32 mipHeight = Math::Max(1, textureData.Height >> mipIndex); + RenderTools::ComputePitch(textureData.Format, mipWidth, mipHeight, rowPitch, slicePitch); + result.Allocate(slicePitch * textureData.GetArraySize()); + + // Copy array slices into mip data (sequential) + for (int32 arrayIndex = 0; arrayIndex < textureData.Items.Count(); arrayIndex++) + { + auto& mipData = textureData.Items[arrayIndex].Mips[mipIndex]; + const byte* src = mipData.Data.Get(); + byte* dst = result.Get() + (slicePitch * arrayIndex); + + // Faster path if source and destination data layout matches + if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch) + { + Platform::MemoryCopy(dst, src, slicePitch); + } + else + { + const uint32 copyRowSize = Math::Min(mipData.RowPitch, rowPitch); + for (uint32 line = 0; line < mipData.Lines; line++) + { + Platform::MemoryCopy(dst, src, copyRowSize); + src += mipData.RowPitch; + dst += rowPitch; + } + } + } + + return false; +} + +#endif + bool TextureTool::GetImageType(const StringView& path, ImageType& type) { const auto extension = FileSystem::GetExtension(path).ToLower(); diff --git a/Source/Engine/Tools/TextureTool/TextureTool.h b/Source/Engine/Tools/TextureTool/TextureTool.h index 5d1160d39..0159eca86 100644 --- a/Source/Engine/Tools/TextureTool/TextureTool.h +++ b/Source/Engine/Tools/TextureTool/TextureTool.h @@ -217,6 +217,9 @@ public: public: static PixelFormat ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress = true); +#if USE_EDITOR + static bool WriteTextureData(BytesContainer& result, const TextureData& textureData, int32 mipIndex); +#endif private: enum class ImageType