Fix RectAtlas regression

This commit is contained in:
Wojtek Figat
2024-06-21 16:24:47 +02:00
parent 6d9f504639
commit c119750896
8 changed files with 60 additions and 68 deletions

View File

@@ -17,8 +17,8 @@ namespace CSG
struct Node : RectPackNode<float>
{
Node(float x, float y, float width, float height)
: RectPackNode<float>(x, y, width, height)
Node(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -50,7 +50,7 @@ namespace CSG
Node* Insert(ChartType chart)
{
return _root.Insert(chart->Size.X, chart->Size.Y, _chartsPadding, chart, _atlasSize);
return _root.Insert(chart->Size.X, chart->Size.Y, chart, _atlasSize);
}
};
}

View File

@@ -72,8 +72,8 @@ void RepackMeshLightmapUVs(ModelData& data)
// Build list of meshes with their area
struct LightmapUVsPack : RectPackNode<float>
{
LightmapUVsPack(float x, float y, float width, float height)
: RectPackNode<float>(x, y, width, height)
LightmapUVsPack(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -110,10 +110,10 @@ void RepackMeshLightmapUVs(ModelData& data)
bool failed = false;
const float chartsPadding = (4.0f / 256.0f) * atlasSize;
RectPackAtlas<LightmapUVsPack> atlas;
atlas.Init(chartsPadding, chartsPadding);
atlas.Init(atlasSize, atlasSize, chartsPadding);
for (auto& entry : entries)
{
entry.Slot = atlas.Insert(entry.Size, entry.Size, chartsPadding);
entry.Slot = atlas.Insert(entry.Size, entry.Size);
if (entry.Slot == nullptr)
{
// Failed to insert surface, increase atlas size and try again

View File

@@ -36,7 +36,8 @@ void FontTextureAtlas::Init(uint32 width, uint32 height)
// Setup
_width = width;
_height = height;
_atlas.Init(_width, _height, GetPaddingAmount());
const uint32 padding = GetPaddingAmount() * 2; // Double the padding so each slot has own border around it
_atlas.Init(_width, _height, padding);
_isDirty = false;
// Reserve upload data memory
@@ -44,9 +45,9 @@ void FontTextureAtlas::Init(uint32 width, uint32 height)
Platform::MemoryClear(_data.Get(), _data.Capacity());
}
FontTextureAtlasSlot* FontTextureAtlas::AddEntry(uint32 targetWidth, uint32 targetHeight, const Array<byte>& data)
FontTextureAtlasSlot* FontTextureAtlas::AddEntry(uint32 width, uint32 height, const Array<byte>& data)
{
if (targetWidth == 0 || targetHeight == 0)
if (width == 0 || height == 0)
return nullptr;
// Try to find slot for the texture
@@ -54,7 +55,7 @@ FontTextureAtlasSlot* FontTextureAtlas::AddEntry(uint32 targetWidth, uint32 targ
for (int32 i = 0; i < _freeSlots.Count(); i++)
{
FontTextureAtlasSlot* e = _freeSlots[i];
if (e->Width == targetWidth && e->Height == targetHeight)
if (e->Width == width && e->Height == height)
{
slot = e;
_freeSlots.RemoveAt(i);
@@ -63,7 +64,7 @@ FontTextureAtlasSlot* FontTextureAtlas::AddEntry(uint32 targetWidth, uint32 targ
}
if (!slot)
{
slot = _atlas.Insert(targetWidth, targetHeight, GetPaddingAmount());
slot = _atlas.Insert(width, height);
}
if (slot)
@@ -100,11 +101,10 @@ bool FontTextureAtlas::Invalidate(uint32 x, uint32 y, uint32 width, uint32 heigh
void FontTextureAtlas::CopyDataIntoSlot(const FontTextureAtlasSlot* slot, const Array<byte>& data)
{
RowData rowData;
rowData.DstData = &_data[slot->Y * _width * _bytesPerPixel + slot->X * _bytesPerPixel];
rowData.DstData = _data.Get() + (slot->Y * _width + slot->X) * _bytesPerPixel;
rowData.SrcData = data.Get();
rowData.DstTextureWidth = _width;
rowData.SrcTextureWidth = slot->Width;
rowData.RowWidth = slot->Width;
rowData.DstWidth = _width;
rowData.SrcWidth = slot->Width;
rowData.Padding = GetPaddingAmount();
// Start with padding
@@ -148,20 +148,20 @@ byte* FontTextureAtlas::GetSlotData(const FontTextureAtlasSlot* slot, uint32& wi
void FontTextureAtlas::copyRow(const RowData& copyRowData) const
{
const byte* srcData = (const byte*)((intptr)copyRowData.SrcData + (intptr)copyRowData.SrcRow * copyRowData.SrcTextureWidth * _bytesPerPixel);
byte* dstData = (byte*)((intptr)copyRowData.DstData + (intptr)copyRowData.DstRow * copyRowData.DstTextureWidth * _bytesPerPixel);
Platform::MemoryCopy(dstData, srcData, copyRowData.SrcTextureWidth * _bytesPerPixel);
const byte* srcData = (const byte*)((intptr)copyRowData.SrcData + (intptr)copyRowData.SrcRow * copyRowData.SrcWidth * _bytesPerPixel);
byte* dstData = (byte*)((intptr)copyRowData.DstData + (intptr)copyRowData.DstRow * copyRowData.DstWidth * _bytesPerPixel);
Platform::MemoryCopy(dstData, srcData, copyRowData.SrcWidth * _bytesPerPixel);
if (copyRowData.Padding > 0)
{
const uint32 padSize = copyRowData.Padding * _bytesPerPixel;
byte* dstPaddingPixelLeft = (byte*)((intptr)copyRowData.DstData + (intptr)copyRowData.DstRow * copyRowData.DstTextureWidth * _bytesPerPixel - padSize);
byte* dstPaddingPixelRight = dstPaddingPixelLeft + copyRowData.RowWidth * _bytesPerPixel + padSize;
byte* dstPaddingPixelLeft = (byte*)((intptr)copyRowData.DstData + (intptr)copyRowData.DstRow * copyRowData.DstWidth * _bytesPerPixel - padSize);
byte* dstPaddingPixelRight = dstPaddingPixelLeft + copyRowData.SrcWidth * _bytesPerPixel + padSize;
if (_paddingStyle == DilateBorder)
{
// Dilate left and right sides of the padded row
const byte* firstPixel = srcData;
const byte* lastPixel = srcData + (copyRowData.SrcTextureWidth - 1) * _bytesPerPixel;
const byte* lastPixel = srcData + (copyRowData.SrcWidth - 1) * _bytesPerPixel;
Platform::MemoryCopy(dstPaddingPixelLeft, firstPixel, padSize);
Platform::MemoryCopy(dstPaddingPixelRight, lastPixel, padSize);
}
@@ -176,8 +176,8 @@ void FontTextureAtlas::copyRow(const RowData& copyRowData) const
void FontTextureAtlas::zeroRow(const RowData& copyRowData) const
{
byte* dstData = (byte*)((intptr)copyRowData.DstData + (intptr)copyRowData.DstRow * copyRowData.DstTextureWidth * _bytesPerPixel);
uint32 dstSize = copyRowData.RowWidth * _bytesPerPixel;
byte* dstData = (byte*)((intptr)copyRowData.DstData + (intptr)copyRowData.DstRow * copyRowData.DstWidth * _bytesPerPixel);
uint32 dstSize = copyRowData.SrcWidth * _bytesPerPixel;
if (copyRowData.Padding > 0)
{
// Extend clear by left and right borders of the padded row

View File

@@ -13,8 +13,8 @@
/// </summary>
struct FontTextureAtlasSlot : RectPackNode<>
{
FontTextureAtlasSlot(uint32 x, uint32 y, uint32 width, uint32 height)
: RectPackNode<>(x, y, width, height)
FontTextureAtlasSlot(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -37,9 +37,8 @@ private:
uint8* DstData;
int32 SrcRow;
int32 DstRow;
int32 RowWidth;
int32 SrcTextureWidth;
int32 DstTextureWidth;
int32 SrcWidth;
int32 DstWidth;
uint32 Padding;
};
@@ -154,11 +153,11 @@ public:
/// <summary>
/// Adds the new entry to the atlas
/// </summary>
/// <param name="targetWidth">Width of the entry.</param>
/// <param name="targetHeight">Height of the entry.</param>
/// <param name="width">Width of the entry.</param>
/// <param name="height">Height of the entry.</param>
/// <param name="data">The data.</param>
/// <returns>The atlas slot occupied by the new entry.</returns>
FontTextureAtlasSlot* AddEntry(uint32 targetWidth, uint32 targetHeight, const Array<byte>& data);
FontTextureAtlasSlot* AddEntry(uint32 width, uint32 height, const Array<byte>& data);
/// <summary>
/// Invalidates the cached dynamic entry from the atlas.

View File

@@ -73,8 +73,8 @@ struct GlobalSurfaceAtlasTile : RectPackNode<uint16>
uint32 Address;
uint32 ObjectAddressOffset;
GlobalSurfaceAtlasTile(uint16 x, uint16 y, uint16 width, uint16 height)
: RectPackNode<uint16>(x, y, width, height)
GlobalSurfaceAtlasTile(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -1236,7 +1236,7 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con
uint16 tilePixels = tileResolution * tileResolution;
GlobalSurfaceAtlasTile* tile = nullptr;
if (tilePixels <= surfaceAtlasData.AtlasPixelsTotal - surfaceAtlasData.AtlasPixelsUsed)
tile = surfaceAtlasData.Atlas.Insert(tileResolution, tileResolution, 0, &surfaceAtlasData, actorObject, tileIndex);
tile = surfaceAtlasData.Atlas.Insert(tileResolution, tileResolution, &surfaceAtlasData, actorObject, tileIndex);
if (tile)
{
if (!object)

View File

@@ -43,8 +43,8 @@ struct ShadowsAtlasRectTile : RectPackNode<uint16>
{
bool IsStatic;
ShadowsAtlasRectTile(uint16 x, uint16 y, uint16 width, uint16 height)
: RectPackNode<uint16>(x, y, width, height)
ShadowsAtlasRectTile(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -682,7 +682,7 @@ bool ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
auto& tile = atlasLight.Tiles[tileIndex];
if (tile.StaticRectTile == nullptr)
{
tile.StaticRectTile = shadows.StaticAtlas.Insert(atlasLight.StaticResolution, atlasLight.StaticResolution, 0, &shadows, true);
tile.StaticRectTile = shadows.StaticAtlas.Insert(atlasLight.StaticResolution, atlasLight.StaticResolution, &shadows, true);
if (!tile.StaticRectTile)
{
// Failed to insert tile to switch back to the default rendering
@@ -1225,7 +1225,7 @@ RETRY_ATLAS_SETUP:
bool failedToInsert = false;
for (int32 tileIndex = 0; tileIndex < atlasLight.TilesNeeded; tileIndex++)
{
auto rectTile = shadows.Atlas.Insert(atlasLight.Resolution, atlasLight.Resolution, 0, &shadows, false);
auto rectTile = shadows.Atlas.Insert(atlasLight.Resolution, atlasLight.Resolution, &shadows, false);
if (!rectTile)
{
// Free any previous tiles that were added

View File

@@ -17,8 +17,8 @@ namespace ShadowsOfMordor
{
Builder::LightmapUVsChart* Chart = nullptr;
Node(int32 x, int32 y, int32 width, int32 height)
: RectPackNode<int32>(x, y, width, height)
Node(Size x, Size y, Size width, Size height)
: RectPackNode(x, y, width, height)
{
}
@@ -63,7 +63,7 @@ namespace ShadowsOfMordor
/// <returns></returns>
Node* Insert(Builder::LightmapUVsChart* chart)
{
return _root.Insert(chart->Width, chart->Height, _settings->ChartsPadding, chart, _settings);
return _root.Insert(chart->Width, chart->Height, chart, _settings);
}
};
};

View File

@@ -31,12 +31,6 @@ struct RectPackNode
, Height(height)
{
}
bool operator<(const RectPackNode& other) const
{
// Sort largest to smallest
return Width * Height > other.Width * other.Height;
}
};
/// <summary>
@@ -118,7 +112,7 @@ public:
/// </summary>
/// <param name="atlasWidth">The atlas width (in pixels).</param>
/// <param name="atlasHeight">The atlas height (in pixels).</param>
/// <param name="bordersPadding">The atlas borders padding (in pixels).</param>
/// <param name="bordersPadding">The nodes padding (in pixels). Distance from node contents to atlas borders or other nodes.</param>
void Init(Size atlasWidth, Size atlasHeight, Size bordersPadding = 0)
{
Width = atlasWidth;
@@ -126,7 +120,7 @@ public:
BordersPadding = bordersPadding;
Nodes.Clear();
FreeNodes.Clear();
Nodes.Add(NodeType(bordersPadding, bordersPadding, atlasWidth - bordersPadding * 2, atlasHeight - bordersPadding * 2));
Nodes.Add(NodeType(bordersPadding, bordersPadding, atlasWidth - bordersPadding, atlasHeight - bordersPadding));
FreeNodes.Add(&Nodes[0]);
}
@@ -145,62 +139,61 @@ public:
/// </summary>
/// <param name="width">The node width (in pixels).</param>
/// <param name="height">The node height (in pixels).</param>
/// <param name="padding">The node padding margin (in pixels) around its contents.</param>
/// <param name="args">The additional arguments.</param>
/// <returns>The node that contains inserted an item or null if failed to find a free space.</returns>
template<class... Args>
NodeType* Insert(Size width, Size height, Size padding, Args&&... args)
NodeType* Insert(Size width, Size height, Args&&... args)
{
NodeType* result = nullptr;
const Size paddedWidth = width + padding;
const Size paddedHeight = height + padding;
const Size paddedWidth = width + BordersPadding;
const Size paddedHeight = height + BordersPadding;
// Search free nodes from back to front and find the one that fits requested item size
// TODO: FreeNodes are sorted so use Binary Search to quickly find the first tile that might have enough space for insert
for (int32 i = FreeNodes.Count() - 1; i >= 0; i--)
{
NodeType& freeNode = *FreeNodes.Get()[i];
if (paddedWidth > freeNode.Width || paddedHeight > freeNode.Height)
NodeType* freeNode = FreeNodes.Get()[i];
if (paddedWidth > freeNode->Width || paddedHeight > freeNode->Height)
{
// Not enough space
continue;
}
// Check if there will be some remaining space left in this node
if (freeNode.Width != paddedWidth || freeNode.Height != paddedHeight)
if (freeNode->Width != width || freeNode->Height != height)
{
// Subdivide this node into up to 2 additional nodes
const Size remainingWidth = freeNode.Width - paddedWidth;
const Size remainingHeight = freeNode.Height - paddedHeight;
const Size remainingWidth = freeNode->Width - paddedWidth;
const Size remainingHeight = freeNode->Height - paddedHeight;
// Split the remaining area around this node into two children
SizeRect bigger, smaller;
if (remainingHeight <= remainingWidth)
{
// Split vertically
smaller = SizeRect(freeNode.X, freeNode.Y + paddedHeight, width, remainingHeight);
bigger = SizeRect(freeNode.X + paddedWidth, freeNode.Y, remainingWidth, freeNode.Height);
smaller = SizeRect(freeNode->X, freeNode->Y + paddedHeight, width, remainingHeight);
bigger = SizeRect(freeNode->X + paddedWidth, freeNode->Y, remainingWidth, freeNode->Height);
}
else
{
// Split horizontally
smaller = SizeRect(freeNode.X + paddedWidth, freeNode.Y, remainingWidth, height);
bigger = SizeRect(freeNode.X, freeNode.Y + paddedHeight, freeNode.Width, remainingHeight);
smaller = SizeRect(freeNode->X + paddedWidth, freeNode->Y, remainingWidth, height);
bigger = SizeRect(freeNode->X, freeNode->Y + paddedHeight, freeNode->Width, remainingHeight);
}
if (smaller.W * smaller.H > bigger.W * bigger.H)
Swap(bigger, smaller);
if (bigger.W * bigger.H > padding)
if (bigger.W * bigger.H > BordersPadding)
AddFreeNode(Nodes.Add(NodeType(bigger.X, bigger.Y, bigger.W, bigger.H)));
if (smaller.W * smaller.H > padding)
if (smaller.W * smaller.H > BordersPadding)
AddFreeNode(Nodes.Add(NodeType(smaller.X, smaller.Y, smaller.W, smaller.H)));
// Shrink to the actual area
freeNode.Width = width;
freeNode.Height = height;
freeNode->Width = width;
freeNode->Height = height;
}
// Insert into this node
result = &freeNode;
result = freeNode;
FreeNodes.RemoveAtKeepOrder(i);
result->OnInsert(Forward<Args>(args)...);
break;