Use row-aligned block data type in BitmapTileArea

Use bit operations to reduce looping over individual bits where possible
This commit is contained in:
Jonathan G Rennison
2024-06-21 02:21:59 +01:00
parent 68b16f6741
commit d4819ad184
3 changed files with 122 additions and 21 deletions

View File

@@ -11,19 +11,38 @@
#define BITMAP_TYPE_HPP #define BITMAP_TYPE_HPP
#include "tilearea_type.h" #include "tilearea_type.h"
#include "core/bitmath_func.hpp"
#include "core/geometry_type.hpp"
#include "core/math_func.hpp"
#include <limits>
#include <vector> #include <vector>
/** Represents a tile area containing containing individually set tiles. /** Represents a tile area containing containing individually set tiles.
* Each tile must be contained within the preallocated area. * Each tile must be contained within the preallocated area.
* A std::vector<bool> is used to mark which tiles are contained. * A std::vector is used to mark which tiles are contained.
*/ */
class BitmapTileArea : public TileArea { class BitmapTileArea : public TileArea {
friend struct BitmapTileIterator;
protected: protected:
std::vector<bool> data; using BlockT = uint32_t;
static constexpr uint BLOCK_BITS = std::numeric_limits<BlockT>::digits;
inline uint Index(uint x, uint y) const { return y * this->w + x; } std::vector<BlockT> data;
inline uint Index(TileIndex tile) const { return Index(TileX(tile) - TileX(this->tile), TileY(tile) - TileY(this->tile)); } inline uint RowPitch() const { return CeilDivT<uint>(this->w, BLOCK_BITS); }
inline void ResetData()
{
this->data.assign(this->h * this->RowPitch(), 0);
}
inline std::pair<uint, uint> Index(uint x, uint y) const
{
return { (y * this->RowPitch()) + (x / BLOCK_BITS), x % BLOCK_BITS };
}
inline std::pair<uint, uint> Index(TileIndex tile) const { return this->Index(TileX(tile) - TileX(this->tile), TileY(tile) - TileY(this->tile)); }
public: public:
BitmapTileArea() BitmapTileArea()
@@ -38,7 +57,7 @@ public:
this->tile = ta.tile; this->tile = ta.tile;
this->w = ta.w; this->w = ta.w;
this->h = ta.h; this->h = ta.h;
this->data.resize(Index(this->w, this->h)); this->ResetData();
} }
/** /**
@@ -61,8 +80,7 @@ public:
this->tile = TileXY(r.left, r.top); this->tile = TileXY(r.left, r.top);
this->w = r.Width(); this->w = r.Width();
this->h = r.Height(); this->h = r.Height();
this->data.clear(); this->ResetData();
this->data.resize(Index(w, h));
} }
void Initialize(const TileArea &ta) void Initialize(const TileArea &ta)
@@ -70,8 +88,7 @@ public:
this->tile = ta.tile; this->tile = ta.tile;
this->w = ta.w; this->w = ta.w;
this->h = ta.h; this->h = ta.h;
this->data.clear(); this->ResetData();
this->data.resize(Index(w, h));
} }
/** /**
@@ -81,7 +98,8 @@ public:
inline void SetTile(TileIndex tile) inline void SetTile(TileIndex tile)
{ {
assert(this->Contains(tile)); assert(this->Contains(tile));
this->data[Index(tile)] = true; auto pos = this->Index(tile);
SetBit(this->data[pos.first], pos.second);
} }
/** /**
@@ -91,16 +109,21 @@ public:
inline void ClrTile(TileIndex tile) inline void ClrTile(TileIndex tile)
{ {
assert(this->Contains(tile)); assert(this->Contains(tile));
this->data[Index(tile)] = false; auto pos = this->Index(tile);
ClrBit(this->data[pos.first], pos.second);
} }
void SetTiles(const TileArea &area);
/** /**
* Test if a tile is part of the tile area. * Test if a tile is part of the tile area.
* @param tile Tile to check * @param tile Tile to check
*/ */
inline bool HasTile(TileIndex tile) const inline bool HasTile(TileIndex tile) const
{ {
return this->Contains(tile) && this->data[Index(tile)]; if (!this->Contains(tile)) return false;
auto pos = this->Index(tile);
return HasBit(this->data[pos.first], pos.second);
} }
inline bool operator==(const BitmapTileArea &other) const inline bool operator==(const BitmapTileArea &other) const
@@ -110,25 +133,62 @@ public:
}; };
/** Iterator to iterate over all tiles belonging to a bitmaptilearea. */ /** Iterator to iterate over all tiles belonging to a bitmaptilearea. */
class BitmapTileIterator : public OrthogonalTileIterator { class BitmapTileIterator : public TileIterator {
protected: protected:
const BitmapTileArea *bitmap; BitmapTileArea::BlockT block; ///< Current block data.
const BitmapTileArea::BlockT *next; ///< Next block pointer.
uint block_x; ///< The current 'x' position in the block iteration.
uint y; ///< The current 'y' position in the rectangle.
TileIndex row_start; ///< Row start tile.
uint pitch; ///< The row pitch.
void advance()
{
while (this->block == 0) {
this->block_x++;
if (this->block_x == this->pitch) {
/* Next row */
this->y--;
if (this->y == 0) {
/* End */
this->tile = INVALID_TILE;
return;
}
this->block_x = 0;
this->row_start += MapSizeX();
}
/* Read next block and advance next pointer */
this->block = *this->next;
this->next++;
}
uint8_t bit = FindFirstBit(this->block);
this->block = KillFirstBit(this->block);
this->tile = this->row_start + (this->block_x * BitmapTileArea::BLOCK_BITS) + bit;
return;
}
public: public:
/** /**
* Construct the iterator. * Construct the iterator.
* @param bitmap BitmapTileArea to iterate. * @param bitmap BitmapTileArea to iterate.
*/ */
BitmapTileIterator(const BitmapTileArea &bitmap) : OrthogonalTileIterator(bitmap), bitmap(&bitmap) BitmapTileIterator(const BitmapTileArea &bitmap) : TileIterator(), block_x(0), y(bitmap.h), row_start(bitmap.tile), pitch(bitmap.RowPitch())
{ {
if (!this->bitmap->HasTile(TileIndex(this->tile))) ++(*this); if (!bitmap.data.empty()) {
this->block = bitmap.data[0];
this->next = bitmap.data.data() + 1;
this->advance();
} else {
this->block = 0;
this->next = nullptr;
}
} }
inline TileIterator& operator ++() override inline TileIterator& operator ++() override
{ {
(*this).OrthogonalTileIterator::operator++(); assert(this->tile != INVALID_TILE);
while (this->tile != INVALID_TILE && !this->bitmap->HasTile(TileIndex(this->tile))) { this->advance();
(*this).OrthogonalTileIterator::operator++();
}
return *this; return *this;
} }

View File

@@ -544,7 +544,7 @@ void Station::RecomputeCatchment(bool no_clear_nearby_lists)
/* This tile sub-loop doesn't need to test any tiles, they are simply added to the catchment set. */ /* This tile sub-loop doesn't need to test any tiles, they are simply added to the catchment set. */
TileArea ta2 = TileArea(tile, 1, 1).Expand(r); TileArea ta2 = TileArea(tile, 1, 1).Expand(r);
for (TileIndex tile2 : ta2) this->catchment_tiles.SetTile(tile2); this->catchment_tiles.SetTiles(ta2);
} }
/* Search catchment tiles for towns and industries */ /* Search catchment tiles for towns and industries */

