Improve re-use of client/compositor shared memory by Wayland buffers

This commit is contained in:
ManoloFLTK 2023-06-05 11:00:33 +02:00
parent 5dfa51a820
commit 5be3fbf913
4 changed files with 126 additions and 156 deletions

View File

@ -486,6 +486,12 @@ Each of these two objects encapsulates a byte array of the same size and the sam
destined to contain the Fl_Window's graphics. The Cairo surface object is where FLTK draws.
The Wayland buffer is what Wayland maps on the display. FLTK copies the Cairo surface's byte array
to the Wayland buffer's byte array before beginning the mapping operation.
If \c width and \c height are a window's dimensions in pixels,
\code
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
int size = stride * height;
\endcode
give \c size, the common size of both byte arrays.
Section \ref wayland-buffer-factory below details how FLTK creates \c wl_buffer objects.
@ -552,75 +558,72 @@ Wayland names <em>buffer factory</em> a software procedure that constructs objec
<tt>struct wl_buffer</tt> for use by a client application.
FLTK creates a \c wl_buffer object each time an Fl_Window is mapped on a display or resized.
That's done by member function \c Fl_Wayland_Graphics_Driver::create_shm_buffer()
which follows this 3-step procedure to create a "buffer factory" for FLTK and construct
which follows this 3-step procedure to create a "buffer factory" for FLTK and to construct
Wayland buffers from it:
- Libdecor function <tt>os_create_anonymous_file(off_t size)</tt> creates an adequate file and mmap's
it. This file lives in RAM because it is created by function \c memfd_create().
FLTK initially sets this file size to \c pool_size = 10 MB. This size will be increased when and
if necessary.
FLTK stores in variable \c pool_memory the address of the beginning of the mmap'ed memory structure.
- Libdecor function <tt>os_create_anonymous_file(off_t size)</tt> creates an adequate file
and mmap's it. This file lives in RAM because it is created by function \c memfd_create().
FLTK sets this file size to 10 MB unless the size of the buffer to be created
is larger; in that case the anonymous file is sized to twice the buffer size.
- Wayland function \c wl_shm_create_pool() shares this mmap'ed memory with the
Wayland compositor and returns an object of type <tt>struct wl_shm_pool</tt> which encapsulates
this memory. FLTK initializes
to 0 a variable called \c chunk_offset that represents the offset inside the mmap'ed memory available
for further \c wl_buffer objects.
- Wayland function \c wl_shm_pool_create_buffer() creates from the \c wl_shm_pool object a
\c wl_buffer object which encapsulates a section of a given size of the shared memory structure
beginning at offset \c chunk_offset in it. This function returns a pointer to the resulting
\c wl_buffer object. Quantity <tt>pool_memory + chunk_offset</tt> is therefore the address of the
Wayland compositor and returns an object of type <tt>struct wl_shm_pool</tt> which
encapsulates this memory. A record of type
<tt>struct Fl_Wayland_Graphics_Driver::wld_shm_pool_data</tt> is created and associated to
the newly created \c wl_shm_pool by \c wl_shm_pool_set_user_data(). This record stores
the starting address (\c pool_memory) and size (\c pool_size) of the pool's encapsulated
memory. The record also contains member \c buffers of type <tt>struct wl_list</tt> which
stores the access point to the linked list of \c wl_buffer objects that will be created from
the \c wl_shm_pool.
- A variable named \c chunk_offset represents the offset within the pool's shared
memory available for the buffer being constructed. It equals 0 when the pool has just been
created and is updated as detailed below when one or more buffers have been previously created
from the pool. A record of type <tt>struct fl_wld_buffer</tt> is created. This record will
contain (member \c wl_buffer) the address of a \c wl_buffer object that's created by function
\c wl_shm_pool_create_buffer(). This \c wl_buffer object encapsulates a section of a given
size of the pool's shared memory beginning at offset \c chunk_offset in it.
Quantity <tt>pool_memory + chunk_offset</tt> is therefore the address of the
beginning of the mmap'ed memory section encapsulated by this \c wl_buffer.
Variable \c chunk_offset is then increased by the length of this section.
A window's \c wl_buffer is re-used each time the window gets redrawn, and is destroyed by function
\c Fl_Wayland_Graphics_Driver::buffer_release() which calls \c wl_buffer_destroy() when
\c Fl_Window::hide() runs or the window is resized.
If \c width and \c height are a window's dimensions in pixels,
Member \c shm_pool of the newly constructed \c fl_wld_buffer object is set to the address of
the current \c wl_shm_pool object. This record is added to the head of the linked list of
current pool's buffers by a call to \c wl_list_insert().
At that point, a <tt>struct fl_wld_buffer</tt> record is part of the linked list of all
such records corresponding to \c wl_buffer objects created from the same \c wl_shm_pool
object, and member \c shm_pool of this record gives the address of this \c wl_shm_pool.
When a new <tt>struct fl_wld_buffer</tt> record is to be created,
\code
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
int size = stride * height;
struct wld_shm_pool_data *pool_data =
(struct wld_shm_pool_data *)wl_shm_pool_get_user_data(pool);
struct fl_wld_buffer *record = wl_container_of(pool_data->buffers.next, record, link);
int chunk_offset = ((char*)record->data - pool_data->pool_memory) + record->data_size;
\endcode
give \c size, the size in bytes of a memory buffer needed to store the window's graphics.
If <tt>chunk_offset + size > pool_size</tt> holds when function \c create_shm_buffer() attempts to
create a new \c wl_buffer object, \c chunk_offset is reset to 0,
function \c wl_shm_pool_destroy() is called to destroy
the current \c wl_shm_pool object, and a new \c wl_shm_pool object is created and used by FLTK's
"buffer factory". If <tt>size > pool_size</tt> holds at that step, the value of \c pool_size
is increased to <tt>2 * size</tt>. This mechanism allows to access new mmap'ed memory when
\c chunk_offset reaches the end of the previous mmap'ed section, and to enlarge the size of the
mmap'ed memory when necessary.
gives the offset within the current pool's mmap'ed memory available for a new \c wl_buffer.
Macro \c wl_container_of() gives the address of a record belonging to a linked list of
records of the same type.
To free the memory used by a \c wl_shm_pool object, it's not enough to destroy
all \c wl_buffer objects built from this \c wl_shm_pool object and to call
\c wl_shm_pool_destroy(). It's also necessary to call \c munmap() on the mmap'ed memory
shared between the client and the compositor and encapsulated by the \c wl_shm_pool.
This explains this type definition inside class \c Fl_Wayland_Graphics_Driver:
\code
struct wld_shm_pool_data { // attached to wl_shm_pool objects
char *pool_memory; // start of mmap'ed memory encapsulated by the wl_shm_pool
int pool_size; // size of encapsulated memory
int use_count; // nber of active wl_buffer objects in this wl_shm_pool
bool destroyed; // true after the wl_shm_pool was destroyed
};
\endcode
A record of this type is created for each \c wl_shm_pool object and attached to it with
a call to \c wl_shm_pool_set_user_data(). This record contains the starting address
and the size of the mmap'ed memory section necessary for a future \c munmap() call.
Member \c destroyed is turned to \c true after the call to \c wl_shm_pool_destroy().
Member \c use_count starts at 0, is incremented each time a \c wl_buffer is created from
this \c wl_shm_pool and decremented each time a \c wl_buffer is destroyed.
The same record is also associated to each \c wl_buffer object created from
this \c wl_shm_pool by \c wl_buffer_set_user_data().
Overall, this allows FLTK to detect when all \c wl_buffer objects
from a \c wl_shm_pool have been destroyed and to call \c munmap() to complete the release
of that pool's memory resources.
A window's \c wl_buffer is re-used each time the window gets redrawn, and is destroyed by
function \c Fl_Wayland_Graphics_Driver::buffer_release() when \c Fl_Window::hide() runs or
the window is resized. Function \c Fl_Wayland_Graphics_Driver::buffer_release() destroys the
\c wl_buffer with \c wl_buffer_destroy() and removes the corresponding
\c fl_wld_buffer record from the linked list of buffers from the same \c wl_shm_pool.
Since new \c fl_wld_buffer records are added at the head of the linked list, and since
the record at the head of this list is used to compute the offset within the pool's mmap'ed
memory available for a new \c wl_buffer, destruction of the last created \c wl_buffer
allows to re-use the destroyed buffer's pool's memory for a new \c wl_buffer.
When the linked list results empty, the \c wl_shm_pool is destroyed by
\c wl_shm_pool_destroy(), the pool's mmap'ed memory is munmap'ed, and the pool's associated
<tt>struct wld_shm_pool_data</tt> is freed.
Wayland uses also \c wl_buffer objects to support cursors, and
FLTK uses the "buffer factory" described here also when creating custom cursors (see
\ref custom-cursor) with
function <tt>Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *,…)</tt> because
\c create_shm_buffer() runs as well. In contrast, standard shaped-cursors (e.g., FL_CURSOR_INSERT)
use their own "buffer factory" inside Wayland functions such as \c wl_cursor_theme_get_cursor().
If the sum of \c chunk_offset plus the buffer size is larger than the current pool's size
when function \c create_shm_buffer() is called, \c chunk_offset is reset
to 0, and a new \c wl_shm_pool object is created and used by FLTK's "buffer factory".
This mechanism allows to access new mmap'ed memory when \c chunk_offset reaches the end of
the previous mmap'ed section.
Wayland uses also \c wl_buffer objects to support cursors. FLTK uses the "buffer factory"
described here when creating custom cursors (see \ref custom-cursor) with
function <tt>Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *,…)</tt> which calls
\c create_shm_buffer(). In contrast, standard shaped-cursors (e.g., FL_CURSOR_INSERT)
use their own "buffer factory" inside Wayland functions such as
\c wl_cursor_theme_get_cursor().
Therefore, the fact that the \c wl_buffer objects behind standard cursors are never destroyed
doesn't prevent disused <tt>struct wl_shm_pool</tt> objects from being freed because those
buffers come from a distinct "buffer factory".

