CelticCraft/Plugins/VoxelFree/Source/Voxel/Private/VoxelTexture.cpp

422 lines
12 KiB
C++
Raw Normal View History

2023-07-03 16:17:13 +00:00
// Copyright 2020 Phyronnaz
#include "VoxelTexture.h"
#include "VoxelMessages.h"
#include "VoxelContainers/VoxelStaticArray.h"
#include "Engine/Texture2D.h"
#include "Engine/TextureRenderTarget2D.h"
DEFINE_VOXEL_MEMORY_STAT(STAT_VoxelTextureMemory);
static FAutoConsoleCommand CmdClearCache(
TEXT("voxel.texture.ClearCache"),
TEXT("Clears the voxel textures memory cache"),
FConsoleCommandDelegate::CreateStatic(&FVoxelTextureUtilities::ClearCache));
struct FVoxelTextureCacheKey
{
TWeakObjectPtr<UTexture> Texture;
// Channel, in case it's a color texture converted to float
EVoxelRGBA Channel = EVoxelRGBA(-1);
FVoxelTextureCacheKey() = default;
explicit FVoxelTextureCacheKey(TWeakObjectPtr<UTexture> Texture)
: Texture(Texture)
{
}
explicit FVoxelTextureCacheKey(TWeakObjectPtr<UTexture> Texture, EVoxelRGBA Channel)
: Texture(Texture)
, Channel(Channel)
{
}
bool operator==(const FVoxelTextureCacheKey& Other) const
{
return Texture == Other.Texture && Channel == Other.Channel;
}
friend uint32 GetTypeHash(const FVoxelTextureCacheKey& Key)
{
return HashCombine(GetTypeHash(Key.Texture), GetTypeHash(Key.Channel));
}
};
template<typename T>
inline auto& GetVoxelTextureCacheMap()
{
check(IsInGameThread());
static TMap<FVoxelTextureCacheKey, TVoxelSharedPtr<typename TVoxelTexture<T>::FTextureData>> Map;
return Map;
}
inline void ExtractTextureData(UTexture* Texture, int32& OutSizeX, int32& OutSizeY, TArray<FColor>& OutData)
{
VOXEL_FUNCTION_COUNTER();
check(IsInGameThread());
if (auto* Texture2D = Cast<UTexture2D>(Texture))
{
FTexture2DMipMap& Mip = Texture2D->GetPlatformData()->Mips[0];
OutSizeX = Mip.SizeX;
OutSizeY = Mip.SizeY;
const int32 Size = OutSizeX * OutSizeY;
OutData.SetNumUninitialized(Size);
auto& BulkData = Mip.BulkData;
if (!ensureAlways(BulkData.GetBulkDataSize() > 0))
{
OutSizeX = 1;
OutSizeY = 1;
OutData.SetNum(1);
return;
}
void* Data = BulkData.Lock(LOCK_READ_ONLY);
if (!ensureAlways(Data))
{
Mip.BulkData.Unlock();
OutSizeX = 1;
OutSizeY = 1;
OutData.SetNum(1);
return;
}
FMemory::Memcpy(OutData.GetData(), Data, Size * sizeof(FColor));
Mip.BulkData.Unlock();
return;
}
if (auto* TextureRenderTarget = Cast<UTextureRenderTarget2D>(Texture))
{
FRenderTarget* RenderTarget = TextureRenderTarget->GameThread_GetRenderTargetResource();
if (ensure(RenderTarget))
{
const auto Format = TextureRenderTarget->GetFormat();
OutSizeX = TextureRenderTarget->GetSurfaceWidth();
OutSizeY = TextureRenderTarget->GetSurfaceHeight();
const int32 Size = OutSizeX * OutSizeY;
OutData.SetNumUninitialized(Size);
switch (Format)
{
case PF_B8G8R8A8:
{
if (ensure(RenderTarget->ReadPixels(OutData))) return;
break;
}
case PF_R8G8B8A8:
{
if (ensure(RenderTarget->ReadPixels(OutData))) return;
break;
}
case PF_FloatRGBA:
{
TArray<FLinearColor> LinearColors;
LinearColors.SetNumUninitialized(Size);
if (ensure(RenderTarget->ReadLinearColorPixels(LinearColors)))
{
for (int32 Index = 0; Index < Size; Index++)
{
OutData[Index] = LinearColors[Index].ToFColor(false);
}
return;
}
break;
}
default:
ensure(false);
}
}
}
ensure(false);
OutSizeX = 1;
OutSizeY = 1;
OutData.SetNum(1);
}
TVoxelTexture<FColor> FVoxelTextureUtilities::CreateFromTexture_Color(UTexture* Texture)
{
VOXEL_FUNCTION_COUNTER();
auto& Data = GetVoxelTextureCacheMap<FColor>().FindOrAdd(FVoxelTextureCacheKey(Texture));
if (!Data.IsValid())
{
FString Error;
if (!CanCreateFromTexture(Texture, Error))
{
FVoxelMessages::Error("Can't create Voxel Texture: " + Error, Texture);
return {};
}
int32 SizeX = -1;
int32 SizeY = -1;
TArray<FColor> TextureData;
ExtractTextureData(Texture, SizeX, SizeY, TextureData);
Data = MakeVoxelShared<TVoxelTexture<FColor>::FTextureData>();
Data->SetSize(SizeX, SizeY);
for (int32 Index = 0; Index < SizeX * SizeY; Index++)
{
Data->SetValue(Index, TextureData[Index]);
}
}
return TVoxelTexture<FColor>(Data.ToSharedRef());
}
TVoxelTexture<float> FVoxelTextureUtilities::CreateFromTexture_Float(UTexture* Texture, EVoxelRGBA Channel)
{
VOXEL_FUNCTION_COUNTER();
auto& Data = GetVoxelTextureCacheMap<float>().FindOrAdd(FVoxelTextureCacheKey(Texture, Channel));
if (!Data.IsValid())
{
FString Error;
if (!CanCreateFromTexture(Texture, Error))
{
FVoxelMessages::Error("Can't create Voxel Texture: " + Error, Texture);
return {};
}
const auto ColorTexture = CreateFromTexture_Color(Texture);
Data = MakeVoxelShared<TVoxelTexture<float>::FTextureData>();
Data->SetSize(ColorTexture.GetSizeX(), ColorTexture.GetSizeY());
const int32 Num = ColorTexture.GetSizeX() * ColorTexture.GetSizeY();
for (int32 Index = 0; Index < Num; Index++)
{
const FColor Color = ColorTexture.GetTextureData()[Index];
uint8 Result = 0;
switch (Channel)
{
case EVoxelRGBA::R:
Result = Color.R;
break;
case EVoxelRGBA::G:
Result = Color.G;
break;
case EVoxelRGBA::B:
Result = Color.B;
break;
case EVoxelRGBA::A:
Result = Color.A;
break;
}
const float Value = FVoxelUtilities::UINT8ToFloat(Result);
Data->SetValue(Index, Value);
}
}
return TVoxelTexture<float>(Data.ToSharedRef());
}
bool FVoxelTextureUtilities::CanCreateFromTexture(UTexture* Texture, FString& OutError)
{
if (!Texture)
{
OutError = "Invalid texture";
return false;
}
if (auto* Texture2D = Cast<UTexture2D>(Texture))
{
#if WITH_EDITORONLY_DATA
if (Texture2D->MipGenSettings != TextureMipGenSettings::TMGS_NoMipmaps)
{
OutError = "Texture MipGenSettings must be NoMipmaps";
return false;
}
#endif
if (Texture2D->CompressionSettings != TextureCompressionSettings::TC_VectorDisplacementmap &&
Texture2D->CompressionSettings != TextureCompressionSettings::TC_EditorIcon)
{
OutError = "Texture CompressionSettings must be VectorDisplacementmap or UserInterface2D";
return false;
}
if (Texture2D->GetPixelFormat() != PF_B8G8R8A8)
{
OutError = "Texture pixel format must be B8G8R8A8, try switching CompressionSettings to VectorDisplacementmap";
return false;
}
return true;
}
if (auto* TextureRenderTarget = Cast<UTextureRenderTarget2D>(Texture))
{
const auto Format = TextureRenderTarget->GetFormat();
if (Format != EPixelFormat::PF_R8G8B8A8 &&
Format != EPixelFormat::PF_FloatRGBA &&
Format != EPixelFormat::PF_B8G8R8A8)
{
OutError = "Render Target PixelFormat must be R8G8B8A8, B8G8R8A8 or R8G8B8A8 (is " + FString(GPixelFormats[Format].Name) + ")";
return false;
}
if (!TextureRenderTarget->GameThread_GetRenderTargetResource())
{
OutError = "Render Target resource must be created";
return false;
}
return true;
}
OutError = "Texture must be a Texture2D or a TextureRenderTarget2D";
return false;
}
void FVoxelTextureUtilities::FixTexture(UTexture* Texture)
{
VOXEL_FUNCTION_COUNTER();
if (!ensure(Texture))
{
return;
}
#if WITH_EDITORONLY_DATA
Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
#endif
Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
Texture->UpdateResource();
Texture->MarkAsGarbage();
}
void FVoxelTextureUtilities::ClearCache()
{
VOXEL_FUNCTION_COUNTER();
GetVoxelTextureCacheMap<FColor>().Empty();
GetVoxelTextureCacheMap<float>().Empty();
}
void FVoxelTextureUtilities::ClearCache(UTexture* Texture)
{
VOXEL_FUNCTION_COUNTER();
GetVoxelTextureCacheMap<FColor>().Remove(FVoxelTextureCacheKey(Texture));
GetVoxelTextureCacheMap<float>().Remove(FVoxelTextureCacheKey(Texture));
GetVoxelTextureCacheMap<float>().Remove(FVoxelTextureCacheKey(Texture, EVoxelRGBA::R));
GetVoxelTextureCacheMap<float>().Remove(FVoxelTextureCacheKey(Texture, EVoxelRGBA::G));
GetVoxelTextureCacheMap<float>().Remove(FVoxelTextureCacheKey(Texture, EVoxelRGBA::B));
GetVoxelTextureCacheMap<float>().Remove(FVoxelTextureCacheKey(Texture, EVoxelRGBA::A));
}
void FVoxelTextureUtilities::CreateOrUpdateUTexture2D(const TVoxelTexture<float>& Texture, UTexture2D*& InOutTexture)
{
VOXEL_FUNCTION_COUNTER();
if (!InOutTexture ||
!InOutTexture->GetPlatformData() ||
InOutTexture->GetPlatformData()->Mips.Num() == 0 ||
InOutTexture->GetPlatformData()->PixelFormat != EPixelFormat::PF_R32_FLOAT ||
InOutTexture->GetSizeX() != Texture.GetSizeX() ||
InOutTexture->GetSizeY() != Texture.GetSizeY())
{
InOutTexture = UTexture2D::CreateTransient(Texture.GetSizeX(), Texture.GetSizeY(), EPixelFormat::PF_R32_FLOAT);
InOutTexture->CompressionSettings = TC_HDR;
InOutTexture->SRGB = false;
InOutTexture->Filter = TF_Bilinear;
}
FTexture2DMipMap& Mip = InOutTexture->GetPlatformData()->Mips[0];
float* Data = reinterpret_cast<float*>(Mip.BulkData.Lock(LOCK_READ_WRITE));
if (!ensureAlways(Data)) return;
FMemory::Memcpy(Data, Texture.GetTextureData().GetData(), Texture.GetSizeX() * Texture.GetSizeY() * sizeof(float));
Mip.BulkData.Unlock();
InOutTexture->UpdateResource();
}
void FVoxelTextureUtilities::CreateOrUpdateUTexture2D(const TVoxelTexture<FColor>& Texture, UTexture2D*& InOutTexture)
{
VOXEL_FUNCTION_COUNTER();
if (!InOutTexture ||
!InOutTexture->GetPlatformData() ||
InOutTexture->GetPlatformData()->Mips.Num() == 0 ||
InOutTexture->GetPlatformData()->PixelFormat != EPixelFormat::PF_B8G8R8A8 ||
InOutTexture->GetSizeX() != Texture.GetSizeX() ||
InOutTexture->GetSizeY() != Texture.GetSizeY())
{
InOutTexture = UTexture2D::CreateTransient(Texture.GetSizeX(), Texture.GetSizeY(), EPixelFormat::PF_B8G8R8A8);
InOutTexture->CompressionSettings = TC_VectorDisplacementmap;
InOutTexture->SRGB = false;
InOutTexture->Filter = TF_Bilinear;
}
FTexture2DMipMap& Mip = InOutTexture->GetPlatformData()->Mips[0];
FColor* Data = reinterpret_cast<FColor*>(Mip.BulkData.Lock(LOCK_READ_WRITE));
if (!ensureAlways(Data)) return;
FMemory::Memcpy(Data, Texture.GetTextureData().GetData(), Texture.GetSizeX() * Texture.GetSizeY() * sizeof(FColor));
Mip.BulkData.Unlock();
InOutTexture->UpdateResource();
}
TVoxelTexture<FColor> FVoxelTextureUtilities::CreateColorTextureFromFloatTexture(const TVoxelTexture<float>& Texture, EVoxelRGBA Channel, bool bNormalize)
{
VOXEL_FUNCTION_COUNTER();
const auto GetColor = [&](float Value)
{
if (bNormalize)
{
Value = (Value - Texture.GetMin()) / (Texture.GetMax() - Texture.GetMin());
}
const float ByteValue = FVoxelUtilities::FloatToUINT8(Value);
FColor Color(ForceInit);
switch (Channel)
{
case EVoxelRGBA::R:
Color.R = ByteValue;
break;
case EVoxelRGBA::G:
Color.G = ByteValue;
break;
case EVoxelRGBA::B:
Color.B = ByteValue;
break;
case EVoxelRGBA::A:
Color.A = ByteValue;
break;
}
return Color;
};
auto Data = MakeVoxelShared<TVoxelTexture<FColor>::FTextureData>();
Data->SetSize(Texture.GetSizeX(), Texture.GetSizeY());
Data->SetBounds(GetColor(Texture.GetMin()), GetColor(Texture.GetMax()));
const int32 Num = Texture.GetSizeX() * Texture.GetSizeY();
for (int32 Index = 0; Index < Num; Index++)
{
const float Value = Texture.GetTextureData()[Index];
Data->SetValue_NoBounds(Index, GetColor(Value));
}
return TVoxelTexture<FColor>(Data);
}
TVoxelTexture<float> FVoxelTextureUtilities::Normalize(const TVoxelTexture<float>& Texture)
{
VOXEL_ASYNC_FUNCTION_COUNTER();
auto Data = MakeVoxelShared<TVoxelTexture<float>::FTextureData>();
Data->SetSize(Texture.GetSizeX(), Texture.GetSizeY());
Data->SetBounds(0, 1);
const float Min = Texture.GetMin();
const float Max = Texture.GetMax();
const int32 Num = Texture.GetSizeX() * Texture.GetSizeY();
for (int32 Index = 0; Index < Num; Index++)
{
const float Value = Texture.GetTextureData()[Index];
Data->SetValue_NoBounds(Index,(Value - Min) / (Max - Min));
}
return TVoxelTexture<float>(Data);
}