Added a texture cache for pango_text render output

This commit is contained in:
Charles Dang 2017-07-14 12:00:30 +11:00
parent a0f8ad67a5
commit 8647ff63c0
5 changed files with 124 additions and 29 deletions

View file

@ -2201,7 +2201,7 @@ void display::refresh_report(const std::string& report_name, const config * new_
text.set_text(t, true);
text.set_maximum_width(area.w);
text.set_maximum_height(area.h, false);
surface s = text.render();
surface s = text.render_and_get_surface();
// check if next element is text with almost no space to show it
const int minimal_text = 12; // width in pixels
@ -2214,7 +2214,7 @@ void display::refresh_report(const std::string& report_name, const config * new_
//NOTE this space should be longer than minimal_text pixels
t = t + " ";
text.set_text(t, true);
s = text.render();
s = text.render_and_get_surface();
// use the area of this element for next tooltips
used_ellipsis = true;
ellipsis_area.x = x;

View file

@ -108,7 +108,7 @@ texture floating_label::create_texture()
renderer.set_text(text_, use_markup_);
surface& foreground = renderer.render();
surface& foreground = renderer.render_and_get_surface();
if(foreground == nullptr) {
ERR_FT << "could not create floating label's text" << std::endl;

View file

@ -33,6 +33,7 @@
#include "preferences/general.hpp"
#include <boost/algorithm/string/replace.hpp>
#include <boost/functional/hash_fwd.hpp>
#include <cassert>
#include <cstring>
@ -40,6 +41,9 @@
namespace font {
// Cache
//pango_text_cache_t rendered_text_cache {};
pango_text::pango_text()
: context_(pango_font_map_create_context(pango_cairo_font_map_get_default()), g_object_unref)
, layout_(pango_layout_new(context_.get()), g_object_unref)
@ -65,6 +69,7 @@ pango_text::pango_text()
, length_(0)
, surface_dirty_(true)
, surface_buffer_()
, hash_(0)
{
// With 72 dpi the sizes are the same as with SDL_TTF so hardcoded.
pango_cairo_context_set_resolution(context_.get(), 72.0);
@ -88,13 +93,18 @@ pango_text::pango_text()
cairo_font_options_destroy(fo);
}
surface& pango_text::render()
texture& pango_text::render_and_get_texture()
{
this->rerender();
return rendered_text_cache[hash_];
}
surface& pango_text::render_and_get_surface()
{
this->rerender();
return surface_;
}
int pango_text::get_width() const
{
return this->get_size().x;
@ -694,6 +704,16 @@ void pango_text::rerender(const bool force)
this->recalculate(force);
surface_dirty_ = false;
// Update hash
hash_ = std::hash<pango_text>()(*this);
// If we already have the appropriate texture in-cache, exit.
auto iter = rendered_text_cache.find(hash_);
if(iter != rendered_text_cache.end()) {
return;
}
// Else, render the updated text...
int width = rect_.x + rect_.width;
int height = rect_.y + rect_.height;
if(maximum_width_ > 0) { width = std::min(width, maximum_width_); }
@ -729,6 +749,9 @@ void pango_text::rerender(const bool force)
surface_.assign(SDL_CreateRGBSurfaceFrom(
&surface_buffer_[0], width, height, 32, stride, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
#endif
// ...and add it to the cache.
rendered_text_cache.emplace(hash_, texture(surface_));
}
}
@ -873,3 +896,37 @@ pango_text& get_text_renderer()
}
} // namespace font
namespace std
{
size_t hash<font::pango_text>::operator()(const font::pango_text& t) const
{
using boost::hash_value;
using boost::hash_combine;
//
// Text hashing uses 32-bit FNV-1a.
// http://isthe.com/chongo/tech/comp/fnv/#FNV-1a
//
size_t hash = 2166136261;
for(const char& c : t.text_) {
hash |= c;
hash *= 16777619;
}
hash_combine(hash, t.font_class_);
hash_combine(hash, t.font_size_);
hash_combine(hash, t.font_style_);
hash_combine(hash, t.foreground_color_.to_rgba_bytes());
hash_combine(hash, t.get_width());
hash_combine(hash, t.get_height());
hash_combine(hash, t.maximum_width_);
hash_combine(hash, t.maximum_height_);
hash_combine(hash, t.alignment_);
hash_combine(hash, t.ellipse_mode_);
return hash;
}
} // namespace std

View file

@ -17,6 +17,7 @@
#include "font/font_options.hpp"
#include "color.hpp"
#include "sdl/surface.hpp"
#include "sdl/texture.hpp"
#include "serialization/string_utils.hpp"
#include "serialization/unicode_types.hpp"
@ -24,19 +25,20 @@
#include <pango/pangocairo.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
/***
/**
* Note: This is the cairo-pango code path, not the SDL_TTF code path.
*/
struct language_def;
struct point;
namespace font {
namespace font
{
// add background color and also font markup.
/**
@ -82,12 +84,20 @@ public:
pango_text & operator = (const pango_text &) = delete;
/**
* Returns the rendered text.
* Returns the rendered text texture from the cache.
*
* Before rendering it tests whether a redraw is needed and if so it first
* redraws the surface before returning it.
* If the surface is flagged dirty it will first be re-rendered and a new
* texture added to the cache upon redraw.
*/
surface& render();
texture& render_and_get_texture();
/**
* Returns the rendered text surface directly.
*
* If the surface is flagged dirty it will first be re-rendered and a new
* texture added to the cache upon redraw.
*/
surface& render_and_get_surface();
/** Returns the width needed for the text. */
int get_width() const;
@ -445,6 +455,12 @@ private:
std::vector<std::string> find_links(utils::string_view text) const;
void format_links(std::string& text, const std::vector<std::string>& links) const;
/** Hash for the current settings (text, size, etc) configuration. */
size_t hash_;
// Allow specialization of std::hash for pango_text
friend struct std::hash<pango_text>;
};
/**
@ -456,4 +472,29 @@ private:
*/
pango_text& get_text_renderer();
using pango_text_cache_t = std::map<size_t, texture>;
/**
* The text texture cache.
*
* Each time a specific bit of text is rendered, a corresponding texture is created and
* added to the cache. We don't store the surface since there isn't really any use for
* it. If we need texture size that can be easily queried.
*
* @todo Figure out how this can be optimized with a texture atlas. It should be possible
* to store smaller bits of text in the atlas and construct new textures from them.
*/
static pango_text_cache_t rendered_text_cache;
} // namespace font
// Specialize std::hash for pango_text
namespace std
{
template<>
struct hash<font::pango_text>
{
size_t operator()(const font::pango_text& t) const;
};
} // namespace std

View file

@ -1293,22 +1293,27 @@ void text_shape::draw(
: PANGO_ELLIPSIZE_END)
.set_characters_per_line(characters_per_line_);
surface& surf = text_renderer.render();
if(surf->w == 0) {
// Get the resulting texture.
texture& txt = text_renderer.render_and_get_texture();
// TODO: should use pango_text::get_size but the dimensions are inaccurate. Investigate.
texture::info info = txt.get_info();
if(info.w == 0) {
DBG_GUI_D << "Text: Rendering '" << text
<< "' resulted in an empty canvas, leave.\n";
return;
}
wfl::map_formula_callable local_variables(variables);
local_variables.add("text_width", wfl::variant(surf->w));
local_variables.add("text_height", wfl::variant(surf->h));
local_variables.add("text_width", wfl::variant(info.w));
local_variables.add("text_height", wfl::variant(info.h));
/*
std::cerr << "Text: drawing text '" << text
<< " maximum width " << maximum_width_(variables)
<< " maximum height " << maximum_height_(variables)
<< " text width " << surf->w
<< " text height " << surf->h;
<< " text width " << info.w
<< " text height " << info.h;
*/
///@todo formulas are now recalculated every draw cycle which is a
// bit silly unless there has been a resize. So to optimize we should
@ -1327,25 +1332,17 @@ void text_shape::draw(
_("Text doesn't start on canvas."));
// A text might be to long and will be clipped.
if(surf->w > static_cast<int>(w)) {
if(info.w > static_cast<int>(w)) {
WRN_GUI_D << "Text: text is too wide for the "
"canvas and will be clipped.\n";
}
if(surf->h > static_cast<int>(h)) {
if(info.h > static_cast<int>(h)) {
WRN_GUI_D << "Text: text is too high for the "
"canvas and will be clipped.\n";
}
SDL_Rect dst = sdl::create_rect(x, y, surf->w, surf->h);
/* NOTE: we cannot use SDL_UpdateTexture to copy the surface pixel data directly to the canvas texture
* since no alpha blending occurs; values (even pure alpha) totally overwrite the underlying pixel data.
*
* To work around that, we create a texture from the surface and copy it to the renderer. This cleanly
* copies the surface to the canvas texture with the appropriate alpha blending.
*/
texture txt(surf);
SDL_Rect dst = sdl::create_rect(x, y, info.w, info.h);
CVideo::get_singleton().render_copy(txt, nullptr, &dst);
}