1000 lines
30 KiB
C++
1000 lines
30 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "TextureBase.h"
|
|
#include "TextureData.h"
|
|
#include "Engine/Core/Math/Color32.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Graphics/Textures/GPUTexture.h"
|
|
#include "Engine/Graphics/RenderTools.h"
|
|
#include "Engine/Graphics/PixelFormatExtensions.h"
|
|
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
|
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
|
|
#include "Engine/Profiler/ProfilerCPU.h"
|
|
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
|
#include "Engine/Scripting/Enums.h"
|
|
#include "Engine/Tools/TextureTool/TextureTool.h"
|
|
#include "Engine/Threading/Threading.h"
|
|
|
|
TextureMipData::TextureMipData()
|
|
: RowPitch(0)
|
|
, DepthPitch(0)
|
|
, Lines(0)
|
|
{
|
|
}
|
|
|
|
TextureMipData::TextureMipData(const TextureMipData& other)
|
|
: RowPitch(other.RowPitch)
|
|
, DepthPitch(other.DepthPitch)
|
|
, Lines(other.Lines)
|
|
, Data(other.Data)
|
|
{
|
|
}
|
|
|
|
TextureMipData::TextureMipData(TextureMipData&& other) noexcept
|
|
: RowPitch(other.RowPitch)
|
|
, DepthPitch(other.DepthPitch)
|
|
, Lines(other.Lines)
|
|
, Data(MoveTemp(other.Data))
|
|
{
|
|
}
|
|
|
|
TextureMipData& TextureMipData::operator=(const TextureMipData& other)
|
|
{
|
|
if (this == &other)
|
|
return *this;
|
|
RowPitch = other.RowPitch;
|
|
DepthPitch = other.DepthPitch;
|
|
Lines = other.Lines;
|
|
Data = other.Data;
|
|
return *this;
|
|
}
|
|
|
|
TextureMipData& TextureMipData::operator=(TextureMipData&& other) noexcept
|
|
{
|
|
if (this == &other)
|
|
return *this;
|
|
RowPitch = other.RowPitch;
|
|
DepthPitch = other.DepthPitch;
|
|
Lines = other.Lines;
|
|
Data = MoveTemp(other.Data);
|
|
return *this;
|
|
}
|
|
|
|
bool TextureMipData::GetPixels(Array<Color32>& pixels, int32 width, int32 height, PixelFormat format) const
|
|
{
|
|
const int32 size = width * height;
|
|
if (Data.IsInvalid() || size < 1)
|
|
return true;
|
|
pixels.Resize(size);
|
|
byte* dst = (byte*)pixels.Get();
|
|
const int32 dstRowSize = width * sizeof(Color32);
|
|
const byte* src = Data.Get();
|
|
const int32 srcRowSize = RowPitch;
|
|
switch (format)
|
|
{
|
|
case PixelFormat::R8G8B8A8_SInt:
|
|
case PixelFormat::R8G8B8A8_Typeless:
|
|
case PixelFormat::R8G8B8A8_SNorm:
|
|
case PixelFormat::R8G8B8A8_UInt:
|
|
case PixelFormat::R8G8B8A8_UNorm:
|
|
case PixelFormat::R8G8B8A8_UNorm_sRGB:
|
|
case PixelFormat::R8G8_B8G8_UNorm:
|
|
case PixelFormat::B8G8R8A8_Typeless:
|
|
case PixelFormat::B8G8R8A8_UNorm:
|
|
case PixelFormat::B8G8R8A8_UNorm_sRGB:
|
|
case PixelFormat::B8G8R8X8_Typeless:
|
|
case PixelFormat::B8G8R8X8_UNorm:
|
|
case PixelFormat::B8G8R8X8_UNorm_sRGB:
|
|
if (srcRowSize == dstRowSize)
|
|
Platform::MemoryCopy(dst, src, RowPitch * Lines);
|
|
else
|
|
{
|
|
for (uint32 row = 0; row < Lines; row++)
|
|
{
|
|
Platform::MemoryCopy(dst, src, dstRowSize);
|
|
dst += dstRowSize;
|
|
src += srcRowSize;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
// Try to use texture sampler utility
|
|
auto sampler = TextureTool::GetSampler(format);
|
|
if (sampler)
|
|
{
|
|
for (int32 y = 0; y < height; y++)
|
|
{
|
|
for (int32 x = 0; x < width; x++)
|
|
{
|
|
Color c = TextureTool::SamplePoint(sampler, x, y, src, RowPitch);
|
|
*(Color32*)(dst + dstRowSize * y + x * sizeof(Color32)) = Color32(c);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
LOG(Error, "Unsupported texture data format {0}.", ScriptingEnum::ToString(format));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TextureMipData::GetPixels(Array<Color>& pixels, int32 width, int32 height, PixelFormat format) const
|
|
{
|
|
const int32 size = width * height;
|
|
if (Data.IsInvalid() || size < 1)
|
|
return true;
|
|
pixels.Resize(size);
|
|
byte* dst = (byte*)pixels.Get();
|
|
const int32 dstRowSize = width * sizeof(Color);
|
|
const byte* src = Data.Get();
|
|
const int32 srcRowSize = RowPitch;
|
|
switch (format)
|
|
{
|
|
case PixelFormat::R32G32B32A32_Typeless:
|
|
case PixelFormat::R32G32B32A32_Float:
|
|
if (srcRowSize == dstRowSize)
|
|
Platform::MemoryCopy(dst, src, RowPitch * Lines);
|
|
else
|
|
{
|
|
for (uint32 row = 0; row < Lines; row++)
|
|
{
|
|
Platform::MemoryCopy(dst, src, dstRowSize);
|
|
dst += dstRowSize;
|
|
src += srcRowSize;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
// Try to use texture sampler utility
|
|
auto sampler = TextureTool::GetSampler(format);
|
|
if (sampler)
|
|
{
|
|
for (int32 y = 0; y < height; y++)
|
|
{
|
|
for (int32 x = 0; x < width; x++)
|
|
{
|
|
Color c = TextureTool::SamplePoint(sampler, x, y, src, RowPitch);
|
|
*(Color*)(dst + dstRowSize * y + x * sizeof(Color)) = c;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
LOG(Error, "Unsupported texture data format {0}.", ScriptingEnum::ToString(format));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TextureMipData::Copy(void* data, uint32 dataRowPitch, uint32 dataDepthPitch, uint32 dataDepthSlices, uint32 targetRowPitch)
|
|
{
|
|
// Check if target row pitch is the same
|
|
if (targetRowPitch == dataRowPitch || targetRowPitch == 0)
|
|
{
|
|
Lines = dataDepthPitch / dataRowPitch;
|
|
DepthPitch = dataDepthPitch;
|
|
RowPitch = dataRowPitch;
|
|
|
|
// Single memory copy
|
|
Data.Copy((byte*)data, dataDepthPitch * dataDepthSlices);
|
|
}
|
|
else
|
|
{
|
|
Lines = dataDepthPitch / dataRowPitch;
|
|
DepthPitch = targetRowPitch * Lines;
|
|
RowPitch = targetRowPitch;
|
|
|
|
// Convert row by row
|
|
Data.Allocate(DepthPitch * dataDepthSlices);
|
|
for (uint32 depth = 0; depth < dataDepthSlices; depth++)
|
|
{
|
|
byte* src = (byte*)data + depth * dataDepthPitch;
|
|
byte* dst = Data.Get() + depth * DepthPitch;
|
|
for (uint32 row = 0; row < Lines; row++)
|
|
{
|
|
Platform::MemoryCopy(dst + row * RowPitch, src + row * dataRowPitch, RowPitch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
REGISTER_BINARY_ASSET_ABSTRACT(TextureBase, "FlaxEngine.TextureBase");
|
|
|
|
TextureBase::TextureBase(const SpawnParams& params, const AssetInfo* info)
|
|
: BinaryAsset(params, info)
|
|
, _texture(this, info->Path)
|
|
, _customData(nullptr)
|
|
, _parent(this)
|
|
{
|
|
}
|
|
|
|
Float2 TextureBase::Size() const
|
|
{
|
|
return Float2(static_cast<float>(_texture.TotalWidth()), static_cast<float>(_texture.TotalHeight()));
|
|
}
|
|
|
|
int32 TextureBase::GetArraySize() const
|
|
{
|
|
return _texture.TotalArraySize();
|
|
}
|
|
|
|
int32 TextureBase::GetMipLevels() const
|
|
{
|
|
return _texture.TotalMipLevels();
|
|
}
|
|
|
|
int32 TextureBase::GetResidentMipLevels() const
|
|
{
|
|
return _texture.GetTexture()->ResidentMipLevels();
|
|
}
|
|
|
|
uint64 TextureBase::GetCurrentMemoryUsage() const
|
|
{
|
|
return _texture.GetTexture()->GetMemoryUsage();
|
|
}
|
|
|
|
uint64 TextureBase::GetTotalMemoryUsage() const
|
|
{
|
|
return _texture.GetTotalMemoryUsage();
|
|
}
|
|
|
|
int32 TextureBase::GetTextureGroup() const
|
|
{
|
|
return _texture._header.TextureGroup;
|
|
}
|
|
|
|
void TextureBase::SetTextureGroup(int32 textureGroup)
|
|
{
|
|
if (_texture._header.TextureGroup != textureGroup)
|
|
{
|
|
_texture._header.TextureGroup = textureGroup;
|
|
_texture.RequestStreamingUpdate();
|
|
}
|
|
}
|
|
|
|
bool TextureBase::HasStreamingError() const
|
|
{
|
|
return _texture.Streaming.Error;
|
|
}
|
|
|
|
BytesContainer TextureBase::GetMipData(int32 mipIndex, int32& rowPitch, int32& slicePitch)
|
|
{
|
|
BytesContainer result;
|
|
if (IsVirtual())
|
|
{
|
|
if (_customData == nullptr)
|
|
{
|
|
LOG(Error, "Missing virtual texture init data.");
|
|
return result;
|
|
}
|
|
|
|
// Get description
|
|
rowPitch = _customData->Mips[mipIndex].RowPitch;
|
|
slicePitch = _customData->Mips[mipIndex].SlicePitch;
|
|
}
|
|
else
|
|
{
|
|
if (WaitForLoaded())
|
|
return result;
|
|
|
|
// Get description
|
|
uint32 rowPitch1, slicePitch1;
|
|
const int32 mipWidth = Math::Max(1, Width() >> mipIndex);
|
|
const int32 mipHeight = Math::Max(1, Height() >> mipIndex);
|
|
RenderTools::ComputePitch(Format(), mipWidth, mipHeight, rowPitch1, slicePitch1);
|
|
rowPitch = rowPitch1;
|
|
slicePitch = slicePitch1;
|
|
|
|
// Ensure to have chunk loaded
|
|
if (LoadChunk(CalculateChunkIndex(mipIndex)))
|
|
return result;
|
|
}
|
|
|
|
// Get data
|
|
GetMipData(mipIndex, result);
|
|
return result;
|
|
}
|
|
|
|
bool TextureBase::GetTextureData(TextureData& result, bool copyData)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.GetTextureData");
|
|
if (!IsVirtual() && WaitForLoaded())
|
|
{
|
|
LOG(Error, "Asset load failed.");
|
|
return true;
|
|
}
|
|
auto dataLock = LockData();
|
|
|
|
// Setup description
|
|
result.Width = _texture.TotalWidth();
|
|
result.Height = _texture.TotalHeight();
|
|
result.Depth = 1;
|
|
result.Format = _texture.GetHeader()->Format;
|
|
result.Items.Resize(_texture.TotalArraySize());
|
|
|
|
// Setup mips data
|
|
for (int32 arraySlice = 0; arraySlice < result.Items.Count(); arraySlice++)
|
|
{
|
|
auto& slice = result.Items[arraySlice];
|
|
slice.Mips.Resize(_texture.TotalMipLevels());
|
|
for (int32 mipIndex = 0; mipIndex < slice.Mips.Count(); mipIndex++)
|
|
{
|
|
auto& mip = slice.Mips[mipIndex];
|
|
int32 rowPitch, slicePitch;
|
|
BytesContainer mipData = GetMipData(mipIndex, rowPitch, slicePitch);
|
|
if (mipData.IsInvalid())
|
|
{
|
|
LOG(Error, "Failed to get texture mip data.");
|
|
return true;
|
|
}
|
|
if (mipData.Length() != slicePitch * _texture.TotalArraySize())
|
|
{
|
|
LOG(Error, "Invalid custom texture data (slice pitch * array size is different than data bytes count).");
|
|
return true;
|
|
}
|
|
mip.RowPitch = rowPitch;
|
|
mip.DepthPitch = slicePitch;
|
|
mip.Lines = Math::Max(1, Height() >> mipIndex);
|
|
if (copyData)
|
|
mip.Data.Copy(mipData.Get() + (arraySlice * slicePitch), slicePitch);
|
|
else
|
|
mip.Data.Link(mipData.Get() + (arraySlice * slicePitch), slicePitch);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TextureBase::GetTextureMipData(TextureMipData& result, int32 mipIndex, int32 arrayIndex, bool copyData)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.GetTextureMipData");
|
|
if (!IsVirtual() && WaitForLoaded())
|
|
{
|
|
LOG(Error, "Asset load failed.");
|
|
return true;
|
|
}
|
|
if (mipIndex < 0 || mipIndex > GetMipLevels() || arrayIndex < 0 || arrayIndex > GetArraySize())
|
|
{
|
|
Log::ArgumentOutOfRangeException();
|
|
return true;
|
|
}
|
|
|
|
// Get raw texture data
|
|
int32 rowPitch, slicePitch;
|
|
BytesContainer mipData = GetMipData(mipIndex, rowPitch, slicePitch);
|
|
if (mipData.IsInvalid())
|
|
{
|
|
LOG(Error, "Failed to get texture mip data.");
|
|
return true;
|
|
}
|
|
if (mipData.Length() != slicePitch * _texture.TotalArraySize())
|
|
{
|
|
LOG(Error, "Invalid custom texture data (slice pitch * array size is different than data bytes count).");
|
|
return true;
|
|
}
|
|
|
|
// Fill result
|
|
result.RowPitch = rowPitch;
|
|
result.DepthPitch = slicePitch;
|
|
result.Lines = Math::Max(1, Height() >> mipIndex);
|
|
if (copyData)
|
|
result.Data.Copy(mipData.Get() + (arrayIndex * slicePitch), slicePitch);
|
|
else
|
|
result.Data.Link(mipData.Get() + (arrayIndex * slicePitch), slicePitch);
|
|
return false;
|
|
}
|
|
|
|
bool TextureBase::GetPixels(Array<Color32>& pixels, int32 mipIndex, int32 arrayIndex)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.GetPixels");
|
|
ScopeLock lock(Locker);
|
|
|
|
// Get mip data
|
|
auto dataLock = LockData();
|
|
TextureMipData mipData;
|
|
if (GetTextureMipData(mipData, mipIndex, arrayIndex, false))
|
|
return true;
|
|
const int32 mipWidth = Math::Max(1, Width() >> mipIndex);
|
|
const int32 mipHeight = Math::Max(1, Height() >> mipIndex);
|
|
|
|
// Convert into pixels
|
|
return mipData.GetPixels(pixels, mipWidth, mipHeight, Format());
|
|
}
|
|
|
|
bool TextureBase::GetPixels(Array<Color>& pixels, int32 mipIndex, int32 arrayIndex)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.GetPixels");
|
|
|
|
// Get mip data
|
|
auto dataLock = LockData();
|
|
TextureMipData mipData;
|
|
if (GetTextureMipData(mipData, mipIndex, arrayIndex, false))
|
|
return true;
|
|
const int32 mipWidth = Math::Max(1, Width() >> mipIndex);
|
|
const int32 mipHeight = Math::Max(1, Height() >> mipIndex);
|
|
|
|
// Convert into pixels
|
|
return mipData.GetPixels(pixels, mipWidth, mipHeight, Format());
|
|
}
|
|
|
|
bool TextureBase::SetPixels(const Span<Color32>& pixels, int32 mipIndex, int32 arrayIndex, bool generateMips)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.SetPixels");
|
|
if (!IsVirtual())
|
|
{
|
|
LOG(Error, "Texture must be virtual.");
|
|
return true;
|
|
}
|
|
ScopeLock lock(Locker);
|
|
if (_customData == nullptr || Width() == 0)
|
|
{
|
|
LOG(Error, "Texture must be initialized.");
|
|
return true;
|
|
}
|
|
const PixelFormat format = Format();
|
|
const int32 width = Math::Max(1, Width() >> mipIndex);
|
|
const int32 height = Math::Max(1, Height() >> mipIndex);
|
|
auto& mipData = _customData->Mips[mipIndex];
|
|
const int32 rowPitch = mipData.RowPitch;
|
|
const int32 sliceSize = mipData.SlicePitch;
|
|
if (pixels.Length() != width * height)
|
|
{
|
|
Log::ArgumentOutOfRangeException();
|
|
return true;
|
|
}
|
|
|
|
// Convert pixels to the texture format
|
|
ASSERT(mipData.Data.IsAllocated());
|
|
byte* dst = mipData.Data.Get() + sliceSize * arrayIndex;
|
|
bool error = true;
|
|
switch (format)
|
|
{
|
|
case PixelFormat::R8G8B8A8_SInt:
|
|
case PixelFormat::R8G8B8A8_Typeless:
|
|
case PixelFormat::R8G8B8A8_SNorm:
|
|
case PixelFormat::R8G8B8A8_UInt:
|
|
case PixelFormat::R8G8B8A8_UNorm:
|
|
case PixelFormat::R8G8B8A8_UNorm_sRGB:
|
|
case PixelFormat::R8G8_B8G8_UNorm:
|
|
case PixelFormat::B8G8R8A8_Typeless:
|
|
case PixelFormat::B8G8R8A8_UNorm:
|
|
case PixelFormat::B8G8R8A8_UNorm_sRGB:
|
|
case PixelFormat::B8G8R8X8_Typeless:
|
|
case PixelFormat::B8G8R8X8_UNorm:
|
|
case PixelFormat::B8G8R8X8_UNorm_sRGB:
|
|
if (rowPitch == width * sizeof(Color32))
|
|
{
|
|
Platform::MemoryCopy(dst, pixels.Get(), sliceSize);
|
|
error = false;
|
|
}
|
|
break;
|
|
}
|
|
if (error)
|
|
{
|
|
// Try to use texture sampler utility
|
|
auto sampler = TextureTool::GetSampler(format);
|
|
if (sampler)
|
|
{
|
|
for (int32 y = 0; y < height; y++)
|
|
{
|
|
for (int32 x = 0; x < width; x++)
|
|
{
|
|
Color c(pixels.Get()[x + y * width]);
|
|
TextureTool::Store(sampler, x, y, dst, rowPitch, c);
|
|
}
|
|
}
|
|
error = false;
|
|
}
|
|
}
|
|
if (error)
|
|
{
|
|
LOG(Error, "Unsupported texture data format {0}.", ScriptingEnum::ToString(format));
|
|
return true;
|
|
}
|
|
|
|
// Generate mips optionally
|
|
if (generateMips && mipIndex + 1 < _customData->Mips.Count())
|
|
{
|
|
for (int32 i = mipIndex + 1; i < _customData->Mips.Count(); i++)
|
|
_customData->GenerateMip(i);
|
|
}
|
|
|
|
// Request texture data streaming to GPU
|
|
_texture.GetTexture()->SetResidentMipLevels(0);
|
|
_texture.RequestStreamingUpdate();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TextureBase::SetPixels(const Span<Color>& pixels, int32 mipIndex, int32 arrayIndex, bool generateMips)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.SetPixels");
|
|
if (!IsVirtual())
|
|
{
|
|
LOG(Error, "Texture must be virtual.");
|
|
return true;
|
|
}
|
|
ScopeLock lock(Locker);
|
|
if (_customData == nullptr || Width() == 0)
|
|
{
|
|
LOG(Error, "Texture must be initialized.");
|
|
return true;
|
|
}
|
|
const PixelFormat format = Format();
|
|
const int32 width = Math::Max(1, Width() >> mipIndex);
|
|
const int32 height = Math::Max(1, Height() >> mipIndex);
|
|
auto& mipData = _customData->Mips[mipIndex];
|
|
const int32 rowPitch = mipData.RowPitch;
|
|
const int32 sliceSize = mipData.SlicePitch;
|
|
if (pixels.Length() != width * height)
|
|
{
|
|
Log::ArgumentOutOfRangeException();
|
|
return true;
|
|
}
|
|
|
|
// Convert pixels to the texture format
|
|
ASSERT(mipData.Data.IsAllocated());
|
|
byte* dst = mipData.Data.Get() + sliceSize * arrayIndex;
|
|
bool error = true;
|
|
switch (format)
|
|
{
|
|
case PixelFormat::R32G32B32A32_Typeless:
|
|
case PixelFormat::R32G32B32A32_Float:
|
|
if (rowPitch == width * sizeof(Color))
|
|
{
|
|
Platform::MemoryCopy(dst, pixels.Get(), sliceSize);
|
|
error = false;
|
|
}
|
|
break;
|
|
}
|
|
if (error)
|
|
{
|
|
// Try to use texture sampler utility
|
|
auto sampler = TextureTool::GetSampler(format);
|
|
if (sampler)
|
|
{
|
|
for (int32 y = 0; y < height; y++)
|
|
{
|
|
for (int32 x = 0; x < width; x++)
|
|
{
|
|
Color c(pixels.Get()[x + y * width]);
|
|
TextureTool::Store(sampler, x, y, dst, rowPitch, c);
|
|
}
|
|
}
|
|
error = false;
|
|
}
|
|
}
|
|
if (error)
|
|
{
|
|
LOG(Error, "Unsupported texture data format {0}.", ScriptingEnum::ToString(format));
|
|
return true;
|
|
}
|
|
|
|
// Generate mips optionally
|
|
if (generateMips && mipIndex + 1 < _customData->Mips.Count())
|
|
{
|
|
for (int32 i = mipIndex + 1; i < _customData->Mips.Count(); i++)
|
|
_customData->GenerateMip(i);
|
|
}
|
|
|
|
// Request texture data streaming to GPU
|
|
_texture.GetTexture()->SetResidentMipLevels(0);
|
|
_texture.RequestStreamingUpdate();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TextureBase::Init(InitData* initData)
|
|
{
|
|
// Validate state
|
|
if (!IsVirtual())
|
|
{
|
|
LOG(Error, "Texture must be virtual.");
|
|
Delete(initData);
|
|
return true;
|
|
}
|
|
if (initData->Format == PixelFormat::Unknown ||
|
|
Math::IsNotInRange(initData->Width, 1, GPU_MAX_TEXTURE_SIZE) ||
|
|
Math::IsNotInRange(initData->Height, 1, GPU_MAX_TEXTURE_SIZE) ||
|
|
Math::IsNotInRange(initData->ArraySize, 1, GPU_MAX_TEXTURE_ARRAY_SIZE) ||
|
|
Math::IsNotInRange(initData->Mips.Count(), 1, GPU_MAX_TEXTURE_MIP_LEVELS))
|
|
{
|
|
Log::ArgumentOutOfRangeException();
|
|
Delete(initData);
|
|
return true;
|
|
}
|
|
ScopeLock lock(Locker);
|
|
|
|
// Release texture
|
|
_texture.UnloadTexture();
|
|
|
|
// Prepare descriptor
|
|
if (_customData != nullptr)
|
|
Delete(_customData);
|
|
_customData = initData;
|
|
|
|
// Create texture
|
|
TextureHeader textureHeader;
|
|
textureHeader.Format = _customData->Format;
|
|
textureHeader.Width = _customData->Width;
|
|
textureHeader.Height = _customData->Height;
|
|
textureHeader.IsCubeMap = _customData->ArraySize == 6;
|
|
textureHeader.MipLevels = _customData->Mips.Count();
|
|
textureHeader.Type = TextureFormatType::ColorRGBA;
|
|
textureHeader.NeverStream = true;
|
|
if (_texture.Create(textureHeader))
|
|
{
|
|
LOG(Warning, "Cannot initialize texture.");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if !COMPILE_WITHOUT_CSHARP
|
|
|
|
bool TextureBase::InitCSharp(void* ptr)
|
|
{
|
|
PROFILE_CPU_NAMED("Texture.Init");
|
|
struct InternalInitData
|
|
{
|
|
PixelFormat Format;
|
|
int32 Width;
|
|
int32 Height;
|
|
int32 ArraySize;
|
|
int32 MipLevels;
|
|
int32 GenerateMips;
|
|
int32 DataRowPitch[14];
|
|
int32 DataSlicePitch[14];
|
|
byte* Data[14];
|
|
};
|
|
auto initDataObj = (InternalInitData*)ptr;
|
|
auto initData = New<InitData>();
|
|
|
|
initData->Format = initDataObj->Format;
|
|
initData->Width = initDataObj->Width;
|
|
initData->Height = initDataObj->Height;
|
|
initData->ArraySize = initDataObj->ArraySize;
|
|
initData->Mips.Resize(initDataObj->GenerateMips ? MipLevelsCount(initDataObj->Width, initDataObj->Height) : initDataObj->MipLevels);
|
|
|
|
// Copy source mips data
|
|
for (int32 mipIndex = 0; mipIndex < initDataObj->MipLevels; mipIndex++)
|
|
{
|
|
auto& mip = initData->Mips[mipIndex];
|
|
mip.RowPitch = initDataObj->DataRowPitch[mipIndex];
|
|
mip.SlicePitch = initDataObj->DataSlicePitch[mipIndex];
|
|
mip.Data.Copy(initDataObj->Data[mipIndex], mip.SlicePitch * initData->ArraySize);
|
|
}
|
|
|
|
// Generate mips
|
|
for (int32 mipIndex = initDataObj->MipLevels; mipIndex < initData->Mips.Count(); mipIndex++)
|
|
{
|
|
initData->GenerateMip(mipIndex, initDataObj->GenerateMips & 2);
|
|
}
|
|
|
|
return Init(initData);
|
|
}
|
|
|
|
#endif
|
|
|
|
uint64 TextureBase::GetMemoryUsage() const
|
|
{
|
|
Locker.Lock();
|
|
uint64 result = BinaryAsset::GetMemoryUsage();
|
|
result += sizeof(TextureBase) - sizeof(BinaryAsset);
|
|
if (_customData)
|
|
{
|
|
result += sizeof(InitData);
|
|
for (auto& mip : _customData->Mips)
|
|
result += mip.Data.Length();
|
|
}
|
|
Locker.Unlock();
|
|
return result;
|
|
}
|
|
|
|
void TextureBase::CancelStreaming()
|
|
{
|
|
Asset::CancelStreaming();
|
|
_texture.CancelStreamingTasks();
|
|
}
|
|
|
|
int32 TextureBase::CalculateChunkIndex(int32 mipIndex) const
|
|
{
|
|
// Mips are in 0-13 chunks
|
|
return mipIndex;
|
|
}
|
|
|
|
CriticalSection& TextureBase::GetOwnerLocker() const
|
|
{
|
|
return _parent->Locker;
|
|
}
|
|
|
|
void TextureBase::unload(bool isReloading)
|
|
{
|
|
if (!isReloading)
|
|
{
|
|
// Release texture
|
|
_texture.UnloadTexture();
|
|
SAFE_DELETE(_customData);
|
|
}
|
|
}
|
|
|
|
Task* TextureBase::RequestMipDataAsync(int32 mipIndex)
|
|
{
|
|
if (_customData)
|
|
return nullptr;
|
|
|
|
auto chunkIndex = CalculateChunkIndex(mipIndex);
|
|
return (Task*)_parent->RequestChunkDataAsync(chunkIndex);
|
|
}
|
|
|
|
FlaxStorage::LockData TextureBase::LockData()
|
|
{
|
|
return _parent->Storage ? _parent->Storage->Lock() : FlaxStorage::LockData::Invalid;
|
|
}
|
|
|
|
void TextureBase::GetMipData(int32 mipIndex, BytesContainer& data) const
|
|
{
|
|
if (_customData)
|
|
{
|
|
data.Link(_customData->Mips[mipIndex].Data);
|
|
return;
|
|
}
|
|
|
|
auto chunkIndex = CalculateChunkIndex(mipIndex);
|
|
_parent->GetChunkData(chunkIndex, data);
|
|
}
|
|
|
|
void TextureBase::GetMipDataWithLoading(int32 mipIndex, BytesContainer& data) const
|
|
{
|
|
if (_customData)
|
|
{
|
|
data.Link(_customData->Mips[mipIndex].Data);
|
|
return;
|
|
}
|
|
|
|
const auto chunkIndex = CalculateChunkIndex(mipIndex);
|
|
_parent->LoadChunk(chunkIndex);
|
|
_parent->GetChunkData(chunkIndex, data);
|
|
}
|
|
|
|
bool TextureBase::GetMipDataCustomPitch(int32 mipIndex, uint32& rowPitch, uint32& slicePitch) const
|
|
{
|
|
bool result = _customData != nullptr;
|
|
if (result)
|
|
{
|
|
rowPitch = _customData->Mips[mipIndex].RowPitch;
|
|
slicePitch = _customData->Mips[mipIndex].SlicePitch;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool TextureBase::init(AssetInitData& initData)
|
|
{
|
|
if (IsVirtual())
|
|
return false;
|
|
|
|
// Get texture header for asset custom data (fast access)
|
|
TextureHeader textureHeader;
|
|
if (initData.CustomData.Length() == sizeof(TextureHeader))
|
|
{
|
|
Platform::MemoryCopy(&textureHeader, initData.CustomData.Get(), sizeof(textureHeader));
|
|
}
|
|
else if (initData.CustomData.Length() == sizeof(TextureHeader_Deprecated))
|
|
{
|
|
textureHeader = TextureHeader(*(TextureHeader_Deprecated*)initData.CustomData.Get());
|
|
}
|
|
else
|
|
{
|
|
LOG(Error, "Missing texture header.");
|
|
return true;
|
|
}
|
|
|
|
return _texture.Create(textureHeader);
|
|
}
|
|
|
|
Asset::LoadResult TextureBase::load()
|
|
{
|
|
// Loading textures is very fast xD
|
|
return LoadResult::Ok;
|
|
}
|
|
|
|
TextureBase::InitData::MipData::MipData(MipData&& other) noexcept
|
|
: Data(MoveTemp(other.Data))
|
|
, RowPitch(other.RowPitch)
|
|
, SlicePitch(other.SlicePitch)
|
|
{
|
|
}
|
|
|
|
TextureBase::InitData::InitData(InitData&& other) noexcept
|
|
: Format(other.Format)
|
|
, Width(other.Width)
|
|
, Height(other.Height)
|
|
, ArraySize(other.ArraySize)
|
|
, Mips(MoveTemp(other.Mips))
|
|
{
|
|
}
|
|
|
|
bool TextureBase::InitData::GenerateMip(int32 mipIndex, bool linear)
|
|
{
|
|
// Validate input
|
|
if (mipIndex < 1 || mipIndex >= Mips.Count())
|
|
{
|
|
LOG(Warning, "Invalid mip map to generate.");
|
|
return true;
|
|
}
|
|
if (ArraySize < 1)
|
|
{
|
|
LOG(Warning, "Invalid array size.");
|
|
return true;
|
|
}
|
|
if (PixelFormatExtensions::IsCompressed(Format))
|
|
{
|
|
LOG(Warning, "Cannot generate mip map for compressed format data.");
|
|
return true;
|
|
}
|
|
const auto& srcMip = Mips[mipIndex - 1];
|
|
auto& dstMip = Mips[mipIndex];
|
|
if (srcMip.RowPitch == 0 || srcMip.SlicePitch == 0 || srcMip.Data.IsInvalid())
|
|
{
|
|
LOG(Warning, "Missing data for source mip map.");
|
|
return true;
|
|
}
|
|
|
|
PROFILE_CPU_NAMED("Texture.GenerateMip");
|
|
|
|
// Allocate data
|
|
const int32 dstMipWidth = Math::Max(1, Width >> mipIndex);
|
|
const int32 dstMipHeight = Math::Max(1, Height >> mipIndex);
|
|
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(Format);
|
|
dstMip.RowPitch = dstMipWidth * pixelStride;
|
|
dstMip.SlicePitch = dstMip.RowPitch * dstMipHeight;
|
|
dstMip.Data.Allocate(dstMip.SlicePitch * ArraySize);
|
|
|
|
// Perform filtering
|
|
if (linear)
|
|
{
|
|
switch (Format)
|
|
{
|
|
// 4 component, 32 bit with 8 bits per component - use Color32 type
|
|
case PixelFormat::R8G8B8A8_SInt:
|
|
case PixelFormat::R8G8B8A8_Typeless:
|
|
case PixelFormat::R8G8B8A8_SNorm:
|
|
case PixelFormat::R8G8B8A8_UInt:
|
|
case PixelFormat::R8G8B8A8_UNorm:
|
|
case PixelFormat::R8G8B8A8_UNorm_sRGB:
|
|
case PixelFormat::R8G8_B8G8_UNorm:
|
|
case PixelFormat::B8G8R8A8_Typeless:
|
|
case PixelFormat::B8G8R8A8_UNorm:
|
|
case PixelFormat::B8G8R8A8_UNorm_sRGB:
|
|
case PixelFormat::B8G8R8X8_Typeless:
|
|
case PixelFormat::B8G8R8X8_UNorm:
|
|
case PixelFormat::B8G8R8X8_UNorm_sRGB:
|
|
{
|
|
// Linear downscale filter
|
|
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
|
|
{
|
|
const byte* dstData = dstMip.Data.Get() + arrayIndex * dstMip.SlicePitch;
|
|
const byte* srcData = srcMip.Data.Get() + arrayIndex * srcMip.SlicePitch;
|
|
for (int32 y = 0; y < dstMipHeight; y++)
|
|
{
|
|
for (int32 x = 0; x < dstMipWidth; x++)
|
|
{
|
|
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
|
|
|
|
#define SAMPLE(var, dx, dy) const auto var = Color(*(Color32*)(srcData + ((y * 2 + (dy)) * srcMip.RowPitch + x * pixelStride * 2 + (pixelStride * (dx)))))
|
|
SAMPLE(v00, 0, 0);
|
|
SAMPLE(v01, 0, 1);
|
|
SAMPLE(v10, 1, 0);
|
|
SAMPLE(v11, 1, 1);
|
|
#undef SAMPLE
|
|
|
|
*(Color32*)(dstData + dstIndex) = Color32((v00 + v01 + v10 + v11) * 0.25f);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
LOG(Error, "Unsupported texture data format {0}.", ScriptingEnum::ToString(Format));
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Point downscale filter
|
|
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
|
|
{
|
|
byte* dstData = dstMip.Data.Get() + arrayIndex * dstMip.SlicePitch;
|
|
const byte* srcData = srcMip.Data.Get() + arrayIndex * srcMip.SlicePitch;
|
|
for (int32 y = 0; y < dstMipHeight; y++)
|
|
{
|
|
for (int32 x = 0; x < dstMipWidth; x++)
|
|
{
|
|
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
|
|
const auto srcIndex = y * 2 * srcMip.RowPitch + x * pixelStride * 2;
|
|
|
|
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fix right and bottom edges to preserve the original values
|
|
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
|
|
{
|
|
byte* dstData = dstMip.Data.Get() + arrayIndex * dstMip.SlicePitch;
|
|
const byte* srcData = srcMip.Data.Get() + arrayIndex * srcMip.SlicePitch;
|
|
for (int32 y = 0; y < dstMipHeight - 1; y++)
|
|
{
|
|
const int32 x = dstMipWidth - 1;
|
|
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
|
|
const auto srcIndex = y * 2 * srcMip.RowPitch + x * pixelStride * 2 + pixelStride;
|
|
|
|
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
|
|
}
|
|
for (int32 x = 0; x < dstMipWidth - 1; x++)
|
|
{
|
|
const int32 y = dstMipHeight - 1;
|
|
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
|
|
const auto srcIndex = (y * 2 + 1) * srcMip.RowPitch + x * pixelStride * 2;
|
|
|
|
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
|
|
}
|
|
{
|
|
const int32 x = dstMipWidth - 1;
|
|
const int32 y = dstMipHeight - 1;
|
|
const auto dstIndex = y * dstMip.RowPitch + x * pixelStride;
|
|
const auto srcIndex = (y * 2 + 1) * srcMip.RowPitch + x * pixelStride * 2 + pixelStride;
|
|
|
|
Platform::MemoryCopy(dstData + dstIndex, srcData + srcIndex, pixelStride);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TextureBase::InitData::FromTextureData(const TextureData& textureData, bool generateMips)
|
|
{
|
|
Format = textureData.Format;
|
|
Width = textureData.Width;
|
|
Height = textureData.Height;
|
|
ArraySize = textureData.GetArraySize();
|
|
if (generateMips)
|
|
Mips.Resize(MipLevelsCount(textureData.Width, textureData.Height));
|
|
else
|
|
Mips.Resize(textureData.GetMipLevels());
|
|
|
|
for (int32 mipIndex = 0; mipIndex < textureData.GetMipLevels(); mipIndex++)
|
|
{
|
|
auto& mip = Mips[mipIndex];
|
|
auto& data = *textureData.GetData(0, mipIndex);
|
|
mip.Data.Allocate(data.Data.Length() * ArraySize);
|
|
mip.RowPitch = data.RowPitch;
|
|
mip.SlicePitch = data.Data.Length();
|
|
|
|
byte* dst = mip.Data.Get();
|
|
for (int32 arrayIndex = 0; arrayIndex < ArraySize; arrayIndex++)
|
|
{
|
|
auto& d = *textureData.GetData(arrayIndex, mipIndex);
|
|
ASSERT(data.RowPitch == d.RowPitch);
|
|
ASSERT(data.Data.Length() == d.Data.Length());
|
|
Platform::MemoryCopy(dst, d.Data.Get(), d.Data.Length());
|
|
dst += data.Data.Length();
|
|
ASSERT((int32)(dst - mip.Data.Get()) <= mip.Data.Length());
|
|
}
|
|
}
|
|
|
|
if (generateMips)
|
|
{
|
|
for (int32 mipIndex = textureData.GetMipLevels(); mipIndex < Mips.Count(); mipIndex++)
|
|
{
|
|
GenerateMip(mipIndex, true);
|
|
}
|
|
}
|
|
}
|