Files
2025-09-18 17:55:52 -04:00

248 lines
9.2 KiB
C++

#include "stdafx.h"
#include "TextureAtlas.h"
#include "VisualEngine.h"
#include "Image.h"
#include "GfxCore/Device.h"
#include "GfxCore/Texture.h"
DYNAMIC_FASTINTVARIABLE(TexAtlasUpdateLineHeight, 150)
namespace RBX
{
namespace Graphics
{
TextureAtlas::TextureAtlas(VisualEngine* visualEngine, unsigned width, unsigned height)
: textureSize(width, height)
, uploadRect(UINT_MAX, 0)
, lastFailedSize(0x7FFF, 0x7FFF)
{
reset();
RBXASSERT((height & (height - 1)) == 0); // glyph texture height has to be power of two!
moduloParam = height - 1;
updateWindow = new unsigned char[textureSize.x * 2 * DFInt::TexAtlasUpdateLineHeight];
texture = visualEngine->getDevice()->createTexture(Texture::Type_2D, Texture::Format_L8, textureSize.x, textureSize.y, 1, 1, Texture::Usage_Dynamic);
}
TextureAtlas::~TextureAtlas()
{
delete[] updateWindow;
}
bool TextureAtlas::getRegion(const boost::uint64_t& id, const TextureRegion*& region)
{
AtlasEntries::const_iterator it = atlasEntries.find(id);
if (it != atlasEntries.end())
{
region = &it->second;
int distance = (int)updateLineY - (int)region->y;
if (distance > ((int)textureSize.y - DFInt::TexAtlasUpdateLineHeight))
return false;
return true;
}
return false;
}
void TextureAtlas::reset()
{
skyline.clear();
skyline.push_back(Vector2uint32(0,0));
skyline.push_back(Vector2uint32(textureSize.x-1, 0)); // sentinel
atlasEntries.clear();
updateWindowPosition = 0;
updateLineY = 0;
}
void TextureAtlas::upload()
{
updateLineY = skyline.begin()->y;
unsigned newUpdateWindowPos = UINT_MAX;
for (SkylineArray::iterator it = skyline.begin(); it != skyline.end(); ++it)
{
updateLineY = std::max(updateLineY, it->y);
newUpdateWindowPos = std::min(newUpdateWindowPos, it->y);
}
// update skyline to fit to update window next frame
newUpdateWindowPos = std::max((int)updateLineY - DFInt::TexAtlasUpdateLineHeight, (int)newUpdateWindowPos);
for (SkylineArray::iterator it = skyline.begin(); it != skyline.end(); ++it)
{
it->y = std::max(it->y, newUpdateWindowPos);
}
skyline[skyline.size()-1].y = newUpdateWindowPos;
if (uploadRect.min < uploadRect.max)
{
unsigned texureMin = uploadRect.min & moduloParam;
unsigned textureMax = uploadRect.max & moduloParam;
unsigned uploadWindowStartIdx = (uploadRect.min - updateWindowPosition) * textureSize.x;
unsigned char* dataPtr = &updateWindow[uploadWindowStartIdx];
unsigned height = textureMax > texureMin ? textureMax - texureMin : textureSize.y - texureMin;
texture.getTexture()->upload(0, 0, RBX::Graphics::TextureRegion(0, texureMin, textureSize.x, height), dataPtr, textureSize.x * (height));
if (textureMax < texureMin)
{
dataPtr = &updateWindow[uploadWindowStartIdx + height * textureSize.x];
texture.getTexture()->upload(0, 0, RBX::Graphics::TextureRegion(0, 0, textureSize.x, textureMax), dataPtr, textureSize.x * textureMax);
}
texture.getTexture()->commitChanges();
}
unsigned updateLineShift = newUpdateWindowPos - updateWindowPosition;
unsigned srcIdx = updateLineShift * textureSize.x;
memmove(updateWindow, &updateWindow[srcIdx], (DFInt::TexAtlasUpdateLineHeight*2 - updateLineShift - 1) * textureSize.x);
updateWindowPosition = newUpdateWindowPos;
uploadRect = Interval1D(UINT_MAX, 0);
lastFailedSize = Vector2int16(0x7FFF, 0x7FFF);
if (updateLineY >= UINT_MAX - DFInt::TexAtlasUpdateLineHeight)
reset();
}
bool TextureAtlas::insert(const boost::uint64_t& id, const Vector2int16& size, unsigned dataPitch, unsigned char* data, const TextureRegion*& outRegion)
{
if (getRegion(id, outRegion))
return true;
Vector2int16 realSize = size + Vector2int16(2,2);
unsigned bestFitY = UINT_MAX;
unsigned bestFitOffset = 0;
unsigned bestFitIdx = 0;
for (unsigned i = 0; 0 < skyline.size() || skyline[i].x >= textureSize.x-1; ++i)
{
unsigned fitEnd = i + 1;
if (fitEnd >= skyline.size())
break;
int fitWidth = 0;
unsigned fitYOffset = 0;
do
{
fitWidth = skyline[fitEnd].x - skyline[i].x;
if (fitWidth >= realSize.x)
{
// fit found
break;
}
if (skyline[fitEnd].y > skyline[i].y)
{
unsigned thisOffset = skyline[fitEnd].y - skyline[i].y;
fitYOffset = std::max(fitYOffset, thisOffset);
}
++fitEnd;
}
while(fitEnd < skyline.size());
if (fitWidth >= realSize.x && skyline[i].y + fitYOffset < bestFitY)
{
bestFitY = skyline[i].y + fitYOffset;
bestFitOffset = fitYOffset;
bestFitIdx = i;
}
}
Vector2uint32 borderPos = skyline[bestFitIdx];
borderPos.y += bestFitOffset;
Vector2uint32 dataPos = borderPos;
dataPos.x += 1;
dataPos.y += 1;
// if it doesnt fit in updateLine skip it (we will render it next frame)
if (skyline[bestFitIdx].y + realSize.y + bestFitOffset > updateLineY + DFInt::TexAtlasUpdateLineHeight)
{
lastFailedSize = size;
return false;
}
// find have much space we need to make in skyline array
unsigned adjustIdx = bestFitIdx;
while(adjustIdx < skyline.size() && skyline[adjustIdx].x < borderPos.x + realSize.x)
++adjustIdx;
// shift the array (and make space for 2 new values]
int offset = bestFitIdx - adjustIdx + 2;
if (offset > 0)
{
skyline.resize(skyline.size() + offset);
for (int i = (skyline.size() - 1 - offset); i >= (int)adjustIdx; --i)
{
skyline[i+offset] = skyline[i];
}
}
if (offset < 0)
{
for(unsigned i = adjustIdx + offset; i < skyline.size() + offset; ++i)
skyline[i] = skyline[i-offset];
skyline.resize(skyline.size() + offset);
}
skyline[bestFitIdx ] = Vector2uint32(borderPos.x, borderPos.y + realSize.y);
skyline[bestFitIdx + 1] = Vector2uint32(borderPos.x + realSize.x, borderPos.y);
// cut duplicates at the end
if (skyline[skyline.size() - 1].x == skyline[skyline.size()-2].x)
skyline.resize(skyline.size() - 1);
// clean borders (2px border to clean, we have to cleanup after previous glyph too)
Vector2uint32 borderPosUpdateWindow = borderPos;
borderPosUpdateWindow.y -= updateWindowPosition;
memset(&updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x, (int)borderPosUpdateWindow.y - 1 )], 0, realSize.x);
memset(&updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x, (int)borderPosUpdateWindow.y )], 0, realSize.x);
memset(&updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x, (int)borderPosUpdateWindow.y + realSize.y )], 0, realSize.x);
memset(&updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x, (int)borderPosUpdateWindow.y + realSize.y - 1)], 0, realSize.x);
for (int i = 0; i < realSize.y; ++i)
{
updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x - 1, (int)borderPosUpdateWindow.y + i)] = 0;
updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x, borderPosUpdateWindow.y + i)] = 0;
updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x + realSize.x - 1, (int)borderPosUpdateWindow.y + i)] = 0;
updateWindow[getUpdateWindowIdx((int)borderPosUpdateWindow.x + realSize.x, (int)borderPosUpdateWindow.y + i)] = 0;
}
// insert the data to update Window
for (int i = 0; i < size.y; ++i)
{
unsigned yPos = dataPos.y + i - updateWindowPosition;
RBXASSERT((int)yPos < DFInt::TexAtlasUpdateLineHeight * 2);
unsigned dataIdx = (yPos * textureSize.x) + dataPos.x;
unsigned sourceIdx = (i * dataPitch);
memcpy(&updateWindow[dataIdx], &data[sourceIdx], size.x);
}
uploadRect.min = std::min(uploadRect.min, (unsigned)(dataPos.y - 1)); // -1 to include 2px border cleanup
uploadRect.max = std::max(uploadRect.max, (unsigned)(dataPos.y - 1 + realSize.y)); // +1 for same reason
atlasEntries[id] = TextureRegion(dataPos.x, dataPos.y, size.x, size.y);
outRegion = &atlasEntries[id];
return true;
}
unsigned TextureAtlas::getUpdateWindowIdx(int x, int y)
{
return std::max(0, std::min(DFInt::TexAtlasUpdateLineHeight*2-1, y)) * textureSize.x + std::max(0, std::min(x, (int)textureSize.x - 1));
}
}
}