WIP: texture atlas class
Currently only loading images and applying IPFs is implemented. Most code here is copy-pasted from picture.cpp, I'm planning to eventually remove that file. Next task is implementing the actual sprite packing.
This commit is contained in:
parent
c81095ac8b
commit
af418d86f4
5 changed files with 351 additions and 0 deletions
|
@ -2486,6 +2486,13 @@
|
|||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)Hotkeys\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)Hotkeys\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ogl\texture_atlas.cpp">
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)OGL\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='ReleaseDEBUG|Win32'">$(IntDir)OGL\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Debug|Win32'">$(IntDir)OGL\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)OGL\</ObjectFileName>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Test_Release|Win32'">$(IntDir)OGL\</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\picture.cpp" />
|
||||
<ClCompile Include="..\..\src\image_modifications.cpp" />
|
||||
<ClCompile Include="..\..\src\key.cpp" />
|
||||
|
@ -3878,6 +3885,7 @@
|
|||
<ClInclude Include="..\..\src\hotkey\hotkey_handler_sp.hpp" />
|
||||
<ClInclude Include="..\..\src\hotkey\hotkey_item.hpp" />
|
||||
<ClInclude Include="..\..\src\hotkey\hotkey_manager.hpp" />
|
||||
<ClInclude Include="..\..\src\ogl\texture_atlas.hpp" />
|
||||
<ClInclude Include="..\..\src\picture.hpp" />
|
||||
<ClInclude Include="..\..\src\image_modifications.hpp" />
|
||||
<ClInclude Include="..\..\src\key.hpp" />
|
||||
|
|
|
@ -1556,6 +1556,9 @@
|
|||
<Filter>OGL</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\thread_pool.cpp" />
|
||||
<ClCompile Include="..\..\src\ogl\texture_atlas.cpp">
|
||||
<Filter>OGL</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\src\addon\client.hpp">
|
||||
|
@ -3031,6 +3034,9 @@
|
|||
<Filter>OGL</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\thread_pool.hpp" />
|
||||
<ClInclude Include="..\..\src\ogl\texture_atlas.hpp">
|
||||
<Filter>OGL</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="..\..\src\tests\test_sdl_utils.hpp">
|
||||
|
|
|
@ -2,6 +2,7 @@ ogl/context.cpp
|
|||
ogl/shader.cpp
|
||||
ogl/sprite.cpp
|
||||
ogl/texture.cpp
|
||||
ogl/texture_atlas.cpp
|
||||
ogl/utils.cpp
|
||||
sdl/exception.cpp
|
||||
sdl/rect.cpp
|
||||
|
|
272
src/ogl/texture_atlas.cpp
Normal file
272
src/ogl/texture_atlas.cpp
Normal file
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
Copyright (C) 2018 by Jyrki Vesterinen <sandgtx@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#include "ogl/texture_atlas.hpp"
|
||||
|
||||
#include "filesystem.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "image_modifications.hpp"
|
||||
#include "log.hpp"
|
||||
#include "sdl/utils.hpp"
|
||||
#include "serialization/string_utils.hpp"
|
||||
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <SDL_image.h>
|
||||
|
||||
#include <future>
|
||||
#include <set>
|
||||
|
||||
static lg::log_domain log_opengl("opengl");
|
||||
#define LOG_GL LOG_STREAM(info, log_opengl)
|
||||
#define ERR_GL LOG_STREAM(err, log_opengl)
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void standardize_surface_format(surface& surf)
|
||||
{
|
||||
if(!surf.null() && !is_neutral(surf)) {
|
||||
surf = make_neutral_surface(surf);
|
||||
assert(is_neutral(surf));
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_base_image_name(const std::string& image_path)
|
||||
{
|
||||
return utils::split(image_path, '~')[0];
|
||||
}
|
||||
|
||||
std::string get_ipf_string(const std::string& image_path)
|
||||
{
|
||||
std::size_t index = image_path.find('~');
|
||||
if(index == std::string::npos) {
|
||||
return "";
|
||||
} else {
|
||||
return image_path.substr(index);
|
||||
}
|
||||
}
|
||||
|
||||
// Load overlay image and compose it with the original surface.
|
||||
void add_localized_overlay(const std::string& ovr_file, surface& orig_surf)
|
||||
{
|
||||
filesystem::rwops_ptr rwops = filesystem::make_read_RWops(ovr_file);
|
||||
surface ovr_surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
|
||||
if(ovr_surf.null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
standardize_surface_format(ovr_surf);
|
||||
|
||||
SDL_Rect area{0, 0, ovr_surf->w, ovr_surf->h};
|
||||
|
||||
sdl_blit(ovr_surf, 0, orig_surf, &area);
|
||||
}
|
||||
|
||||
// Check if localized file is up-to-date according to l10n track index.
|
||||
// Make sure only that the image is not explicitly recorded as fuzzy,
|
||||
// in order to be able to use non-tracked images (e.g. from UMC).
|
||||
static std::set<std::string> fuzzy_localized_files;
|
||||
bool localized_file_uptodate(const std::string& loc_file)
|
||||
{
|
||||
if(fuzzy_localized_files.empty()) {
|
||||
// First call, parse track index to collect fuzzy files by path.
|
||||
std::string fsep = "\xC2\xA6"; // UTF-8 for "broken bar"
|
||||
std::string trackpath = filesystem::get_binary_file_location("", "l10n-track");
|
||||
|
||||
// l10n-track file not present. Assume image is up-to-date.
|
||||
if(trackpath.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string contents = filesystem::read_file(trackpath);
|
||||
|
||||
for(const std::string& line : utils::split(contents, '\n')) {
|
||||
std::size_t p1 = line.find(fsep);
|
||||
if(p1 == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string state = line.substr(0, p1);
|
||||
boost::trim(state);
|
||||
if(state == "fuzzy") {
|
||||
std::size_t p2 = line.find(fsep, p1 + fsep.length());
|
||||
if(p2 == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string relpath = line.substr(p1 + fsep.length(), p2 - p1 - fsep.length());
|
||||
fuzzy_localized_files.insert(game_config::path + '/' + relpath);
|
||||
}
|
||||
}
|
||||
|
||||
fuzzy_localized_files.insert(""); // make sure not empty any more
|
||||
}
|
||||
|
||||
return fuzzy_localized_files.count(loc_file) == 0;
|
||||
}
|
||||
|
||||
// Return path to localized counterpart of the given file, if any, or empty string.
|
||||
// Localized counterpart may also be requested to have a suffix to base name.
|
||||
std::string get_localized_path(const std::string& file, const std::string& suff = "")
|
||||
{
|
||||
std::string dir = filesystem::directory_name(file);
|
||||
std::string base = filesystem::base_name(file);
|
||||
|
||||
const std::size_t pos_ext = base.rfind(".");
|
||||
|
||||
std::string loc_base;
|
||||
if(pos_ext != std::string::npos) {
|
||||
loc_base = base.substr(0, pos_ext) + suff + base.substr(pos_ext);
|
||||
}
|
||||
else {
|
||||
loc_base = base + suff;
|
||||
}
|
||||
|
||||
// TRANSLATORS: This is the language code which will be used
|
||||
// to store and fetch localized non-textual resources, such as images,
|
||||
// when they exist. Normally it is just the code of the PO file itself,
|
||||
// e.g. "de" of de.po for German. But it can also be a comma-separated
|
||||
// list of language codes by priority, when the localized resource
|
||||
// found for first of those languages will be used. This is useful when
|
||||
// two languages share sufficient commonality, that they can use each
|
||||
// other's resources rather than duplicating them. For example,
|
||||
// Swedish (sv) and Danish (da) are such, so Swedish translator could
|
||||
// translate this message as "sv,da", while Danish as "da,sv".
|
||||
std::vector<std::string> langs = utils::split(_("language code for localized resources^en_US"));
|
||||
|
||||
// In case even the original image is split into base and overlay,
|
||||
// add en_US with lowest priority, since the message above will
|
||||
// not have it when translated.
|
||||
langs.push_back("en_US");
|
||||
for(const std::string& lang : langs) {
|
||||
std::string loc_file = dir + "/" + "l10n" + "/" + lang + "/" + loc_base;
|
||||
if(filesystem::file_exists(loc_file) && localized_file_uptodate(loc_file)) {
|
||||
return loc_file;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
base_images.emplace(get_base_image_name(i), surface());
|
||||
}
|
||||
|
||||
std::vector<sprite_data> base_image_data;
|
||||
base_image_data.reserve(base_images.size());
|
||||
for(const auto& s : base_images) {
|
||||
sprite_data data;
|
||||
data.name = s.first;
|
||||
base_image_data.push_back(data);
|
||||
}
|
||||
|
||||
// Load base images from disk.
|
||||
thread_pool.run(base_image_data, &load_image).wait();
|
||||
|
||||
for(const sprite_data& s : base_image_data) {
|
||||
base_images[s.name] = s.surf;
|
||||
}
|
||||
|
||||
std::vector<sprite_data> sprites;
|
||||
sprites.reserve(images.size());
|
||||
for(const std::string& i : images) {
|
||||
sprite_data data;
|
||||
data.name = i;
|
||||
data.surf = base_images[get_base_image_name(i)];
|
||||
sprites.push_back(data);
|
||||
}
|
||||
|
||||
// Apply IPFs.
|
||||
thread_pool.run(sprites, &apply_IPFs).wait();
|
||||
|
||||
// TODO: pack sprites into the texture atlas
|
||||
}
|
||||
|
||||
void texture_atlas::load_image(sprite_data& sprite)
|
||||
{
|
||||
std::string location = filesystem::get_binary_file_location("images", sprite.name);
|
||||
|
||||
{
|
||||
if(!location.empty()) {
|
||||
// Check if there is a localized image.
|
||||
const std::string loc_location = get_localized_path(location);
|
||||
if(!loc_location.empty()) {
|
||||
location = loc_location;
|
||||
}
|
||||
|
||||
filesystem::rwops_ptr rwops = filesystem::make_read_RWops(location);
|
||||
sprite.surf = IMG_Load_RW(rwops.release(), true); // SDL takes ownership of rwops
|
||||
|
||||
standardize_surface_format(sprite.surf);
|
||||
|
||||
// If there was no standalone localized image, check if there is an overlay.
|
||||
if(!sprite.surf.null() && loc_location.empty()) {
|
||||
const std::string ovr_location = get_localized_path(location, "--overlay");
|
||||
if(!ovr_location.empty()) {
|
||||
add_localized_overlay(ovr_location, sprite.surf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sprite.surf.null()) {
|
||||
ERR_GL << "could not open image '" << sprite.name << "'" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void texture_atlas::apply_IPFs(sprite_data& sprite)
|
||||
{
|
||||
surface surf = sprite.surf;
|
||||
image::modification_queue mods = image::modification::decode(get_ipf_string(sprite.name));
|
||||
|
||||
while(!mods.empty()) {
|
||||
image::modification* mod = mods.top();
|
||||
|
||||
try {
|
||||
surf = (*mod)(surf);
|
||||
}
|
||||
catch(const image::modification::imod_exception& e) {
|
||||
std::ostringstream ss;
|
||||
ss << "\n";
|
||||
|
||||
for(const std::string& mod2 : utils::parenthetical_split(get_ipf_string(sprite.name), '~')) {
|
||||
ss << "\t" << mod2 << "\n";
|
||||
}
|
||||
|
||||
ERR_GL << "Failed to apply a modification to an image:\n"
|
||||
<< "Image: " << get_base_image_name(sprite.name) << "\n"
|
||||
<< "Modifications: " << ss.str() << "\n"
|
||||
<< "Error: " << e.message << "\n";
|
||||
}
|
||||
|
||||
// NOTE: do this *after* applying the mod or you'll get crashes!
|
||||
mods.pop();
|
||||
}
|
||||
|
||||
sprite.surf = surf;
|
||||
}
|
||||
|
||||
}
|
64
src/ogl/texture_atlas.hpp
Normal file
64
src/ogl/texture_atlas.hpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright (C) 2018 by Jyrki Vesterinen <sandgtx@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY.
|
||||
|
||||
See the COPYING file for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ogl/sprite.hpp"
|
||||
#include "ogl/texture.hpp"
|
||||
#include "sdl/surface.hpp"
|
||||
#include "thread_pool.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace gl
|
||||
{
|
||||
class texture_atlas
|
||||
{
|
||||
public:
|
||||
/** Initializes the texture atlas.
|
||||
Clears existing sprites, if any.
|
||||
@param images Images to pack into the texture atlas.
|
||||
As full image paths including IPF chains.
|
||||
@param thread_pool Thread pool that will be used to load the images. */
|
||||
void init(const std::vector<std::string>& images, thread_pool& thread_pool);
|
||||
|
||||
/** Gets a sprite by name.
|
||||
For performance, do not call this in every frame!
|
||||
Cache the sprite instead. */
|
||||
const sprite& get_sprite(const std::string& name) const
|
||||
{
|
||||
return *sprites_by_name_.at(name);
|
||||
}
|
||||
|
||||
private:
|
||||
struct sprite_data
|
||||
{
|
||||
std::string name;
|
||||
surface surf;
|
||||
unsigned int x_min;
|
||||
unsigned int x_max;
|
||||
unsigned int y_min;
|
||||
unsigned int y_max;
|
||||
};
|
||||
|
||||
texture texture_;
|
||||
std::list<sprite> sprites_;
|
||||
std::unordered_map<std::string, const sprite*> sprites_by_name_;
|
||||
|
||||
static void load_image(sprite_data& sprite);
|
||||
static void apply_IPFs(sprite_data& sprite);
|
||||
};
|
||||
}
|
Loading…
Add table
Reference in a new issue