Skip to content

Commit

Permalink
Add bitpacked maps
Browse files Browse the repository at this point in the history
Refactor map shape attribute

Modify tests to use new bitpacked maps

Major improvement in performance benchmarks after switching to these
reduced memory maps. Performance is at least back to normal.
  • Loading branch information
HexDecimal committed Nov 5, 2024
1 parent 5bd9b58 commit 6ee1c81
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 37 deletions.
1 change: 1 addition & 0 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"bhex",
"bitdepth",
"bitfield",
"BITPACKED",
"BKGND",
"blambot",
"BLENDMODE",
Expand Down
2 changes: 2 additions & 0 deletions buildsys/autotools/sources.am
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ libtcod_fov_include_HEADERS = \
../../include/libtcod-fov/fov.h \
../../include/libtcod-fov/fov.hpp \
../../include/libtcod-fov/fov_types.h \
../../include/libtcod-fov/libtcod_int.h \
../../include/libtcod-fov/logging.h \
../../include/libtcod-fov/map.hpp \
../../include/libtcod-fov/map_inline.h \
../../include/libtcod-fov/map_types.h \
../../include/libtcod-fov/version.h
Expand Down
2 changes: 2 additions & 0 deletions include/libtcod-fov.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@
#include "libtcod-fov/error.h"
#include "libtcod-fov/fov.h"
#include "libtcod-fov/logging.h"
#include "libtcod-fov/map_inline.h"
#include "libtcod-fov/map_types.h"
#include "libtcod-fov/version.h"

#ifdef __cplusplus
#include "libtcod-fov/bresenham.hpp"
#include "libtcod-fov/fov.hpp"
#include "libtcod-fov/map.hpp"
#endif // __cplusplus

