SpriteSet.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. #include <algorithm>
  2. #include "SpriteSet.h"
  3. namespace {
  4. constexpr unsigned nullIndex = -1;
  5. constexpr unsigned maxChangedPixels = 256 * 256;
  6. constexpr unsigned splitThreshold = 8;
  7. template<typename T>
  8. void initializePool(std::vector<T> &pool) {
  9. const unsigned
  10. poolSize = static_cast<unsigned>(pool.size()),
  11. lastPoolIndex = poolSize - 1;
  12. for (unsigned i = 0; i != lastPoolIndex; ++i) pool[i].nextIndex = i + 1;
  13. pool[lastPoolIndex].nextIndex = nullIndex;
  14. }
  15. template<typename T>
  16. unsigned allocateEntry(std::vector<T> &pool, unsigned &nextFreeIndex) {
  17. if (nextFreeIndex == nullIndex) {
  18. const unsigned
  19. oldPoolSize = static_cast<unsigned>(pool.size()),
  20. newPoolSize = oldPoolSize << 1,
  21. lastPoolIndex = newPoolSize - 1;
  22. pool.resize(newPoolSize);
  23. for (unsigned i = oldPoolSize; i != lastPoolIndex; ++i) pool[i].nextIndex = i + 1;
  24. pool[lastPoolIndex].nextIndex = nullIndex;
  25. nextFreeIndex = oldPoolSize;
  26. }
  27. const unsigned index = nextFreeIndex;
  28. nextFreeIndex = pool[index].nextIndex;
  29. return index;
  30. }
  31. template<typename T>
  32. void freeEntry(std::vector<T> &pool, unsigned &nextFreeIndex, const unsigned index) {
  33. pool[index].nextIndex = nextFreeIndex;
  34. nextFreeIndex = index;
  35. }
  36. }
  37. SpriteSet::SpriteSet(const unsigned bytesPerPixel, const unsigned pageSize, const unsigned spritePadding):
  38. bytesPerPixel(bytesPerPixel), pageSize(pageSize), spritePadding(spritePadding)
  39. {
  40. initializePool(pagePool);
  41. initializePool(shelfPool);
  42. initializePool(slotPool);
  43. }
  44. void SpriteSet::tick() {
  45. unsigned changedPixels = 0;
  46. // Try compaction by moving sprites from the last page to the first page.
  47. while (changedPixels <= maxChangedPixels && firstPageIndex != lastPageIndex) {
  48. const Page &sourcePage = pagePool[lastPageIndex];
  49. unsigned sourceShelfIndex = sourcePage.firstShelfIndex;
  50. while (!shelfPool[sourceShelfIndex].allocated) sourceShelfIndex = shelfPool[sourceShelfIndex].nextIndex;
  51. const Shelf &sourceShelf = shelfPool[sourceShelfIndex];
  52. unsigned sourceSlotIndex = sourceShelf.firstSlotIndex;
  53. while (!slotPool[sourceSlotIndex].allocated) sourceSlotIndex = slotPool[sourceSlotIndex].nextIndex;
  54. const Slot &sourceSlot = slotPool[sourceSlotIndex];
  55. const unsigned destinationSlotIndex = tryAllocateInPage(firstPageIndex, sourceSlot.width, sourceSlot.height);
  56. if (destinationSlotIndex == nullIndex) break;
  57. const Slot &destinationSlot = slotPool[destinationSlotIndex];
  58. const Shelf &destinationShelf = shelfPool[destinationSlot.shelfIndex];
  59. Page &destinationPage = pagePool[firstPageIndex];
  60. const unsigned
  61. sourceX = sourceSlot.x, sourceY = sourceShelf.y,
  62. destinationX = destinationSlot.x, destinationY = destinationShelf.y,
  63. width = sourceSlot.width, height = sourceSlot.height;
  64. for (unsigned localY = 0; localY != height; ++localY) {
  65. const auto dataStart = sourcePage.textureData->begin() + ((sourceY + localY) * pageSize + sourceX);
  66. std::copy(
  67. dataStart, dataStart + width,
  68. destinationPage.textureData->begin() + ((destinationY + localY) * pageSize + destinationX)
  69. );
  70. }
  71. destinationPage.firstDirtyY = std::min(destinationPage.firstDirtyY, destinationY);
  72. destinationPage.pastLastDirtyY = std::max(destinationPage.pastLastDirtyY, destinationY + height);
  73. destinationPage.firstDirtyX = std::min(destinationPage.firstDirtyX, destinationX);
  74. destinationPage.pastLastDirtyX = std::max(destinationPage.pastLastDirtyX, destinationX + width);
  75. changedPixels += remove(sourceSlotIndex);
  76. if (migrationCallback) migrationCallback(sourceSlotIndex, {destinationSlotIndex, destinationSlot.epoch});
  77. }
  78. }
  79. SpriteSet::Handle SpriteSet::add(
  80. const unsigned width, const unsigned height, const unsigned char *const data, const unsigned rowStride
  81. ) {
  82. const unsigned paddedWidth = width + spritePadding * 2, paddedHeight = height + spritePadding * 2;
  83. const unsigned slotIndex = allocate(paddedWidth, paddedHeight);
  84. const Slot &slot = slotPool[slotIndex];
  85. const Shelf &shelf = shelfPool[slot.shelfIndex];
  86. Page &page = pagePool[shelf.pageIndex];
  87. for (
  88. unsigned i = 0, topPaddingY = shelf.y, bottomPaddingY = shelf.y + paddedHeight - 1;
  89. i != spritePadding; ++i, ++topPaddingY, --bottomPaddingY
  90. ) {
  91. auto textureStart = page.textureData->begin() + (topPaddingY * pageSize + slot.x) * bytesPerPixel;
  92. std::fill(textureStart, textureStart + paddedWidth * bytesPerPixel, 0);
  93. textureStart = page.textureData->begin() + (bottomPaddingY * pageSize + slot.x) * bytesPerPixel;
  94. std::fill(textureStart, textureStart + paddedWidth * bytesPerPixel, 0);
  95. }
  96. const unsigned textureY = shelf.y + spritePadding;
  97. for (unsigned localY = 0; localY != height; ++localY) {
  98. const unsigned char *const dataStart = data + localY * rowStride * bytesPerPixel;
  99. const auto textureStart = page.textureData->begin() + ((textureY + localY) * pageSize + slot.x) * bytesPerPixel;
  100. std::fill(textureStart, textureStart + spritePadding * bytesPerPixel, 0);
  101. std::fill(
  102. textureStart + (spritePadding + width) * bytesPerPixel, textureStart + paddedWidth * bytesPerPixel, 0
  103. );
  104. std::copy(dataStart, dataStart + width * bytesPerPixel, textureStart + spritePadding * bytesPerPixel);
  105. }
  106. page.firstDirtyY = std::min(page.firstDirtyY, shelf.y);
  107. page.pastLastDirtyY = std::max(page.pastLastDirtyY, shelf.y + paddedHeight);
  108. page.firstDirtyX = std::min(page.firstDirtyX, slot.x);
  109. page.pastLastDirtyX = std::max(page.pastLastDirtyX, slot.x + paddedWidth);
  110. return {slotIndex, slot.epoch};
  111. }
  112. unsigned SpriteSet::allocate(const unsigned width, const unsigned height) {
  113. // Try to allocate in an existing page.
  114. if (firstPageIndex != nullIndex) {
  115. unsigned pageIndex = firstPageIndex;
  116. while (true) {
  117. const unsigned slotIndex = tryAllocateInPage(pageIndex, width, height);
  118. if (slotIndex != nullIndex) return slotIndex;
  119. if (pageIndex == lastPageIndex) break;
  120. pageIndex = pagePool[pageIndex].nextIndex;
  121. }
  122. }
  123. // No page could fit, allocate a new page.
  124. if (firstPageIndex == nullIndex) {
  125. firstPageIndex = lastPageIndex;
  126. Page &page = pagePool[lastPageIndex];
  127. page.previousIndex = nullIndex;
  128. } else {
  129. unsigned pageIndex = pagePool[lastPageIndex].nextIndex;
  130. if (pageIndex == nullIndex) {
  131. const unsigned
  132. oldPoolSize = static_cast<unsigned>(pagePool.size()),
  133. newPoolSize = oldPoolSize << 1,
  134. lastPoolIndex = newPoolSize - 1;
  135. pagePool.resize(newPoolSize);
  136. for (unsigned i = oldPoolSize; i != lastPoolIndex; ++i) pagePool[i].nextIndex = i + 1;
  137. pagePool[lastPoolIndex].nextIndex = nullIndex;
  138. pagePool[lastPageIndex].nextIndex = oldPoolSize;
  139. pageIndex = oldPoolSize;
  140. }
  141. pagePool[pageIndex].previousIndex = lastPageIndex;
  142. lastPageIndex = pageIndex;
  143. }
  144. Page &page = pagePool[lastPageIndex];
  145. page.textureId = pageCount;
  146. page.textureData = std::make_unique<std::vector<unsigned char>>(pageSize * pageSize * bytesPerPixel);
  147. page.firstDirtyY = pageSize;
  148. page.pastLastDirtyY = 0;
  149. page.firstDirtyX = pageSize;
  150. page.pastLastDirtyX = 0;
  151. const unsigned shelfIndex = allocateEntry<Shelf>(shelfPool, nextFreeShelfIndex);
  152. const unsigned slotIndex = allocateEntry<Slot>(slotPool, nextFreeSlotIndex);
  153. slotPool[slotIndex] = {shelfIndex, pageCount, 0, 0, pageSize, 0, 0, nullIndex, nullIndex, nullIndex, nullIndex, 0, false};
  154. shelfPool[shelfIndex] = {lastPageIndex, 0, pageSize, nullIndex, nullIndex, slotIndex, slotIndex, false};
  155. page.firstShelfIndex = shelfIndex;
  156. ++pageCount;
  157. return tryAllocateInPage(lastPageIndex, width, height);
  158. }
  159. unsigned SpriteSet::tryAllocateInPage(const unsigned pageIndex, const unsigned width, const unsigned height) {
  160. Page &page = pagePool[pageIndex];
  161. unsigned selectedShelfIndex = nullIndex, selectedSlotIndex = nullIndex, selectedShelfHeight = -1;
  162. for (
  163. unsigned shelfIndex = page.firstShelfIndex;
  164. shelfIndex != nullIndex; shelfIndex = shelfPool[shelfIndex].nextIndex
  165. ) {
  166. const Shelf &shelf = shelfPool[shelfIndex];
  167. if (
  168. shelf.height < height || shelf.height >= selectedShelfHeight
  169. || (shelf.allocated && shelf.height > height * 3 / 2)
  170. ) continue;
  171. bool found = false;
  172. for (
  173. unsigned slotIndex = shelf.firstFreeSlotIndex;
  174. slotIndex != nullIndex; slotIndex = slotPool[slotIndex].nextFreeIndex
  175. ) {
  176. const Slot &slot = slotPool[slotIndex];
  177. if (slot.width < width) continue;
  178. selectedShelfIndex = shelfIndex;
  179. selectedSlotIndex = slotIndex;
  180. selectedShelfHeight = shelf.height;
  181. found = true;
  182. break;
  183. }
  184. if (found && shelf.height == height) break;
  185. }
  186. if (selectedSlotIndex == nullIndex) return nullIndex;
  187. Shelf *shelf = &shelfPool[selectedShelfIndex];
  188. if (!shelf->allocated) {
  189. shelf->allocated = true;
  190. if (shelf->height - height >= splitThreshold) {
  191. const unsigned newShelfIndex = allocateEntry<Shelf>(shelfPool, nextFreeShelfIndex);
  192. const unsigned newSlotIndex = allocateEntry<Slot>(slotPool, nextFreeSlotIndex);
  193. shelf = &shelfPool[selectedShelfIndex];
  194. slotPool[newSlotIndex] = {
  195. newShelfIndex, page.textureId, 0, shelf->y + height, pageSize, 0, 0, nullIndex, nullIndex, nullIndex, nullIndex, 0, false
  196. };
  197. shelfPool[newShelfIndex] = {
  198. pageIndex, shelf->y + height, shelf->height - height,
  199. selectedShelfIndex, shelf->nextIndex, newSlotIndex, newSlotIndex, false
  200. };
  201. if (shelf->nextIndex != nullIndex) shelfPool[shelf->nextIndex].previousIndex = newShelfIndex;
  202. shelf->nextIndex = newShelfIndex;
  203. shelf->height = height;
  204. }
  205. }
  206. Slot *slot = &slotPool[selectedSlotIndex];
  207. if (slot->width - width >= splitThreshold) {
  208. const unsigned newSlotIndex = allocateEntry<Slot>(slotPool, nextFreeSlotIndex);
  209. slot = &slotPool[selectedSlotIndex];
  210. slotPool[newSlotIndex] = {
  211. selectedShelfIndex, page.textureId, slot->x + width, shelf->y, slot->width - width, 0, 0,
  212. selectedSlotIndex, slot->nextIndex, slot->previousFreeIndex, slot->nextFreeIndex, 0, false
  213. };
  214. if (slot->nextIndex != nullIndex) slotPool[slot->nextIndex].previousIndex = newSlotIndex;
  215. slot->nextIndex = newSlotIndex;
  216. if (slot->previousFreeIndex == nullIndex) shelf->firstFreeSlotIndex = newSlotIndex;
  217. else slotPool[slot->previousFreeIndex].nextFreeIndex = newSlotIndex;
  218. if (slot->nextFreeIndex != nullIndex) slotPool[slot->nextFreeIndex].previousFreeIndex = newSlotIndex;
  219. slot->width = width;
  220. } else {
  221. if (slot->previousFreeIndex == nullIndex) shelf->firstFreeSlotIndex = slot->nextFreeIndex;
  222. else slotPool[slot->previousFreeIndex].nextFreeIndex = slot->nextFreeIndex;
  223. if (slot->nextFreeIndex != nullIndex) slotPool[slot->nextFreeIndex].previousFreeIndex = slot->previousFreeIndex;
  224. }
  225. slot->allocated = true;
  226. slot->actualWidth = width;
  227. slot->height = height;
  228. slot->epoch = currentEpoch;
  229. if (pageIndex == firstPageIndex) firstPageAllocatedPixels += width * shelf->height;
  230. return selectedSlotIndex;
  231. }
  232. unsigned SpriteSet::remove(const unsigned slotIndex) {
  233. Slot &slot = slotPool[slotIndex];
  234. Shelf &shelf = shelfPool[slot.shelfIndex];
  235. const unsigned pageIndex = shelf.pageIndex;
  236. Page &page = pagePool[pageIndex];
  237. for (int offsetY = 0; offsetY < slot.height; ++offsetY) {
  238. for (int offsetX = 0; offsetX < slot.width; ++offsetX) {
  239. const auto pixel = page.textureData->begin() + ((slot.y + offsetY) * pageSize + slot.x + offsetX) * 4;
  240. pixel[0] = 0;
  241. pixel[1] = 0;
  242. pixel[2] = 255;
  243. pixel[3] = 255;
  244. }
  245. }
  246. const unsigned slotPixels = slot.width * slot.height;
  247. slot.allocated = false;
  248. slot.previousFreeIndex = nullIndex;
  249. if (shelf.firstFreeSlotIndex == nullIndex) {
  250. slot.nextFreeIndex = nullIndex;
  251. } else {
  252. slot.nextFreeIndex = shelf.firstFreeSlotIndex;
  253. slotPool[shelf.firstFreeSlotIndex].previousFreeIndex = slotIndex;
  254. }
  255. shelf.firstFreeSlotIndex = slotIndex;
  256. ++slot.epoch;
  257. // Merge consecutive empty slots.
  258. if (slot.nextIndex != nullIndex) {
  259. Slot &nextSlot = slotPool[slot.nextIndex];
  260. if (!nextSlot.allocated) {
  261. slot.width += nextSlot.width;
  262. const unsigned nextIndex = slot.nextIndex;
  263. slot.nextIndex = nextSlot.nextIndex;
  264. if (nextSlot.previousFreeIndex != nullIndex)
  265. slotPool[nextSlot.previousFreeIndex].nextFreeIndex = nextSlot.nextFreeIndex;
  266. if (nextSlot.nextFreeIndex != nullIndex)
  267. slotPool[nextSlot.nextFreeIndex].previousFreeIndex = nextSlot.previousFreeIndex;
  268. freeEntry<Slot>(slotPool, nextFreeSlotIndex, nextIndex);
  269. if (slot.nextIndex != nullIndex) slotPool[slot.nextIndex].previousIndex = slotIndex;
  270. }
  271. }
  272. if (slot.previousIndex != nullIndex) {
  273. Slot &previousSlot = slotPool[slot.previousIndex];
  274. if (!previousSlot.allocated) {
  275. slot.x -= previousSlot.width;
  276. slot.width += previousSlot.width;
  277. const unsigned previousIndex = slot.previousIndex;
  278. slot.previousIndex = previousSlot.previousIndex;
  279. if (previousSlot.previousFreeIndex != nullIndex)
  280. slotPool[previousSlot.previousFreeIndex].nextFreeIndex = previousSlot.nextFreeIndex;
  281. if (previousSlot.nextFreeIndex != nullIndex)
  282. slotPool[previousSlot.nextFreeIndex].previousFreeIndex = previousSlot.previousFreeIndex;
  283. freeEntry<Slot>(slotPool, nextFreeSlotIndex, previousIndex);
  284. if (slot.previousIndex == nullIndex) {
  285. shelf.firstSlotIndex = slotIndex;
  286. if (slot.nextIndex == nullIndex) shelf.allocated = false;
  287. } else {
  288. slotPool[slot.previousIndex].nextIndex = slotIndex;
  289. }
  290. }
  291. }
  292. // Merge consecutive empty shelves.
  293. if (shelf.allocated) return slotPixels;
  294. if (shelf.nextIndex != nullIndex) {
  295. Shelf &nextShelf = shelfPool[shelf.nextIndex];
  296. if (!nextShelf.allocated) {
  297. shelf.height += nextShelf.height;
  298. const unsigned nextIndex = shelf.nextIndex;
  299. shelf.nextIndex = nextShelf.nextIndex;
  300. freeEntry<Slot>(slotPool, nextFreeSlotIndex, nextShelf.firstSlotIndex);
  301. freeEntry<Shelf>(shelfPool, nextFreeShelfIndex, nextIndex);
  302. if (shelf.nextIndex != nullIndex) shelfPool[shelf.nextIndex].previousIndex = slot.shelfIndex;
  303. }
  304. }
  305. if (shelf.previousIndex != nullIndex) {
  306. Shelf &previousShelf = shelfPool[shelf.previousIndex];
  307. if (!previousShelf.allocated) {
  308. shelf.y -= previousShelf.height;
  309. shelf.height += previousShelf.height;
  310. slot.y = shelf.y;
  311. const unsigned previousIndex = shelf.previousIndex;
  312. shelf.previousIndex = previousShelf.previousIndex;
  313. freeEntry<Slot>(slotPool, nextFreeSlotIndex, previousShelf.firstSlotIndex);
  314. freeEntry<Shelf>(shelfPool, nextFreeShelfIndex, previousIndex);
  315. if (shelf.previousIndex == nullIndex) page.firstShelfIndex = slot.shelfIndex;
  316. else shelfPool[shelf.previousIndex].nextIndex = slot.shelfIndex;
  317. }
  318. }
  319. // Deallocate the page if it becomes empty, except when it's the first one.
  320. if (pageIndex == firstPageIndex) {
  321. firstPageAllocatedPixels -= slot.width * shelf.height;
  322. return slotPixels;
  323. }
  324. if (shelf.height != pageSize) return slotPixels;
  325. freeEntry<Slot>(slotPool, nextFreeSlotIndex, slotIndex);
  326. freeEntry<Shelf>(shelfPool, nextFreeShelfIndex, page.firstShelfIndex);
  327. page.textureData.reset();
  328. pagePool[page.previousIndex].nextIndex = page.nextIndex;
  329. if (pageIndex != lastPageIndex) pagePool[page.nextIndex].previousIndex = page.previousIndex;
  330. pagePool[lastPageIndex].nextIndex = pageIndex;
  331. return slotPixels;
  332. }
  333. void SpriteSet::remove(const Handle handle) {
  334. Slot &slot = slotPool[handle.slotIndex];
  335. if (slot.epoch != handle.epoch) return;
  336. remove(handle.slotIndex);
  337. }
  338. SpriteSet::SpriteData SpriteSet::get(const Handle handle) const {
  339. const Slot &slot = slotPool[handle.slotIndex];
  340. return {
  341. slot.textureId, slot.x + spritePadding, slot.y + spritePadding,
  342. slot.actualWidth - spritePadding * 2, slot.height - spritePadding * 2
  343. };
  344. }
  345. std::vector<const unsigned char*> SpriteSet::getTextures() const {
  346. if (firstPageIndex == nullIndex)
  347. return {};
  348. std::vector<const unsigned char*> textures;
  349. unsigned pageIndex = firstPageIndex;
  350. while (true) {
  351. const Page &page = pagePool[pageIndex];
  352. textures.push_back(page.textureData->data());
  353. if (pageIndex == lastPageIndex) break;
  354. pageIndex = page.nextIndex;
  355. }
  356. return textures;
  357. }
  358. void SpriteSet::dump(std::vector<SpriteSet::SpriteData> &sprites) const {
  359. if (firstPageIndex == nullIndex) return;
  360. for (
  361. unsigned shelfIndex = pagePool[firstPageIndex].firstShelfIndex;
  362. shelfIndex != nullIndex; shelfIndex = shelfPool[shelfIndex].nextIndex
  363. ) {
  364. const unsigned shelfY = shelfPool[shelfIndex].y;
  365. for (
  366. unsigned slotIndex = shelfPool[shelfIndex].firstSlotIndex;
  367. slotIndex != nullIndex; slotIndex = slotPool[slotIndex].nextIndex
  368. ) {
  369. const Slot &slot = slotPool[slotIndex];
  370. if (slot.allocated) sprites.push_back({slot.x, shelfY, slot.width, slot.height});
  371. }
  372. }
  373. }