Skip to content

Commit

Permalink
Added a texture cache for pango_text render output
Browse files Browse the repository at this point in the history
  • Loading branch information
Vultraz committed Jul 14, 2017
1 parent 5fce25a commit eb1e7d6
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 30 deletions.
4 changes: 2 additions & 2 deletions src/display.cpp
Expand Up @@ -2171,7 +2171,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
Expand All @@ -2184,7 +2184,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;
Expand Down
2 changes: 1 addition & 1 deletion src/floating_label.cpp
Expand Up @@ -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;
Expand Down
62 changes: 60 additions & 2 deletions src/font/text.cpp
Expand Up @@ -32,12 +32,17 @@
#include "serialization/unicode.hpp"
#include "preferences/general.hpp"

#include <boost/functional/hash_fwd.hpp>

#include <cassert>
#include <cstring>
#include <stdexcept>

namespace font {

// Cache
//pango_text_cache_t rendered_text_cache {};

pango_text::pango_text()
#if PANGO_VERSION_CHECK(1,22,0)
: context_(pango_font_map_create_context(pango_cairo_font_map_get_default()), g_object_unref)
Expand Down Expand Up @@ -67,6 +72,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);
Expand All @@ -90,12 +96,17 @@ pango_text::pango_text()
cairo_font_options_destroy(fo);
}

surface& pango_text::render()
texture& pango_text::render_and_get_texture()
{
this->rerender();
return surface_;
return rendered_text_cache[hash_];
}

surface& pango_text::render_and_get_surface()
{
this->rerender();
return surface_;
}

int pango_text::get_width() const
{
Expand Down Expand Up @@ -690,6 +701,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_); }
Expand Down Expand Up @@ -722,6 +743,9 @@ void pango_text::rerender(const bool force)

surface_.assign(SDL_CreateRGBSurfaceFrom(
&surface_buffer_[0], width, height, 32, stride, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));

// ...and add it to the cache.
rendered_text_cache.emplace(hash_, texture(surface_));
}
}

Expand Down Expand Up @@ -851,3 +875,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
58 changes: 50 additions & 8 deletions src/font/text.hpp
Expand Up @@ -17,29 +17,32 @@
#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"

#include <pango/pango.h>
#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;

namespace gui2 {
namespace gui2
{
struct point;
} // namespace gui2;

namespace font {

namespace font
{
// add background color and also font markup.

/**
Expand Down Expand Up @@ -85,12 +88,20 @@ class pango_text
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;
Expand Down Expand Up @@ -436,6 +447,12 @@ class pango_text
std::string format_link_tokens(const std::string & text) const;

std::string handle_token(const std::string & token) 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>;
};

/**
Expand All @@ -447,4 +464,29 @@ class pango_text
*/
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
31 changes: 14 additions & 17 deletions src/gui/core/canvas.cpp
Expand Up @@ -1301,22 +1301,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
Expand All @@ -1335,25 +1340,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);
}
Expand Down

0 comments on commit eb1e7d6

Please sign in to comment.