# st

Unnamed repository; edit this file 'description' to name the repository.

| Log | Files | Refs | README | LICENSE

    commit ae1923d27533ff46400d93765e971558201ca1ee
    parent 38af006b5e4a36c77f25affbb3f7e28899db75ed
    Author: suigin <suigin@national.shitposting.agency>
    Date:   Tue,  5 May 2015 20:24:00 -0700

    Clean up xdraws and optimize glyph drawing with non-unit kerning values

    I have another patch here for review that optimizes the performance of
    glyph drawing, primarily when using non-unit kerning values, and fixes a
    few other minor issues. It's dependent on the earlier patch from me that
    stores unicode codepoints in a Rune type, typedef'd to uint_least32_t.

    This patch is a pretty big change to xdraws so your scrutiny is
    appreciated.

    First, some performance numbers. I used Yu-Jie Lin termfps.sh shell
    script to benchmark before and after, and you can find it in the
    attachments. On my Kaveri A10 7850k machine, I get the following
    results:

    Before Patch
    ============

    1) Font: "Liberation Mono:pixelsize=12:antialias=false:autohint=false"
       cwscale: 1.0, chscale: 1.0
       For 273x83 100 frames.
       Elapsed time :     1.553
       Frames/second:    64.352
       Chars /second: 1,458,159

    2) Font: "Inconsolata:pixelsize=14:antialias=true:autohint=true"
       cwscale: 1.001, chscale: 1.001
       For 239x73 100 frames.
       Elapsed time :   159.286
       Frames/second:     0.627
       Chars /second:    10,953

    After Patch
    ===========

    3) Font: "Liberation Mono:pixelsize=12:antialias=false:autohint=false"
       cwscale: 1.0, chscale: 1.0
       For 273x83 100 frames.
       Elapsed time :     1.544
       Frames/second:    64.728
       Chars /second: 1,466,690

    4) Font: "Inconsolata:pixelsize=14:antialias=true:autohint=true"
       cwscale: 1.001, chscale: 1.001
       For 239x73 100 frames.
       Elapsed time :     1.955
       Frames/second:    51.146
       Chars /second:   892,361

    As you can see, while the improvements for fonts with unit-kerning is
    marginal, there's a huge ~81x performance increase with the patch when
    using kerning values other than 1.0.

    So what does the patch do?

    The `xdraws' function would render each glyph one at a time if non-unit
    kerning values were configured, and this was the primary cause of the
    slow down. Xft provides a handful of functions which allow you to render
    multiple characters or glyphs at time, each with a unique <x,y> position,
    so it was simply a matter of massaging the data into a format that would
    allow us to use one of these functions.

    I've split `xdraws' up into two functions. In the first pass with
    `xmakeglyphfontspecs' it will iterate over all of the glyphs in a given
    row and it will build up an array of corresponding XftGlyphFontSpec
    records. Much of the old logic for resolving fonts for glyphs using Xft
    and fontconfig went into this function.

    The second pass is done with `xrenderglyphfontspecs' which contains the
    old logic for determining colors, clearing the background, and finally
    rendering the array of XftGlyphFontSpec records.

    There's a couple of other things that have been improved by this patch.
    For instance, the UTF-32 codepoints in the Line's were being re-encoded
    back into UTF-8 strings to be passed to `xdraws' which in turn would then
    decode back to UTF-32 to verify that the Font contained a matching glyph
    for the code point. Next, the UTF-8 string was being passed to
    `XftDrawStringUtf8' which internally mallocs a scratch buffer and decodes
    back to UTF-32 and does the lookup of the glyphs all over again.

    This patch gets rid of all of this redundant round-trip encoding and
    decoding of characters to be rendered and only looks up the glyph index
    once (per font) during the font resolution phase. So this is probably
    what's responsible for the marginal improvements seen when kerning values
    are kept to 1.0.

    I imagine there are other performance improvements here too, not seen in
    the above benchmarks, if the user has lots of non-ASCII code plane characters
    on the screen, or several different fonts are being utilized during
    screen redraw.

    Anyway, if you see any problems, please let me know and I can fix them.

    Diffstat:
    Mst.c | 383+++++++++++++++++++++++++++++++++++++++----------------------------------------

    1 file changed, 187 insertions(+), 196 deletions(-)
    diff --git a/st.c b/st.c
    @@ -58,7 +58,6 @@ char *argv0;
     #define ESC_ARG_SIZ   16
     #define STR_BUF_SIZ   ESC_BUF_SIZ
     #define STR_ARG_SIZ   ESC_ARG_SIZ
    -#define DRAW_BUF_SIZ  20*1024
     #define XK_ANY_MOD    UINT_MAX
     #define XK_NO_MOD     0
     #define XK_SWITCH_MOD (1<<13)
    @@ -87,18 +86,19 @@ char *argv0;

     enum glyph_attribute {
    -	ATTR_NULL      = 0,
    -	ATTR_BOLD      = 1 << 0,
    -	ATTR_FAINT     = 1 << 1,
    -	ATTR_ITALIC    = 1 << 2,
    -	ATTR_UNDERLINE = 1 << 3,
    -	ATTR_BLINK     = 1 << 4,
    -	ATTR_REVERSE   = 1 << 5,
    -	ATTR_INVISIBLE = 1 << 6,
    -	ATTR_STRUCK    = 1 << 7,
    -	ATTR_WRAP      = 1 << 8,
    -	ATTR_WIDE      = 1 << 9,
    -	ATTR_WDUMMY    = 1 << 10,
    +	ATTR_NULL       = 0,
    +	ATTR_BOLD       = 1 << 0,
    +	ATTR_FAINT      = 1 << 1,
    +	ATTR_ITALIC     = 1 << 2,
    +	ATTR_UNDERLINE  = 1 << 3,
    +	ATTR_BLINK      = 1 << 4,
    +	ATTR_REVERSE    = 1 << 5,
    +	ATTR_INVISIBLE  = 1 << 6,
    +	ATTR_STRUCK     = 1 << 7,
    +	ATTR_WRAP       = 1 << 8,
    +	ATTR_WIDE       = 1 << 9,
    +	ATTR_WDUMMY     = 1 << 10,
    +	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
     };

     enum cursor_movement {
    @@ -232,6 +232,7 @@ typedef struct {
     	Line *line;   /* screen */
     	Line *alt;    /* alternate screen */
     	bool *dirty;  /* dirtyness of lines */
    +	XftGlyphFontSpec *specbuf; /* font spec buffer used for rendering */
     	TCursor c;    /* cursor */
     	int top;      /* top    scroll limit */
     	int bot;      /* bottom scroll limit */
    @@ -418,7 +419,8 @@ static void ttywrite(const char *, size_t);
     static void tstrsequence(uchar);

     static inline ushort sixd_to_16bit(int);
    -static void xdraws(char *, Glyph, int, int, int, int);
    +static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
    +static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);
     static void xdrawglyph(Glyph, int, int);
     static void xhints(void);
     static void xclear(int, int, int, int);
    @@ -2819,6 +2821,9 @@ tresize(int col, int row) {
     		free(term.alt[i]);
     	}

    +	/* resize to new width */
    +	term.specbuf = xrealloc(term.specbuf, col * sizeof(XftGlyphFontSpec));
    +
     	/* resize to new height */
     	term.line = xrealloc(term.line, row * sizeof(Line));
     	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
    @@ -3257,38 +3262,155 @@ xinit(void) {
     	XSync(xw.dpy, False);
     }

    -void
    -xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
    -	int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
    -	    width = charlen * xw.cw, xp, i;
    -	int frcflags, charexists;
    -	int u8fl, u8fblen, u8cblen, doesexist;
    -	char *u8c, *u8fs;
    -	Rune unicodep;
    +int
    +xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)
    +{
    +	float winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, xp, yp;
    +	ushort mode, prevmode = USHRT_MAX;
     	Font *font = &dc.font;
    +	int frcflags = FRC_NORMAL;
    +	float runewidth = xw.cw;
    +	Rune rune;
    +	FT_UInt glyphidx;
     	FcResult fcres;
     	FcPattern *fcpattern, *fontpattern;
     	FcFontSet *fcsets[] = { NULL };
     	FcCharSet *fccharset;
    +	int i, f, numspecs = 0;
    +
    +	for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
    +		/* Fetch rune and mode for current glyph. */
    +		rune = glyphs[i].u;
    +		mode = glyphs[i].mode;
    +
    +		/* Skip dummy wide-character spacing. */
    +		if(mode == ATTR_WDUMMY)
    +			continue;
    +
    +		/* Determine font for glyph if different from previous glyph. */
    +		if(prevmode != mode) {
    +			prevmode = mode;
    +			font = &dc.font;
    +			frcflags = FRC_NORMAL;
    +			runewidth = xw.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);
    +			if((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {
    +				font = &dc.ibfont;
    +				frcflags = FRC_ITALICBOLD;
    +			} else if(mode & ATTR_ITALIC) {
    +				font = &dc.ifont;
    +				frcflags = FRC_ITALIC;
    +			} else if(mode & ATTR_BOLD) {
    +				font = &dc.bfont;
    +				frcflags = FRC_BOLD;
    +			}
    +			yp = winy + font->ascent;
    +		}
    +
    +		/* Lookup character index with default font. */
    +		glyphidx = XftCharIndex(xw.dpy, font->match, rune);
    +		if(glyphidx) {
    +			specs[numspecs].font = font->match;
    +			specs[numspecs].glyph = glyphidx;
    +			specs[numspecs].x = (short)xp;
    +			specs[numspecs].y = (short)yp;
    +			xp += runewidth;
    +			numspecs++;
    +			continue;
    +		}
    +
    +		/* Fallback on font cache, search the font cache for match. */
    +		for(f = 0; f < frclen; f++) {
    +			glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
    +			/* Everything correct. */
    +			if(glyphidx && frc[f].flags == frcflags)
    +				break;
    +			/* We got a default font for a not found glyph. */
    +			if(!glyphidx && frc[f].flags == frcflags
    +					&& frc[f].unicodep == rune) {
    +				break;
    +			}
    +		}
    +
    +		/* Nothing was found. Use fontconfig to find matching font. */
    +		if(f >= frclen) {
    +			if(!font->set)
    +				font->set = FcFontSort(0, font->pattern,
    +				                       FcTrue, 0, &fcres);
    +			fcsets[0] = font->set;
    +
    +			/*
    +			 * Nothing was found in the cache. Now use
    +			 * some dozen of Fontconfig calls to get the
    +			 * font for one single character.
    +			 *
    +			 * Xft and fontconfig are design failures.
    +			 */
    +			fcpattern = FcPatternDuplicate(font->pattern);
    +			fccharset = FcCharSetCreate();
    +
    +			FcCharSetAddChar(fccharset, rune);
    +			FcPatternAddCharSet(fcpattern, FC_CHARSET,
    +					fccharset);
    +			FcPatternAddBool(fcpattern, FC_SCALABLE,
    +					FcTrue);
    +
    +			FcConfigSubstitute(0, fcpattern,
    +					FcMatchPattern);
    +			FcDefaultSubstitute(fcpattern);
    +
    +			fontpattern = FcFontSetMatch(0, fcsets, 1,
    +					fcpattern, &fcres);
    +
    +			/*
    +			 * Overwrite or create the new cache entry.
    +			 */
    +			if(frclen >= LEN(frc)) {
    +				frclen = LEN(frc) - 1;
    +				XftFontClose(xw.dpy, frc[frclen].font);
    +				frc[frclen].unicodep = 0;
    +			}
    +
    +			frc[frclen].font = XftFontOpenPattern(xw.dpy,
    +					fontpattern);
    +			frc[frclen].flags = frcflags;
    +			frc[frclen].unicodep = rune;
    +
    +			glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
    +
    +			f = frclen;
    +			frclen++;
    +
    +			FcPatternDestroy(fcpattern);
    +			FcCharSetDestroy(fccharset);
    +		}
    +
    +		specs[numspecs].font = frc[f].font;
    +		specs[numspecs].glyph = glyphidx;
    +		specs[numspecs].x = (short)xp;
    +		specs[numspecs].y = (short)(winy + frc[f].font->ascent);
    +		xp += runewidth;
    +		numspecs++;
    +	}
    +
    +	return numspecs;
    +}
    +
    +void
    +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) {
    +	int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);
    +	int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
    +	    width = charlen * xw.cw;
     	Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;
     	XRenderColor colfg, colbg;
     	XRectangle r;
    -	int oneatatime;

    -	frcflags = FRC_NORMAL;
    -
    -	if(base.mode & ATTR_ITALIC) {
    -		if(base.fg == defaultfg)
    +	/* Determine foreground and background colors based on mode. */
    +	if(base.fg == defaultfg) {
    +		if(base.mode & ATTR_ITALIC)
     			base.fg = defaultitalic;
    -		font = &dc.ifont;
    -		frcflags = FRC_ITALIC;
    -	} else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) {
    -		if(base.fg == defaultfg)
    +		else if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD))
     			base.fg = defaultitalic;
    -		font = &dc.ibfont;
    -		frcflags = FRC_ITALICBOLD;
    -	} else if(base.mode & ATTR_UNDERLINE) {
    -		if(base.fg == defaultfg)
    +		else if(base.mode & ATTR_UNDERLINE)
     			base.fg = defaultunderline;
     	}

    @@ -3314,22 +3436,9 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
     		bg = &dc.col[base.bg];
     	}

    -	if(base.mode & ATTR_BOLD) {
    -		/*
    -		 * change basic system colors [0-7]
    -		 * to bright system colors [8-15]
    -		 */
    -		if(BETWEEN(base.fg, 0, 7) && !(base.mode & ATTR_FAINT))
    -			fg = &dc.col[base.fg + 8];
    -
    -		if(base.mode & ATTR_ITALIC) {
    -			font = &dc.ibfont;
    -			frcflags = FRC_ITALICBOLD;
    -		} else {
    -			font = &dc.bfont;
    -			frcflags = FRC_BOLD;
    -		}
    -	}
    +	/* Change basic system colors [0-7] to bright system colors [8-15] */
    +	if((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))
    +		fg = &dc.col[base.fg + 8];

     	if(IS_SET(MODE_REVERSE)) {
     		if(fg == &dc.col[defaultfg]) {
    @@ -3363,7 +3472,7 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
     		bg = temp;
     	}

    -	if(base.mode & ATTR_FAINT && !(base.mode & ATTR_BOLD)) {
    +	if((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {
     		colfg.red = fg->color.red / 2;
     		colfg.green = fg->color.green / 2;
     		colfg.blue = fg->color.blue / 2;
    @@ -3401,136 +3510,17 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {
     	r.width = width;
     	XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);

    -	for(xp = winx; bytelen > 0;) {
    -		/*
    -		 * Search for the range in the to be printed string of glyphs
    -		 * that are in the main font. Then print that range. If
    -		 * some glyph is found that is not in the font, do the
    -		 * fallback dance.
    -		 */
    -		u8fs = s;
    -		u8fblen = 0;
    -		u8fl = 0;
    -		oneatatime = font->width != xw.cw;
    -		for(;;) {
    -			u8c = s;
    -			u8cblen = utf8decode(s, &unicodep, UTF_SIZ);
    -			s += u8cblen;
    -			bytelen -= u8cblen;
    -
    -			doesexist = XftCharExists(xw.dpy, font->match, unicodep);
    -			if(doesexist) {
    -					u8fl++;
    -					u8fblen += u8cblen;
    -					if(!oneatatime && bytelen > 0)
    -							continue;
    -			}
    -
    -			if(u8fl > 0) {
    -				XftDrawStringUtf8(xw.draw, fg,
    -						font->match, xp,
    -						winy + font->ascent,
    -						(FcChar8 *)u8fs,
    -						u8fblen);
    -				xp += xw.cw * u8fl;
    -			}
    -			break;
    -		}
    -		if(doesexist) {
    -			if(oneatatime)
    -				continue;
    -			break;
    -		}
    -
    -		/* Search the font cache. */
    -		for(i = 0; i < frclen; i++) {
    -			charexists = XftCharExists(xw.dpy, frc[i].font, unicodep);
    -			/* Everything correct. */
    -			if(charexists && frc[i].flags == frcflags)
    -				break;
    -			/* We got a default font for a not found glyph. */
    -			if(!charexists && frc[i].flags == frcflags \
    -					&& frc[i].unicodep == unicodep) {
    -				break;
    -			}
    -		}
    -
    -		/* Nothing was found. */
    -		if(i >= frclen) {
    -			if(!font->set)
    -				font->set = FcFontSort(0, font->pattern,
    -				                       FcTrue, 0, &fcres);
    -			fcsets[0] = font->set;
    -
    -			/*
    -			 * Nothing was found in the cache. Now use
    -			 * some dozen of Fontconfig calls to get the
    -			 * font for one single character.
    -			 *
    -			 * Xft and fontconfig are design failures.
    -			 */
    -			fcpattern = FcPatternDuplicate(font->pattern);
    -			fccharset = FcCharSetCreate();
    -
    -			FcCharSetAddChar(fccharset, unicodep);
    -			FcPatternAddCharSet(fcpattern, FC_CHARSET,
    -					fccharset);
    -			FcPatternAddBool(fcpattern, FC_SCALABLE,
    -					FcTrue);
    -
    -			FcConfigSubstitute(0, fcpattern,
    -					FcMatchPattern);
    -			FcDefaultSubstitute(fcpattern);
    -
    -			fontpattern = FcFontSetMatch(0, fcsets, 1,
    -					fcpattern, &fcres);
    -
    -			/*
    -			 * Overwrite or create the new cache entry.
    -			 */
    -			if(frclen >= LEN(frc)) {
    -				frclen = LEN(frc) - 1;
    -				XftFontClose(xw.dpy, frc[frclen].font);
    -				frc[frclen].unicodep = 0;
    -			}
    -
    -			frc[frclen].font = XftFontOpenPattern(xw.dpy,
    -					fontpattern);
    -			frc[frclen].flags = frcflags;
    -			frc[frclen].unicodep = unicodep;
    -
    -			i = frclen;
    -			frclen++;
    -
    -			FcPatternDestroy(fcpattern);
    -			FcCharSetDestroy(fccharset);
    -		}
    -
    -		XftDrawStringUtf8(xw.draw, fg, frc[i].font,
    -				xp, winy + frc[i].font->ascent,
    -				(FcChar8 *)u8c, u8cblen);
    -
    -		xp += xw.cw * wcwidth(unicodep);
    -	}
    -
    -	/*
    -	 * This is how the loop above actually should be. Why does the
    -	 * application have to care about font details?
    -	 *
    -	 * I have to repeat: Xft and Fontconfig are design failures.
    -	 */
    -	/*
    -	XftDrawStringUtf8(xw.draw, fg, font->set, winx,
    -			winy + font->ascent, (FcChar8 *)s, bytelen);
    -	*/
    +	/* Render the glyphs. */
    +	XftDrawGlyphFontSpec(xw.draw, fg, specs, len);

    +	/* Render underline and strikethrough. */
     	if(base.mode & ATTR_UNDERLINE) {
    -		XftDrawRect(xw.draw, fg, winx, winy + font->ascent + 1,
    +		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,
     				width, 1);
     	}

     	if(base.mode & ATTR_STRUCK) {
    -		XftDrawRect(xw.draw, fg, winx, winy + 2 * font->ascent / 3,
    +		XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,
     				width, 1);
     	}

    @@ -3540,11 +3530,10 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) {

     void
     xdrawglyph(Glyph g, int x, int y) {
    -	static char buf[UTF_SIZ];
    -	size_t len = utf8encode(g.u, buf);
    -	int width = g.mode & ATTR_WIDE ? 2 : 1;
    -
    -	xdraws(buf, g, x, y, width, len);
    +	int numspecs;
    +	XftGlyphFontSpec spec;
    +	numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);
    +	xdrawglyphfontspecs(&spec, g, numspecs, x, y);
     }

     void
    @@ -3658,9 +3647,9 @@ draw(void) {

     void
     drawregion(int x1, int y1, int x2, int y2) {
    -	int ic, ib, x, y, ox;
    +	int i, x, y, ox, numspecs;
     	Glyph base, new;
    -	char buf[DRAW_BUF_SIZ];
    +	XftGlyphFontSpec* specs;
     	bool ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN);

     	if(!(xw.state & WIN_VISIBLE))
    @@ -3672,29 +3661,31 @@ drawregion(int x1, int y1, int x2, int y2) {

     		xtermclear(0, y, term.col, y);
     		term.dirty[y] = 0;
    -		base = term.line[y][0];
    -		ic = ib = ox = 0;
    -		for(x = x1; x < x2; x++) {
    +
    +		specs = term.specbuf;
    +		numspecs = xmakeglyphfontspecs(specs, &term.line[y][0], x2 - x1, x1, y);
    +
    +		i = ox = 0;
    +		for(x = x1; x < x2 && i < numspecs; x++) {
     			new = term.line[y][x];
     			if(new.mode == ATTR_WDUMMY)
     				continue;
     			if(ena_sel && selected(x, y))
     				new.mode ^= ATTR_REVERSE;
    -			if(ib > 0 && (ATTRCMP(base, new)
    -					|| ib >= DRAW_BUF_SIZ-UTF_SIZ)) {
    -				xdraws(buf, base, ox, y, ic, ib);
    -				ic = ib = 0;
    +			if(i > 0 && ATTRCMP(base, new)) {
    +				xdrawglyphfontspecs(specs, base, i, ox, y);
    +				specs += i;
    +				numspecs -= i;
    +				i = 0;
     			}
    -			if(ib == 0) {
    +			if(i == 0) {
     				ox = x;
     				base = new;
     			}
    -
    -			ib += utf8encode(new.u, buf+ib);
    -			ic += (new.mode & ATTR_WIDE)? 2 : 1;
    +			i++;
     		}
    -		if(ib > 0)
    -			xdraws(buf, base, ox, y, ic, ib);
    +		if(i > 0)
    +			xdrawglyphfontspecs(specs, base, i, ox, y);
     	}
     	xdrawcursor();
     }