1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-05-09 13:42:09 -05:00

GTK: faster rendering of X fonts under Cairo

Switching to server-side rendering of X fonts under Cairo turned out to
make text rendering much slower, at least on my laptop.  This appears to
be because PuTTY was asking the X server to render text into a
1-bit-per-pixel (bpp) pixmap before having Cairo composite into the
terminal surface.  On modern X servers 1bpp pixmaps are slow, being
largely un-accelerated.

Happily, it is possible to get Cairo to use an 8bpp pixmap instead, and
this is rather faster.  It's a bit inconvenient, though, because first
we have to confirm that that the X Rendering Extensions is present and
find the correct picture format.  That requires linking in libXrender,
which means a bit of CMake faff.  For now, I've make libXrender
mandatory (for X11 builds), but it and the corresponding Cairo functions
could be made optional fairly simply.

This hasn't actually made text rendering as much faster as I'd like on
my laptop.  While creating 1bpp pixmaps is nearly free, creating 8bpp
pixmaps takes significant time because they actually involve the GPU.
So I think now I need to rework my persistent-pixmap patch to work on
top of this one.
This commit is contained in:
Ben Harris 2025-05-05 23:02:18 +01:00
parent a0a92da035
commit 19353dca85
3 changed files with 46 additions and 14 deletions

View File

@ -89,7 +89,7 @@ if(GTK_FOUND)
list(APPEND CMAKE_REQUIRED_INCLUDES ${GTK_INCLUDE_DIRS})
check_include_file(gdk/gdkx.h HAVE_GDK_GDKX_H)
if(X11_FOUND AND HAVE_GDK_GDKX_H)
if(X11_FOUND AND X11_Xrender_FOUND AND HAVE_GDK_GDKX_H)
set(NOT_X_WINDOWS OFF PARENT_SCOPE)
else()
set(NOT_X_WINDOWS ON PARENT_SCOPE)

View File

