Refactor Texture Data function to be reusable

This commit is contained in:
Wojtek Figat
2026-03-06 11:40:43 +01:00
parent c0056f3d9d
commit 0fa3472f24
6 changed files with 94 additions and 97 deletions

View File

@@ -742,57 +742,8 @@ bool ProcessTextureBase(CookAssetsStep::AssetCookData& data)
{
auto chunk = New<FlaxChunk>();
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)

View File

@@ -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;
/// <inheritdoc />
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();
}
}
/// <inheritdoc />

View File

@@ -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;
/// <inheritdoc />
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();
}
}
}
/// <inheritdoc />

View File

@@ -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<uint32>(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

View File

@@ -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();

View File

@@ -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