View File

@@ -10,6 +10,7 @@
#include "stdafx.h" #include "stdafx.h"
#include "tilearea_type.h" #include "tilearea_type.h"
#include "bitmap_type.h"
#include "safeguards.h" #include "safeguards.h"
@@ -295,3 +296,43 @@ TileIterator &DiagonalTileIterator::operator++()
} }
return std::make_unique<OrthogonalTileIterator>(corner1, corner2); return std::make_unique<OrthogonalTileIterator>(corner1, corner2);
} }
/**
* Add a set of tiles as part of the tile area.
* @param area Tile area to add.
*/
void BitmapTileArea::SetTiles(const TileArea &area)
{
if (area.w == 0 || area.h == 0) return;
assert(this->Contains(area.tile));
assert(this->Contains(area.tile + TileDiffXY(area.w - 1, area.h - 1)));
auto [row_idx, start_bit] = this->Index(area.tile);
if (start_bit + area.w <= BLOCK_BITS) {
/* Update only one block */
BlockT mask = GetBitMaskSC<BlockT>(start_bit, area.w);
for (uint h = area.h; h > 0; h--, row_idx += this->RowPitch()) {
this->data[row_idx] |= mask;
}
} else {
/* Update multiple blocks */
for (uint h = area.h; h > 0; h--, row_idx += this->RowPitch()) {
uint idx = row_idx;
this->data[idx] |= (~static_cast<BlockT>(0)) << start_bit;
uint w = area.w - (BLOCK_BITS - start_bit);
while (w > 0) {
idx++;
if (w < BLOCK_BITS) {
/* Set LSBs, and finish row */
this->data[idx] |= GetBitMaskSC<BlockT>(0, w);
break;
} else {
/* Set all bits */
this->data[idx] = ~static_cast<BlockT>(0);
w -= BLOCK_BITS;
}
}
}
}
}