Browse Source

Reformatted code of `SpriteSet` class.

Lê Duy Quang 10 months ago
parent
commit
b60ddd1ca2

+ 7 - 7
Source/Core/FontEngineDefault/FontFaceLayer.cpp

@@ -131,7 +131,7 @@ bool FontFaceLayer::Generate(
 			{
 				if (glyph.color_format == ColorFormat::RGBA8)
 				{
-					sprite_set_handle = sprite_set.add(glyph_dimensions.x, glyph_dimensions.y, glyph.bitmap_data);
+					sprite_set_handle = sprite_set.Add(glyph_dimensions.x, glyph_dimensions.y, glyph.bitmap_data);
 				}
 				else
 				{
@@ -140,19 +140,19 @@ bool FontFaceLayer::Generate(
 					for (int i = 0; i < glyph_pixel_count; ++i)
 						for (int c = 0; c < 4; ++c)
 							unpacked_bitmap[i * 4 + c] = glyph.bitmap_data[i];
-					sprite_set_handle = sprite_set.add(glyph_dimensions.x, glyph_dimensions.y, unpacked_bitmap.data());
+					sprite_set_handle = sprite_set.Add(glyph_dimensions.x, glyph_dimensions.y, unpacked_bitmap.data());
 				}
 			}
 			else
 			{
 				Vector<unsigned char> processed_bitmap(glyph_dimensions.x * glyph_dimensions.y * 4);
 				effect->GenerateGlyphTexture(processed_bitmap.data(), glyph_dimensions, glyph_dimensions.x * 4, glyph);
-				sprite_set_handle = sprite_set.add(glyph_dimensions.x, glyph_dimensions.y, processed_bitmap.data());
+				sprite_set_handle = sprite_set.Add(glyph_dimensions.x, glyph_dimensions.y, processed_bitmap.data());
 			}
 			sprite_set_handle_map.emplace(character, sprite_set_handle);
-			const auto sprite_data = sprite_set.get(sprite_set_handle);
+			const auto sprite_data = sprite_set.Get(sprite_set_handle);
 			// Set the character's texture index.
-			box.texture_index = sprite_data.textureId;
+			box.texture_index = sprite_data.texture_id;
 			// Generate the character's texture coordinates.
 			const float texture_size = 1024.f;
 			box.texcoords[0].x = static_cast<float>(sprite_data.x) / texture_size;
@@ -192,7 +192,7 @@ bool FontFaceLayer::Generate(
 		}
 		*/
 
-		sprite_set_textures = sprite_set.getTextures();
+		sprite_set_textures = sprite_set.GetTextures();
 		const FontEffect* effect_ptr = effect.get();
 		const int handle_version = handle->GetVersion();
 
@@ -300,7 +300,7 @@ void FontFaceLayer::RemoveGlyphs(const Vector<Character> &characters)
 	for (const auto character : characters)
 	{
 		character_boxes.erase(character);
-		sprite_set.remove(sprite_set_handle_map[character]);
+		sprite_set.Remove(sprite_set_handle_map[character]);
 		sprite_set_handle_map.erase(character);
 	}
 }

+ 1 - 1
Source/Core/FontEngineDefault/LruList.h

@@ -170,4 +170,4 @@ unsigned LruList<T>::getLastEntryAge() const {
 	return size == 0 ? 0 : currentEpoch - pool[tailIndex].lastUsed;
 }
 
-#endif // GRAPHICS_LRULIST_H
+#endif // GRAPHICS_LRULIST_H

+ 380 - 283
Source/Core/FontEngineDefault/SpriteSet.cpp

@@ -1,396 +1,493 @@
 #include <algorithm>
 
+#include "../../../Include/RmlUi/Core/Types.h"
+
 #include "SpriteSet.h"
 
+namespace Rml {
+
 namespace {
-	constexpr unsigned nullIndex = -1;
-	constexpr unsigned maxChangedPixels = 256 * 256;
-	constexpr unsigned splitThreshold = 8;
+	constexpr unsigned int null_index = static_cast<unsigned int>(-1);
+	constexpr unsigned int max_changed_pixels = 256 * 256;
+	constexpr unsigned int split_threshold = 8;
 
 	template<typename T>
-	void initializePool(std::vector<T> &pool) {
-		const unsigned
-			poolSize = static_cast<unsigned>(pool.size()),
-			lastPoolIndex = poolSize - 1;
-		for (unsigned i = 0; i != lastPoolIndex; ++i) pool[i].nextIndex = i + 1;
-		pool[lastPoolIndex].nextIndex = nullIndex;
+	void InitializePool(Vector<T> &pool)
+	{
+		const unsigned int pool_size = static_cast<unsigned int>(pool.size());
+		const unsigned int last_pool_index = pool_size - 1;
+		for (unsigned int i = 0; i < last_pool_index; ++i)
+			pool[i].next_index = i + 1;
+		pool[last_pool_index].next_index = null_index;
 	}
 
 	template<typename T>
-	unsigned allocateEntry(std::vector<T> &pool, unsigned &nextFreeIndex) {
-		if (nextFreeIndex == nullIndex) {
-			const unsigned
-				oldPoolSize = static_cast<unsigned>(pool.size()),
-				newPoolSize = oldPoolSize << 1,
-				lastPoolIndex = newPoolSize - 1;
-			pool.resize(newPoolSize);
-			for (unsigned i = oldPoolSize; i != lastPoolIndex; ++i) pool[i].nextIndex = i + 1;
-			pool[lastPoolIndex].nextIndex = nullIndex;
-			nextFreeIndex = oldPoolSize;
+	unsigned int AllocateEntry(Vector<T>& pool, unsigned int& next_free_index)
+	{
+		if (next_free_index == null_index)
+		{
+			const unsigned int old_pool_size = static_cast<unsigned int>(pool.size());
+			const unsigned int new_pool_size = old_pool_size << 1;
+			const unsigned int last_pool_index = new_pool_size - 1;
+			pool.resize(new_pool_size);
+			for (unsigned int i = old_pool_size; i < last_pool_index; ++i)
+				pool[i].next_index = i + 1;
+			pool[last_pool_index].next_index = null_index;
+			next_free_index = old_pool_size;
 		}
-		const unsigned index = nextFreeIndex;
-		nextFreeIndex = pool[index].nextIndex;
+		const unsigned int index = next_free_index;
+		next_free_index = pool[index].next_index;
 		return index;
 	}
 
 	template<typename T>
-	void freeEntry(std::vector<T> &pool, unsigned &nextFreeIndex, const unsigned index) {
-		pool[index].nextIndex = nextFreeIndex;
-		nextFreeIndex = index;
+	void FreeEntry(Vector<T>& pool, unsigned int& next_free_index, const unsigned int index)
+	{
+		pool[index].next_index = next_free_index;
+		next_free_index = index;
 	}
 }
 
-SpriteSet::SpriteSet(const unsigned bytesPerPixel, const unsigned pageSize, const unsigned spritePadding):
-	bytesPerPixel(bytesPerPixel), pageSize(pageSize), spritePadding(spritePadding)
+SpriteSet::SpriteSet(
+	const unsigned int bytes_per_pixel, const unsigned int page_size, const unsigned int sprite_padding
+) : bytes_per_pixel(bytes_per_pixel), page_size(page_size), sprite_padding(sprite_padding)
 {
-	initializePool(pagePool);
-	initializePool(shelfPool);
-	initializePool(slotPool);
+	InitializePool(page_pool);
+	InitializePool(shelf_pool);
+	InitializePool(slot_pool);
 }
 
-void SpriteSet::tick() {
-	unsigned changedPixels = 0;
+void SpriteSet::Tick()
+{
+	unsigned int changed_pixels = 0;
 	// Try compaction by moving sprites from the last page to the first page.
-	while (changedPixels <= maxChangedPixels && firstPageIndex != lastPageIndex) {
-		const Page &sourcePage = pagePool[lastPageIndex];
-		unsigned sourceShelfIndex = sourcePage.firstShelfIndex;
-		while (!shelfPool[sourceShelfIndex].allocated) sourceShelfIndex = shelfPool[sourceShelfIndex].nextIndex;
-		const Shelf &sourceShelf = shelfPool[sourceShelfIndex];
-		unsigned sourceSlotIndex = sourceShelf.firstSlotIndex;
-		while (!slotPool[sourceSlotIndex].allocated) sourceSlotIndex = slotPool[sourceSlotIndex].nextIndex;
-		const Slot &sourceSlot = slotPool[sourceSlotIndex];
-		const unsigned destinationSlotIndex = tryAllocateInPage(firstPageIndex, sourceSlot.width, sourceSlot.height);
-		if (destinationSlotIndex == nullIndex) break;
-		const Slot &destinationSlot = slotPool[destinationSlotIndex];
-		const Shelf &destinationShelf = shelfPool[destinationSlot.shelfIndex];
-		Page &destinationPage = pagePool[firstPageIndex];
-		const unsigned
-			sourceX = sourceSlot.x, sourceY = sourceShelf.y,
-			destinationX = destinationSlot.x, destinationY = destinationShelf.y,
-			width = sourceSlot.width, height = sourceSlot.height;
-		for (unsigned localY = 0; localY != height; ++localY) {
-			const auto dataStart = sourcePage.textureData->begin() + ((sourceY + localY) * pageSize + sourceX);
+	while (changed_pixels <= max_changed_pixels && first_page_index != last_page_index)
+	{
+		const Page& source_page = page_pool[last_page_index];
+		unsigned int source_shelf_index = source_page.first_shelf_index;
+		while (!shelf_pool[source_shelf_index].allocated)
+			source_shelf_index = shelf_pool[source_shelf_index].next_index;
+		const Shelf& source_shelf = shelf_pool[source_shelf_index];
+		unsigned int source_slot_index = source_shelf.first_slot_index;
+		while (!slot_pool[source_slot_index].allocated)
+			source_slot_index = slot_pool[source_slot_index].next_index;
+		const Slot& source_slot = slot_pool[source_slot_index];
+		const unsigned int destination_slot_index = TryAllocateInPage(
+			first_page_index, source_slot.width, source_slot.height
+		);
+		if (destination_slot_index == null_index)
+			break;
+		const Slot& destination_slot = slot_pool[destination_slot_index];
+		const Shelf& destination_shelf = shelf_pool[destination_slot.shelf_index];
+		Page& destination_page = page_pool[first_page_index];
+		const unsigned int source_x = source_slot.x;
+		const unsigned int source_y = source_shelf.y;
+		const unsigned int destination_x = destination_slot.x;
+		const unsigned int destination_y = destination_shelf.y;
+		const unsigned int width = source_slot.width;
+		const unsigned int height = source_slot.height;
+		for (unsigned int local_y = 0; local_y != height; ++local_y)
+		{
+			const auto data_start = source_page.texture_data->begin() + ((source_y + local_y) * page_size + source_x);
 			std::copy(
-				dataStart, dataStart + width,
-				destinationPage.textureData->begin() + ((destinationY + localY) * pageSize + destinationX)
+				data_start, data_start + width,
+				destination_page.texture_data->begin() + ((destination_y + local_y) * page_size + destination_x)
 			);
 		}
-		destinationPage.firstDirtyY = std::min(destinationPage.firstDirtyY, destinationY);
-		destinationPage.pastLastDirtyY = std::max(destinationPage.pastLastDirtyY, destinationY + height);
-		destinationPage.firstDirtyX = std::min(destinationPage.firstDirtyX, destinationX);
-		destinationPage.pastLastDirtyX = std::max(destinationPage.pastLastDirtyX, destinationX + width);
-		changedPixels += remove(sourceSlotIndex);
-		if (migrationCallback) migrationCallback(sourceSlotIndex, {destinationSlotIndex, destinationSlot.epoch});
+		destination_page.first_dirty_y = std::min(destination_page.first_dirty_y, destination_y);
+		destination_page.past_last_dirty_y = std::max(destination_page.past_last_dirty_y, destination_y + height);
+		destination_page.first_dirty_x = std::min(destination_page.first_dirty_x, destination_x);
+		destination_page.past_last_dirty_x = std::max(destination_page.past_last_dirty_x, destination_x + width);
+		changed_pixels += Remove(source_slot_index);
+		if (migration_callback)
+			migration_callback(source_slot_index, {destination_slot_index, destination_slot.epoch});
 	}
 }
 
-SpriteSet::Handle SpriteSet::add(
-	const unsigned width, const unsigned height, const unsigned char *const data, const unsigned rowStride
-) {
-	const unsigned paddedWidth = width + spritePadding * 2, paddedHeight = height + spritePadding * 2;
-	const unsigned slotIndex = allocate(paddedWidth, paddedHeight);
-	const Slot &slot = slotPool[slotIndex];
-	const Shelf &shelf = shelfPool[slot.shelfIndex];
-	Page &page = pagePool[shelf.pageIndex];
+SpriteSet::Handle SpriteSet::Add(
+	const unsigned int width, const unsigned int height, const unsigned char* const data, const unsigned int row_stride
+)
+{
+	const unsigned int padded_width = width + sprite_padding * 2;
+	const unsigned int padded_height = height + sprite_padding * 2;
+	const unsigned int slot_index = Allocate(padded_width, padded_height);
+	const Slot& slot = slot_pool[slot_index];
+	const Shelf& shelf = shelf_pool[slot.shelf_index];
+	Page& page = page_pool[shelf.page_index];
 	for (
-		unsigned i = 0, topPaddingY = shelf.y, bottomPaddingY = shelf.y + paddedHeight - 1;
-		i != spritePadding; ++i, ++topPaddingY, --bottomPaddingY
-	) {
-		auto textureStart = page.textureData->begin() + (topPaddingY * pageSize + slot.x) * bytesPerPixel;
-		std::fill(textureStart, textureStart + paddedWidth * bytesPerPixel, 0);
-		textureStart = page.textureData->begin() + (bottomPaddingY * pageSize + slot.x) * bytesPerPixel;
-		std::fill(textureStart, textureStart + paddedWidth * bytesPerPixel, 0);
+		unsigned int i = 0, top_padding_y = shelf.y, bottom_padding_y = shelf.y + padded_height - 1;
+		i < sprite_padding; ++i, ++top_padding_y, --bottom_padding_y
+	)
+	{
+		auto texture_start = page.texture_data->begin() + (top_padding_y * page_size + slot.x) * bytes_per_pixel;
+		std::fill(texture_start, texture_start + padded_width * bytes_per_pixel, 0);
+		texture_start = page.texture_data->begin() + (bottom_padding_y * page_size + slot.x) * bytes_per_pixel;
+		std::fill(texture_start, texture_start + padded_width * bytes_per_pixel, 0);
 	}
-	const unsigned textureY = shelf.y + spritePadding;
-	for (unsigned localY = 0; localY != height; ++localY) {
-		const unsigned char *const dataStart = data + localY * rowStride * bytesPerPixel;
-		const auto textureStart = page.textureData->begin() + ((textureY + localY) * pageSize + slot.x) * bytesPerPixel;
-		std::fill(textureStart, textureStart + spritePadding * bytesPerPixel, 0);
+	const unsigned int texture_y = shelf.y + sprite_padding;
+	for (unsigned int local_y = 0; local_y != height; ++local_y)
+	{
+		const unsigned char* const data_start = data + local_y * row_stride * bytes_per_pixel;
+		const auto texture_start = page.texture_data->begin()
+			+ ((texture_y + local_y) * page_size + slot.x) * bytes_per_pixel;
+		std::fill(texture_start, texture_start + sprite_padding * bytes_per_pixel, 0);
 		std::fill(
-			textureStart + (spritePadding + width) * bytesPerPixel, textureStart + paddedWidth * bytesPerPixel, 0
+			texture_start + (sprite_padding + width) * bytes_per_pixel, texture_start + padded_width * bytes_per_pixel, 0
 		);
-		std::copy(dataStart, dataStart + width * bytesPerPixel, textureStart + spritePadding * bytesPerPixel);
+		std::copy(data_start, data_start + width * bytes_per_pixel, texture_start + sprite_padding * bytes_per_pixel);
 	}
-	page.firstDirtyY = std::min(page.firstDirtyY, shelf.y);
-	page.pastLastDirtyY = std::max(page.pastLastDirtyY, shelf.y + paddedHeight);
-	page.firstDirtyX = std::min(page.firstDirtyX, slot.x);
-	page.pastLastDirtyX = std::max(page.pastLastDirtyX, slot.x + paddedWidth);
-	return {slotIndex, slot.epoch};
+	page.first_dirty_y = std::min(page.first_dirty_y, shelf.y);
+	page.past_last_dirty_y = std::max(page.past_last_dirty_y, shelf.y + padded_height);
+	page.first_dirty_x = std::min(page.first_dirty_x, slot.x);
+	page.past_last_dirty_x = std::max(page.past_last_dirty_x, slot.x + padded_width);
+	return {slot_index, slot.epoch};
 }
 
-unsigned SpriteSet::allocate(const unsigned width, const unsigned height) {
+unsigned int SpriteSet::Allocate(const unsigned int width, const unsigned int height)
+{
 	// Try to allocate in an existing page.
-	if (firstPageIndex != nullIndex) {
-		unsigned pageIndex = firstPageIndex;
-		while (true) {
-			const unsigned slotIndex = tryAllocateInPage(pageIndex, width, height);
-			if (slotIndex != nullIndex) return slotIndex;
-			if (pageIndex == lastPageIndex) break;
-			pageIndex = pagePool[pageIndex].nextIndex;
+	if (first_page_index != null_index)
+	{
+		unsigned int page_index = first_page_index;
+		while (true)
+		{
+			const unsigned int slot_index = TryAllocateInPage(page_index, width, height);
+			if (slot_index != null_index)
+				return slot_index;
+			if (page_index == last_page_index)
+				break;
+			page_index = page_pool[page_index].next_index;
 		}
 	}
 
 	// No page could fit, allocate a new page.
-	if (firstPageIndex == nullIndex) {
-		firstPageIndex = lastPageIndex;
-		Page &page = pagePool[lastPageIndex];
-		page.previousIndex = nullIndex;
-	} else {
-		unsigned pageIndex = pagePool[lastPageIndex].nextIndex;
-		if (pageIndex == nullIndex) {
-			const unsigned
-				oldPoolSize = static_cast<unsigned>(pagePool.size()),
-				newPoolSize = oldPoolSize << 1,
-				lastPoolIndex = newPoolSize - 1;
-			pagePool.resize(newPoolSize);
-			for (unsigned i = oldPoolSize; i != lastPoolIndex; ++i) pagePool[i].nextIndex = i + 1;
-			pagePool[lastPoolIndex].nextIndex = nullIndex;
-			pagePool[lastPageIndex].nextIndex = oldPoolSize;
-			pageIndex = oldPoolSize;
+	if (first_page_index == null_index)
+	{
+		first_page_index = last_page_index;
+		Page& page = page_pool[last_page_index];
+		page.previous_index = null_index;
+	}
+	else
+	{
+		unsigned int page_index = page_pool[last_page_index].next_index;
+		if (page_index == null_index)
+		{
+			const unsigned int old_pool_size = static_cast<unsigned int>(page_pool.size());
+			const unsigned int new_pool_size = old_pool_size << 1;
+			const unsigned int last_pool_index = new_pool_size - 1;
+			page_pool.resize(new_pool_size);
+			for (unsigned int i = old_pool_size; i != last_pool_index; ++i)
+				page_pool[i].next_index = i + 1;
+			page_pool[last_pool_index].next_index = null_index;
+			page_pool[last_page_index].next_index = old_pool_size;
+			page_index = old_pool_size;
 		}
-		pagePool[pageIndex].previousIndex = lastPageIndex;
-		lastPageIndex = pageIndex;
+		page_pool[page_index].previous_index = last_page_index;
+		last_page_index = page_index;
 	}
 
-	Page &page = pagePool[lastPageIndex];
-	page.textureId = pageCount;
-	page.textureData = std::make_unique<std::vector<unsigned char>>(pageSize * pageSize * bytesPerPixel);
-	page.firstDirtyY = pageSize;
-	page.pastLastDirtyY = 0;
-	page.firstDirtyX = pageSize;
-	page.pastLastDirtyX = 0;
+	Page& page = page_pool[last_page_index];
+	page.texture_id = page_count;
+	page.texture_data = MakeUnique<Vector<unsigned char>>(page_size * page_size * bytes_per_pixel);
+	page.first_dirty_y = page_size;
+	page.past_last_dirty_y = 0;
+	page.first_dirty_x = page_size;
+	page.past_last_dirty_x = 0;
 
-	const unsigned shelfIndex = allocateEntry<Shelf>(shelfPool, nextFreeShelfIndex);
-	const unsigned slotIndex = allocateEntry<Slot>(slotPool, nextFreeSlotIndex);
-	slotPool[slotIndex] = {shelfIndex, pageCount, 0, 0, pageSize, 0, 0, nullIndex, nullIndex, nullIndex, nullIndex, 0, false};
-	shelfPool[shelfIndex] = {lastPageIndex, 0, pageSize, nullIndex, nullIndex, slotIndex, slotIndex, false};
-	page.firstShelfIndex = shelfIndex;
-	++pageCount;
-	return tryAllocateInPage(lastPageIndex, width, height);
+	const unsigned int shelf_index = AllocateEntry<Shelf>(shelf_pool, next_free_shelf_index);
+	const unsigned int slot_index = AllocateEntry<Slot>(slot_pool, next_free_slot_index);
+	slot_pool[slot_index] = {shelf_index, page_count, 0, 0, page_size, 0, 0, null_index, null_index, null_index, null_index, 0, false};
+	shelf_pool[shelf_index] = {last_page_index, 0, page_size, null_index, null_index, slot_index, slot_index, false};
+	page.first_shelf_index = shelf_index;
+	++page_count;
+	return TryAllocateInPage(last_page_index, width, height);
 }
 
-unsigned SpriteSet::tryAllocateInPage(const unsigned pageIndex, const unsigned width, const unsigned height) {
-	Page &page = pagePool[pageIndex];
-	unsigned selectedShelfIndex = nullIndex, selectedSlotIndex = nullIndex, selectedShelfHeight = -1;
+unsigned int SpriteSet::TryAllocateInPage(const unsigned int page_index, const unsigned int width, const unsigned int height)
+{
+	Page& page = page_pool[page_index];
+	unsigned int selected_shelf_index = null_index;
+	unsigned int selected_slot_index = null_index;
+	unsigned int selected_shelf_height = static_cast<unsigned int>(-1);
 	for (
-		unsigned shelfIndex = page.firstShelfIndex;
-		shelfIndex != nullIndex; shelfIndex = shelfPool[shelfIndex].nextIndex
-	) {
-		const Shelf &shelf = shelfPool[shelfIndex];
+		unsigned int shelf_index = page.first_shelf_index;
+		shelf_index != null_index; shelf_index = shelf_pool[shelf_index].next_index
+	)
+	{
+		const Shelf& shelf = shelf_pool[shelf_index];
 		if (
-			shelf.height < height || shelf.height >= selectedShelfHeight
+			shelf.height < height || shelf.height >= selected_shelf_height
 			|| (shelf.allocated && shelf.height > height * 3 / 2)
-		) continue;
+		)
+			continue;
 		bool found = false;
 		for (
-			unsigned slotIndex = shelf.firstFreeSlotIndex;
-			slotIndex != nullIndex; slotIndex = slotPool[slotIndex].nextFreeIndex
-		) {
-			const Slot &slot = slotPool[slotIndex];
-			if (slot.width < width) continue;
-			selectedShelfIndex = shelfIndex;
-			selectedSlotIndex = slotIndex;
-			selectedShelfHeight = shelf.height;
+			unsigned int slot_index = shelf.first_free_slot_index;
+			slot_index != null_index; slot_index = slot_pool[slot_index].next_free_index
+		)
+		{
+			const Slot& slot = slot_pool[slot_index];
+			if (slot.width < width)
+				continue;
+			selected_shelf_index = shelf_index;
+			selected_slot_index = slot_index;
+			selected_shelf_height = shelf.height;
 			found = true;
 			break;
 		}
-		if (found && shelf.height == height) break;
+		if (found && shelf.height == height)
+			break;
 	}
-	if (selectedSlotIndex == nullIndex) return nullIndex;
-	Shelf *shelf = &shelfPool[selectedShelfIndex];
-	if (!shelf->allocated) {
+	if (selected_slot_index == null_index)
+		return null_index;
+	Shelf* shelf = &shelf_pool[selected_shelf_index];
+	if (!shelf->allocated)
+	{
 		shelf->allocated = true;
-		if (shelf->height - height >= splitThreshold) {
-			const unsigned newShelfIndex = allocateEntry<Shelf>(shelfPool, nextFreeShelfIndex);
-			const unsigned newSlotIndex = allocateEntry<Slot>(slotPool, nextFreeSlotIndex);
-			shelf = &shelfPool[selectedShelfIndex];
-			slotPool[newSlotIndex] = {
-				newShelfIndex, page.textureId, 0, shelf->y + height, pageSize, 0, 0, nullIndex, nullIndex, nullIndex, nullIndex, 0, false
+		if (shelf->height - height >= split_threshold)
+		{
+			const unsigned int new_shelf_index = AllocateEntry<Shelf>(shelf_pool, next_free_shelf_index);
+			const unsigned int new_slot_index = AllocateEntry<Slot>(slot_pool, next_free_slot_index);
+			shelf = &shelf_pool[selected_shelf_index];
+			slot_pool[new_slot_index] = {
+				new_shelf_index, page.texture_id, 0, shelf->y + height, page_size, 0, 0, null_index, null_index, null_index, null_index, 0, false
 			};
-			shelfPool[newShelfIndex] = {
-				pageIndex, shelf->y + height, shelf->height - height,
-				selectedShelfIndex, shelf->nextIndex,  newSlotIndex, newSlotIndex, false
+			shelf_pool[new_shelf_index] = {
+				page_index, shelf->y + height, shelf->height - height,
+				selected_shelf_index, shelf->next_index,  new_slot_index, new_slot_index, false
 			};
-			if (shelf->nextIndex != nullIndex) shelfPool[shelf->nextIndex].previousIndex = newShelfIndex;
-			shelf->nextIndex = newShelfIndex;
+			if (shelf->next_index != null_index)
+				shelf_pool[shelf->next_index].previous_index = new_shelf_index;
+			shelf->next_index = new_shelf_index;
 			shelf->height = height;
 		}
 	}
-	Slot *slot = &slotPool[selectedSlotIndex];
-	if (slot->width - width >= splitThreshold) {
-		const unsigned newSlotIndex = allocateEntry<Slot>(slotPool, nextFreeSlotIndex);
-		slot = &slotPool[selectedSlotIndex];
-		slotPool[newSlotIndex] = {
-			selectedShelfIndex, page.textureId, slot->x + width, shelf->y, slot->width - width, 0, 0,
-			selectedSlotIndex, slot->nextIndex, slot->previousFreeIndex, slot->nextFreeIndex, 0, false
+	Slot* slot = &slot_pool[selected_slot_index];
+	if (slot->width - width >= split_threshold)
+	{
+		const unsigned int new_slot_index = AllocateEntry<Slot>(slot_pool, next_free_slot_index);
+		slot = &slot_pool[selected_slot_index];
+		slot_pool[new_slot_index] = {
+			selected_shelf_index, page.texture_id, slot->x + width, shelf->y, slot->width - width, 0, 0,
+			selected_slot_index, slot->next_index, slot->previous_free_index, slot->next_free_index, 0, false
 		};
-		if (slot->nextIndex != nullIndex) slotPool[slot->nextIndex].previousIndex = newSlotIndex;
-		slot->nextIndex = newSlotIndex;
-		if (slot->previousFreeIndex == nullIndex) shelf->firstFreeSlotIndex = newSlotIndex;
-		else slotPool[slot->previousFreeIndex].nextFreeIndex = newSlotIndex;
-		if (slot->nextFreeIndex != nullIndex) slotPool[slot->nextFreeIndex].previousFreeIndex = newSlotIndex;
+		if (slot->next_index != null_index)
+			slot_pool[slot->next_index].previous_index = new_slot_index;
+		slot->next_index = new_slot_index;
+		if (slot->previous_free_index == null_index)
+			shelf->first_free_slot_index = new_slot_index;
+		else
+			slot_pool[slot->previous_free_index].next_free_index = new_slot_index;
+		if (slot->next_free_index != null_index)
+			slot_pool[slot->next_free_index].previous_free_index = new_slot_index;
 		slot->width = width;
-	} else {
-		if (slot->previousFreeIndex == nullIndex) shelf->firstFreeSlotIndex = slot->nextFreeIndex;
-		else slotPool[slot->previousFreeIndex].nextFreeIndex = slot->nextFreeIndex;
-		if (slot->nextFreeIndex != nullIndex) slotPool[slot->nextFreeIndex].previousFreeIndex = slot->previousFreeIndex;
+	}
+	else
+	{
+		if (slot->previous_free_index == null_index)
+			shelf->first_free_slot_index = slot->next_free_index;
+		else
+			slot_pool[slot->previous_free_index].next_free_index = slot->next_free_index;
+		if (slot->next_free_index != null_index)
+			slot_pool[slot->next_free_index].previous_free_index = slot->previous_free_index;
 	}
 	slot->allocated = true;
-	slot->actualWidth = width;
+	slot->actual_width = width;
 	slot->height = height;
-	slot->epoch = currentEpoch;
-	if (pageIndex == firstPageIndex) firstPageAllocatedPixels += width * shelf->height;
-	return selectedSlotIndex;
+	slot->epoch = current_epoch;
+	if (page_index == first_page_index)
+		first_page_allocated_pixels += width * shelf->height;
+	return selected_slot_index;
 }
 
-unsigned SpriteSet::remove(const unsigned slotIndex) {
-	Slot &slot = slotPool[slotIndex];
-	Shelf &shelf = shelfPool[slot.shelfIndex];
-	const unsigned pageIndex = shelf.pageIndex;
-	Page &page = pagePool[pageIndex];
+unsigned int SpriteSet::Remove(const unsigned int slot_index)
+{
+	Slot& slot = slot_pool[slot_index];
+	Shelf& shelf = shelf_pool[slot.shelf_index];
+	const unsigned int page_index = shelf.page_index;
+	Page& page = page_pool[page_index];
 
-	for (int offsetY = 0; offsetY < slot.height; ++offsetY) {
-		for (int offsetX = 0; offsetX < slot.width; ++offsetX) {
-			const auto pixel = page.textureData->begin() + ((slot.y + offsetY) * pageSize + slot.x + offsetX) * 4;
+	/* DEBUG: Fill the removed area with blue.
+	for (unsigned int offset_y = 0; offset_y < slot.height; ++offset_y)
+	{
+		for (unsigned int offset_x = 0; offset_x < slot.width; ++offset_x)
+		{
+			const auto pixel = page.texture_data->begin() + ((slot.y + offset_y) * page_size + slot.x + offset_x) * 4;
 			pixel[0] = 0;
 			pixel[1] = 0;
 			pixel[2] = 255;
 			pixel[3] = 255;
 		}
 	}
+	*/
 
-	const unsigned slotPixels = slot.width * slot.height;
+	const unsigned int slot_pixels = slot.width * slot.height;
 	slot.allocated = false;
-	slot.previousFreeIndex = nullIndex;
-	if (shelf.firstFreeSlotIndex == nullIndex) {
-		slot.nextFreeIndex = nullIndex;
-	} else {
-		slot.nextFreeIndex = shelf.firstFreeSlotIndex;
-		slotPool[shelf.firstFreeSlotIndex].previousFreeIndex = slotIndex;
+	slot.previous_free_index = null_index;
+	if (shelf.first_free_slot_index == null_index)
+	{
+		slot.next_free_index = null_index;
+	}
+	else
+	{
+		slot.next_free_index = shelf.first_free_slot_index;
+		slot_pool[shelf.first_free_slot_index].previous_free_index = slot_index;
 	}
-	shelf.firstFreeSlotIndex = slotIndex;
+	shelf.first_free_slot_index = slot_index;
 	++slot.epoch;
 
 	// Merge consecutive empty slots.
-	if (slot.nextIndex != nullIndex) {
-		Slot &nextSlot = slotPool[slot.nextIndex];
-		if (!nextSlot.allocated) {
-			slot.width += nextSlot.width;
-			const unsigned nextIndex = slot.nextIndex;
-			slot.nextIndex = nextSlot.nextIndex;
-			if (nextSlot.previousFreeIndex != nullIndex)
-				slotPool[nextSlot.previousFreeIndex].nextFreeIndex = nextSlot.nextFreeIndex;
-			if (nextSlot.nextFreeIndex != nullIndex)
-				slotPool[nextSlot.nextFreeIndex].previousFreeIndex = nextSlot.previousFreeIndex;
-			freeEntry<Slot>(slotPool, nextFreeSlotIndex, nextIndex);
-			if (slot.nextIndex != nullIndex) slotPool[slot.nextIndex].previousIndex = slotIndex;
+	if (slot.next_index != null_index)
+	{
+		Slot& next_slot = slot_pool[slot.next_index];
+		if (!next_slot.allocated)
+		{
+			slot.width += next_slot.width;
+			const unsigned int next_index = slot.next_index;
+			slot.next_index = next_slot.next_index;
+			if (next_slot.previous_free_index != null_index)
+				slot_pool[next_slot.previous_free_index].next_free_index = next_slot.next_free_index;
+			if (next_slot.next_free_index != null_index)
+				slot_pool[next_slot.next_free_index].previous_free_index = next_slot.previous_free_index;
+			FreeEntry<Slot>(slot_pool, next_free_slot_index, next_index);
+			if (slot.next_index != null_index)
+				slot_pool[slot.next_index].previous_index = slot_index;
 		}
 	}
-	if (slot.previousIndex != nullIndex) {
-		Slot &previousSlot = slotPool[slot.previousIndex];
-		if (!previousSlot.allocated) {
-			slot.x -= previousSlot.width;
-			slot.width += previousSlot.width;
-			const unsigned previousIndex = slot.previousIndex;
-			slot.previousIndex = previousSlot.previousIndex;
-			if (previousSlot.previousFreeIndex != nullIndex)
-				slotPool[previousSlot.previousFreeIndex].nextFreeIndex = previousSlot.nextFreeIndex;
-			if (previousSlot.nextFreeIndex != nullIndex)
-				slotPool[previousSlot.nextFreeIndex].previousFreeIndex = previousSlot.previousFreeIndex;
-			freeEntry<Slot>(slotPool, nextFreeSlotIndex, previousIndex);
-			if (slot.previousIndex == nullIndex) {
-				shelf.firstSlotIndex = slotIndex;
-				if (slot.nextIndex == nullIndex) shelf.allocated = false;
-			} else {
-				slotPool[slot.previousIndex].nextIndex = slotIndex;
+	if (slot.previous_index != null_index)
+	{
+		Slot& previous_slot = slot_pool[slot.previous_index];
+		if (!previous_slot.allocated)
+		{
+			slot.x -= previous_slot.width;
+			slot.width += previous_slot.width;
+			const unsigned int previous_index = slot.previous_index;
+			slot.previous_index = previous_slot.previous_index;
+			if (previous_slot.previous_free_index != null_index)
+				slot_pool[previous_slot.previous_free_index].next_free_index = previous_slot.next_free_index;
+			if (previous_slot.next_free_index != null_index)
+				slot_pool[previous_slot.next_free_index].previous_free_index = previous_slot.previous_free_index;
+			FreeEntry<Slot>(slot_pool, next_free_slot_index, previous_index);
+			if (slot.previous_index == null_index) {
+				shelf.first_slot_index = slot_index;
+				if (slot.next_index == null_index)
+					shelf.allocated = false;
+			}
+			else
+			{
+				slot_pool[slot.previous_index].next_index = slot_index;
 			}
 		}
 	}
 
 	// Merge consecutive empty shelves.
-	if (shelf.allocated) return slotPixels;
-	if (shelf.nextIndex != nullIndex) {
-		Shelf &nextShelf = shelfPool[shelf.nextIndex];
-		if (!nextShelf.allocated) {
-			shelf.height += nextShelf.height;
-			const unsigned nextIndex = shelf.nextIndex;
-			shelf.nextIndex = nextShelf.nextIndex;
-			freeEntry<Slot>(slotPool, nextFreeSlotIndex, nextShelf.firstSlotIndex);
-			freeEntry<Shelf>(shelfPool, nextFreeShelfIndex, nextIndex);
-			if (shelf.nextIndex != nullIndex) shelfPool[shelf.nextIndex].previousIndex = slot.shelfIndex;
+	if (shelf.allocated)
+		return slot_pixels;
+	if (shelf.next_index != null_index)
+	{
+		Shelf& next_shelf = shelf_pool[shelf.next_index];
+		if (!next_shelf.allocated)
+		{
+			shelf.height += next_shelf.height;
+			const unsigned int next_index = shelf.next_index;
+			shelf.next_index = next_shelf.next_index;
+			FreeEntry<Slot>(slot_pool, next_free_slot_index, next_shelf.first_slot_index);
+			FreeEntry<Shelf>(shelf_pool, next_free_shelf_index, next_index);
+			if (shelf.next_index != null_index)
+				shelf_pool[shelf.next_index].previous_index = slot.shelf_index;
 		}
 	}
-	if (shelf.previousIndex != nullIndex) {
-		Shelf &previousShelf = shelfPool[shelf.previousIndex];
-		if (!previousShelf.allocated) {
-			shelf.y -= previousShelf.height;
-			shelf.height += previousShelf.height;
+	if (shelf.previous_index != null_index)
+	{
+		Shelf& previous_shelf = shelf_pool[shelf.previous_index];
+		if (!previous_shelf.allocated)
+		{
+			shelf.y -= previous_shelf.height;
+			shelf.height += previous_shelf.height;
 			slot.y = shelf.y;
-			const unsigned previousIndex = shelf.previousIndex;
-			shelf.previousIndex = previousShelf.previousIndex;
-			freeEntry<Slot>(slotPool, nextFreeSlotIndex, previousShelf.firstSlotIndex);
-			freeEntry<Shelf>(shelfPool, nextFreeShelfIndex, previousIndex);
-			if (shelf.previousIndex == nullIndex) page.firstShelfIndex = slot.shelfIndex;
-			else shelfPool[shelf.previousIndex].nextIndex = slot.shelfIndex;
+			const unsigned int previous_index = shelf.previous_index;
+			shelf.previous_index = previous_shelf.previous_index;
+			FreeEntry<Slot>(slot_pool, next_free_slot_index, previous_shelf.first_slot_index);
+			FreeEntry<Shelf>(shelf_pool, next_free_shelf_index, previous_index);
+			if (shelf.previous_index == null_index)
+				page.first_shelf_index = slot.shelf_index;
+			else
+				shelf_pool[shelf.previous_index].next_index = slot.shelf_index;
 		}
 	}
 
 	// Deallocate the page if it becomes empty, except when it's the first one.
-	if (pageIndex == firstPageIndex) {
-		firstPageAllocatedPixels -= slot.width * shelf.height;
-		return slotPixels;
+	if (page_index == first_page_index)
+	{
+		first_page_allocated_pixels -= slot.width * shelf.height;
+		return slot_pixels;
 	}
-	if (shelf.height != pageSize) return slotPixels;
-	freeEntry<Slot>(slotPool, nextFreeSlotIndex, slotIndex);
-	freeEntry<Shelf>(shelfPool, nextFreeShelfIndex, page.firstShelfIndex);
-	page.textureData.reset();
-	pagePool[page.previousIndex].nextIndex = page.nextIndex;
-	if (pageIndex != lastPageIndex) pagePool[page.nextIndex].previousIndex = page.previousIndex;
-	pagePool[lastPageIndex].nextIndex = pageIndex;
-	return slotPixels;
+	if (shelf.height != page_size)
+		return slot_pixels;
+	FreeEntry<Slot>(slot_pool, next_free_slot_index, slot_index);
+	FreeEntry<Shelf>(shelf_pool, next_free_shelf_index, page.first_shelf_index);
+	page.texture_data.reset();
+	page_pool[page.previous_index].next_index = page.next_index;
+	if (page_index != last_page_index)
+		page_pool[page.next_index].previous_index = page.previous_index;
+	page_pool[last_page_index].next_index = page_index;
+	return slot_pixels;
 }
 
-void SpriteSet::remove(const Handle handle) {
-	Slot &slot = slotPool[handle.slotIndex];
-	if (slot.epoch != handle.epoch) return;
-	remove(handle.slotIndex);
+void SpriteSet::Remove(const Handle handle)
+{
+	Slot& slot = slot_pool[handle.slot_index];
+	if (slot.epoch != handle.epoch)
+		return;
+	Remove(handle.slot_index);
 }
 
-SpriteSet::SpriteData SpriteSet::get(const Handle handle) const {
-	const Slot &slot = slotPool[handle.slotIndex];
+SpriteSet::SpriteData SpriteSet::Get(const Handle handle) const
+{
+	const Slot& slot = slot_pool[handle.slot_index];
 	return {
-		slot.textureId, slot.x + spritePadding, slot.y + spritePadding,
-		slot.actualWidth - spritePadding * 2, slot.height - spritePadding * 2
+		slot.texture_id, slot.x + sprite_padding, slot.y + sprite_padding,
+		slot.actual_width - sprite_padding * 2, slot.height - sprite_padding * 2
 	};
 }
 
-std::vector<const unsigned char*> SpriteSet::getTextures() const {
-	if (firstPageIndex == nullIndex)
+Vector<const unsigned char*> SpriteSet::GetTextures() const
+{
+	if (first_page_index == null_index)
 		return {};
-	std::vector<const unsigned char*> textures;
-	unsigned pageIndex = firstPageIndex;
-	while (true) {
-		const Page &page = pagePool[pageIndex];
-		textures.push_back(page.textureData->data());
-		if (pageIndex == lastPageIndex) break;
-		pageIndex = page.nextIndex;
+	Vector<const unsigned char*> textures;
+	unsigned int page_index = first_page_index;
+	while (true)
+	{
+		const Page& page = page_pool[page_index];
+		textures.push_back(page.texture_data->data());
+		if (page_index == last_page_index)
+			break;
+		page_index = page.next_index;
 	}
 	return textures;
 }
 
-void SpriteSet::dump(std::vector<SpriteSet::SpriteData> &sprites) const {
-	if (firstPageIndex == nullIndex) return;
+void SpriteSet::Dump(Vector<SpriteSet::SpriteData> &sprites) const
+{
+	if (first_page_index == null_index)
+		return;
 	for (
-		unsigned shelfIndex = pagePool[firstPageIndex].firstShelfIndex;
-		shelfIndex != nullIndex; shelfIndex = shelfPool[shelfIndex].nextIndex
-	) {
-		const unsigned shelfY = shelfPool[shelfIndex].y;
+		unsigned int shelf_index = page_pool[first_page_index].first_shelf_index;
+		shelf_index != null_index; shelf_index = shelf_pool[shelf_index].next_index
+	)
+	{
+		const unsigned int shelfY = shelf_pool[shelf_index].y;
 		for (
-			unsigned slotIndex = shelfPool[shelfIndex].firstSlotIndex;
-			slotIndex != nullIndex; slotIndex = slotPool[slotIndex].nextIndex
-		) {
-			const Slot &slot = slotPool[slotIndex];
-			if (slot.allocated) sprites.push_back({slot.x, shelfY, slot.width, slot.height});
+			unsigned int slot_index = shelf_pool[shelf_index].first_slot_index;
+			slot_index != null_index; slot_index = slot_pool[slot_index].next_index
+		)
+		{
+			const Slot& slot = slot_pool[slot_index];
+			if (slot.allocated)
+				sprites.push_back({slot.x, shelfY, slot.width, slot.height});
 		}
 	}
-}
+}
+
+} // namespace Rml

+ 124 - 64
Source/Core/FontEngineDefault/SpriteSet.h

@@ -1,71 +1,131 @@
-#ifndef GRAPHICS_SPRITESET_H
+#ifndef GRAPHICS_SPRITESET_H // TODO for PR: Change the header guard when the name and location are finalised.
 #define GRAPHICS_SPRITESET_H
 
 #include <cstdlib>
-#include <functional>
-#include <vector>
 
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+/**
+    A texture atlas allocator that uses the shelf packing algorithm and supports fast addition and
+    removal of images.
+
+    @author Lê Duy Quang
+*/
+
+// TODO for PR: Give a better name.
 class SpriteSet final {
-	private:
-		struct Page {
-			unsigned previousIndex, nextIndex;
-			unsigned firstShelfIndex;
-			unsigned textureId;
-			unsigned firstDirtyY, pastLastDirtyY, firstDirtyX, pastLastDirtyX;
-			std::unique_ptr<std::vector<unsigned char>> textureData;
-		};
-		struct Shelf {
-			unsigned pageIndex;
-			unsigned y, height;
-			unsigned previousIndex, nextIndex;
-			unsigned firstSlotIndex, firstFreeSlotIndex;
-			bool allocated;
-		};
-		struct Slot {
-			unsigned shelfIndex;
-			unsigned textureId;
-			unsigned x, y, width, height, actualWidth;
-			unsigned previousIndex, nextIndex;
-			unsigned previousFreeIndex, nextFreeIndex;
-			unsigned epoch;
-			bool allocated;
-		};
-
-		unsigned bytesPerPixel, pageSize, spritePadding;
-		std::vector<Page> pagePool{1 << 3};
-		std::vector<Shelf> shelfPool{1 << 8};
-		std::vector<Slot> slotPool{1 << 10};
-		unsigned
-			pageCount = 0, firstPageIndex = -1, lastPageIndex = 0,
-			nextFreeShelfIndex = 0, nextFreeSlotIndex = 0;
-		unsigned firstPageAllocatedPixels = 0;
-		unsigned currentEpoch = 0;
-
-		unsigned allocate(unsigned width, unsigned height);
-		unsigned tryAllocateInPage(unsigned pageIndex, unsigned width, unsigned height);
-		unsigned remove(unsigned slotIndex);
-	public:
-		struct Handle {
-			unsigned slotIndex;
-			unsigned epoch;
-		};
-		struct SpriteData {
-			unsigned textureId, x, y, width, height;
-		};
-
-		std::function<void(unsigned)> removalCallback;
-		std::function<void(unsigned, Handle)> migrationCallback;
-
-		SpriteSet(unsigned bytesPerPixel, unsigned pageSize, unsigned spritePadding);
-		void tick();
-		Handle add(const unsigned width, const unsigned height, const unsigned char *const data) {
-			return add(width, height, data, width);
-		}
-		Handle add(unsigned width, unsigned height, const unsigned char *data, unsigned rowStride);
-		void remove(Handle handle);
-		SpriteData get(Handle handle) const;
-		std::vector<const unsigned char*> getTextures() const;
-		void dump(std::vector<SpriteData> &sprites) const;
+public:
+	struct Handle {
+		unsigned int slot_index;
+		unsigned int epoch;
+	};
+	struct SpriteData {
+		unsigned int texture_id;
+		unsigned int x;
+		unsigned int y;
+		unsigned int width;
+		unsigned int height;
+	};
+
+	/// Callback for when an image is migrated from one page to another.
+	Function<void(unsigned int, Handle)> migration_callback;
+
+	/// @param[in] bytes_per_pixel The number of bytes for each pixel (the pixel format is not needed for operation).
+	/// @param[in] page_size The edge length in pixels of each texture page.
+	/// @param[in] sprite_padding Number of blank pixels surrounding each image.
+	SpriteSet(unsigned int bytes_per_pixel, unsigned int page_size, unsigned int sprite_padding);
+
+	void Tick();
+
+	/// Adds an image to the texture atlas.
+	/// @param[in] width The width of the image.
+	/// @param[in] height The height of the image.
+	/// @param[in] data The image data.
+	/// @return The handle to the image.
+	Handle Add(const unsigned int width, const unsigned int height, const unsigned char* data) {
+		return Add(width, height, data, width);
+	}
+
+	/// Adds a subimage to the texture atlas.
+	/// @param[in] width The width of the subimage.
+	/// @param[in] height The height of the subimage.
+	/// @param[in] data Pointer to the first pixel of the subimage.
+	/// @param[in] row_stride The width of the whole image that contains the subimage.
+	/// @return The handle to the image.
+	Handle Add(unsigned int width, unsigned int height, const unsigned char* data, unsigned int row_stride);
+
+	/// Removes an image from the texture atlas.
+	/// @param[in] handle The handle to the image.
+	void Remove(Handle handle);
+
+	/// Retrieves information about an image in the texture atlas.
+	/// @param[in] handle The handle to the image.
+	/// @return The information about the image.
+	SpriteData Get(Handle handle) const;
+
+	/// Retrieves texture data for all pages of the texture atlas.
+	/// @return An array of pointers to each page's texture data.
+	Vector<const unsigned char*> GetTextures() const;
+
+	void Dump(std::vector<SpriteData> &sprites) const;
+
+private:
+	struct Page {
+		unsigned int previous_index, next_index;
+		unsigned int first_shelf_index;
+		unsigned int texture_id;
+		unsigned int first_dirty_y;
+		unsigned int past_last_dirty_y;
+		unsigned int first_dirty_x;
+		unsigned int past_last_dirty_x;
+		UniquePtr<std::vector<unsigned char>> texture_data;
+	};
+	struct Shelf {
+		unsigned int page_index;
+		unsigned int y;
+		unsigned int height;
+		unsigned int previous_index;
+		unsigned int next_index;
+		unsigned int first_slot_index;
+		unsigned int first_free_slot_index;
+		bool allocated;
+	};
+	struct Slot {
+		unsigned int shelf_index;
+		unsigned int texture_id;
+		unsigned int x;
+		unsigned int y;
+		unsigned int width;
+		unsigned int height;
+		unsigned int actual_width;
+		unsigned int previous_index;
+		unsigned int next_index;
+		unsigned int previous_free_index;
+		unsigned int next_free_index;
+		unsigned int epoch;
+		bool allocated;
+	};
+
+	unsigned int bytes_per_pixel;
+	unsigned int page_size;
+	unsigned int sprite_padding;
+	Vector<Page> page_pool{1 << 3};
+	Vector<Shelf> shelf_pool{1 << 8};
+	Vector<Slot> slot_pool{1 << 10};
+	unsigned int page_count = 0;
+	unsigned int first_page_index = static_cast<unsigned int>(-1);
+	unsigned int last_page_index = 0;
+	unsigned int next_free_shelf_index = 0;
+	unsigned int next_free_slot_index = 0;
+	unsigned int first_page_allocated_pixels = 0;
+	unsigned int current_epoch = 0;
+
+	unsigned int Allocate(unsigned int width, unsigned int height);
+	unsigned int TryAllocateInPage(unsigned int page_index, unsigned int width, unsigned int height);
+	unsigned int Remove(unsigned int slot_index);
 };
 
-#endif // GRAPHICS_SPRITESET_H
+} // namespace Rml
+#endif