1
0
Fork 0

(svn r25583) [1.3] -Backport from trunk:

- Fix: Layouter caused significant slowdown with text heavy windows, cache it to make it managable (r25574, r25570, r25569, r25567, r25564)
release/1.3
rubidium 2013-07-10 19:41:31 +00:00
parent 6cde48e49f
commit 909502dc41
5 changed files with 245 additions and 95 deletions

View File

@ -17,6 +17,7 @@
#include "core/smallmap_type.hpp" #include "core/smallmap_type.hpp"
#include "strings_func.h" #include "strings_func.h"
#include "zoom_type.h" #include "zoom_type.h"
#include "gfx_layout.h"
#include "table/sprites.h" #include "table/sprites.h"
#include "table/control_codes.h" #include "table/control_codes.h"
@ -39,6 +40,7 @@ FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs), height(_
{ {
assert(parent == NULL || this->fs == parent->fs); assert(parent == NULL || this->fs == parent->fs);
FontCache::caches[this->fs] = this; FontCache::caches[this->fs] = this;
Layouter::ResetFontCache(this->fs);
} }
/** Clean everything up. */ /** Clean everything up. */
@ -46,6 +48,7 @@ FontCache::~FontCache()
{ {
assert(this->fs == parent->fs); assert(this->fs == parent->fs);
FontCache::caches[this->fs] = this->parent; FontCache::caches[this->fs] = this->parent;
Layouter::ResetFontCache(this->fs);
} }

View File

@ -51,43 +51,6 @@ byte _colour_gradient[COLOUR_END][8];
static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE); static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE);
static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE, ZoomLevel zoom = ZOOM_LVL_NORMAL); static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE, ZoomLevel zoom = ZOOM_LVL_NORMAL);
/**
* Text drawing parameters, which can change while drawing a line, but are kept between multiple parts
* of the same text, e.g. on line breaks.
*/
struct DrawStringParams {
FontSize fontsize;
TextColour cur_colour, prev_colour;
DrawStringParams(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour), prev_colour(colour) {}
/**
* Switch to new colour \a c.
* @param c New colour to use.
*/
inline void SetColour(TextColour c)
{
assert(c >= TC_BLUE && c <= TC_BLACK);
this->prev_colour = this->cur_colour;
this->cur_colour = c;
}
/** Switch to previous colour. */
inline void SetPreviousColour()
{
Swap(this->cur_colour, this->prev_colour);
}
/**
* Switch to using a new font \a f.
* @param f New font to use.
*/
inline void SetFontSize(FontSize f)
{
this->fontsize = f;
}
};
static ReusableBuffer<uint8> _cursor_backup; static ReusableBuffer<uint8> _cursor_backup;
/** /**
@ -453,9 +416,22 @@ static int DrawLayoutLine(ParagraphLayout::Line *line, int y, int left, int righ
* will be drawn in the right direction. * will be drawn in the right direction.
* @param underline Whether to underline what has been drawn or not. * @param underline Whether to underline what has been drawn or not.
* @param fontsize The size of the initial characters. * @param fontsize The size of the initial characters.
* @return In case of left or center alignment the right most pixel we have drawn to.
* In case of right alignment the left most pixel we have drawn to.
*/ */
int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
{ {
/* The string may contain control chars to change the font, just use the biggest font for clipping. */
int max_height = max(max(FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL), max(FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO));
/* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
int extra = max_height / 2;
if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
_cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
return 0;
}
Layouter layout(str, INT32_MAX, colour, fontsize); Layouter layout(str, INT32_MAX, colour, fontsize);
if (layout.Length() == 0) return 0; if (layout.Length() == 0) return 0;
@ -475,6 +451,8 @@ int DrawString(int left, int right, int top, const char *str, TextColour colour,
* will be drawn in the right direction. * will be drawn in the right direction.
* @param underline Whether to underline what has been drawn or not. * @param underline Whether to underline what has been drawn or not.
* @param fontsize The size of the initial characters. * @param fontsize The size of the initial characters.
* @return In case of left or center alignment the right most pixel we have drawn to.
* In case of right alignment the left most pixel we have drawn to.
*/ */
int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize) int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
{ {

View File

@ -20,6 +20,14 @@
#include <unicode/ustring.h> #include <unicode/ustring.h>
#endif /* WITH_ICU */ #endif /* WITH_ICU */
/** Cache of ParagraphLayout lines. */
Layouter::LineCache *Layouter::linecache;
/** Cache of Font instances. */
Layouter::FontColourMap Layouter::fonts[FS_END];
/** /**
* Construct a new font. * Construct a new font.
* @param size The font size to use for this font. * @param size The font size to use for this font.
@ -129,6 +137,8 @@ ParagraphLayout *Layouter::GetParagraphLayout(UChar *buff, UChar *buff_end, Font
} }
LEErrorCode status = LE_NO_ERROR; LEErrorCode status = LE_NO_ERROR;
/* ParagraphLayout does not copy "buff", so it must stay valid.
* "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status); return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
} }
@ -272,6 +282,14 @@ ParagraphLayout::ParagraphLayout(WChar *buffer, int length, FontMap &runs) : buf
assert(runs.End()[-1].first == length); assert(runs.End()[-1].first == length);
} }
/**
* Reset the position to the start of the paragraph.
*/
void ParagraphLayout::reflow()
{
this->buffer = this->buffer_begin;
}
/** /**
* Construct a new line with a maximum width. * Construct a new line with a maximum width.
* @param max_width The maximum width of the string. * @param max_width The maximum width of the string.
@ -295,7 +313,7 @@ ParagraphLayout::Line *ParagraphLayout::nextLine(int max_width)
} }
const WChar *begin = this->buffer; const WChar *begin = this->buffer;
WChar *last_space = NULL; const WChar *last_space = NULL;
const WChar *last_char = begin; const WChar *last_char = begin;
int width = 0; int width = 0;
@ -407,75 +425,77 @@ ParagraphLayout *Layouter::GetParagraphLayout(WChar *buff, WChar *buff_end, Font
*/ */
Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize)
{ {
const CharType *buffer_last = lastof(this->buffer); FontState state(colour, fontsize);
CharType *buff = this->buffer;
TextColour cur_colour = colour, prev_colour = colour;
WChar c = 0; WChar c = 0;
do { do {
Font *f = new Font(fontsize, cur_colour); /* Scan string for end of line */
CharType *buff_begin = buff; const char *lineend = str;
FontMap fontMapping; for (;;) {
size_t len = Utf8Decode(&c, lineend);
if (c == '\0' || c == '\n') break;
lineend += len;
}
/* LineCacheItem& line = GetCachedParagraphLayout(str, lineend - str, state);
* Go through the whole string while adding Font instances to the font map if (line.layout != NULL) {
* whenever the font changes, and convert the wide characters into a format /* Line is in cache */
* usable by ParagraphLayout. str = lineend + 1;
*/ state = line.state_after;
for (; buff < buffer_last;) { line.layout->reflow();
c = Utf8Consume(const_cast<const char **>(&str)); } else {
if (c == '\0' || c == '\n') { /* Line is new, layout it */
break; const CharType *buffer_last = lastof(line.buffer);
} else if (c >= SCC_BLUE && c <= SCC_BLACK) { CharType *buff_begin = line.buffer;
prev_colour = cur_colour; CharType *buff = buff_begin;
cur_colour = (TextColour)(c - SCC_BLUE); FontMap &fontMapping = line.runs;
} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour. Font *f = GetFont(state.fontsize, state.cur_colour);
Swap(prev_colour, cur_colour);
} else if (c == SCC_TINYFONT) { /*
fontsize = FS_SMALL; * Go through the whole string while adding Font instances to the font map
} else if (c == SCC_BIGFONT) { * whenever the font changes, and convert the wide characters into a format
fontsize = FS_LARGE; * usable by ParagraphLayout.
} else { */
buff += AppendToBuffer(buff, buffer_last, c); for (; buff < buffer_last;) {
continue; c = Utf8Consume(const_cast<const char **>(&str));
if (c == '\0' || c == '\n') {
break;
} else if (c >= SCC_BLUE && c <= SCC_BLACK) {
state.SetColour((TextColour)(c - SCC_BLUE));
} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
state.SetPreviousColour();
} else if (c == SCC_TINYFONT) {
state.SetFontSize(FS_SMALL);
} else if (c == SCC_BIGFONT) {
state.SetFontSize(FS_LARGE);
} else {
buff += AppendToBuffer(buff, buffer_last, c);
continue;
}
if (!fontMapping.Contains(buff - buff_begin)) {
fontMapping.Insert(buff - buff_begin, f);
}
f = GetFont(state.fontsize, state.cur_colour);
} }
/* Better safe than sorry. */
*buff = '\0';
if (!fontMapping.Contains(buff - buff_begin)) { if (!fontMapping.Contains(buff - buff_begin)) {
fontMapping.Insert(buff - buff_begin, f); fontMapping.Insert(buff - buff_begin, f);
*this->fonts.Append() = f;
} else {
delete f;
} }
f = new Font(fontsize, cur_colour); line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
line.state_after = state;
} }
/* Better safe than sorry. */
*buff = '\0';
if (!fontMapping.Contains(buff - buff_begin)) {
fontMapping.Insert(buff - buff_begin, f);
*this->fonts.Append() = f;
}
ParagraphLayout *p = GetParagraphLayout(buff_begin, buff, fontMapping);
/* Copy all lines into a local cache so we can reuse them later on more easily. */ /* Copy all lines into a local cache so we can reuse them later on more easily. */
ParagraphLayout::Line *l; ParagraphLayout::Line *l;
while ((l = p->nextLine(maxw)) != NULL) { while ((l = line.layout->nextLine(maxw)) != NULL) {
*this->Append() = l; *this->Append() = l;
} }
delete p; } while (c != '\0');
} while (c != '\0' && buff < buffer_last);
}
/** Free everything we allocated. */
Layouter::~Layouter()
{
for (Font **iter = this->fonts.Begin(); iter != this->fonts.End(); iter++) {
delete *iter;
}
} }
/** /**
@ -491,3 +511,71 @@ Dimension Layouter::GetBounds()
} }
return d; return d;
} }
/**
* Get a static font instance.
*/
Font *Layouter::GetFont(FontSize size, TextColour colour)
{
FontColourMap::iterator it = fonts[size].Find(colour);
if (it != fonts[size].End()) return it->second;
Font *f = new Font(size, colour);
*fonts[size].Append() = FontColourMap::Pair(colour, f);
return f;
}
/**
* Reset cached font information.
* @param size Font size to reset.
*/
void Layouter::ResetFontCache(FontSize size)
{
for (FontColourMap::iterator it = fonts[size].Begin(); it != fonts[size].End(); ++it) {
delete it->second;
}
fonts[size].Clear();
/* We must reset the linecache since it references the just freed fonts */
ResetLineCache();
}
/**
* Get reference to cache item.
* If the item does not exist yet, it is default constructed.
* @param str Source string of the line (including colour and font size codes).
* @param len Length of \a str in bytes (no termination).
* @param state State of the font at the beginning of the line.
* @return Reference to cache item.
*/
Layouter::LineCacheItem &Layouter::GetCachedParagraphLayout(const char *str, size_t len, const FontState &state)
{
if (linecache == NULL) {
/* Create linecache on first access to avoid trouble with initialisation order of static variables. */
linecache = new LineCache();
}
LineCacheKey key;
key.state_before = state;
key.str.assign(str, len);
return (*linecache)[key];
}
/**
* Clear line cache.
*/
void Layouter::ResetLineCache()
{
if (linecache != NULL) linecache->clear();
}
/**
* Reduce the size of linecache if necessary to prevent infinite growth.
*/
void Layouter::ReduceLineCache()
{
if (linecache != NULL) {
/* TODO LRU cache would be fancy, but not exactly necessary */
if (linecache->size() > 4096) ResetLineCache();
}
}