@ -153,7 +153,7 @@ if(GTK_FOUND)
be_list(pterm pterm)
target_link_libraries(pterm
guiterminal eventloop settings utils ptermxpms
${GTK_LIBRARIES} ${X11_LIBRARIES})
${GTK_LIBRARIES} ${X11_LIBRARIES} ${X11_Xrender_LIB})
installed_program(pterm)
if(GTK_VERSION GREATER_EQUAL 3)
@ -170,7 +170,7 @@ if(GTK_FOUND)
be_list(ptermapp pterm)
target_link_libraries(ptermapp
guiterminal eventloop settings utils ptermxpms
${GTK_LIBRARIES} ${X11_LIBRARIES})
${GTK_LIBRARIES} ${X11_LIBRARIES} ${X11_Xrender_LIB})
endif()
add_executable(putty
@ -181,7 +181,7 @@ if(GTK_FOUND)
target_link_libraries(putty
guiterminal eventloop sshclient otherbackends settings
network crypto utils puttyxpms
${GTK_LIBRARIES} ${X11_LIBRARIES})
${GTK_LIBRARIES} ${X11_LIBRARIES} ${X11_Xrender_LIB})
set_target_properties(putty
PROPERTIES LINK_INTERFACE_MULTIPLICITY 2)
installed_program(putty)
@ -196,7 +196,7 @@ if(GTK_FOUND)
target_link_libraries(puttyapp
guiterminal eventloop sshclient otherbackends settings
network crypto utils puttyxpms
${GTK_LIBRARIES} ${X11_LIBRARIES})
${GTK_LIBRARIES} ${X11_LIBRARIES} ${X11_Xrender_LIB})
endif()
add_executable(puttytel
@ -213,7 +213,7 @@ if(GTK_FOUND)
target_link_libraries(puttytel
guiterminal eventloop otherbackends settings network utils
puttyxpms
${GTK_LIBRARIES} ${X11_LIBRARIES})
${GTK_LIBRARIES} ${X11_LIBRARIES} ${X11_Xrender_LIB})
add_executable(test_lineedit
${CMAKE_SOURCE_DIR}/test/test_lineedit.c

View File

@ -29,6 +29,8 @@
#ifndef NOT_X_WINDOWS
#ifdef DRAW_TEXT_CAIRO
#include <cairo-xlib.h>
#include <cairo-xlib-xrender.h>
#include <X11/extensions/Xrender.h>
#endif
#include <gdk/gdkx.h>
#include <X11/Xlib.h>
@ -154,6 +156,7 @@ typedef struct x11font_individual {
bool allocated;
#ifdef DRAW_TEXT_CAIRO
XRenderPictFormat *pictformat;
GC gc;
#endif
@ -607,7 +610,26 @@ static void x11font_gdk_draw(unifont_drawctx *ctx, x11font_individual *xfi,
static void x11font_cairo_setup(
unifont_drawctx *ctx, x11font_individual *xfi, Display *disp)
{
/*
* To render X fonts under Cairo, we use the usual X font rendering
* requests to draw text into a pixmap, make a Cairo surface out
* of it, and then use that as a mask to paint the current colour
* into the terminal surface. This means that colours are
* entirely in the hands of Cairo and we don't have to think about
* X colourmaps. But we do need to be able to create that pixmap.
*
* All X servers are required to support 1bpp pixmaps, and Cairo
* can always use one of them as a mask. But on 2025's X servers,
* 1bpp font rendering is unaccelerated and hence much slower than
* on deeper drawables. So we find out if we can make an 8-bit
* alpha-only surface, and only fall back to a 1bpp pixmap if that
* fails.
*
* XRenderFindStandardFormat() will return NULL if the X Rendering
* Extension is missing or unusable, so we don't need to check
* that in advance.
*/
xfi->pictformat = XRenderFindStandardFormat(disp, PictStandardA8);
}
/*
@ -621,7 +643,7 @@ static void x11font_cairo_init_gc(x11font_individual *xfi, Display *disp,
if (xfi->gc == None) {
XGCValues gcvals = {
.function = GXclear,
.foreground = 1,
.foreground = 0xffffffff,
.background = 0,
.font = xfi->xfs->fid,
};
@ -647,16 +669,26 @@ static void x11font_cairo_draw(
pixwidth = bounds.rbearing - bounds.lbearing;
pixheight = bounds.ascent + bounds.descent;
if (pixwidth > 0 && pixheight > 0) {
cairo_surface_t *surface;
if (xfi->pictformat != NULL) {
pixmap = XCreatePixmap(disp, GDK_DRAWABLE_XID(widgetwin),
pixwidth, pixheight, 8);
surface = cairo_xlib_surface_create_with_xrender_format(
disp, pixmap, ScreenOfDisplay(disp, widgetscr),
xfi->pictformat, pixwidth, pixheight);
} else {
pixmap = XCreatePixmap(disp, GDK_DRAWABLE_XID(widgetwin),
pixwidth, pixheight, 1);
surface = cairo_xlib_surface_create_for_bitmap(
disp, pixmap, ScreenOfDisplay(disp, widgetscr),
pixwidth, pixheight);
}
x11font_cairo_init_gc(xfi, disp, pixmap);
XFillRectangle(disp, pixmap, xfi->gc, 0, 0, pixwidth, pixheight);
XDrawImageString16(disp, pixmap, xfi->gc,
-bounds.lbearing, bounds.ascent,
string+start, length);
cairo_surface_t *surface = cairo_xlib_surface_create_for_bitmap(
disp, pixmap, ScreenOfDisplay(disp, widgetscr),
pixwidth, pixheight);
cairo_surface_mark_dirty(surface);
cairo_pattern_t *pattern = cairo_pattern_create_for_surface(surface);
/* We really don't want bilinear interpolation of bitmap fonts. */
cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);