From eb1e7d6a83031e85746f0f4d694fc3ede3d21c08 Mon Sep 17 00:00:00 2001 From: Charles Dang Date: Fri, 14 Jul 2017 12:00:30 +1100 Subject: [PATCH] Added a texture cache for pango_text render output --- src/display.cpp | 4 +-- src/floating_label.cpp | 2 +- src/font/text.cpp | 62 +++++++++++++++++++++++++++++++++++++++-- src/font/text.hpp | 58 ++++++++++++++++++++++++++++++++------ src/gui/core/canvas.cpp | 31 ++++++++++----------- 5 files changed, 127 insertions(+), 30 deletions(-) diff --git a/src/display.cpp b/src/display.cpp index d741c3f76af0..fa42b570ab7d 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -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 @@ -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; diff --git a/src/floating_label.cpp b/src/floating_label.cpp index 39c1c6138495..a192d90d2317 100644 --- a/src/floating_label.cpp +++ b/src/floating_label.cpp @@ -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; diff --git a/src/font/text.cpp b/src/font/text.cpp index a3fa8ab4895c..5478467d48eb 100644 --- a/src/font/text.cpp +++ b/src/font/text.cpp @@ -32,12 +32,17 @@ #include "serialization/unicode.hpp" #include "preferences/general.hpp" +#include + #include #include #include 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) @@ -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); @@ -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 { @@ -690,6 +701,16 @@ void pango_text::rerender(const bool force) this->recalculate(force); surface_dirty_ = false; + // Update hash + hash_ = std::hash()(*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_); } @@ -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_)); } } @@ -851,3 +875,37 @@ pango_text& get_text_renderer() } } // namespace font + +namespace std +{ +size_t hash::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 diff --git a/src/font/text.hpp b/src/font/text.hpp index bf0b6654aaf8..391f8b0cf1a6 100644 --- a/src/font/text.hpp +++ b/src/font/text.hpp @@ -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,22 +25,24 @@ #include #include +#include #include #include #include -/*** +/** * 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. /** @@ -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; @@ -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; }; /** @@ -447,4 +464,29 @@ class pango_text */ pango_text& get_text_renderer(); +using pango_text_cache_t = std::map; + +/** + * 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 +{ + size_t operator()(const font::pango_text& t) const; +}; + +} // namespace std diff --git a/src/gui/core/canvas.cpp b/src/gui/core/canvas.cpp index 91cdc1ea244c..65a964aab914 100644 --- a/src/gui/core/canvas.cpp +++ b/src/gui/core/canvas.cpp @@ -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 @@ -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(w)) { + if(info.w > static_cast(w)) { WRN_GUI_D << "Text: text is too wide for the " "canvas and will be clipped.\n"; } - if(surf->h > static_cast(h)) { + if(info.h > static_cast(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); }