View File

@ -16,6 +16,9 @@
#include "gfx_func.h" #include "gfx_func.h"
#include "core/smallmap_type.hpp" #include "core/smallmap_type.hpp"
#include <map>
#include <string>
#ifdef WITH_ICU #ifdef WITH_ICU
#include "layout/ParagraphLayout.h" #include "layout/ParagraphLayout.h"
#define ICU_FONTINSTANCE : public LEFontInstance #define ICU_FONTINSTANCE : public LEFontInstance
@ -23,6 +26,45 @@
#define ICU_FONTINSTANCE #define ICU_FONTINSTANCE
#endif /* WITH_ICU */ #endif /* WITH_ICU */
/**
* Text drawing parameters, which can change while drawing a line, but are kept between multiple parts
* of the same text, e.g. on line breaks.
*/
struct FontState {
FontSize fontsize; ///< Current font size.
TextColour cur_colour; ///< Current text colour.
TextColour prev_colour; ///< Text colour from before the last colour switch.
FontState() : fontsize(FS_END), cur_colour(TC_INVALID), prev_colour(TC_INVALID) {}
FontState(TextColour colour, FontSize fontsize) : fontsize(fontsize), cur_colour(colour), prev_colour(colour) {}
/**
* Switch to new colour \a c.
* @param c New colour to use.
*/
inline void SetColour(TextColour c)
{
assert(c >= TC_BLUE && c <= TC_BLACK);
this->prev_colour = this->cur_colour;
this->cur_colour = c;
}
/** Switch to previous colour. */
inline void SetPreviousColour()
{
Swap(this->cur_colour, this->prev_colour);
}
/**
* Switch to using a new font \a f.
* @param f New font to use.
*/
inline void SetFontSize(FontSize f)
{
this->fontsize = f;
}
};
/** /**
* Container with information about a font. * Container with information about a font.
*/ */
@ -105,10 +147,11 @@ public:
}; };
const WChar *buffer_begin; ///< Begin of the buffer. const WChar *buffer_begin; ///< Begin of the buffer.
WChar *buffer; ///< The current location in the buffer. const WChar *buffer; ///< The current location in the buffer.
FontMap &runs; ///< The fonts we have to use for this paragraph. FontMap &runs; ///< The fonts we have to use for this paragraph.
ParagraphLayout(WChar *buffer, int length, FontMap &runs); ParagraphLayout(WChar *buffer, int length, FontMap &runs);
void reflow();
Line *nextLine(int max_width); Line *nextLine(int max_width);
}; };
#endif /* !WITH_ICU */ #endif /* !WITH_ICU */
@ -128,13 +171,48 @@ class Layouter : public AutoDeleteSmallVector<ParagraphLayout::Line *, 4> {
size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c); size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c);
ParagraphLayout *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping); ParagraphLayout *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
CharType buffer[DRAW_STRING_BUFFER]; ///< Buffer for the text that is going to be drawn. /** Key into the linecache */
SmallVector<Font *, 4> fonts; ///< The fonts needed for drawing. struct LineCacheKey {
FontState state_before; ///< Font state at the beginning of the line.
std::string str; ///< Source string of the line (including colour and font size codes).
/** Comparison operator for std::map */
bool operator<(const LineCacheKey &other) const
{
if (this->state_before.fontsize != other.state_before.fontsize) return this->state_before.fontsize < other.state_before.fontsize;
if (this->state_before.cur_colour != other.state_before.cur_colour) return this->state_before.cur_colour < other.state_before.cur_colour;
if (this->state_before.prev_colour != other.state_before.prev_colour) return this->state_before.prev_colour < other.state_before.prev_colour;
return this->str < other.str;
}
};
/** Item in the linecache */
struct LineCacheItem {
/* Stuff that cannot be freed until the ParagraphLayout is freed */
CharType buffer[DRAW_STRING_BUFFER]; ///< Accessed by both ICU's and our ParagraphLayout::nextLine.
FontMap runs; ///< Accessed by our ParagraphLayout::nextLine.
FontState state_after; ///< Font state after the line.
ParagraphLayout *layout; ///< Layout of the line.
LineCacheItem() : layout(NULL) {}
~LineCacheItem() { delete layout; }
};
typedef std::map<LineCacheKey, LineCacheItem> LineCache;
static LineCache *linecache;
static LineCacheItem &GetCachedParagraphLayout(const char *str, size_t len, const FontState &state);
typedef SmallMap<TextColour, Font *> FontColourMap;
static FontColourMap fonts[FS_END];
static Font *GetFont(FontSize size, TextColour colour);
public: public:
Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL); Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL);
~Layouter();
Dimension GetBounds(); Dimension GetBounds();
static void ResetFontCache(FontSize size);
static void ResetLineCache();
static void ReduceLineCache();
}; };
#endif /* GFX_LAYOUT_H */ #endif /* GFX_LAYOUT_H */

View File

@ -61,6 +61,7 @@
#include "game/game_config.hpp" #include "game/game_config.hpp"
#include "town.h" #include "town.h"
#include "subsidy_func.h" #include "subsidy_func.h"
#include "gfx_layout.h"
#include <stdarg.h> #include <stdarg.h>
@ -1308,6 +1309,8 @@ void StateGameLoop()
ClearStorageChanges(false); ClearStorageChanges(false);
Layouter::ReduceLineCache();
if (_game_mode == GM_EDITOR) { if (_game_mode == GM_EDITOR) {
RunTileLoop(); RunTileLoop();
CallVehicleTicks(); CallVehicleTicks();