Unravel Engine C++ Reference
Loading...
Searching...
No Matches
cube_atlas.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2013 Jeremie Roy. All rights reserved.
3 * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
4 */
5
6#include "common.h"
7#include <bgfx/bgfx.h>
8
9#include <limits.h> // INT_MAX
10#include <vector>
11
12#include "cube_atlas.h"
13
14namespace gfx
15{
17{
18public:
20 RectanglePacker(uint32_t _width, uint32_t _height);
21
23 void init(uint32_t _width, uint32_t _height);
24
27 bool addRectangle(uint16_t _width, uint16_t _height, uint16_t& _outX, uint16_t& _outY);
28
30 uint32_t getUsedSurface()
31 {
32 return m_usedSpace;
33 }
34
36 uint32_t getTotalSurface()
37 {
38 return m_width * m_height;
39 }
40
42 float getUsageRatio();
43
45 void clear();
46
47private:
48 int32_t fit(uint32_t _skylineNodeIndex, uint16_t _width, uint16_t _height);
49
51 void merge();
52
53 struct Node
54 {
55 Node(int16_t _x, int16_t _y, int16_t _width) : x(_x), y(_y), width(_width)
56 {
57 }
58
59 int16_t x; //< The starting x-coordinate (leftmost).
60 int16_t y; //< The y-coordinate of the skyline level line.
61 int32_t width; //< The line _width. The ending coordinate (inclusive) will be x+width-1.
62 };
63
64 uint32_t m_width; //< width (in pixels) of the underlying texture
65 uint32_t m_height; //< height (in pixels) of the underlying texture
66 uint32_t m_usedSpace; //< Surface used in squared pixel
67 std::vector<Node> m_skyline; //< node of the skyline algorithm
68};
69
70RectanglePacker::RectanglePacker() : m_width(0), m_height(0), m_usedSpace(0)
71{
72}
73
74RectanglePacker::RectanglePacker(uint32_t _width, uint32_t _height) : m_width(_width), m_height(_height), m_usedSpace(0)
75{
76 // We want a one pixel border around the whole atlas to avoid any artefact when
77 // sampling texture
78 m_skyline.push_back(Node(1, 1, uint16_t(_width - 2)));
79}
80
81void RectanglePacker::init(uint32_t _width, uint32_t _height)
82{
83 BX_ASSERT(_width > 2, "_width must be > 2");
84 BX_ASSERT(_height > 2, "_height must be > 2");
85 m_width = _width;
86 m_height = _height;
87 m_usedSpace = 0;
88
89 m_skyline.clear();
90 // We want a one pixel border around the whole atlas to avoid any artifact when
91 // sampling texture
92 m_skyline.push_back(Node(1, 1, uint16_t(_width - 2)));
93}
94
95bool RectanglePacker::addRectangle(uint16_t _width, uint16_t _height, uint16_t& _outX, uint16_t& _outY)
96{
97 int best_height, best_index;
98 int32_t best_width;
99 Node* node;
100 Node* prev;
101 _outX = 0;
102 _outY = 0;
103
104 best_height = INT_MAX;
105 best_index = -1;
106 best_width = INT_MAX;
107 for(uint16_t ii = 0, num = uint16_t(m_skyline.size()); ii < num; ++ii)
108 {
109 int32_t yy = fit(ii, _width, _height);
110 if(yy >= 0)
111 {
112 node = &m_skyline[ii];
113 if(((yy + _height) < best_height) || (((yy + _height) == best_height) && (node->width < best_width)))
114 {
115 best_height = uint16_t(yy) + _height;
116 best_index = ii;
117 best_width = node->width;
118 _outX = node->x;
119 _outY = uint16_t(yy);
120 }
121 }
122 }
123
124 if(best_index == -1)
125 {
126 return false;
127 }
128
129 Node newNode(_outX, _outY + _height, _width);
130 m_skyline.insert(m_skyline.begin() + best_index, newNode);
131
132 for(uint16_t ii = uint16_t(best_index + 1), num = uint16_t(m_skyline.size()); ii < num; ++ii)
133 {
134 node = &m_skyline[ii];
135 prev = &m_skyline[ii - 1];
136 if(node->x < (prev->x + prev->width))
137 {
138 uint16_t shrink = uint16_t(prev->x + prev->width - node->x);
139 node->x += shrink;
140 node->width -= shrink;
141 if(node->width <= 0)
142 {
143 m_skyline.erase(m_skyline.begin() + ii);
144 --ii;
145 --num;
146 }
147 else
148 {
149 break;
150 }
151 }
152 else
153 {
154 break;
155 }
156 }
157
158 merge();
159 m_usedSpace += _width * _height;
160 return true;
161}
162
164{
165 uint32_t total = m_width * m_height;
166 if(total > 0)
167 {
168 return (float)m_usedSpace / (float)total;
169 }
170
171 return 0.0f;
172}
173
175{
176 m_skyline.clear();
177 m_usedSpace = 0;
178
179 // We want a one pixel border around the whole atlas to avoid any artefact when
180 // sampling texture
181 m_skyline.push_back(Node(1, 1, uint16_t(m_width - 2)));
182}
183
184int32_t RectanglePacker::fit(uint32_t _skylineNodeIndex, uint16_t _width, uint16_t _height)
185{
186 int32_t width = _width;
187 int32_t height = _height;
188
189 const Node& baseNode = m_skyline[_skylineNodeIndex];
190
191 int32_t xx = baseNode.x, yy;
192 int32_t widthLeft = width;
193 int32_t ii = _skylineNodeIndex;
194
195 if((xx + width) > (int32_t)(m_width - 1))
196 {
197 return -1;
198 }
199
200 yy = baseNode.y;
201 while(widthLeft > 0)
202 {
203 const Node& node = m_skyline[ii];
204 if(node.y > yy)
205 {
206 yy = node.y;
207 }
208
209 if((yy + height) > (int32_t)(m_height - 1))
210 {
211 return -1;
212 }
213
214 widthLeft -= node.width;
215 ++ii;
216 }
217
218 return yy;
219}
220
221void RectanglePacker::merge()
222{
223 Node* node;
224 Node* next;
225 uint32_t ii;
226
227 for(ii = 0; ii < m_skyline.size() - 1; ++ii)
228 {
229 node = (Node*)&m_skyline[ii];
230 next = (Node*)&m_skyline[ii + 1];
231 if(node->y == next->y)
232 {
233 node->width += next->width;
234 m_skyline.erase(m_skyline.begin() + ii + 1);
235 --ii;
236 }
237 }
238}
239
245
246Atlas::Atlas(uint16_t _textureSize, uint16_t _maxRegionsCount)
247 : m_usedLayers(0)
248 , m_usedFaces(0)
249 , m_textureSize(_textureSize)
250 , m_regionCount(0)
251 , m_maxRegionCount(_maxRegionsCount)
252{
253 BX_ASSERT(_textureSize >= 64 && _textureSize <= 4096, "Invalid _textureSize %d.", _textureSize);
254 BX_ASSERT(_maxRegionsCount >= 64 && _maxRegionsCount <= 32000, "Invalid _maxRegionsCount %d.", _maxRegionsCount);
255
256 m_texelSize = float(UINT16_MAX) / float(m_textureSize);
257
258 m_layers = new PackedLayer[6];
259 for(int ii = 0; ii < 6; ++ii)
260 {
261 m_layers[ii].packer.init(_textureSize, _textureSize);
262 }
263
264 m_regions = new AtlasRegion[_maxRegionsCount];
265 m_textureBuffer = new uint8_t[_textureSize * _textureSize * 6 * 4];
266 bx::memSet(m_textureBuffer, 0, _textureSize * _textureSize * 6 * 4);
267
268 m_textureHandle = bgfx::createTextureCube(_textureSize, false, 1, bgfx::TextureFormat::BGRA8);
269}
270
271Atlas::Atlas(uint16_t _textureSize,
272 const uint8_t* _textureBuffer,
273 uint16_t _regionCount,
274 const uint8_t* _regionBuffer,
275 uint16_t _maxRegionsCount)
276 : m_usedLayers(6)
277 , m_usedFaces(6)
278 , m_textureSize(_textureSize)
279 , m_regionCount(_regionCount)
280 , m_maxRegionCount(_regionCount < _maxRegionsCount ? _regionCount : _maxRegionsCount)
281{
282 BX_ASSERT(_regionCount <= 64 && _maxRegionsCount <= 4096,
283 "_regionCount %d, _maxRegionsCount %d",
284 _regionCount,
285 _maxRegionsCount);
286
287 m_texelSize = float(UINT16_MAX) / float(m_textureSize);
288
289 m_regions = new AtlasRegion[_regionCount];
290 m_textureBuffer = new uint8_t[getTextureBufferSize()];
291
292 bx::memCopy(m_regions, _regionBuffer, _regionCount * sizeof(AtlasRegion));
293 bx::memCopy(m_textureBuffer, _textureBuffer, getTextureBufferSize());
294
295 m_textureHandle = bgfx::createTextureCube(_textureSize,
296 false,
297 1,
298 bgfx::TextureFormat::BGRA8,
299 BGFX_SAMPLER_NONE,
300 bgfx::makeRef(m_textureBuffer, getTextureBufferSize()));
301}
302
304{
305 bgfx::destroy(m_textureHandle);
306
307 delete[] m_layers;
308 delete[] m_regions;
309 delete[] m_textureBuffer;
310}
311
312uint16_t Atlas::addRegion(uint16_t _width,
313 uint16_t _height,
314 const uint8_t* _bitmapBuffer,
315 AtlasRegion::Type _type,
316 uint16_t outline)
317{
318 if(m_regionCount >= m_maxRegionCount)
319 {
320 return UINT16_MAX;
321 }
322
323 uint16_t xx = 0;
324 uint16_t yy = 0;
325 uint32_t idx = 0;
326 while(idx < m_usedLayers)
327 {
328 if(m_layers[idx].faceRegion.getType() == _type &&
329 m_layers[idx].packer.addRectangle(_width + 1, _height + 1, xx, yy))
330 {
331 break;
332 }
333
334 idx++;
335 }
336
337 if(idx >= m_usedLayers)
338 {
339 if((idx + _type) > 24 || m_usedFaces >= 6)
340 {
341 return UINT16_MAX;
342 }
343
344 // for (int ii = 0; ii < _type; ++ii)
345 {
346 int ii = 0;
347 AtlasRegion& region = m_layers[idx + ii].faceRegion;
348 region.x = 0;
349 region.y = 0;
350 region.width = m_textureSize;
351 region.height = m_textureSize;
352 region.setMask(_type, m_usedFaces, ii);
353 }
354
355 m_usedLayers++;
356 m_usedFaces++;
357
358 if(!m_layers[idx].packer.addRectangle(_width + 1, _height + 1, xx, yy))
359 {
360 return UINT16_MAX;
361 }
362 }
363
364 AtlasRegion& region = m_regions[m_regionCount];
365 region.x = xx;
366 region.y = yy;
367 region.width = _width;
368 region.height = _height;
369 region.mask = m_layers[idx].faceRegion.mask;
370
371 updateRegion(region, _bitmapBuffer);
372
373 region.x += outline;
374 region.y += outline;
375 region.width -= (outline * 2);
376 region.height -= (outline * 2);
377
378 return m_regionCount++;
379}
380
381void Atlas::updateRegion(const AtlasRegion& _region, const uint8_t* _bitmapBuffer)
382{
383 uint32_t size = _region.width * _region.height * 4;
384 if(0 < size)
385 {
386 const bgfx::Memory* mem = bgfx::alloc(size);
387 bx::memSet(mem->data, 0, mem->size);
388 if(_region.getType() == AtlasRegion::TYPE_BGRA8)
389 {
390 const uint8_t* inLineBuffer = _bitmapBuffer;
391 uint8_t* outLineBuffer = m_textureBuffer + _region.getFaceIndex() * (m_textureSize * m_textureSize * 4) +
392 (((_region.y * m_textureSize) + _region.x) * 4);
393
394 for(int yy = 0; yy < _region.height; ++yy)
395 {
396 bx::memCopy(outLineBuffer, inLineBuffer, _region.width * 4);
397 inLineBuffer += _region.width * 4;
398 outLineBuffer += m_textureSize * 4;
399 }
400
401 bx::memCopy(mem->data, _bitmapBuffer, mem->size);
402 }
403 else
404 {
405 uint32_t layer = _region.getComponentIndex();
406 const uint8_t* inLineBuffer = _bitmapBuffer;
407 uint8_t* outLineBuffer = (m_textureBuffer + _region.getFaceIndex() * (m_textureSize * m_textureSize * 4) +
408 (((_region.y * m_textureSize) + _region.x) * 4));
409
410 for(int yy = 0; yy < _region.height; ++yy)
411 {
412 for(int xx = 0; xx < _region.width; ++xx)
413 {
414 outLineBuffer[(xx * 4) + layer] = inLineBuffer[xx];
415 }
416
417 bx::memCopy(mem->data + yy * _region.width * 4, outLineBuffer, _region.width * 4);
418 inLineBuffer += _region.width;
419 outLineBuffer += m_textureSize * 4;
420 }
421 }
422
423 bgfx::updateTextureCube(m_textureHandle,
424 0,
425 (uint8_t)_region.getFaceIndex(),
426 0,
427 _region.x,
428 _region.y,
429 _region.width,
430 _region.height,
431 mem);
432 }
433}
434
435void Atlas::packFaceLayerUV(uint32_t _idx, uint8_t* _vertexBuffer, uint32_t _offset, uint32_t _stride) const
436{
437 packUV(m_layers[_idx].faceRegion, _vertexBuffer, _offset, _stride);
438}
439
440void Atlas::packUV(uint16_t _regionHandle, uint8_t* _vertexBuffer, uint32_t _offset, uint32_t _stride) const
441{
442 const AtlasRegion& region = m_regions[_regionHandle];
443 packUV(region, _vertexBuffer, _offset, _stride);
444}
445
446static void writeUV(uint8_t* _vertexBuffer, int16_t _x, int16_t _y, int16_t _z, int16_t _w)
447{
448 uint16_t* xyzw = (uint16_t*)_vertexBuffer;
449 xyzw[0] = _x;
450 xyzw[1] = _y;
451 xyzw[2] = _z;
452 xyzw[3] = _w;
453}
454
455void Atlas::packUV(const AtlasRegion& _region, uint8_t* _vertexBuffer, uint32_t _offset, uint32_t _stride) const
456{
457 int16_t x0 = (int16_t)(((float)_region.x * m_texelSize) - float(INT16_MAX));
458 int16_t y0 = (int16_t)(((float)_region.y * m_texelSize) - float(INT16_MAX));
459 int16_t x1 = (int16_t)((((float)_region.x + _region.width) * m_texelSize) - float(INT16_MAX));
460 int16_t y1 = (int16_t)((((float)_region.y + _region.height) * m_texelSize) - float(INT16_MAX));
461 int16_t ww = (int16_t)((float(INT16_MAX) / 4.0f) * (float)_region.getComponentIndex());
462
463 _vertexBuffer += _offset;
464 switch(_region.getFaceIndex())
465 {
466 case 0: // +X
467 x0 = -x0;
468 x1 = -x1;
469 y0 = -y0;
470 y1 = -y1;
471 writeUV(_vertexBuffer, INT16_MAX, y0, x0, ww);
472 _vertexBuffer += _stride;
473 writeUV(_vertexBuffer, INT16_MAX, y1, x0, ww);
474 _vertexBuffer += _stride;
475 writeUV(_vertexBuffer, INT16_MAX, y1, x1, ww);
476 _vertexBuffer += _stride;
477 writeUV(_vertexBuffer, INT16_MAX, y0, x1, ww);
478 _vertexBuffer += _stride;
479 break;
480
481 case 1: // -X
482 y0 = -y0;
483 y1 = -y1;
484 writeUV(_vertexBuffer, INT16_MIN, y0, x0, ww);
485 _vertexBuffer += _stride;
486 writeUV(_vertexBuffer, INT16_MIN, y1, x0, ww);
487 _vertexBuffer += _stride;
488 writeUV(_vertexBuffer, INT16_MIN, y1, x1, ww);
489 _vertexBuffer += _stride;
490 writeUV(_vertexBuffer, INT16_MIN, y0, x1, ww);
491 _vertexBuffer += _stride;
492 break;
493
494 case 2: // +Y
495 writeUV(_vertexBuffer, x0, INT16_MAX, y0, ww);
496 _vertexBuffer += _stride;
497 writeUV(_vertexBuffer, x0, INT16_MAX, y1, ww);
498 _vertexBuffer += _stride;
499 writeUV(_vertexBuffer, x1, INT16_MAX, y1, ww);
500 _vertexBuffer += _stride;
501 writeUV(_vertexBuffer, x1, INT16_MAX, y0, ww);
502 _vertexBuffer += _stride;
503 break;
504
505 case 3: // -Y
506 y0 = -y0;
507 y1 = -y1;
508 writeUV(_vertexBuffer, x0, INT16_MIN, y0, ww);
509 _vertexBuffer += _stride;
510 writeUV(_vertexBuffer, x0, INT16_MIN, y1, ww);
511 _vertexBuffer += _stride;
512 writeUV(_vertexBuffer, x1, INT16_MIN, y1, ww);
513 _vertexBuffer += _stride;
514 writeUV(_vertexBuffer, x1, INT16_MIN, y0, ww);
515 _vertexBuffer += _stride;
516 break;
517
518 case 4: // +Z
519 y0 = -y0;
520 y1 = -y1;
521 writeUV(_vertexBuffer, x0, y0, INT16_MAX, ww);
522 _vertexBuffer += _stride;
523 writeUV(_vertexBuffer, x0, y1, INT16_MAX, ww);
524 _vertexBuffer += _stride;
525 writeUV(_vertexBuffer, x1, y1, INT16_MAX, ww);
526 _vertexBuffer += _stride;
527 writeUV(_vertexBuffer, x1, y0, INT16_MAX, ww);
528 _vertexBuffer += _stride;
529 break;
530
531 case 5: // -Z
532 x0 = -x0;
533 x1 = -x1;
534 y0 = -y0;
535 y1 = -y1;
536 writeUV(_vertexBuffer, x0, y0, INT16_MIN, ww);
537 _vertexBuffer += _stride;
538 writeUV(_vertexBuffer, x0, y1, INT16_MIN, ww);
539 _vertexBuffer += _stride;
540 writeUV(_vertexBuffer, x1, y1, INT16_MIN, ww);
541 _vertexBuffer += _stride;
542 writeUV(_vertexBuffer, x1, y0, INT16_MIN, ww);
543 _vertexBuffer += _stride;
544 break;
545 }
546}
547
548}
void updateRegion(const AtlasRegion &_region, const uint8_t *_bitmapBuffer)
update a preallocated region
uint16_t addRegion(uint16_t _width, uint16_t _height, const uint8_t *_bitmapBuffer, AtlasRegion::Type _type=AtlasRegion::TYPE_BGRA8, uint16_t outline=0)
add a region to the atlas, and copy the content of mem to the underlying texture
uint32_t getTextureBufferSize() const
retrieve the byte size of the texture
Definition cube_atlas.h:139
void packFaceLayerUV(uint32_t _idx, uint8_t *_vertexBuffer, uint32_t _offset, uint32_t _stride) const
Same as packUV but pack a whole face of the atlas cube, mostly used for debugging and visualizing atl...
void packUV(uint16_t _regionHandle, uint8_t *_vertexBuffer, uint32_t _offset, uint32_t _stride) const
Atlas(uint16_t _textureSize, uint16_t _maxRegionsCount=4096)
float getUsageRatio()
return the usage ratio of the available surface [0:1]
void clear()
reset to initial state
void init(uint32_t _width, uint32_t _height)
non constructor initialization
bool addRectangle(uint16_t _width, uint16_t _height, uint16_t &_outX, uint16_t &_outY)
uint32_t getTotalSurface()
return the total available surface in squared unit
uint32_t getUsedSurface()
return the used surface in squared unit
utfchar32_t next(octet_iterator &it, octet_iterator end)
Definition checked.h:151
RectanglePacker packer
uint16_t height
Definition cube_atlas.h:31
void setMask(Type _type, uint32_t _faceIndex, uint32_t _componentIndex)
Definition cube_atlas.h:49
uint32_t getFaceIndex() const
Definition cube_atlas.h:39
Type getType() const
Definition cube_atlas.h:34
uint32_t getComponentIndex() const
Definition cube_atlas.h:44