View File

@ -1,7 +1,7 @@
//
// Definition of class Fl_Wayland_Graphics_Driver.
//
// Copyright 2021-2022 by Bill Spitzak and others.
// Copyright 2021-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
@ -22,37 +22,10 @@
#ifndef FL_WAYLAND_GRAPHICS_DRIVER_H
#define FL_WAYLAND_GRAPHICS_DRIVER_H
/* Implementation note about buffers FLTK uses to support display graphics under Wayland.
Each window is associated to an FLTK-defined object of type struct wld_window
containing itself an FLTK-defined struct fl_wld_buffer object holding all graphics data.
Among members of this latter structure are:
- struct wl_buffer wl_buffer
is a Wayland-defined type for a graphics buffer able to be attached to a wl_surface;
- void *data
points to the beginning of the memory zone where wl_buffer stores its graphics data;
- unsigned char *draw_buffer
contains a graphics buffer to which all Cairo drawings are directed;
draw_buffer and data both have the same organization called CAIRO_FORMAT_ARGB32 in Cairo parlance
and WL_SHM_FORMAT_ARGB8888 in Wayland parlance which means BGRA byte order.
- int width
gives the pixel width of the graphics buffer;
- int stride
gives the stride of this buffer;
- size_t data_size
gives the total buffer size in bytes (thus, data_size / stride gives the buffer height);
- struct wl_callback *cb
is used to synchronize drawing with the compositor during progressive drawing.
When a graphics scene is to be committed, the data_size bytes of draw_buffer are copied by memcpy()
starting at data, and wl_buffer is attached to the wl_surface which is committed for display
by wl_surface_commit().
*/
#include "../Cairo/Fl_Cairo_Graphics_Driver.H"
#include <stdint.h> // for uint32_t
#include <wayland-client.h> // for wl_list
struct fl_wld_buffer {
struct wl_buffer *wl_buffer;
@ -64,19 +37,19 @@ struct fl_wld_buffer {
struct wl_callback *cb;
bool draw_buffer_needs_commit;
cairo_t *cairo_;
struct wl_shm_pool *shm_pool;
struct wl_list link; // links all buffers from the same wl_shm_pool
};
struct wld_window;
class Fl_Wayland_Graphics_Driver : public Fl_Cairo_Graphics_Driver {
private:
struct fl_wld_buffer *buffer_;
public:
struct wld_shm_pool_data { // attached to wl_shm_pool objects
struct wld_shm_pool_data { // one record attached to each wl_shm_pool object
char *pool_memory; // start of mmap'ed memory encapsulated by the wl_shm_pool
int pool_size; // size of encapsulated memory
int use_count; // nber of active wl_buffer objects in this wl_shm_pool
bool destroyed; // true after the wl_shm_pool was destroyed
struct wl_list buffers; // to list of fl_wld_buffer's from this pool
};
Fl_Wayland_Graphics_Driver();
static const uint32_t wld_format;

View File

@ -27,53 +27,56 @@ extern "C" {
# include "../../../libdecor/src/os-compatibility.h" // for os_create_anonymous_file()
}
// used by create_shm_buffer and buffer_release
static struct wl_shm_pool *pool = NULL; // the current pool
struct fl_wld_buffer *Fl_Wayland_Graphics_Driver::create_shm_buffer(int width, int height)
{
struct fl_wld_buffer *buffer;
int stride = cairo_format_stride_for_width(Fl_Cairo_Graphics_Driver::cairo_format, width);
int size = stride * height;
static int pool_size = 10000000; // gets increased if necessary
static int chunk_offset = pool_size;
static int fd = -1;
static struct wl_shm_pool *pool = NULL;
if (chunk_offset + size > pool_size) {
const int default_pool_size = 10000000; // larger pools are possible if needed
int chunk_offset = 0; // offset to start of available memory in pool
struct wld_shm_pool_data *pool_data = pool ? // data record attached to current pool
(struct wld_shm_pool_data *)wl_shm_pool_get_user_data(pool) : NULL;
int pool_size = pool ? pool_data->pool_size : default_pool_size; // current pool size
if (pool) {
// last fl_wld_buffer created from current pool
struct fl_wld_buffer *record = wl_container_of(pool_data->buffers.next, record, link);
chunk_offset = ((char*)record->data - pool_data->pool_memory) + record->data_size;
}
if (!pool || chunk_offset + size > pool_size) { // if true, a new pool is needed
chunk_offset = 0;
if (pool) {
struct wld_shm_pool_data *pool_data =
(struct wld_shm_pool_data *)wl_shm_pool_get_user_data(pool);
wl_shm_pool_destroy(pool);
close(fd);
pool_data->destroyed = true;
if (pool_data->use_count == 0) {
/*int err =*/ munmap(pool_data->pool_memory, pool_data->pool_size);
//printf("munmap(%p)->%d\n", pool_data->pool_memory, err);
free(pool_data);
}
pool_size = default_pool_size;
if (size > pool_size) pool_size = 2 * size; // a larger pool is needed
int fd = os_create_anonymous_file(pool_size);
if (fd < 0) {
Fl::fatal("os_create_anonymous_file failed: %s\n", strerror(errno));
}
if (size > pool_size) pool_size = 2 * size;
fd = os_create_anonymous_file(pool_size);
struct wld_shm_pool_data *pool_data = (struct wld_shm_pool_data*)
calloc(1, sizeof(struct wld_shm_pool_data));
pool_data->pool_memory = (char*)mmap(NULL, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
pool_data = (struct wld_shm_pool_data*)calloc(1, sizeof(struct wld_shm_pool_data));
pool_data->pool_memory = (char*)mmap(NULL, pool_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (pool_data->pool_memory == MAP_FAILED) {
close(fd);
Fl::fatal("mmap failed: %s\n", strerror(errno));
}
Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
pool = wl_shm_create_pool(scr_driver->wl_shm, fd, pool_size);
close(fd); // does not prevent the mmap'ed memory from being used
//puts("wl_shm_create_pool");
pool_data->pool_size = pool_size;
wl_list_init(&pool_data->buffers);
wl_shm_pool_set_user_data(pool, pool_data);
}
buffer = (struct fl_wld_buffer*)calloc(1, sizeof(struct fl_wld_buffer));
buffer->stride = stride;
buffer->wl_buffer = wl_shm_pool_create_buffer(pool, chunk_offset, width, height, stride, Fl_Wayland_Graphics_Driver::wld_format);
struct wld_shm_pool_data *pool_data =
(struct wld_shm_pool_data *)wl_shm_pool_get_user_data(pool);
pool_data->use_count++;
wl_buffer_set_user_data(buffer->wl_buffer, pool_data);
// add this buffer to head of list of current pool's buffers
wl_list_insert(&pool_data->buffers, &buffer->link);
buffer->shm_pool = pool;
buffer->data = (void*)(pool_data->pool_memory + chunk_offset);
chunk_offset += size;
//fprintf(stderr, "last=%p chunk_offset=%d ", pool_data->buffers.next, chunk_offset);
buffer->data_size = size;
buffer->width = width;
buffer->draw_buffer = new uchar[buffer->data_size];
@ -174,15 +177,20 @@ void Fl_Wayland_Graphics_Driver::buffer_release(struct wld_window *window)
{
if (window->buffer) {
if (window->buffer->cb) wl_callback_destroy(window->buffer->cb);
struct wl_shm_pool *my_pool = window->buffer->shm_pool;
struct wld_shm_pool_data *pool_data =
(struct wld_shm_pool_data *)wl_buffer_get_user_data(window->buffer->wl_buffer);
(struct wld_shm_pool_data*)wl_shm_pool_get_user_data(my_pool);
wl_buffer_destroy(window->buffer->wl_buffer);
//printf("wl_buffer_destroy(%p)\n",window->buffer->wl_buffer);
pool_data->use_count--;
if (pool_data->destroyed && pool_data->use_count == 0) {
/*int err =*/ munmap(pool_data->pool_memory, pool_data->pool_size);
// remove fl_wld_buffer from list of pool's buffers
wl_list_remove(&window->buffer->link);
//printf("last=%p\n", wl_list_empty(&pool_data->buffers) ? NULL : pool_data->buffers.next);
if (wl_list_empty(&pool_data->buffers)) { // all buffers from pool are gone
wl_shm_pool_destroy(my_pool);
/*int err = */munmap(pool_data->pool_memory, pool_data->pool_size);
//printf("munmap(%p)->%d\n", pool_data->pool_memory, err);
free(pool_data);
if (my_pool == pool) pool = NULL;
}
delete[] window->buffer->draw_buffer;
window->buffer->draw_buffer = NULL;

View File

@ -71,22 +71,12 @@ Fl_Wayland_Window_Driver::Fl_Wayland_Window_Driver(Fl_Window *win) : Fl_Window_D
}
struct custom_cursor_data {
struct fl_wld_buffer *offscreen;
struct Fl_Wayland_Graphics_Driver::wld_shm_pool_data *buf_data;
};
void Fl_Wayland_Window_Driver::delete_cursor_(struct wld_window *xid, bool delete_rgb) {
struct wld_window::custom_cursor_ *custom = xid->custom_cursor;
if (custom) {
struct wl_cursor *wl_cursor = custom->wl_cursor;
struct cursor_image *new_image = (struct cursor_image*)wl_cursor->images[0];
custom_cursor_data *buf_data =
(custom_cursor_data *)wl_buffer_get_user_data(new_image->buffer);
struct fl_wld_buffer *offscreen = buf_data->offscreen;
wl_buffer_set_user_data(new_image->buffer, buf_data->buf_data);
free(buf_data);
struct fl_wld_buffer *offscreen = (struct fl_wld_buffer *)wl_buffer_get_user_data(new_image->buffer);
struct wld_window fake_xid;
fake_xid.buffer = offscreen;
Fl_Wayland_Graphics_Driver::buffer_release(&fake_xid);
@ -676,13 +666,13 @@ static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_o
i++;
}
}
if (window->kind == Fl_Wayland_Window_Driver::POPUP) {
Fl_Wayland_Graphics_Driver::buffer_release(window);
window->fl_win->redraw();
} else {
float post_scale = Fl::screen_scale(win_driver->screen_num()) * output->wld_scale;
//printf("pre_scale=%.1f post_scale=%.1f\n", pre_scale, post_scale);
if (window->fl_win->as_gl_window() || post_scale != pre_scale) {
float post_scale = Fl::screen_scale(win_driver->screen_num()) * output->wld_scale;
//printf("pre_scale=%.1f post_scale=%.1f\n", pre_scale, post_scale);
if (window->fl_win->as_gl_window() || post_scale != pre_scale) {
if (window->kind == Fl_Wayland_Window_Driver::POPUP) {
Fl_Wayland_Graphics_Driver::buffer_release(window);
window->fl_win->redraw();
} else {
win_driver->is_a_rescale(true);
window->fl_win->size(window->fl_win->w(), window->fl_win->h());
win_driver->is_a_rescale(false);
@ -691,16 +681,17 @@ static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_o
win_driver->Fl_Window_Driver::flush();
Fl::add_timeout(0.01, (Fl_Timeout_Handler)delayed_redraw, window->fl_win);
}
} else if (window->buffer) {
if (!window->buffer->cb) {
Fl_Wayland_Graphics_Driver::buffer_commit(window);
}
}
if (window->fl_win->as_gl_window())
wl_surface_set_buffer_scale(window->wl_surface, output->wld_scale);
} else if (window->buffer) {
if (!window->buffer->cb) {
Fl_Wayland_Graphics_Driver::buffer_commit(window);
}
}
if (window->fl_win->as_gl_window())
wl_surface_set_buffer_scale(window->wl_surface, output->wld_scale);
}
static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output)
{
// Do nothing because surface_leave old display arrives **after** surface_enter new display
@ -1565,12 +1556,7 @@ int Fl_Wayland_Window_Driver::set_cursor_4args(const Fl_RGB_Image *rgb, int hotx
//create a Wayland buffer and have it used as an image of the new cursor
struct fl_wld_buffer *offscreen = Fl_Wayland_Graphics_Driver::create_shm_buffer(new_image->image.width, new_image->image.height);
new_image->buffer = offscreen->wl_buffer;
custom_cursor_data *new_buf_data =
(custom_cursor_data*)malloc(sizeof(custom_cursor_data));
new_buf_data->buf_data = (struct Fl_Wayland_Graphics_Driver::wld_shm_pool_data *)
wl_buffer_get_user_data(new_image->buffer);
new_buf_data->offscreen = offscreen;
wl_buffer_set_user_data(new_image->buffer, new_buf_data);
wl_buffer_set_user_data(new_image->buffer, offscreen);
new_cursor->image_count = 1;
new_cursor->images = (struct wl_cursor_image**)malloc(sizeof(struct wl_cursor_image*));
new_cursor->images[0] = (struct wl_cursor_image*)new_image;