High-level sprite packing code for the 'pack everything' case

This commit is contained in:
Jyrki Vesterinen 2018-10-13 12:30:57 +03:00
parent 62bbdab065
commit 16f20d5286
3 changed files with 109 additions and 10 deletions

View file

@ -20,15 +20,20 @@
#include "log.hpp"
#include "sdl/utils.hpp"
#include "serialization/string_utils.hpp"
#include "utils/math.hpp"
#include <boost/algorithm/string/trim.hpp>
#include <SDL_image.h>
#include <algorithm>
#include <chrono>
#include <future>
#include <iomanip>
#include <numeric>
#include <set>
static lg::log_domain log_opengl("opengl");
#define DBG_GL LOG_STREAM(debug, log_opengl)
#define LOG_GL LOG_STREAM(info, log_opengl)
#define ERR_GL LOG_STREAM(err, log_opengl)
@ -193,9 +198,6 @@ namespace gl
void texture_atlas::init(const std::vector<std::string>& images, thread_pool& thread_pool)
{
sprites_.clear();
sprites_by_name_.clear();
// Determine unique base images.
std::unordered_map<std::string, surface> base_images;
for(const std::string& i : images) {
@ -229,7 +231,8 @@ void texture_atlas::init(const std::vector<std::string>& images, thread_pool& th
// Apply IPFs.
thread_pool.run(sprites, &apply_IPFs).wait();
// TODO: pack sprites into the texture atlas
// Pack sprites.
pack_sprites_wrapper(sprites);
}
bool texture_atlas::sprite_data::operator<(const sprite_data& other) const
@ -239,6 +242,70 @@ bool texture_atlas::sprite_data::operator<(const sprite_data& other) const
return my_dims > other_dims;
}
void texture_atlas::pack_sprites_wrapper(std::vector<sprite_data>& sprites)
{
using std::chrono::duration_cast;
using std::chrono::milliseconds;
auto start_time = std::chrono::high_resolution_clock::now();
sprites_.clear();
sprites_by_name_.clear();
// Sort the sprites to make them pack better.
std::stable_sort(sprites.begin(), sprites.end());
unsigned int total_size = std::accumulate(sprites.begin(), sprites.end(), 0u,
[](const unsigned int& size, const sprite_data& sprite)
{
return size + sprite.surf->w * sprite.surf->h;
});
std::pair<int, int> texture_size = calculate_initial_texture_size(total_size);
if(texture_size.first > texture::MAX_DIMENSION ||
texture_size.second > texture::MAX_DIMENSION) {
// No way the sprites would fit.
throw packing_error();
}
texture_.set_size(texture_size);
while(true) {
try {
pack_sprites(sprites);
break;
} catch(packing_error&) {
// Double the shorter dimension (width in case of tie).
if(texture_size.first <= texture_size.second) {
texture_size.first *= 2;
} else {
texture_size.second *= 2;
}
if(texture_size.first > texture::MAX_DIMENSION ||
texture_size.second > texture::MAX_DIMENSION) {
// Ran out of space.
throw;
}
texture_.set_size(texture_size);
}
}
auto end_time = std::chrono::high_resolution_clock::now();
auto time = end_time - start_time;
const double BYTES_PER_PIXEL = 4.0;
double efficiency = static_cast<double>(total_size) /
(texture_size.first * texture_size.second);
DBG_GL << std::setprecision(3u) << "Texture atlas packed: " << sprites.size() <<
" sprites (" << (BYTES_PER_PIXEL * total_size / (1 << 20)) << " MB)" <<
" packed to a " << texture_size.first << "x" << texture_size.second <<
" texture, efficiency << " << 100.0 * efficiency << " %, packing took " <<
duration_cast<milliseconds>(time).count() << " ms";
// TODO: construct the texture atlas
}
void texture_atlas::pack_sprites(std::vector<sprite_data>& sprites)
{
free_rectangles_.clear();
@ -327,8 +394,7 @@ void texture_atlas::apply_IPFs(sprite_data& sprite)
try {
surf = (*mod)(surf);
}
catch(const image::modification::imod_exception& e) {
} catch(const image::modification::imod_exception& e) {
std::ostringstream ss;
ss << "\n";
@ -359,8 +425,7 @@ bool texture_atlas::better_fit(const sprite_data& sprite, const SDL_Rect& rect_a
if(std::min(leftover_a, leftover_b) < 0) {
// ...choose the other rectangle, it might fit.
return leftover_b < leftover_a;
}
else {
} else {
// Otherwise choose the rectangle with less leftover space.
return leftover_a < leftover_b;
}
@ -381,8 +446,7 @@ std::pair<SDL_Rect, SDL_Rect> texture_atlas::split_rectangle(const SDL_Rect& rec
rect_b.y = rectangle.y;
rect_b.w = sprite.surf->w;
rect_b.h = rectangle.h - sprite.surf->h;
}
else {
} else {
rect_a.x = rectangle.x;
rect_a.y = rectangle.y;
rect_a.w = rectangle.w;
@ -397,4 +461,12 @@ std::pair<SDL_Rect, SDL_Rect> texture_atlas::split_rectangle(const SDL_Rect& rec
return {rect_a, rect_b};
}
std::pair<int, int> texture_atlas::calculate_initial_texture_size(unsigned int combined_sprite_size)
{
double num_bits = bit_width<unsigned int>() - count_leading_zeros(combined_sprite_size) + 1;
int width = 1 << static_cast<unsigned int>(std::ceil(num_bits / 2.0));
int height = 1 << static_cast<unsigned int>(std::floor(num_bits / 2.0));
return {width, height};
}
}

View file

@ -82,6 +82,7 @@ private:
std::unordered_map<std::string, const sprite*> sprites_by_name_;
std::vector<SDL_Rect> free_rectangles_;
void pack_sprites_wrapper(std::vector<sprite_data>& sprites);
void pack_sprites(std::vector<sprite_data>& sprites);
void place_sprite(sprite_data& sprite);
static void load_image(sprite_data& sprite);
@ -89,5 +90,6 @@ private:
/// @return true if it would be better to place the @param sprite to @param rect_a than @param rect_b.
static bool better_fit(const sprite_data& sprite, const SDL_Rect& rect_a, const SDL_Rect& rect_b);
static std::pair<SDL_Rect, SDL_Rect> split_rectangle(const SDL_Rect& rectangle, const sprite_data& sprite);
static std::pair<int, int> calculate_initial_texture_size(unsigned int combined_sprite_size);
};
}

View file

@ -207,6 +207,26 @@ inline unsigned int count_leading_zeros_impl(N n, std::size_t w) {
}
#endif
/**
* Rounds `n` up to the nearest power of two. As examples, returns 64 for 64,
* and 128 for 65.
*
* @tparam N The type of `n`. Requirements are the same as for
* @ref count_leading_zeros().
*
* @param n An integer upon which to operate.
*
* @returns The nearest power of two that's equal or larger than n.
*/
template<typename N>
inline N round_up_to_power_of_two(N n) {
if(!is_power_of_two(n)) {
return static_cast<N>(1) << (bit_width<N>() - count_leading_zeros(n));
} else {
return n;
}
}
/**
* Returns the quantity of leading `0` bits in `n` i.e., the quantity of
* bits in `n`, minus the 1-based bit index of the most significant `1` bit in
@ -287,6 +307,11 @@ inline int rounded_division(int a, int b)
return 2 * res.rem > b ? (res.quot + 1) : res.quot;
}
template<typename N>
bool is_power_of_two(N n) {
return n != 0 && (n & (n - 1)) == 0;
}
#if 1
typedef int32_t fixed_t;