// ================================================================================ // == This file is a part of Turbo Badger. (C) 2011-2014, Emil Segerås == // == See tb_core.h for more information. == // ================================================================================ #include "tb_bitmap_fragment.h" #include "tb_renderer.h" #include "tb_system.h" namespace tb { int TBGetNearestPowerOfTwo(int val) { int i; for(i = 31; i >= 0; i--) if ((val - 1) & (1< m_available_space) return false; if (IsAllAvailable()) return true; for (Space *fs = m_free_space_list.GetFirst(); fs; fs = fs->GetNext()) { if (needed_w <= fs->width) return true; } return false; } TBSpaceAllocator::Space *TBSpaceAllocator::AllocSpace(int needed_w) { if (Space *available_space = GetSmallestAvailableSpace(needed_w)) { if (Space *new_space = new Space) { new_space->x = available_space->x; new_space->width = needed_w; m_used_space_list.AddLast(new_space); // Consume the used space from the available space available_space->x += needed_w; available_space->width -= needed_w; m_available_space -= needed_w; // Remove it if empty if (!available_space->width) m_free_space_list.Delete(available_space); return new_space; } } return nullptr; } TBSpaceAllocator::Space *TBSpaceAllocator::GetSmallestAvailableSpace(int needed_w) { assert(needed_w > 0); // Add free space covering all available space if empty. if (!m_free_space_list.HasLinks() && IsAllAvailable()) { if (Space *fs = new Space) { fs->x = 0; fs->width = m_available_space; m_free_space_list.AddLast(fs); } } // Check for the smallest space where we fit Space *best_fs = nullptr; for (Space *fs = m_free_space_list.GetFirst(); fs; fs = fs->GetNext()) { if (needed_w == fs->width) return fs; // It can't be better than a perfect match! if (needed_w < fs->width) if (!best_fs || fs->width < best_fs->width) best_fs = fs; } return best_fs; } void TBSpaceAllocator::FreeSpace(Space *space) { m_used_space_list.Remove(space); m_available_space += space->width; // Find where in m_free_space_list we should insert the space, // or which existing space we can extend. Space *preceeding = nullptr; Space *succeeding = nullptr; for (Space *fs = m_free_space_list.GetFirst(); fs; fs = fs->GetNext()) { if (fs->x < space->x) preceeding = fs; if (fs->x > space->x) { succeeding = fs; break; } } if (preceeding && preceeding->x + preceeding->width == space->x) { preceeding->width += space->width; delete space; } else if (succeeding && succeeding->x == space->x + space->width) { succeeding->x -= space->width; succeeding->width += space->width; delete space; } else { if (preceeding) m_free_space_list.AddAfter(space, preceeding); else if (succeeding) m_free_space_list.AddBefore(space, succeeding); else { assert(!m_free_space_list.HasLinks()); m_free_space_list.AddLast(space); } } // Merge free spaces Space *fs = m_free_space_list.GetFirst(); while (fs) { Space *next_fs = fs->GetNext(); if (!next_fs) break; if (fs->x + fs->width == next_fs->x) { fs->width += next_fs->width; m_free_space_list.Delete(next_fs); continue; } fs = next_fs; } #ifdef TB_RUNTIME_DEBUG_INFO // Check that free space is in order Space *tmp = m_free_space_list.GetFirst(); int x = 0; while (tmp) { assert(tmp->x >= x); x = tmp->x + tmp->width; tmp = tmp->GetNext(); } #endif // TB_RUNTIME_DEBUG_INFO } // == TBBitmapFragmentMap =================================================================================== TBBitmapFragmentMap::TBBitmapFragmentMap() : m_bitmap_w(0) , m_bitmap_h(0) , m_bitmap_data(nullptr) , m_bitmap(nullptr) , m_need_update(false) , m_allocated_pixels(0) { } bool TBBitmapFragmentMap::Init(int bitmap_w, int bitmap_h) { m_bitmap_data = new uint32[bitmap_w * bitmap_h]; m_bitmap_w = bitmap_w; m_bitmap_h = bitmap_h; #ifdef TB_RUNTIME_DEBUG_INFO if (m_bitmap_data) memset(m_bitmap_data, 0x88, bitmap_w * bitmap_h * sizeof(uint32)); #endif return m_bitmap_data ? true : false; } TBBitmapFragmentMap::~TBBitmapFragmentMap() { delete m_bitmap; delete [] m_bitmap_data; } TBBitmapFragment *TBBitmapFragmentMap::CreateNewFragment(int frag_w, int frag_h, int data_stride, uint32 *frag_data, bool add_border) { // Finding available space works like this: // The map size is sliced up horizontally in rows (initially just one row covering // the entire map). When adding a new fragment, put it in the row with smallest height. // If the smallest row is empty, it may slice the row to make a even smaller row. // When a image is stretched up to a larger size, the filtering will read // pixels closest (but outside) of the src_rect. When we pack images together // those pixels would be read from neighbour images, so we must add border space // around each image to avoid artifacts. We must also fill in that border with // the "clamp" of the image itself so we don't get any filtering artifacts at all. // Allways add border except when we're using the entire map for one fragment. int border = 0; int needed_w = frag_w; int needed_h = frag_h; if (add_border) { if (needed_w != m_bitmap_w || needed_h != m_bitmap_h) { border = 1; needed_w += 2; needed_h += 2; } } // Snap the fragments to a certain granularity. This could maybe ease the stress // on the space allocator when allocating & deallocating lots of small fragments. // I'm not sure there is any performance issue though and it would be better to // optimize the algorithm instead (so disabled it for now). //const int granularity = 8; //needed_w = (needed_w + granularity - 1) / granularity * granularity; //needed_h = (needed_h + granularity - 1) / granularity * granularity; if (!m_rows.GetNumItems()) { // Create a row covering the entire bitmap. TBFragmentSpaceAllocator *row; if (!m_rows.GrowIfNeeded() || !(row = new TBFragmentSpaceAllocator(0, m_bitmap_w, m_bitmap_h))) return nullptr; m_rows.Add(row); } // Get the smallest row where we fit int best_row_index = -1; TBFragmentSpaceAllocator *best_row = nullptr; for (int i = 0; i < m_rows.GetNumItems(); i++) { TBFragmentSpaceAllocator *row = m_rows[i]; if (!best_row || row->height < best_row->height) { // This is the best row so far, if we fit if (needed_h <= row->height && row->HasSpace(needed_w)) { best_row = row; best_row_index = i; if (needed_h == row->height) break; // We can't find a smaller line, so we're done } } } // Return if we're full if (!best_row) return nullptr; // If the row is unused, create a smaller row to only consume needed height for fragment if (best_row->IsAllAvailable() && needed_h < best_row->height) { TBFragmentSpaceAllocator *row; if (!m_rows.GrowIfNeeded() || !(row = new TBFragmentSpaceAllocator(best_row->y + needed_h, m_bitmap_w, best_row->height - needed_h))) return nullptr; // Keep the rows sorted from top to bottom m_rows.Add(row, best_row_index + 1); best_row->height = needed_h; } // Allocate the fragment and copy the fragment data into the map data. if (TBFragmentSpaceAllocator::Space *space = best_row->AllocSpace(needed_w)) { if (TBBitmapFragment *frag = new TBBitmapFragment) { frag->m_map = this; frag->m_row = best_row; frag->m_space = space; frag->m_rect.Set(space->x + border, best_row->y + border, frag_w, frag_h); frag->m_row_height = best_row->height; frag->m_batch_id = 0xffffffff; CopyData(frag, data_stride, frag_data, border); m_need_update = true; m_allocated_pixels += frag->m_space->width * frag->m_row->height; return frag; } else best_row->FreeSpace(space); } return nullptr; } void TBBitmapFragmentMap::FreeFragmentSpace(TBBitmapFragment *frag) { if (!frag) return; assert(frag->m_map == this); #ifdef TB_RUNTIME_DEBUG_INFO // Debug code to clear the area in debug builds so it's easier to // see & debug the allocation & deallocation of fragments in maps. if (uint32 *data32 = new uint32[frag->m_space->width * frag->m_row->height]) { static int c = 0; memset(data32, (c++) * 32, sizeof(uint32) * frag->m_space->width * frag->m_row->height); CopyData(frag, frag->m_space->width, data32, false); m_need_update = true; delete [] data32; } #endif // TB_RUNTIME_DEBUG_INFO m_allocated_pixels -= frag->m_space->width * frag->m_row->height; frag->m_row->FreeSpace(frag->m_space); frag->m_space = nullptr; frag->m_row_height = 0; // If the row is now empty, merge empty rows so larger fragments // have a chance of allocating the space. if (frag->m_row->IsAllAvailable()) { for (int i = 0; i < m_rows.GetNumItems() - 1; i++) { assert(i >= 0); assert(i < m_rows.GetNumItems() - 1); TBFragmentSpaceAllocator *row = m_rows.Get(i); TBFragmentSpaceAllocator *next_row = m_rows.Get(i + 1); if (row->IsAllAvailable() && next_row->IsAllAvailable()) { row->height += next_row->height; m_rows.Delete(i + 1); i--; } } } } void TBBitmapFragmentMap::CopyData(TBBitmapFragment *frag, int data_stride, uint32 *frag_data, int border) { // Copy the bitmap data uint32 *dst = m_bitmap_data + frag->m_rect.x + frag->m_rect.y * m_bitmap_w; uint32 *src = frag_data; for (int i = 0; i < frag->m_rect.h; i++) { memcpy(dst, src, frag->m_rect.w * sizeof(uint32)); dst += m_bitmap_w; src += data_stride; } // Copy the bitmap data to the border around the fragment if (border) { TBRect rect = frag->m_rect.Expand(border, border); // Copy vertical edges dst = m_bitmap_data + rect.x + (rect.y + 1) * m_bitmap_w; src = frag_data; for (int i = 0; i < frag->m_rect.h; i++) { dst[0] = src[0] & 0x00ffffff; dst[rect.w - 1] = src[frag->m_rect.w - 1] & 0x00ffffff; dst += m_bitmap_w; src += data_stride; } // Copy horizontal edges dst = m_bitmap_data + rect.x + 1 + rect.y * m_bitmap_w; src = frag_data; for (int i = 0; i < frag->m_rect.w; i++) dst[i] = src[i] & 0x00ffffff; dst = m_bitmap_data + rect.x + 1 + (rect.y + rect.h - 1) * m_bitmap_w; src = frag_data + (frag->m_rect.h - 1) * data_stride; for (int i = 0; i < frag->m_rect.w; i++) dst[i] = src[i] & 0x00ffffff; } } TBBitmap *TBBitmapFragmentMap::GetBitmap(TB_VALIDATE_TYPE validate_type) { if (m_bitmap && validate_type == TB_VALIDATE_FIRST_TIME) return m_bitmap; ValidateBitmap(); return m_bitmap; } bool TBBitmapFragmentMap::ValidateBitmap() { if (m_need_update) { if (m_bitmap) m_bitmap->SetData(m_bitmap_data); else m_bitmap = g_renderer->CreateBitmap(m_bitmap_w, m_bitmap_h, m_bitmap_data); m_need_update = false; } return m_bitmap ? true : false; } void TBBitmapFragmentMap::DeleteBitmap() { delete m_bitmap; m_bitmap = nullptr; m_need_update = true; } // == TBBitmapFragmentManager ============================================================================= TBBitmapFragmentManager::TBBitmapFragmentManager() : m_num_maps_limit(0) , m_add_border(false) , m_default_map_w(512) , m_default_map_h(512) { } TBBitmapFragmentManager::~TBBitmapFragmentManager() { Clear(); } TBBitmapFragment *TBBitmapFragmentManager::GetFragmentFromFile(const char *filename, bool dedicated_map) { TBID id(filename); // If we already have a fragment for this filename, return that TBBitmapFragment *frag = m_fragments.Get(id); if (frag) return frag; // Load the file TBImageLoader *img = TBImageLoader::CreateFromFile(filename); if (!img) return nullptr; frag = CreateNewFragment(id, dedicated_map, img->Width(), img->Height(), img->Width(), img->Data()); delete img; return frag; } TBBitmapFragment *TBBitmapFragmentManager::CreateNewFragment(const TBID &id, bool dedicated_map, int data_w, int data_h, int data_stride, uint32 *data) { assert(!GetFragment(id)); TBBitmapFragment *frag = nullptr; // Create a fragment in any of the fragment maps. Doing it in the reverse order // would be faster since it's most likely to succeed, but we want to maximize // the amount of fragments per map, so do it in the creation order. if (!dedicated_map) { for (int i = 0; i < m_fragment_maps.GetNumItems(); i++) { if ((frag = m_fragment_maps[i]->CreateNewFragment(data_w, data_h, data_stride, data, m_add_border))) break; } } // If we couldn't create the fragment in any map, create a new map where we know it will fit. bool allow_another_map = (m_num_maps_limit == 0 || m_fragment_maps.GetNumItems() < m_num_maps_limit); if (!frag && allow_another_map && m_fragment_maps.GrowIfNeeded()) { int po2w = TBGetNearestPowerOfTwo(MAX(data_w, m_default_map_w)); int po2h = TBGetNearestPowerOfTwo(MAX(data_h, m_default_map_h)); if (dedicated_map) { po2w = TBGetNearestPowerOfTwo(data_w); po2h = TBGetNearestPowerOfTwo(data_h); } TBBitmapFragmentMap *fm = new TBBitmapFragmentMap(); if (fm && fm->Init(po2w, po2h)) { m_fragment_maps.Add(fm); frag = fm->CreateNewFragment(data_w, data_h, data_stride, data, m_add_border); } else delete fm; } // Finally, add the new fragment to the hash. if (frag && m_fragments.Add(id, frag)) { frag->m_id = id; return frag; } delete frag; return nullptr; } void TBBitmapFragmentManager::FreeFragment(TBBitmapFragment *frag) { if (frag) { g_renderer->FlushBitmapFragment(frag); TBBitmapFragmentMap *map = frag->m_map; frag->m_map->FreeFragmentSpace(frag); m_fragments.Delete(frag->m_id); // If the map is now empty, delete it. if (map->m_allocated_pixels == 0) m_fragment_maps.Delete(m_fragment_maps.Find(map)); } } TBBitmapFragment *TBBitmapFragmentManager::GetFragment(const TBID &id) const { return m_fragments.Get(id); } void TBBitmapFragmentManager::Clear() { m_fragment_maps.DeleteAll(); m_fragments.DeleteAll(); } bool TBBitmapFragmentManager::ValidateBitmaps() { bool success = true; for (int i = 0; i < m_fragment_maps.GetNumItems(); i++) if (!m_fragment_maps[i]->ValidateBitmap()) success = false; return success; } void TBBitmapFragmentManager::DeleteBitmaps() { for (int i = 0; i < m_fragment_maps.GetNumItems(); i++) m_fragment_maps[i]->DeleteBitmap(); } void TBBitmapFragmentManager::SetNumMapsLimit(int num_maps_limit) { m_num_maps_limit = num_maps_limit; } void TBBitmapFragmentManager::SetDefaultMapSize(int w, int h) { assert(TBGetNearestPowerOfTwo(w) == w); assert(TBGetNearestPowerOfTwo(h) == h); m_default_map_w = w; m_default_map_h = h; } int TBBitmapFragmentManager::GetUseRatio() const { int used = 0; int total = 0; for (int i = 0; i < m_fragment_maps.GetNumItems(); i++) { used += m_fragment_maps[i]->m_allocated_pixels; total += m_fragment_maps[i]->m_bitmap_w * m_fragment_maps[i]->m_bitmap_h; } return total ? (used * 100) / total : 0; } #ifdef TB_RUNTIME_DEBUG_INFO void TBBitmapFragmentManager::Debug() { int x = 0; for (int i = 0; i < m_fragment_maps.GetNumItems(); i++) { TBBitmapFragmentMap *fm = m_fragment_maps[i]; if (TBBitmap *bitmap = fm->GetBitmap()) g_renderer->DrawBitmap(TBRect(x, 0, fm->m_bitmap_w, fm->m_bitmap_h), TBRect(0, 0, fm->m_bitmap_w, fm->m_bitmap_h), bitmap); x += fm->m_bitmap_w + 5; } } #endif // TB_RUNTIME_DEBUG_INFO }; // namespace tb