#endif // TCODFOV_H_
Original file line number Diff line number Diff line change
Expand Up @@ -46,50 +46,50 @@ extern "C" {
#endif

/* fov internal stuff */
TCODFOV_Error TCODFOV_map_compute_fov_circular_raycasting(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_circular_raycasting(
const TCODFOV_Map2D* __restrict transparent,
TCODFOV_Map2D* __restrict fov,
int pov_x,
int pov_y,
int max_radius,
bool light_walls);
TCODFOV_Error TCODFOV_map_compute_fov_diamond_raycasting(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_diamond_raycasting(
const TCODFOV_Map2D* __restrict transparent,
TCODFOV_Map2D* __restrict fov,
int pov_x,
int pov_y,
int max_radius,
bool light_walls);
TCODFOV_Error TCODFOV_map_compute_fov_recursive_shadowcasting(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_recursive_shadowcasting(
const TCODFOV_Map2D* __restrict transparent,
TCODFOV_Map2D* __restrict fov,
int pov_x,
int pov_y,
int max_radius,
bool light_walls);
TCODFOV_Error TCODFOV_map_compute_fov_permissive2(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_permissive2(
const TCODFOV_Map2D* __restrict transparent,
TCODFOV_Map2D* __restrict fov,
int pov_x,
int pov_y,
int max_radius,
bool light_walls,
int permissiveness);
TCODFOV_Error TCODFOV_map_compute_fov_restrictive_shadowcasting(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_restrictive_shadowcasting(
const TCODFOV_Map2D* __restrict transparent,
TCODFOV_Map2D* __restrict fov,
int pov_x,
int pov_y,
int max_radius,
bool light_walls);
TCODFOV_Error TCODFOV_map_compute_fov_symmetric_shadowcast(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_symmetric_shadowcast(
const TCODFOV_Map2D* __restrict transparent,
TCODFOV_Map2D* __restrict fov,
int pov_x,
int pov_y,
int max_radius,
bool light_walls);
TCODFOV_Error TCODFOV_map_postprocess(
TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_postprocess(
const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int radius);
/**
Return true if `x` and `y` are in the boundaries of `map`.
Expand Down
14 changes: 14 additions & 0 deletions include/libtcod-fov/map.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <memory>

#include "map_inline.h"
#include "map_types.h"

namespace tcod::fov {

struct MapDeleter {
void operator()(TCODFOV_Map2D* map) const { TCODFOV_map2d_delete(map); }
};

typedef std::unique_ptr<TCODFOV_Map2D, MapDeleter> Map2DPtr;

} // namespace tcod::fov
42 changes: 40 additions & 2 deletions include/libtcod-fov/map_inline.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
#pragma once
#ifndef TCODFOV_MAP_INLINE_H_
#define TCODFOV_MAP_INLINE_H_
#include <stddef.h>
#include <stdlib.h>

#include "fov_types.h"
#include "map_types.h"

/// @brief Return minimum byte length which can hold the given number of bits.
static inline ptrdiff_t TCODFOV_round_to_byte_(ptrdiff_t bits) { return (((bits - 1) | 7) + 1) / 8; }

/// @brief Return a new bitpacked map of the given size.
/// @return The new map, or NULL if memory could not be allocated.
static inline TCODFOV_Map2D* TCODFOV_map2d_new_bitpacked(int width, int height) {
const ptrdiff_t byte_width = TCODFOV_round_to_byte_(width);
TCODFOV_Map2D* map = (TCODFOV_Map2D*)calloc(1, sizeof(*map) + byte_width * height);
if (!map) return NULL;
map->bitpacked.shape[0] = height;
map->bitpacked.shape[1] = width;
map->bitpacked.y_stride = byte_width;
map->bitpacked.data = (uint8_t*)map + sizeof(*map);
return map;
}

/// @brief Delete a map created by any TCODFOV_map2d_new function.
static inline void TCODFOV_map2d_delete(TCODFOV_Map2D* map) {
if (map) free(map);
}

/// @brief Return the width of a 2D map.
/// @param map Map union pointer, can be NULL.
/// @return The map width in tiles, or zero if `map` is NULL.
static inline int TCODFOV_map2d_get_width(const TCODFOV_Map2D* __restrict map) {
if (!map) return 0;
switch (map->type) {
case TCODFOV_MAP2D_CALLBACK:
return map->bool_callback.width;
case TCODFOV_MAP2D_BITPACKED:
// Multiple structs share the same shape format
return map->bool_callback.shape[1];
case TCODFOV_MAP2D_DEPRECATED:
return map->deprecated_map.map.width;
default:
Expand All @@ -25,7 +51,8 @@ static inline int TCODFOV_map2d_get_height(const TCODFOV_Map2D* __restrict map)
if (!map) return 0;
switch (map->type) {
case TCODFOV_MAP2D_CALLBACK:
return map->bool_callback.height;
case TCODFOV_MAP2D_BITPACKED:
return map->bool_callback.shape[0];
case TCODFOV_MAP2D_DEPRECATED:
return map->deprecated_map.map.height;
default:
Expand Down Expand Up @@ -67,10 +94,15 @@ static inline bool TCODFOV_map2d_get_bool(const TCODFOV_Map2D* __restrict map, i
return cell->fov;
}
}
case TCODFOV_MAP2D_BITPACKED: {
const uint8_t active_bit = 1 << (x % 8);
return (map->bitpacked.data[map->bitpacked.y_stride * y + (x / 8)] & active_bit) != 0;
}
default:
return 0;
}
}

/// @brief Assign the boolean `value` to `{x, y}` on `map`. Out-of-bounds writes are ignored.
/// @param map Map union pointer, can be NULL.
/// @param x X coordinate.
Expand Down Expand Up @@ -99,6 +131,12 @@ static inline void TCODFOV_map2d_set_bool(TCODFOV_Map2D* __restrict map, int x,
return;
}
}
case TCODFOV_MAP2D_BITPACKED: {
const uint8_t active_bit = 1 << (x % 8);
const ptrdiff_t index = map->bitpacked.y_stride * y + (x / 8);
map->bitpacked.data[index] = (map->bitpacked.data[index] & ~active_bit) | (value ? active_bit : 0);
return;
}
default:
return;
}
Expand Down
15 changes: 13 additions & 2 deletions include/libtcod-fov/map_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#define TCODFOV_MAP_TYPES_H_

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "fov_types.h"

Expand All @@ -11,13 +13,13 @@ typedef enum TCODFOV_Map2DType {
TCODFOV_MAP2D_UNDEFINED = 0,
TCODFOV_MAP2D_CALLBACK = 1,
TCODFOV_MAP2D_DEPRECATED = 2,
TCODFOV_MAP2D_BITPACKED = 3,
} TCODFOV_Map2DType;

/// @brief Callbacks to get/set on 2D grids.
struct TCODFOV_Map2DCallback {
TCODFOV_Map2DType type; // Must be TCODFOV_MAP2D_CALLBACK
int width;
int height;
int shape[2]; // {height, width}
void* userdata;
bool (*get)(void* userdata, int x, int y); // Get callback
void (*set)(void* userdata, int x, int y, bool v); // Set callback
Expand All @@ -30,10 +32,19 @@ struct TCODFOV_Map2DDeprecated {
TCODFOV_Map map;
};

/// @brief Bitpacked 2D grid.
struct TCODFOV_Map2DBitpacked {
TCODFOV_Map2DType type; // Must be TCODFOV_MAP2D_BITPACKED
int shape[2]; // {height, width}
uint8_t* __restrict data; // Boolean data packed into bytes
ptrdiff_t y_stride; // Array stride along the y-axis
};

/// @brief Union type for 2D maps.
typedef union TCODFOV_Map2D {
TCODFOV_Map2DType type;
struct TCODFOV_Map2DCallback bool_callback;
struct TCODFOV_Map2DDeprecated deprecated_map;
struct TCODFOV_Map2DBitpacked bitpacked;
} TCODFOV_Map2D;
#endif // TCODFOV_MAP_TYPES_H_
3 changes: 2 additions & 1 deletion src/sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ target_sources(${PROJECT_NAME} PRIVATE
libtcod-fov/fov_recursive_shadowcasting.c
libtcod-fov/fov_restrictive.c
libtcod-fov/fov_symmetric_shadowcast.c
libtcod-fov/libtcod_int.h
libtcod-fov/logging.c
libtcod-fov/utility.h
)
Expand All @@ -27,7 +26,9 @@ install(FILES
../include/libtcod-fov/fov.h
../include/libtcod-fov/fov.hpp
../include/libtcod-fov/fov_types.h
../include/libtcod-fov/libtcod_int.h
../include/libtcod-fov/logging.h
../include/libtcod-fov/map.hpp
../include/libtcod-fov/map_inline.h
../include/libtcod-fov/map_types.h
../include/libtcod-fov/version.h
Expand Down
60 changes: 35 additions & 25 deletions tests/test_fov.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@
#include <fstream>
#include <gsl/gsl>
#include <iostream>
#include <libtcod-fov/fov.hpp>
#include <memory>
#include <ranges>
#include <string>
#include <utility>
#include <vector>

#include "libtcod-fov/fov.hpp"
#include "libtcod-fov/libtcod_int.h"
#include "libtcod-fov/map.hpp"

struct MapInfo {
std::string name{};
tcod::fov::MapPtr_ data{}; // Map tile data
tcod::fov::Map2DPtr data{}; // Map tile data
std::tuple<int, int> pov{0, 0};
};

Expand All @@ -32,15 +36,15 @@ static auto load_map(std::string name, const std::filesystem::path& path) {
std::ranges::max(lines | std::views::transform([](const auto& line) { return gsl::narrow<int>(line.size()); }));
auto map = MapInfo{
.name = std::move(name),
.data = tcod::fov::MapPtr_{TCODFOV_map_new(map_width, map_height)},
.data = tcod::fov::Map2DPtr{TCODFOV_map2d_new_bitpacked(map_width, map_height)},
};
for (int y = 0; y < gsl::narrow<int>(lines.size()); ++y) {
const auto& line = lines.at(y);
for (int x = 0; x < map_width; ++x) {
static constexpr auto DEFAULT_CH = '.';
const auto ch = (x < gsl::narrow<int>(line.size()) ? line.at(x) : DEFAULT_CH);
const bool transparent = ch != '#';
TCODFOV_map_set_properties(map.data.get(), x, y, transparent, false);
TCODFOV_map2d_set_bool(map.data.get(), x, y, transparent);
if (ch == '@') {
map.pov = {x, y};
}
Expand All @@ -49,30 +53,34 @@ static auto load_map(std::string name, const std::filesystem::path& path) {
return map;
}

static auto new_map_with_radius(int radius, bool start_transparent) -> tcod::fov::MapPtr_ {
static auto new_map_with_radius(int radius, bool start_transparent) -> tcod::fov::Map2DPtr {
const int size = radius * 2 + 1;
tcod::fov::MapPtr_ map{TCODFOV_map_new(size, size)};
TCODFOV_map_clear(map.get(), start_transparent, 0);
tcod::fov::Map2DPtr map{TCODFOV_map2d_new_bitpacked(size, size)};
for (int y = 0; y < TCODFOV_map2d_get_height(map.get()); ++y) {
for (int x = 0; x < TCODFOV_map2d_get_height(map.get()); ++x) {
TCODFOV_map2d_set_bool(map.get(), x, y, start_transparent);
}
}
return map;
}
static auto new_empty_map(int radius) -> tcod::fov::MapPtr_ { return new_map_with_radius(radius, true); }
static auto new_opaque_map(int radius) -> tcod::fov::MapPtr_ { return new_map_with_radius(radius, false); }
static auto new_corridor_map(int radius) -> tcod::fov::MapPtr_ {
tcod::fov::MapPtr_ map{new_map_with_radius(radius, false)};
static auto new_empty_map(int radius) -> tcod::fov::Map2DPtr { return new_map_with_radius(radius, true); }
static auto new_opaque_map(int radius) -> tcod::fov::Map2DPtr { return new_map_with_radius(radius, false); }
static auto new_corridor_map(int radius) -> tcod::fov::Map2DPtr {
tcod::fov::Map2DPtr map{new_map_with_radius(radius, false)};
for (int i = 0; i < radius * 2 + 1; ++i) {
TCODFOV_map_set_properties(map.get(), radius, i, true, true);
TCODFOV_map_set_properties(map.get(), i, radius, true, true);
TCODFOV_map2d_set_bool(map.get(), radius, i, true);
TCODFOV_map2d_set_bool(map.get(), i, radius, true);
}
return map;
}
static auto new_forest_map(int radius) -> tcod::fov::MapPtr_ {
static auto new_forest_map(int radius) -> tcod::fov::Map2DPtr {
// Forest map with 1 in 4 chance of a blocking tile.
std::mt19937 rng(0);
std::uniform_int_distribution<int> chance(0, 3);
tcod::fov::MapPtr_ map{new_map_with_radius(radius, true)};
for (int i = 0; i < map->nbcells; ++i) {
if (chance(rng) == 0) {
map->cells[i].transparent = false;
tcod::fov::Map2DPtr map{new_map_with_radius(radius, true)};
for (int y = 0; y < TCODFOV_map2d_get_height(map.get()); ++y) {
for (int x = 0; x < TCODFOV_map2d_get_height(map.get()); ++x) {
if (chance(rng) == 0) TCODFOV_map2d_set_bool(map.get(), x, y, false);
}
}
return map;
Expand Down Expand Up @@ -102,25 +110,27 @@ TEST_CASE("FOV Benchmarks", "[.benchmark]") {
};
for (auto& active_test : test_maps) {
const std::string& map_name = active_test.name;
TCODFOV_Map* map = active_test.data.get();
tcod::fov::Map2DPtr& map = active_test.data;
tcod::fov::Map2DPtr out = tcod::fov::Map2DPtr{
TCODFOV_map2d_new_bitpacked(TCODFOV_map2d_get_width(map.get()), TCODFOV_map2d_get_height(map.get()))};
const auto [pov_x, pov_y] = active_test.pov;
BENCHMARK(map_name + " TCODFOV_BASIC") {
(void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_BASIC);
(void)!TCODFOV_map_compute_fov_circular_raycasting(map.get(), out.get(), pov_x, pov_y, 0, true);
};
BENCHMARK(map_name + " TCODFOV_DIAMOND") {
(void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_DIAMOND);
(void)!TCODFOV_map_compute_fov_diamond_raycasting(map.get(), out.get(), pov_x, pov_y, 0, true);
};
BENCHMARK(map_name + " TCODFOV_SHADOW") {
(void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_SHADOW);
(void)!TCODFOV_map_compute_fov_recursive_shadowcasting(map.get(), out.get(), pov_x, pov_y, 0, true);
};
BENCHMARK(map_name + " TCODFOV_RESTRICTIVE") {
(void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_RESTRICTIVE);
(void)!TCODFOV_map_compute_fov_restrictive_shadowcasting(map.get(), out.get(), pov_x, pov_y, 0, true);
};
BENCHMARK(map_name + " TCODFOV_PERMISSIVE_8") {
(void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_PERMISSIVE_8);
(void)!TCODFOV_map_compute_fov_permissive2(map.get(), out.get(), pov_x, pov_y, 0, true, 8);
};
BENCHMARK(map_name + " TCODFOV_SYMMETRIC_SHADOWCAST") {
(void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_SYMMETRIC_SHADOWCAST);
(void)!TCODFOV_map_compute_fov_symmetric_shadowcast(map.get(), out.get(), pov_x, pov_y, 0, true);
};
}
}

0 comments on commit 6ee1c81

Please sign in to comment.