Add PixelFormatSampler utility to quick read/write operations on various data formats

Moved from `TextureTool` to be used in runtime and with more generic use cases (including C# scripting).
This commit is contained in:
Wojtek Figat
2025-01-05 23:49:44 +01:00
parent 933fac6c13
commit 29bfef677f
10 changed files with 610 additions and 426 deletions

View File

@@ -8,6 +8,7 @@
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Config/GameSettings.h"
#include "Editor/Utilities/EditorUtilities.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Content/Content.h"
@@ -240,7 +241,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon)
const TextureMipData* srcPixels = icon->GetData(0, srcPixelsMip);
const Color32* srcPixelsData = (Color32*)srcPixels->Data.Get();
const Int2 srcPixelsSize(Math::Max(1, icon->Width >> srcPixelsMip), Math::Max(1, icon->Height >> srcPixelsMip));
const auto sampler = TextureTool::GetSampler(icon->Format);
const auto sampler = PixelFormatSampler::Get(icon->Format);
ASSERT_LOW_LAYER(sampler);
// Write colors
@@ -252,7 +253,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon)
for (uint32 x = 0; x < width; x++)
{
float u = (float)x / width;
const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
const Color c = sampler->SampleLinear(srcPixelsData, Float2(u, v), srcPixelsSize, srcPixels->RowPitch);
colorData[idx++] = Color32(c).GetAsBGRA();
}
}
@@ -271,7 +272,7 @@ void UpdateIconData(uint8* iconData, const TextureData* icon)
{
uint32 x = packedX * 8 + pixelIdx;
float u = (float)x / width;
const Color c = TextureTool::SampleLinear(sampler, Float2(u, v), srcPixelsData, srcPixelsSize, srcPixels->RowPitch);
const Color c = sampler->SampleLinear(srcPixelsData, Float2(u, v), srcPixelsSize, srcPixels->RowPitch);
if (c.A < 0.25f)
mask |= 1 << (7 - pixelIdx);
}
@@ -322,7 +323,7 @@ bool UpdateExeIcon(const String& path, const TextureData& icon)
const TextureData* iconRGBA8 = &icon;
TextureData tmpData1;
//if (icon.Format != PixelFormat::R8G8B8A8_UNorm)
if (TextureTool::GetSampler(icon.Format) == nullptr)
if (PixelFormatSampler::Get(icon.Format) == nullptr)
{
if (TextureTool::Convert(tmpData1, *iconRGBA8, PixelFormat::R8G8B8A8_UNorm))
{

View File

@@ -8,6 +8,7 @@
#include "Engine/Terrain/TerrainPatch.h"
#include "Engine/Terrain/Terrain.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Graphics/Textures/TextureData.h"
@@ -103,7 +104,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool h
// Decompress or convert data if need to
data.Mip0DataPtr = &data.Mip0Data;
if (PixelFormatExtensions::IsCompressed(data.Format) || TextureTool::GetSampler(data.Format) == nullptr)
if (PixelFormatExtensions::IsCompressed(data.Format) || PixelFormatSampler::Get(data.Format) == nullptr)
{
PROFILE_CPU_NAMED("Decompress");
@@ -136,7 +137,7 @@ bool GetTextureDataForSampling(Texture* texture, TextureDataResult& data, bool h
}
// Check if can even sample the given format
const auto sampler = TextureTool::GetSampler(data.Format);
const auto sampler = PixelFormatSampler::Get(data.Format);
if (sampler == nullptr)
{
LOG(Warning, "Texture format {0} cannot be sampled.", (int32)data.Format);
@@ -188,7 +189,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
TextureDataResult dataHeightmap;
if (GetTextureDataForSampling(heightmap, dataHeightmap, true))
return true;
const auto sampler = TextureTool::GetSampler(dataHeightmap.Format);
const auto sampler = PixelFormatSampler::Get(dataHeightmap.Format);
// Initialize with sub-range of the input heightmap
const Vector2 uvPerPatch = Vector2::One / Vector2(numberOfPatches);
@@ -204,7 +205,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
for (int32 x = 0; x < heightmapSize; x++)
{
const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch;
const Color color = TextureTool::SampleLinear(sampler, uv, dataHeightmap.Mip0DataPtr->Get(), dataHeightmap.Mip0Size, dataHeightmap.RowPitch);
const Color color = sampler->SampleLinear(dataHeightmap.Mip0DataPtr->Get(), uv, dataHeightmap.Mip0Size, dataHeightmap.RowPitch);
heightmapData[z * heightmapSize + x] = color.R * heightmapScale;
}
}
@@ -244,7 +245,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
// Get splatmap data
if (GetTextureDataForSampling(splatmap, data1))
return true;
const auto sampler = TextureTool::GetSampler(data1.Format);
const auto sampler = PixelFormatSampler::Get(data1.Format);
// Modify heightmap splatmaps with sub-range of the input splatmaps
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
@@ -260,7 +261,7 @@ bool TerrainTools::GenerateTerrain(Terrain* terrain, const Int2& numberOfPatches
{
const Vector2 uv = uvStart + Vector2(x * heightmapSizeInv, z * heightmapSizeInv) * uvPerPatch;
const Color color = TextureTool::SampleLinear(sampler, uv, data1.Mip0DataPtr->Get(), data1.Mip0Size, data1.RowPitch);
const Color color = sampler->SampleLinear(data1.Mip0DataPtr->Get(), uv, data1.Mip0Size, data1.RowPitch);
Color32 layers;
layers.R = (byte)(Math::Min(1.0f, color.R) * 255.0f);

View File

@@ -6,6 +6,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/CreateProcessSettings.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Core/Log.h"
@@ -142,7 +143,7 @@ bool EditorUtilities::ExportApplicationImage(const Guid& iconId, int32 width, in
bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 width, int32 height, PixelFormat format, const String& path)
{
// Change format if need to
const TextureData* iconData = &icon;
TextureData* iconData = (TextureData*)&icon;
TextureData tmpData1, tmpData2;
if (icon.Format != format)
{
@@ -150,7 +151,7 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt
if (PixelFormatExtensions::HasAlpha(iconData->Format) && !PixelFormatExtensions::HasAlpha(format))
{
// Pre-multiply alpha if can
auto sampler = TextureTool::GetSampler(iconData->Format);
auto sampler = PixelFormatSampler::Get(iconData->Format);
if (!sampler)
{
if (TextureTool::Convert(tmpData2, *iconData, PixelFormat::R16G16B16A16_Float))
@@ -159,7 +160,7 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt
return true;
}
iconData = &tmpData2;
sampler = TextureTool::GetSampler(iconData->Format);
sampler = PixelFormatSampler::Get(iconData->Format);
}
if (sampler)
{
@@ -168,10 +169,10 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt
{
for (int32 x = 0; x < iconData->Width; x++)
{
Color color = TextureTool::SamplePoint(sampler, x, y, mipData->Data.Get(), mipData->RowPitch);
Color color = sampler->SamplePoint(mipData->Data.Get(), x, y, mipData->RowPitch);
color *= color.A;
color.A = 1.0f;
TextureTool::Store(sampler, x, y, mipData->Data.Get(), mipData->RowPitch, color);
sampler->Store(mipData->Data.Get(), x, y, mipData->RowPitch, color);
}
}
}
@@ -191,7 +192,7 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt
if (PixelFormatExtensions::HasAlpha(icon.Format) && !PixelFormatExtensions::HasAlpha(format))
{
// Pre-multiply alpha if can
auto sampler = TextureTool::GetSampler(icon.Format);
auto sampler = PixelFormatSampler::Get(icon.Format);
if (sampler)
{
auto mipData = iconData->GetData(0, 0);
@@ -199,10 +200,10 @@ bool EditorUtilities::ExportApplicationImage(const TextureData& icon, int32 widt
{
for (int32 x = 0; x < iconData->Width; x++)
{
Color color = TextureTool::SamplePoint(sampler, x, y, mipData->Data.Get(), mipData->RowPitch);
Color color = sampler->SamplePoint(mipData->Data.Get(), x, y, mipData->RowPitch);
color *= color.A;
color.A = 1.0f;
TextureTool::Store(sampler, x, y, mipData->Data.Get(), mipData->RowPitch, color);
sampler->Store(mipData->Data.Get(), x, y, mipData->RowPitch, color);
}
}
}

View File

@@ -1,7 +1,13 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "PixelFormatExtensions.h"
#include "PixelFormatSampler.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Core/Math/Color.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Half.h"
#include "Engine/Core/Math/Packed.h"
#include "Engine/Core/Math/Vector4.h"
// ReSharper disable CppClangTidyClangDiagnosticSwitchEnum
@@ -1060,3 +1066,439 @@ PixelFormat PixelFormatExtensions::FindUncompressedFormat(PixelFormat format)
return format;
}
}
static PixelFormatSampler PixelFormatSamplers[] =
{
{
PixelFormat::R32G32B32A32_Float,
sizeof(Float4),
[](const void* ptr)
{
return *(Float4*)ptr;
},
[](void* ptr, const Float4& value)
{
*(Float4*)ptr = value;
},
},
{
PixelFormat::R32G32B32_Float,
sizeof(Float3),
[](const void* ptr)
{
return Float4(*(Float3*)ptr, 1.0f);
},
[](void* ptr, const Float4& value)
{
*(Float3*)ptr = Float3(value);
},
},
{
PixelFormat::R16G16B16A16_Float,
sizeof(Half4),
[](const void* ptr)
{
return ((Half4*)ptr)->ToFloat4();
},
[](void* ptr, const Float4& value)
{
*(Half4*)ptr = Half4(value.X, value.Y, value.Z, value.W);
},
},
{
PixelFormat::R16G16B16A16_UNorm,
sizeof(RGBA16UNorm),
[](const void* ptr)
{
return ((RGBA16UNorm*)ptr)->ToFloat4();
},
[](void* ptr, const Float4& value)
{
*(RGBA16UNorm*)ptr = RGBA16UNorm(value.X, value.Y, value.Z, value.W);
}
},
{
PixelFormat::R32G32_Float,
sizeof(Float2),
[](const void* ptr)
{
return Float4(((Float2*)ptr)->X, ((Float2*)ptr)->Y, 0.0f, 0.0f);
},
[](void* ptr, const Float4& value)
{
*(Float2*)ptr = Float2(value.X, value.Y);
},
},
{
PixelFormat::R8G8B8A8_UNorm,
sizeof(Color32),
[](const void* ptr)
{
return Float4(Color(*(Color32*)ptr));
},
[](void* ptr, const Float4& value)
{
*(Color32*)ptr = Color32(value);
},
},
{
PixelFormat::R8G8B8A8_UNorm_sRGB,
sizeof(Color32),
[](const void* ptr)
{
return Float4(Color::SrgbToLinear(Color(*(Color32*)ptr)));
},
[](void* ptr, const Float4& value)
{
Color srgb = Color::LinearToSrgb((const Color&)value);
*(Color32*)ptr = Color32(srgb);
},
},
{
PixelFormat::R8G8_UNorm,
sizeof(uint16),
[](const void* ptr)
{
const uint8* rg = (const uint8*)ptr;
return Float4((float)rg[0] / MAX_uint8, (float)rg[1] / MAX_uint8, 0, 1);
},
[](void* ptr, const Float4& value)
{
uint8* rg = (uint8*)ptr;
rg[0] = (uint8)(value.X * MAX_uint8);
rg[1] = (uint8)(value.Y * MAX_uint8);
},
},
{
PixelFormat::R16G16_Float,
sizeof(Half2),
[](const void* ptr)
{
const Float2 rg = ((Half2*)ptr)->ToFloat2();
return Float4(rg.X, rg.Y, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(Half2*)ptr = Half2(value.X, value.Y);
},
},
{
PixelFormat::R16G16_UNorm,
sizeof(RG16UNorm),
[](const void* ptr)
{
const Float2 rg = ((RG16UNorm*)ptr)->ToFloat2();
return Float4(rg.X, rg.Y, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(RG16UNorm*)ptr = RG16UNorm(value.X, value.Y);
},
},
{
PixelFormat::R32_Float,
sizeof(float),
[](const void* ptr)
{
return Float4(*(float*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(float*)ptr = value.X;
},
},
{
PixelFormat::R16_Float,
sizeof(Half),
[](const void* ptr)
{
return Float4(Float16Compressor::Decompress(*(Half*)ptr), 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(Half*)ptr = Float16Compressor::Compress(value.X);
},
},
{
PixelFormat::R16_UNorm,
sizeof(uint16),
[](const void* ptr)
{
return Float4((float)*(uint16*)ptr / MAX_uint16, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(uint16*)ptr = (uint16)(value.X * MAX_uint16);
},
},
{
PixelFormat::R8_UNorm,
sizeof(uint8),
[](const void* ptr)
{
return Float4((float)*(byte*)ptr / MAX_uint8, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(byte*)ptr = (byte)(value.X * MAX_uint8);
},
},
{
PixelFormat::A8_UNorm,
sizeof(uint8),
[](const void* ptr)
{
return Float4(0, 0, 0, (float)*(byte*)ptr / MAX_uint8);
},
[](void* ptr, const Float4& value)
{
*(byte*)ptr = (byte)(value.W * MAX_uint8);
},
},
{
PixelFormat::R32_UInt,
sizeof(uint32),
[](const void* ptr)
{
return Float4((float)*(uint32*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(uint32*)ptr = (uint32)value.X;
},
},
{
PixelFormat::R32_SInt,
sizeof(int32),
[](const void* ptr)
{
return Float4((float)*(int32*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(int32*)ptr = (int32)value.X;
},
},
{
PixelFormat::R16_UInt,
sizeof(uint16),
[](const void* ptr)
{
return Float4((float)*(uint16*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(uint16*)ptr = (uint16)value.X;
},
},
{
PixelFormat::R16_SInt,
sizeof(int16),
[](const void* ptr)
{
return Float4((float)*(int16*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(int16*)ptr = (int16)value.X;
},
},
{
PixelFormat::R8_UInt,
sizeof(uint8),
[](const void* ptr)
{
return Float4((float)*(uint8*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(uint8*)ptr = (uint8)value.X;
},
},
{
PixelFormat::R8_SInt,
sizeof(int8),
[](const void* ptr)
{
return Float4((float)*(int8*)ptr, 0, 0, 1);
},
[](void* ptr, const Float4& value)
{
*(int8*)ptr = (int8)value.X;
},
},
{
PixelFormat::B8G8R8A8_UNorm,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Float4(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A)));
},
[](void* ptr, const Float4& value)
{
*(Color32*)ptr = Color32(byte(value.Z * MAX_uint8), byte(value.Y * MAX_uint8), byte(value.X * MAX_uint8), byte(value.W * MAX_uint8));
},
},
{
PixelFormat::B8G8R8A8_UNorm_sRGB,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Float4(Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A))));
},
[](void* ptr, const Float4& value)
{
Color srgb = Color::LinearToSrgb((const Color&)value);
*(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), byte(srgb.A * MAX_uint8));
},
},
{
PixelFormat::B8G8R8X8_UNorm,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Float4(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8)));
},
[](void* ptr, const Float4& value)
{
*(Color32*)ptr = Color32(byte(value.Z * MAX_uint8), byte(value.Y * MAX_uint8), byte(value.X * MAX_uint8), MAX_uint8);
},
},
{
PixelFormat::B8G8R8X8_UNorm_sRGB,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Float4(Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8))));
},
[](void* ptr, const Float4& value)
{
Color srgb = Color::LinearToSrgb((const Color&)value);
*(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), MAX_uint8);
},
},
{
PixelFormat::R11G11B10_Float,
sizeof(FloatR11G11B10),
[](const void* ptr)
{
const Float3 rgb = ((FloatR11G11B10*)ptr)->ToFloat3();
return Float4(rgb.X, rgb.Y, rgb.Z, 0.0f);
},
[](void* ptr, const Float4& value)
{
*(FloatR11G11B10*)ptr = FloatR11G11B10(value.X, value.Y, value.Z);
},
},
{
PixelFormat::R10G10B10A2_UNorm,
sizeof(Float1010102),
[](const void* ptr)
{
return ((Float1010102*)ptr)->ToFloat4();
},
[](void* ptr, const Float4& value)
{
*(Float1010102*)ptr = Float1010102(value.X, value.Y, value.Z, value.W);
},
},
{
PixelFormat::R8G8B8A8_UInt,
sizeof(Color32),
[](const void* ptr)
{
uint8 data[4];
Platform::MemoryCopy(data, ptr, sizeof(data));
return Float4(data[0], data[1], data[2], data[3]);
},
[](void* ptr, const Float4& value)
{
uint8 data[4] = { (uint8)value.X, (uint8)value.Y, (uint8)value.Z, (uint8)value.W};
Platform::MemoryCopy(ptr, data, sizeof(data));
},
},
{
PixelFormat::R8G8B8A8_SInt,
sizeof(Color32),
[](const void* ptr)
{
int8 data[4];
Platform::MemoryCopy(data, ptr, sizeof(data));
return Float4(data[0], data[1], data[2], data[3]);
},
[](void* ptr, const Float4& value)
{
int8 data[4] = { (int8)value.X, (int8)value.Y, (int8)value.Z, (int8)value.W};
Platform::MemoryCopy(ptr, data, sizeof(data));
},
},
};
void PixelFormatSampler::Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const
{
Write((byte*)data + rowPitch * y + PixelSize * x, (Float4&)color);
}
Float4 PixelFormatSampler::Sample(const void* data, int32 x) const
{
return Read((const byte*)data + x * PixelSize);
}
Color PixelFormatSampler::SamplePoint(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const
{
const Int2 end = size - 1;
const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y));
Float4 result = Read((const byte*)data + rowPitch * uvFloor.Y + PixelSize * uvFloor.X);
return *(Color*)&result;
}
Color PixelFormatSampler::SamplePoint(const void* data, int32 x, int32 y, int32 rowPitch) const
{
Float4 result = Read((const byte*)data + rowPitch * y + PixelSize * x);
return *(Color*)&result;
}
Color PixelFormatSampler::SampleLinear(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const
{
const Int2 end = size - 1;
const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y));
const Int2 uvNext(Math::Min(uvFloor.X + 1, end.X), Math::Min(uvFloor.Y + 1, end.Y));
const Float2 uvFraction(uv.X * size.Y - uvFloor.X, uv.Y * size.Y - uvFloor.Y);
const Float4 v00 = Read((const byte*)data + rowPitch * uvFloor.Y + PixelSize * uvFloor.X);
const Float4 v01 = Read((const byte*)data + rowPitch * uvFloor.Y + PixelSize * uvNext.X);
const Float4 v10 = Read((const byte*)data + rowPitch * uvNext.Y + PixelSize * uvFloor.X);
const Float4 v11 = Read((const byte*)data + rowPitch * uvNext.Y + PixelSize * uvNext.X);
Float4 result = Float4::Lerp(Float4::Lerp(v00, v01, uvFraction.X), Float4::Lerp(v10, v11, uvFraction.X), uvFraction.Y);
return *(Color*)&result;
}
const PixelFormatSampler* PixelFormatSampler::Get(PixelFormat format)
{
format = PixelFormatExtensions::MakeTypelessFloat(format);
for (const auto& sampler : PixelFormatSamplers)
{
if (sampler.Format == format)
return &sampler;
}
return nullptr;
}
#if !COMPILE_WITHOUT_CSHARP
void PixelFormatExtensions::GetSamplerInternal(PixelFormat format, int32& pixelSize, void** read, void** write)
{
if (const PixelFormatSampler* sampler = PixelFormatSampler::Get(format))
{
pixelSize = sampler->PixelSize;
*read = sampler->Read;
*write = sampler->Write;
}
}
#endif

View File

@@ -205,4 +205,10 @@ public:
static PixelFormat FindUnorderedAccessFormat(PixelFormat format);
static PixelFormat FindDepthStencilFormat(PixelFormat format);
static PixelFormat FindUncompressedFormat(PixelFormat format);
private:
// Internal bindings
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) static void GetSamplerInternal(PixelFormat format, API_PARAM(Out) int32& pixelSize, API_PARAM(Out) void** read, API_PARAM(Out) void** write);
#endif
};

View File

@@ -0,0 +1,46 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
namespace FlaxEngine
{
unsafe partial struct PixelFormatSampler
{
/// <summary>
/// Element format.
/// </summary>
public PixelFormat Format;
/// <summary>
/// Element size in bytes.
/// </summary>
public int PixelSize;
/// <summary>
/// Read data function.
/// </summary>
public delegate* unmanaged<void*, Float4> Read;
/// <summary>
/// Write data function.
/// </summary>
public delegate* unmanaged<void*, ref Float4, void> Write;
/// <summary>
/// Tries to get a sampler tool for the specified format to read pixels.
/// </summary>
/// <param name="format">The format.</param>
/// <param name="sampler">The sampler object or empty when cannot sample the given format.</param>
/// <returns>True if got sampler, otherwise false.</returns>
public static bool Get(PixelFormat format, out PixelFormatSampler sampler)
{
PixelFormatExtensions.Internal_GetSamplerInternal(format, out var pixelSize, out var read, out var write);
sampler = new PixelFormatSampler
{
Format = format,
PixelSize = pixelSize,
Read = (delegate* unmanaged<void*, Float4>)read.ToPointer(),
Write = (delegate* unmanaged<void*, ref Float4, void>)write.ToPointer(),
};
return pixelSize != 0;
}
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "PixelFormat.h"
/// <summary>
/// Utility for writing and reading from different pixels formats within a single code path.
/// </summary>
API_STRUCT(NoDefault) struct FLAXENGINE_API PixelFormatSampler
{
DECLARE_SCRIPTING_TYPE_MINIMAL(PixelFormatSampler);
typedef Float4 (*ReadPixel)(const void* data);
typedef void (*WritePixel)(void* data, const Float4& value);
public:
// Element format.
PixelFormat Format;
// Element size in bytes.
int32 PixelSize;
// Read data function.
ReadPixel Read;
// Write data function.
WritePixel Write;
public:
/// <summary>
/// Stores the color into the specified texture data (uses no interpolation).
/// </summary>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="x">The X texture coordinates (normalized to range 0-width).</param>
/// <param name="y">The Y texture coordinates (normalized to range 0-height).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <param name="color">The color to store (linear).</param>
void Store(void* data, int32 x, int32 y, int32 rowPitch, const Color& color) const;
/// <summary>
/// Samples the specified linear data (uses no interpolation).
/// </summary>
/// <param name="data">The data pointer for the data slice (linear buffer or 1D image).</param>
/// <param name="x">Index of the element.</param>
/// <returns>The sampled value.</returns>
Float4 Sample(const void* data, int32 x) const;
/// <summary>
/// Samples the specified texture data (uses no interpolation).
/// </summary>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="uv">The texture coordinates (normalized to range 0-1).</param>
/// <param name="size">The size of the input texture (in pixels).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color (linear).</returns>
Color SamplePoint(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const;
/// <summary>
/// Samples the specified texture data (uses no interpolation).
/// </summary>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="x">The X texture coordinates (normalized to range 0-width).</param>
/// <param name="y">The Y texture coordinates (normalized to range 0-height).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color (linear).</returns>
Color SamplePoint(const void* data, int32 x, int32 y, int32 rowPitch) const;
/// <summary>
/// Samples the specified texture data (uses linear interpolation).
/// </summary>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="uv">The texture coordinates (normalized to range 0-1).</param>
/// <param name="size">The size of the input texture (in pixels).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color (linear).</returns>
Color SampleLinear(const void* data, const Float2& uv, const Int2& size, int32 rowPitch) const;
public:
/// <summary>
/// Tries to get a sampler tool for the specified format to read pixels.
/// </summary>
/// <param name="format">The format.</param>
/// <returns>The pointer to the sampler object or null when cannot sample the given format.</returns>
static const PixelFormatSampler* Get(PixelFormat format);
};

View File

@@ -11,6 +11,7 @@
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Scripting/Enums.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Threading/Threading.h"
@@ -100,14 +101,14 @@ bool TextureMipData::GetPixels(Array<Color32>& pixels, int32 width, int32 height
default:
{
// Try to use texture sampler utility
auto sampler = TextureTool::GetSampler(format);
auto sampler = PixelFormatSampler::Get(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 c = sampler->SamplePoint(src, x, y, RowPitch);
*(Color32*)(dst + dstRowSize * y + x * sizeof(Color32)) = Color32(c);
}
}
@@ -149,14 +150,14 @@ bool TextureMipData::GetPixels(Array<Color>& pixels, int32 width, int32 height,
default:
{
// Try to use texture sampler utility
auto sampler = TextureTool::GetSampler(format);
auto sampler = PixelFormatSampler::Get(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 c = sampler->SamplePoint(src, x, y, RowPitch);
*(Color*)(dst + dstRowSize * y + x * sizeof(Color)) = c;
}
}
@@ -475,7 +476,7 @@ bool TextureBase::SetPixels(const Span<Color32>& pixels, int32 mipIndex, int32 a
if (error)
{
// Try to use texture sampler utility
auto sampler = TextureTool::GetSampler(format);
auto sampler = PixelFormatSampler::Get(format);
if (sampler)
{
for (int32 y = 0; y < height; y++)
@@ -483,7 +484,7 @@ bool TextureBase::SetPixels(const Span<Color32>& pixels, int32 mipIndex, int32 a
for (int32 x = 0; x < width; x++)
{
Color c(pixels.Get()[x + y * width]);
TextureTool::Store(sampler, x, y, dst, rowPitch, c);
sampler->Store(dst, x, y, rowPitch, c);
}
}
error = false;
@@ -553,7 +554,7 @@ bool TextureBase::SetPixels(const Span<Color>& pixels, int32 mipIndex, int32 arr
if (error)
{
// Try to use texture sampler utility
auto sampler = TextureTool::GetSampler(format);
auto sampler = PixelFormatSampler::Get(format);
if (sampler)
{
for (int32 y = 0; y < height; y++)
@@ -561,7 +562,7 @@ bool TextureBase::SetPixels(const Span<Color>& pixels, int32 mipIndex, int32 arr
for (int32 x = 0; x < width; x++)
{
Color c(pixels.Get()[x + y * width]);
TextureTool::Store(sampler, x, y, dst, rowPitch, c);
sampler->Store(dst, x, y, rowPitch, c);
}
}
error = false;

View File

@@ -6,15 +6,13 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DateTime.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Core/Math/Packed.h"
#include "Engine/Core/Math/Color32.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Serialization/JsonWriter.h"
#include "Engine/Serialization/JsonTools.h"
#include "Engine/Scripting/Enums.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Profiler/ProfilerCPU.h"
#if USE_EDITOR
@@ -372,326 +370,6 @@ bool TextureTool::Resize(TextureData& dst, const TextureData& src, int32 dstWidt
#endif
}
TextureTool::PixelFormatSampler PixelFormatSamplers[] =
{
{
PixelFormat::R32G32B32A32_Float,
sizeof(Float4),
[](const void* ptr)
{
return Color(*(Float4*)ptr);
},
[](const void* ptr, const Color& color)
{
*(Float4*)ptr = color.ToFloat4();
},
},
{
PixelFormat::R32G32B32_Float,
sizeof(Float3),
[](const void* ptr)
{
return Color(*(Float3*)ptr, 1.0f);
},
[](const void* ptr, const Color& color)
{
*(Float3*)ptr = color.ToFloat3();
},
},
{
PixelFormat::R16G16B16A16_Float,
sizeof(Half4),
[](const void* ptr)
{
return Color(((Half4*)ptr)->ToFloat4());
},
[](const void* ptr, const Color& color)
{
*(Half4*)ptr = Half4(color.R, color.G, color.B, color.A);
},
},
{
PixelFormat::R16G16B16A16_UNorm,
sizeof(RGBA16UNorm),
[](const void* ptr)
{
return Color(((RGBA16UNorm*)ptr)->ToFloat4());
},
[](const void* ptr, const Color& color)
{
*(RGBA16UNorm*)ptr = RGBA16UNorm(color.R, color.G, color.B, color.A);
}
},
{
PixelFormat::R32G32_Float,
sizeof(Float2),
[](const void* ptr)
{
return Color(((Float2*)ptr)->X, ((Float2*)ptr)->Y, 1.0f);
},
[](const void* ptr, const Color& color)
{
*(Float2*)ptr = Float2(color.R, color.G);
},
},
{
PixelFormat::R8G8B8A8_UNorm,
sizeof(Color32),
[](const void* ptr)
{
return Color(*(Color32*)ptr);
},
[](const void* ptr, const Color& color)
{
*(Color32*)ptr = Color32(color);
},
},
{
PixelFormat::R8G8B8A8_UNorm_sRGB,
sizeof(Color32),
[](const void* ptr)
{
return Color::SrgbToLinear(Color(*(Color32*)ptr));
},
[](const void* ptr, const Color& color)
{
Color srgb = Color::LinearToSrgb(color);
*(Color32*)ptr = Color32(srgb);
},
},
{
PixelFormat::R8G8_UNorm,
sizeof(uint16),
[](const void* ptr)
{
const uint8* rg = (const uint8*)ptr;
return Color((float)rg[0] / MAX_uint8, (float)rg[1] / MAX_uint8, 0, 1);
},
[](const void* ptr, const Color& color)
{
uint8* rg = (uint8*)ptr;
rg[0] = (uint8)(color.R * MAX_uint8);
rg[1] = (uint8)(color.G * MAX_uint8);
},
},
{
PixelFormat::R16G16_Float,
sizeof(Half2),
[](const void* ptr)
{
const Float2 rg = ((Half2*)ptr)->ToFloat2();
return Color(rg.X, rg.Y, 0, 1);
},
[](const void* ptr, const Color& color)
{
*(Half2*)ptr = Half2(color.R, color.G);
},
},
{
PixelFormat::R16G16_UNorm,
sizeof(RG16UNorm),
[](const void* ptr)
{
const Float2 rg = ((RG16UNorm*)ptr)->ToFloat2();
return Color(rg.X, rg.Y, 0, 1);
},
[](const void* ptr, const Color& color)
{
*(RG16UNorm*)ptr = RG16UNorm(color.R, color.G);
},
},
{
PixelFormat::R32_Float,
sizeof(float),
[](const void* ptr)
{
return Color(*(float*)ptr, 0, 0, 1);
},
[](const void* ptr, const Color& color)
{
*(float*)ptr = color.R;
},
},
{
PixelFormat::R16_Float,
sizeof(Half),
[](const void* ptr)
{
return Color(Float16Compressor::Decompress(*(Half*)ptr), 0, 0, 1);
},
[](const void* ptr, const Color& color)
{
*(Half*)ptr = Float16Compressor::Compress(color.R);
},
},
{
PixelFormat::R16_UNorm,
sizeof(uint16),
[](const void* ptr)
{
return Color((float)*(uint16*)ptr / MAX_uint16, 0, 0, 1);
},
[](const void* ptr, const Color& color)
{
*(uint16*)ptr = (uint16)(color.R * MAX_uint16);
},
},
{
PixelFormat::R8_UNorm,
sizeof(uint8),
[](const void* ptr)
{
return Color((float)*(byte*)ptr / MAX_uint8, 0, 0, 1);
},
[](const void* ptr, const Color& color)
{
*(byte*)ptr = (byte)(color.R * MAX_uint8);
},
},
{
PixelFormat::A8_UNorm,
sizeof(uint8),
[](const void* ptr)
{
return Color(0, 0, 0, (float)*(byte*)ptr / MAX_uint8);
},
[](const void* ptr, const Color& color)
{
*(byte*)ptr = (byte)(color.A * MAX_uint8);
},
},
{
PixelFormat::B8G8R8A8_UNorm,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A));
},
[](const void* ptr, const Color& color)
{
*(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), byte(color.A * MAX_uint8));
},
},
{
PixelFormat::B8G8R8A8_UNorm_sRGB,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, bgra.A)));
},
[](const void* ptr, const Color& color)
{
Color srgb = Color::LinearToSrgb(color);
*(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), byte(srgb.A * MAX_uint8));
},
},
{
PixelFormat::B8G8R8X8_UNorm,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8));
},
[](const void* ptr, const Color& color)
{
*(Color32*)ptr = Color32(byte(color.B * MAX_uint8), byte(color.G * MAX_uint8), byte(color.R * MAX_uint8), MAX_uint8);
},
},
{
PixelFormat::B8G8R8X8_UNorm_sRGB,
sizeof(Color32),
[](const void* ptr)
{
const Color32 bgra = *(Color32*)ptr;
return Color::SrgbToLinear(Color(Color32(bgra.B, bgra.G, bgra.R, MAX_uint8)));
},
[](const void* ptr, const Color& color)
{
Color srgb = Color::LinearToSrgb(color);
*(Color32*)ptr = Color32(byte(srgb.B * MAX_uint8), byte(srgb.G * MAX_uint8), byte(srgb.R * MAX_uint8), MAX_uint8);
},
},
{
PixelFormat::R11G11B10_Float,
sizeof(FloatR11G11B10),
[](const void* ptr)
{
const Float3 rgb = ((FloatR11G11B10*)ptr)->ToFloat3();
return Color(rgb.X, rgb.Y, rgb.Z);
},
[](const void* ptr, const Color& color)
{
*(FloatR11G11B10*)ptr = FloatR11G11B10(color.R, color.G, color.B);
},
},
{
PixelFormat::R10G10B10A2_UNorm,
sizeof(Float1010102),
[](const void* ptr)
{
const Float3 rgb = ((Float1010102*)ptr)->ToFloat3();
return Color(rgb.X, rgb.Y, rgb.Z);
},
[](const void* ptr, const Color& color)
{
*(Float1010102*)ptr = Float1010102(color.R, color.G, color.B, color.A);
},
},
};
const TextureTool::PixelFormatSampler* TextureTool::GetSampler(PixelFormat format)
{
format = PixelFormatExtensions::MakeTypelessFloat(format);
for (auto& sampler : PixelFormatSamplers)
{
if (sampler.Format == format)
return &sampler;
}
return nullptr;
}
void TextureTool::Store(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch, const Color& color)
{
ASSERT_LOW_LAYER(sampler);
sampler->Store((byte*)data + rowPitch * y + sampler->PixelSize * x, color);
}
Color TextureTool::SamplePoint(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch)
{
ASSERT_LOW_LAYER(sampler);
const Int2 end = size - 1;
const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y));
return sampler->Sample((byte*)data + rowPitch * uvFloor.Y + sampler->PixelSize * uvFloor.X);
}
Color TextureTool::SamplePoint(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch)
{
ASSERT_LOW_LAYER(sampler);
return sampler->Sample((byte*)data + rowPitch * y + sampler->PixelSize * x);
}
Color TextureTool::SampleLinear(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch)
{
ASSERT_LOW_LAYER(sampler);
const Int2 end = size - 1;
const Int2 uvFloor(Math::Min(Math::FloorToInt(uv.X * size.X), end.X), Math::Min(Math::FloorToInt(uv.Y * size.Y), end.Y));
const Int2 uvNext(Math::Min(uvFloor.X + 1, end.X), Math::Min(uvFloor.Y + 1, end.Y));
const Float2 uvFraction(uv.X * size.Y - uvFloor.X, uv.Y * size.Y - uvFloor.Y);
const Color v00 = sampler->Sample((byte*)data + rowPitch * uvFloor.Y + sampler->PixelSize * uvFloor.X);
const Color v01 = sampler->Sample((byte*)data + rowPitch * uvFloor.Y + sampler->PixelSize * uvNext.X);
const Color v10 = sampler->Sample((byte*)data + rowPitch * uvNext.Y + sampler->PixelSize * uvFloor.X);
const Color v11 = sampler->Sample((byte*)data + rowPitch * uvNext.Y + sampler->PixelSize * uvNext.X);
return Color::Lerp(Color::Lerp(v00, v01, uvFraction.X), Color::Lerp(v10, v11, uvFraction.X), uvFraction.Y);
}
PixelFormat TextureTool::ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress)
{
const bool canUseBlockCompression = width % 4 == 0 && height % 4 == 0;
@@ -795,7 +473,7 @@ bool TextureTool::GetImageType(const StringView& path, ImageType& type)
bool TextureTool::Transform(TextureData& texture, const Function<void(Color&)>& transformation)
{
PROFILE_CPU();
auto sampler = TextureTool::GetSampler(texture.Format);
auto sampler = PixelFormatSampler::Get(texture.Format);
if (!sampler)
return true;
for (auto& slice : texture.Items)
@@ -809,9 +487,9 @@ bool TextureTool::Transform(TextureData& texture, const Function<void(Color&)>&
{
for (int32 x = 0; x < mipWidth; x++)
{
Color color = TextureTool::SamplePoint(sampler, x, y, mip.Data.Get(), mip.RowPitch);
Color color = sampler->SamplePoint(mip.Data.Get(), x, y, mip.RowPitch);
transformation(color);
TextureTool::Store(sampler, x, y, mip.Data.Get(), mip.RowPitch, color);
sampler->Store(mip.Data.Get(), x, y, mip.RowPitch, color);
}
}
}

View File

@@ -176,80 +176,6 @@ public:
static bool Resize(TextureData& dst, const TextureData& src, int32 dstWidth, int32 dstHeight);
public:
typedef Color (*ReadPixel)(const void*);
typedef void (*WritePixel)(const void*, const Color&);
struct PixelFormatSampler
{
PixelFormat Format;
int32 PixelSize;
ReadPixel Sample;
WritePixel Store;
};
/// <summary>
/// Determines whether this tool can sample the specified format to read texture samples and returns the sampler object.
/// </summary>
/// <param name="format">The format.</param>
/// <returns>The pointer to the sampler object or null if cannot sample the given format.</returns>
static const PixelFormatSampler* GetSampler(PixelFormat format);
/// <summary>
/// Stores the color into the specified texture data (uses no interpolation).
/// </summary>
/// <remarks>
/// Use GetSampler for the texture sampler.
/// </remarks>
/// <param name="sampler">The texture data sampler.</param>
/// <param name="x">The X texture coordinates (normalized to range 0-width).</param>
/// <param name="y">The Y texture coordinates (normalized to range 0-height).</param>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <param name="color">The color to store (linear).</param>
static void Store(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch, const Color& color);
/// <summary>
/// Samples the specified texture data (uses no interpolation).
/// </summary>
/// <remarks>
/// Use GetSampler for the texture sampler.
/// </remarks>
/// <param name="sampler">The texture data sampler.</param>
/// <param name="uv">The texture coordinates (normalized to range 0-1).</param>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="size">The size of the input texture (in pixels).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color (linear).</returns>
static Color SamplePoint(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch);
/// <summary>
/// Samples the specified texture data (uses no interpolation).
/// </summary>
/// <remarks>
/// Use GetSampler for the texture sampler.
/// </remarks>
/// <param name="sampler">The texture data sampler.</param>
/// <param name="x">The X texture coordinates (normalized to range 0-width).</param>
/// <param name="y">The Y texture coordinates (normalized to range 0-height).</param>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color (linear).</returns>
static Color SamplePoint(const PixelFormatSampler* sampler, int32 x, int32 y, const void* data, int32 rowPitch);
/// <summary>
/// Samples the specified texture data (uses linear interpolation).
/// </summary>
/// <remarks>
/// Use GetSampler for the texture sampler.
/// </remarks>
/// <param name="sampler">The texture data sampler.</param>
/// <param name="uv">The texture coordinates (normalized to range 0-1).</param>
/// <param name="data">The data pointer for the texture slice (1D or 2D image).</param>
/// <param name="size">The size of the input texture (in pixels).</param>
/// <param name="rowPitch">The row pitch (in bytes). The offset between each image rows.</param>
/// <returns>The sampled color (linear).</returns>
static Color SampleLinear(const PixelFormatSampler* sampler, const Float2& uv, const void* data, const Int2& size, int32 rowPitch);
static PixelFormat ToPixelFormat(TextureFormatType format, int32 width, int32 height, bool canCompress);
private: