From 72d98fd99f350d2592c63ee6e1ac28cd38fbcb93 Mon Sep 17 00:00:00 2001 From: Phil Jones Date: Fri, 10 Jun 2022 00:24:54 +0100 Subject: Avoid an unnecessary copy of the image buffer. By pointing Cairo at the mmap-ed file used to create wl_shm buffers, we can eliminate a memcpy() on every draw, providing a decent speedup (especially for large window sizes). This comes at the expense of having to keep track of two Cairo contexts, one for each of our two buffers used for double buffering. Additionally, a single memcpy() is still required for initialisation of the second buffer, so the startup latency isn't affected much. --- src/entry.c | 79 ++++++++++++++++++++++++++++++++++---------- src/entry.h | 5 +-- src/entry_backend/harfbuzz.c | 35 +++++++++++++------- src/entry_backend/harfbuzz.h | 2 +- src/entry_backend/pango.c | 10 +++--- src/entry_backend/pango.h | 2 +- src/image.h | 1 - src/main.c | 18 ++++++---- src/surface.c | 11 ++---- src/surface.h | 2 +- 10 files changed, 110 insertions(+), 55 deletions(-) diff --git a/src/entry.c b/src/entry.c index 6b7c9e7..0348d06 100644 --- a/src/entry.c +++ b/src/entry.c @@ -5,26 +5,51 @@ #include "log.h" #include "nelem.h" -void entry_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale) +void entry_init(struct entry *entry, uint8_t *restrict buffer, uint32_t width, uint32_t height, uint32_t scale) { entry->image.width = width; entry->image.height = height; entry->image.scale = scale; - width /= scale; - height /= scale; - /* - * Create the cairo surface and context we'll be using. + * Create the cairo surfaces and contexts we'll be using. + * + * In order to avoid an unnecessary copy when passing the image to the + * Wayland server, we accept a pointer to the mmap-ed file that our + * Wayland buffers are created from. This is assumed to be + * (width * height * (sizeof(uint32_t) == 4) * 2) bytes, + * to allow for double buffering. */ - cairo_surface_t *surface = cairo_image_surface_create( + cairo_surface_t *surface = cairo_image_surface_create_for_data( + buffer, CAIRO_FORMAT_ARGB32, - width * scale, - height * scale + width, + height, + width * sizeof(uint32_t) ); cairo_surface_set_device_scale(surface, scale, scale); cairo_t *cr = cairo_create(surface); + entry->cairo[0].surface = surface; + entry->cairo[0].cr = cr; + + entry->cairo[1].surface = cairo_image_surface_create_for_data( + &buffer[width * height * sizeof(uint32_t)], + CAIRO_FORMAT_ARGB32, + width, + height, + width * sizeof(uint32_t) + ); + cairo_surface_set_device_scale(entry->cairo[1].surface, scale, scale); + entry->cairo[1].cr = cairo_create(entry->cairo[1].surface); + + /* + * Cairo drawing operations take the scale into account, + * so we account for that here. + */ + width /= scale; + height /= scale; + /* Draw the outer outline */ struct color color = entry->border.outline_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); @@ -85,26 +110,43 @@ void entry_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t s height -= 2 * entry->padding; cairo_rectangle(cr, 0, 0, width, height); cairo_clip(cr); - - entry->cairo.surface = surface; - entry->cairo.cr = cr; - entry->image.buffer = cairo_image_surface_get_data(surface); /* Setup the backend. */ - entry_backend_init(entry, width, height, scale); + entry_backend_init(entry, &width, &height, scale); + + /* + * To avoid performing all this drawing twice, we take a small + * shortcut. After performing all the drawing as normal on our first + * Cairo context, we can copy over just the important state (the + * transformation matrix and clip rectangle) and perform a memcpy() + * to initialise the other context. + */ + cairo_matrix_t mat; + cairo_get_matrix(cr, &mat); + cairo_set_matrix(entry->cairo[1].cr, &mat); + cairo_rectangle(entry->cairo[1].cr, 0, 0, width, height); + cairo_clip(entry->cairo[1].cr); + + memcpy( + cairo_image_surface_get_data(entry->cairo[1].surface), + cairo_image_surface_get_data(entry->cairo[0].surface), + entry->image.width * entry->image.height * sizeof(uint32_t) + ); } void entry_destroy(struct entry *entry) { entry_backend_destroy(entry); - cairo_destroy(entry->cairo.cr); - cairo_surface_destroy(entry->cairo.surface); + cairo_destroy(entry->cairo[0].cr); + cairo_destroy(entry->cairo[1].cr); + cairo_surface_destroy(entry->cairo[0].surface); + cairo_surface_destroy(entry->cairo[1].surface); } void entry_update(struct entry *entry) { log_debug("Start rendering entry.\n"); - cairo_t *cr = entry->cairo.cr; + cairo_t *cr = entry->cairo[entry->index].cr; cairo_save(cr); /* Clear the image. */ @@ -120,10 +162,13 @@ void entry_update(struct entry *entry) cairo_restore(cr); log_debug("Finish rendering entry.\n"); + + entry->index = !entry->index; } void entry_set_scale(struct entry *entry, uint32_t scale) { entry->image.scale = scale; - cairo_surface_set_device_scale(entry->cairo.surface, scale, scale); + cairo_surface_set_device_scale(entry->cairo[0].surface, scale, scale); + cairo_surface_set_device_scale(entry->cairo[1].surface, scale, scale); } diff --git a/src/entry.h b/src/entry.h index 341e340..95cab5d 100644 --- a/src/entry.h +++ b/src/entry.h @@ -22,7 +22,8 @@ struct entry { struct { cairo_surface_t *surface; cairo_t *cr; - } cairo; + } cairo[2]; + int index; wchar_t input[MAX_INPUT_LENGTH]; /* Assume maximum of 4 bytes per wchar_t (for UTF-8) */ @@ -50,7 +51,7 @@ struct entry { } border; }; -void entry_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale); +void entry_init(struct entry *entry, uint8_t *restrict buffer, uint32_t width, uint32_t height, uint32_t scale); void entry_destroy(struct entry *entry); void entry_update(struct entry *entry); void entry_set_scale(struct entry *entry, uint32_t scale); diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c index d1f54e9..779cbd8 100644 --- a/src/entry_backend/harfbuzz.c +++ b/src/entry_backend/harfbuzz.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -12,6 +11,12 @@ #include "../nelem.h" #include "../xmalloc.h" +/* + * Cairo / FreeType use 72 Pts per inch, but Pango uses 96 DPI, so we have to + * rescale for consistency. + */ +#define PT_TO_DPI (96.0 / 72.0) + static void setup_hb_buffer(hb_buffer_t *buffer) { hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); @@ -69,17 +74,22 @@ static uint32_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer, uint32_t scal return width; } -void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale) +void entry_backend_init(struct entry *entry, uint32_t *width, uint32_t *height, uint32_t scale) { - cairo_t *cr = entry->cairo.cr; + cairo_t *cr = entry->cairo[0].cr; /* Setup FreeType. */ log_debug("Creating FreeType library.\n"); - assert(!FT_Init_FreeType(&entry->backend.ft_library)); + FT_Init_FreeType(&entry->backend.ft_library); log_debug("Loading FreeType font.\n"); - assert(!FT_New_Face(entry->backend.ft_library, "font.ttf", 0, &entry->backend.ft_face)); - assert(!FT_Set_Char_Size(entry->backend.ft_face, entry->font_size * 64, entry->font_size * 64, 0, 0)); + FT_New_Face(entry->backend.ft_library, "font.ttf", 0, &entry->backend.ft_face); + FT_Set_Char_Size( + entry->backend.ft_face, + entry->font_size * 64 * scale * PT_TO_DPI, + entry->font_size * 64 * scale * PT_TO_DPI, + 0, + 0); log_debug("Creating Cairo font.\n"); entry->backend.cairo_face = cairo_ft_font_face_create_for_ft_face(entry->backend.ft_face, 0); @@ -87,10 +97,11 @@ void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, ui struct color color = entry->foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_set_font_face(cr, entry->backend.cairo_face); - cairo_set_font_size(cr, entry->font_size * (96.0 / 72.0)); + cairo_set_font_size(cr, entry->font_size * PT_TO_DPI); - cairo_font_extents_t font_extents; - cairo_font_extents(cr, &font_extents); + /* We also need to set up the font for our other Cairo context. */ + cairo_set_font_face(entry->cairo[1].cr, entry->backend.cairo_face); + cairo_set_font_size(entry->cairo[1].cr, entry->font_size * PT_TO_DPI); log_debug("Creating Harfbuzz font.\n"); entry->backend.hb_font = hb_ft_font_create_referenced(entry->backend.ft_face); @@ -106,8 +117,8 @@ void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, ui /* Move and clip so we don't draw over the prompt */ uint32_t prompt_width = render_hb_buffer(cr, entry->backend.hb_buffer, scale); cairo_translate(cr, prompt_width, 0); - width -= prompt_width; - cairo_rectangle(cr, 0, 0, width, height); + *width -= prompt_width; + cairo_rectangle(cr, 0, 0, *width, *height); cairo_clip(cr); } @@ -121,7 +132,7 @@ void entry_backend_destroy(struct entry *entry) void entry_backend_update(struct entry *entry) { - cairo_t *cr = entry->cairo.cr; + cairo_t *cr = entry->cairo[entry->index].cr; hb_buffer_clear_contents(entry->backend.hb_buffer); setup_hb_buffer(entry->backend.hb_buffer); diff --git a/src/entry_backend/harfbuzz.h b/src/entry_backend/harfbuzz.h index 0b07538..52116a8 100644 --- a/src/entry_backend/harfbuzz.h +++ b/src/entry_backend/harfbuzz.h @@ -19,7 +19,7 @@ struct entry_backend { hb_buffer_t *hb_buffer; }; -void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale); +void entry_backend_init(struct entry *entry, uint32_t *width, uint32_t *height, uint32_t scale); void entry_backend_destroy(struct entry *entry); void entry_backend_update(struct entry *entry); diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c index 569a706..4d43ce3 100644 --- a/src/entry_backend/pango.c +++ b/src/entry_backend/pango.c @@ -7,9 +7,9 @@ #include "../log.h" #include "../nelem.h" -void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale) +void entry_backend_init(struct entry *entry, uint32_t *width, uint32_t *height, uint32_t scale) { - cairo_t *cr = entry->cairo.cr; + cairo_t *cr = entry->cairo[0].cr; /* Setup Pango. */ log_debug("Creating Pango context.\n"); @@ -41,8 +41,8 @@ void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, ui /* Move and clip so we don't draw over the prompt */ cairo_translate(cr, prompt_width, 0); - width -= prompt_width; - cairo_rectangle(cr, 0, 0, width, height); + *width -= prompt_width; + cairo_rectangle(cr, 0, 0, *width, *height); cairo_clip(cr); log_debug("Creating Pango layout.\n"); @@ -70,7 +70,7 @@ void entry_backend_destroy(struct entry *entry) void entry_backend_update(struct entry *entry) { - cairo_t *cr = entry->cairo.cr; + cairo_t *cr = entry->cairo[entry->index].cr; pango_layout_set_text(entry->backend.entry_layout, entry->input_mb, -1); pango_cairo_update_layout(cr, entry->backend.entry_layout); diff --git a/src/entry_backend/pango.h b/src/entry_backend/pango.h index 08572ae..ef58be3 100644 --- a/src/entry_backend/pango.h +++ b/src/entry_backend/pango.h @@ -12,7 +12,7 @@ struct entry_backend { PangoLayout *result_layouts[5]; }; -void entry_backend_init(struct entry *entry, uint32_t width, uint32_t height, uint32_t scale); +void entry_backend_init(struct entry *entry, uint32_t *width, uint32_t *height, uint32_t scale); void entry_backend_destroy(struct entry *entry); void entry_backend_update(struct entry *entry); diff --git a/src/image.h b/src/image.h index 81cb66a..090f32d 100644 --- a/src/image.h +++ b/src/image.h @@ -5,7 +5,6 @@ #include struct image { - uint8_t *buffer; uint32_t width; uint32_t height; uint32_t scale; diff --git a/src/main.c b/src/main.c index 39da872..f3fab1d 100644 --- a/src/main.c +++ b/src/main.c @@ -785,6 +785,15 @@ int main(int argc, char *argv[]) wl_display_roundtrip(tofi.wl_display); log_debug("Third roundtrip done.\n"); + + /* + * Create the various structures for our window surface. This needs to + * be done before calling entry_init as that performs some initial + * drawing, and surface_init allocates the buffers we'll be drawing to. + */ + log_debug("Initialising window surface.\n"); + surface_init(&tofi.window.surface, tofi.wl_shm); + /* * Initialise the structures for rendering the entry. * Cairo needs to know the size of the surface it's creating, and @@ -797,19 +806,14 @@ int main(int argc, char *argv[]) log_debug("Initialising renderer.\n"); entry_init( &tofi.window.entry, + tofi.window.surface.shm_pool_data, tofi.window.surface.width, tofi.window.surface.height, tofi.window.scale); entry_update(&tofi.window.entry); log_debug("Renderer initialised.\n"); - /* - * Create the various structures for each surface, and - * perform an initial render of everything. - */ - log_debug("Initialising main window surface.\n"); - - surface_initialise(&tofi.window.surface, tofi.wl_shm); + /* Perform an initial render. */ surface_draw( &tofi.window.surface, &tofi.window.entry.background_color, diff --git a/src/surface.c b/src/surface.c index d02fd4f..30a46cd 100644 --- a/src/surface.c +++ b/src/surface.c @@ -8,7 +8,7 @@ #undef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) -void surface_initialise( +void surface_init( struct surface *surface, struct wl_shm *wl_shm) { @@ -47,8 +47,6 @@ void surface_initialise( log_debug("Created shm file with size %d KiB.\n", surface->shm_pool_size / 1024); - - surface->index = 0; } void surface_destroy(struct surface *surface) @@ -66,12 +64,9 @@ void surface_draw( struct color *color, struct image *texture) { - surface->index = !surface->index; - int offset = surface->height * surface->stride * surface->index; - uint32_t *pixels = (uint32_t *)&surface->shm_pool_data[offset]; - memcpy(pixels, texture->buffer, surface->height * surface->stride); - wl_surface_attach(surface->wl_surface, surface->buffers[surface->index], 0, 0); wl_surface_damage_buffer(surface->wl_surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(surface->wl_surface); + + surface->index = !surface->index; } diff --git a/src/surface.h b/src/surface.h index 6016551..68f9a31 100644 --- a/src/surface.h +++ b/src/surface.h @@ -22,7 +22,7 @@ struct surface { bool redraw; }; -void surface_initialise( +void surface_init( struct surface *surface, struct wl_shm *wl_shm); void surface_destroy(struct surface *surface); -- cgit v1.2.3