Merge branch 'wip/mvlad/cache-str-values' into 'main'

compositor: Cache weston buffer, surface and view properties as strings

See merge request wayland/weston!1907
This commit is contained in:
Marius Vlad 2026-01-28 07:50:34 +00:00
commit 3e2f6efbd5
5 changed files with 289 additions and 116 deletions

View file

@ -80,6 +80,14 @@ struct weston_commit_timing_target {
struct timespec time;
};
struct weston_cached_string {
bool is_up_to_date;
char *cached_str;
/* assumes FP opened, use it to (re-)compose the string */
void (*regen)(FILE *fp, void *data);
};
struct weston_compositor;
struct weston_surface;
struct weston_buffer;
@ -1588,6 +1596,8 @@ struct weston_buffer {
const struct pixel_format_info *pixel_format;
uint64_t format_modifier;
struct weston_cached_string scene_graph_record;
};
enum weston_buffer_reference_type {
@ -1771,6 +1781,8 @@ struct weston_view {
bool is_mapped;
struct weston_log_pacer subsurface_parent_log_pacer;
struct weston_cached_string scene_graph_record;
};
enum weston_surface_status {
@ -2042,6 +2054,7 @@ struct weston_surface {
/** commit_timing_v1 */
struct weston_commit_timer *commit_timer;
struct weston_cached_string scene_graph_record;
};
struct weston_subsurface {
@ -2822,6 +2835,12 @@ weston_output_get_supported_color_formats(struct weston_output *output);
void
weston_output_set_ready(struct weston_output *output);
const char *
weston_cached_str_get(struct weston_cached_string *s, void *data);
void
weston_cached_str_invalidate(struct weston_cached_string *s);
#ifdef __cplusplus
}
#endif

View file

@ -104,6 +104,9 @@
#define DEFAULT_REPAINT_WINDOW 7 /* milliseconds */
static struct weston_layer *
get_view_layer(struct weston_view *view);
static void
weston_output_transform_scale_init(struct weston_output *output,
uint32_t transform, uint32_t scale);
@ -376,6 +379,29 @@ paint_node_update_late(struct weston_paint_node *pnode)
paint_node_validate_ready(pnode);
}
static void
weston_paint_node_debug_string_regenerate(FILE *fp, void *data)
{
struct weston_paint_node *pnode = data;
struct weston_view *view = NULL;
struct weston_output *output = NULL;
if (!pnode) {
fprintf(fp, "\t\t\tpaint node [pending repaint]:\n");
return;
}
fprintf(fp, "\t\t\tpaint node %p:\n", pnode);
view = pnode->view;
output = pnode->output;
fprintf(fp, "\t\t\t\toutput: %d (%s)%s\n",
output->id, output->name,
(view->output == output) ? " (primary)" : "");
}
static struct weston_paint_node *
weston_paint_node_create(struct weston_surface *surface,
struct weston_view *view,
@ -429,6 +455,9 @@ weston_paint_node_create(struct weston_surface *surface,
pnode->status =
WESTON_PAINT_NODE_ALL_DIRTY & ~WESTON_PAINT_NODE_PLANE_DIRTY;
pnode->scene_graph_record.regen =
weston_paint_node_debug_string_regenerate;
return pnode;
}
@ -480,6 +509,7 @@ weston_paint_node_destroy(struct weston_paint_node *pnode)
pixman_region32_fini(&pnode->visible);
pixman_region32_fini(&pnode->visible_previous);
pixman_region32_fini(&pnode->clipped_view);
free(pnode->scene_graph_record.cached_str);
free(pnode);
}
@ -733,6 +763,39 @@ weston_output_mode_switch_to_temporary(struct weston_output *output,
return 0;
}
static void
weston_view_debug_string_regenerate(FILE *fp, void *data)
{
struct weston_view *ev = data;
pixman_box32_t *box;
if (!weston_view_is_mapped(ev))
fprintf(fp, "\t[view is not mapped!]\n");
if (wl_list_empty(&ev->layer_link.link)) {
if (!get_view_layer(ev))
fprintf(fp, "\t[view is not part of any layer]\n");
else
fprintf(fp, "\t[view is under parent view layer]\n");
}
box = pixman_region32_extents(&ev->transform.boundingbox);
fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n",
box->x1, box->y1, box->x2, box->y2);
box = pixman_region32_extents(&ev->transform.opaque);
if (weston_view_is_opaque(ev, &ev->transform.boundingbox)) {
fprintf(fp, "\t\t[fully opaque]\n");
} else if (!pixman_region32_not_empty(&ev->transform.opaque)) {
fprintf(fp, "\t\t[not opaque]\n");
} else {
fprintf(fp, "\t\t[opaque: (%d, %d) -> (%d, %d)]\n",
box->x1, box->y1, box->x2, box->y2);
}
if (ev->alpha < 1.0)
fprintf(fp, "\t\talpha: %f\n", ev->alpha);
}
static struct weston_view *
weston_view_create_internal(struct weston_surface *surface)
{
@ -765,6 +828,9 @@ weston_view_create_internal(struct weston_surface *surface)
pixman_region32_init(&view->geometry.scissor);
pixman_region32_init(&view->transform.boundingbox);
view->transform.dirty = 1;
view->scene_graph_record.regen =
weston_view_debug_string_regenerate;
weston_view_update_transform(view);
return view;
@ -962,6 +1028,33 @@ weston_surface_update_preferred_color_profile(struct weston_surface *surface)
weston_surface_send_preferred_image_description_changed(surface);
}
static void
weston_surface_debug_string_regenerate(FILE *fp, void *data)
{
struct weston_surface *surface = data;
uint32_t surface_id = 0;
pid_t pid = 0;
char desc[512];
if (surface->resource) {
struct wl_resource *resource = surface->resource;
wl_client_get_credentials(wl_resource_get_client(resource),
&pid, NULL, NULL);
surface_id = wl_resource_get_id(surface->resource);
}
if (!surface->get_label ||
surface->get_label(surface, desc, sizeof(desc)) < 0) {
strcpy(desc, "[no description available]");
}
fprintf(fp, "role %s, PID %d, surface ID %u, %s",
weston_surface_get_role(surface), pid, surface_id, desc);
if (!weston_surface_is_mapped(surface))
fprintf(fp, "\t[surface is not mapped!]\n");
}
WL_EXPORT struct weston_surface *
weston_surface_create(struct weston_compositor *compositor)
{
@ -1029,6 +1122,9 @@ weston_surface_create(struct weston_compositor *compositor)
wl_list_init(&surface->fifo_barrier_link);
surface->scene_graph_record.regen =
weston_surface_debug_string_regenerate;
return surface;
}
@ -1958,6 +2054,8 @@ weston_view_update_transform_internal(struct weston_view *view)
geometry.parent_link) {
weston_view_update_transform(child);
}
weston_cached_str_invalidate(&view->scene_graph_record);
}
WL_EXPORT void
@ -2593,6 +2691,7 @@ weston_view_unmap(struct weston_view *view)
}
weston_view_destroy_paint_nodes(view);
weston_cached_str_invalidate(&view->scene_graph_record);
wl_signal_emit_mutable(&view->unmap_signal, view);
view->surface->compositor->view_list_needs_rebuild = true;
@ -2605,6 +2704,8 @@ static void weston_surface_start_mapping(struct weston_surface *surface)
surface->is_mapping = true;
surface->is_mapped = true;
surface->compositor->view_list_needs_rebuild = true;
weston_cached_str_invalidate(&surface->scene_graph_record);
wl_signal_emit_mutable(&surface->map_signal, surface);
}
@ -2628,6 +2729,8 @@ weston_surface_unmap(struct weston_surface *surface)
wl_list_for_each(view, &surface->views, surface_link)
weston_view_unmap(view);
surface->output = NULL;
weston_cached_str_invalidate(&surface->scene_graph_record);
wl_signal_emit_mutable(&surface->unmap_signal, surface);
}
@ -2660,6 +2763,7 @@ weston_view_destroy(struct weston_view *view)
wl_list_remove(&view->surface_link);
free(view->scene_graph_record.cached_str);
free(view);
}
@ -2747,6 +2851,7 @@ weston_surface_unref(struct weston_surface *surface)
wl_list_remove(&surface->fifo_barrier_link);
free(surface->scene_graph_record.cached_str);
free(surface);
}
@ -2808,9 +2913,60 @@ weston_buffer_destroy_handler(struct wl_listener *listener, void *data)
return;
wl_signal_emit_mutable(&buffer->destroy_signal, buffer);
free(buffer->scene_graph_record.cached_str);
free(buffer);
}
static void
weston_buffer_debug_string_regenerate(FILE *fp, void *data)
{
struct weston_buffer *buffer = data;
char *modifier_name;
switch (buffer->type) {
case WESTON_BUFFER_SHM:
fprintf(fp, "\t\tSHM buffer\n");
break;
case WESTON_BUFFER_DMABUF:
fprintf(fp, "\t\tdmabuf buffer\n");
break;
case WESTON_BUFFER_SOLID:
fprintf(fp, "\t\tsolid-colour buffer\n");
fprintf(fp, "\t\t\t[R %f, G %f, B %f, A %f]\n",
buffer->solid.r, buffer->solid.g, buffer->solid.b,
buffer->solid.a);
break;
case WESTON_BUFFER_RENDERER_OPAQUE:
fprintf(fp, "\t\tEGL buffer:\n");
fprintf(fp, "\t\t\t[format may be inaccurate]\n");
break;
}
if (buffer->pixel_format) {
fprintf(fp, "\t\t\tformat: 0x%lx %s\n",
(unsigned long) buffer->pixel_format->format,
buffer->pixel_format->drm_format_name);
} else {
fprintf(fp, "\t\t\t[unknown format]\n");
}
modifier_name = pixel_format_get_modifier(buffer->format_modifier);
fprintf(fp, "\t\t\tmodifier: %s\n",
modifier_name ?
modifier_name : "Failed to convert to a modifier name");
free(modifier_name);
fprintf(fp, "\t\t\twidth: %d, height: %d\n",
buffer->width, buffer->height);
if (buffer->buffer_origin == ORIGIN_BOTTOM_LEFT)
fprintf(fp, "\t\t\tbottom-left origin\n");
if (buffer->direct_display)
fprintf(fp, "\t\t\tdirect-display buffer (no renderer access)\n");
}
WL_EXPORT struct weston_buffer *
weston_buffer_from_resource(struct weston_compositor *ec,
struct wl_resource *resource)
@ -2833,6 +2989,8 @@ weston_buffer_from_resource(struct weston_compositor *ec,
return NULL;
buffer->resource = resource;
buffer->scene_graph_record.regen =
weston_buffer_debug_string_regenerate;
wl_signal_init(&buffer->destroy_signal);
buffer->destroy_listener.notify = weston_buffer_destroy_handler;
wl_resource_add_destroy_listener(resource, &buffer->destroy_listener);
@ -2901,6 +3059,7 @@ weston_buffer_from_resource(struct weston_compositor *ec,
fail:
wl_list_remove(&buffer->destroy_listener.link);
free(buffer->scene_graph_record.cached_str);
free(buffer);
return NULL;
}
@ -2958,6 +3117,7 @@ weston_buffer_reference(struct weston_buffer_reference *ref,
!old_ref.buffer->resource) {
wl_signal_emit_mutable(&old_ref.buffer->destroy_signal,
old_ref.buffer);
free(old_ref.buffer->scene_graph_record.cached_str);
free(old_ref.buffer);
}
}
@ -3049,6 +3209,8 @@ weston_buffer_create_solid_rgba(struct weston_compositor *compositor,
buffer->solid.g = g;
buffer->solid.b = b;
buffer->solid.a = a;
buffer->scene_graph_record.regen =
weston_buffer_debug_string_regenerate;
if (a == 1.0) {
buffer->pixel_format =
@ -5389,6 +5551,8 @@ weston_surface_set_label_func(struct weston_surface *surface,
surface->get_label = desc;
weston_timeline_refresh_subscription_objects(surface->compositor,
surface);
weston_cached_str_invalidate(&surface->scene_graph_record);
}
/** Get the size of surface contents
@ -9422,31 +9586,13 @@ static void
debug_scene_view_print_buffer(FILE *fp, struct weston_view *view)
{
struct weston_buffer *buffer = view->surface->buffer_ref.buffer;
char *modifier_name;
if (!buffer) {
fprintf(fp, "\t\t[buffer not available]\n");
return;
}
switch (buffer->type) {
case WESTON_BUFFER_SHM:
fprintf(fp, "\t\tSHM buffer\n");
break;
case WESTON_BUFFER_DMABUF:
fprintf(fp, "\t\tdmabuf buffer\n");
break;
case WESTON_BUFFER_SOLID:
fprintf(fp, "\t\tsolid-colour buffer\n");
fprintf(fp, "\t\t\t[R %f, G %f, B %f, A %f]\n",
buffer->solid.r, buffer->solid.g, buffer->solid.b,
buffer->solid.a);
break;
case WESTON_BUFFER_RENDERER_OPAQUE:
fprintf(fp, "\t\tEGL buffer:\n");
fprintf(fp, "\t\t\t[format may be inaccurate]\n");
break;
}
fputs(weston_cached_str_get(&buffer->scene_graph_record, buffer), fp);
if (buffer->busy_count > 0) {
fprintf(fp, "\t\t\t[%d references may use buffer content]\n",
@ -9455,27 +9601,6 @@ debug_scene_view_print_buffer(FILE *fp, struct weston_view *view)
fprintf(fp, "\t\t\t[buffer has been released to client]\n");
}
if (buffer->pixel_format) {
fprintf(fp, "\t\t\tformat: 0x%lx %s\n",
(unsigned long) buffer->pixel_format->format,
buffer->pixel_format->drm_format_name);
} else {
fprintf(fp, "\t\t\t[unknown format]\n");
}
modifier_name = pixel_format_get_modifier(buffer->format_modifier);
fprintf(fp, "\t\t\tmodifier: %s\n",
modifier_name ?
modifier_name : "Failed to convert to a modifier name");
free(modifier_name);
fprintf(fp, "\t\t\twidth: %d, height: %d\n",
buffer->width, buffer->height);
if (buffer->buffer_origin == ORIGIN_BOTTOM_LEFT)
fprintf(fp, "\t\t\tbottom-left origin\n");
if (buffer->direct_display)
fprintf(fp, "\t\t\tdirect-display buffer (no renderer access)\n");
}
static const struct weston_enum_map transforms[] = {
@ -9552,22 +9677,15 @@ debug_scene_view_print_paint_node(FILE *fp,
struct weston_paint_node *pnode;
pnode = weston_view_find_paint_node(view, output);
if (!pnode)
fprintf(fp, "\t\t\tpaint node [pending repaint]:\n");
else
fprintf(fp, "\t\t\tpaint node %p:\n", pnode);
fprintf(fp, "\t\t\t\toutput: %d (%s)%s\n",
output->id, output->name,
(view->output == output) ? " (primary)" : "");
fputs(weston_cached_str_get(&pnode->scene_graph_record, pnode), fp);
if (!pnode)
return;
fprintf(fp, "\t\t\t\tBuffer to output transform: ");
if (!pnode->valid_transform)
if (!pnode->valid_transform) {
fprintf(fp, "Free form\n");
else {
} else {
const char *tform;
tform = weston_transform_to_string(pnode->transform);
@ -9575,11 +9693,10 @@ debug_scene_view_print_paint_node(FILE *fp,
}
if (pnode->try_view_on_plane_failure_reasons) {
char *fr_str = bits_to_str(pnode->try_view_on_plane_failure_reasons,
weston_plane_failure_reasons_to_str);
fprintf(fp, "\t\t\t\tPlane failure reasons: %s\n", fr_str);
free(fr_str);
fprintf(fp, "\t\t\t\tPlane failure reasons: ");
bits_to_str_stream(pnode->try_view_on_plane_failure_reasons,
weston_plane_failure_reasons_to_str, fp);
fprintf(fp, "\n");
}
}
@ -9588,53 +9705,12 @@ debug_scene_view_print(FILE *fp, struct weston_view *view, int view_idx)
{
struct weston_compositor *ec = view->surface->compositor;
struct weston_output *output;
char desc[512];
pixman_box32_t *box;
uint32_t surface_id = 0;
pid_t pid = 0;
if (view->surface->resource) {
struct wl_resource *resource = view->surface->resource;
wl_client_get_credentials(wl_resource_get_client(resource),
&pid, NULL, NULL);
surface_id = wl_resource_get_id(view->surface->resource);
}
fprintf(fp, "\tView %d (%s %p):\n", view_idx,
weston_cached_str_get(&view->surface->scene_graph_record, view->surface),
view);
if (!view->surface->get_label ||
view->surface->get_label(view->surface, desc, sizeof(desc)) < 0) {
strcpy(desc, "[no description available]");
}
fprintf(fp, "\tView %d (role %s, PID %d, surface ID %u, %s, %p):\n",
view_idx, view->surface->role_name, pid, surface_id,
desc, view);
if (!weston_view_is_mapped(view))
fprintf(fp, "\t[view is not mapped!]\n");
if (!weston_surface_is_mapped(view->surface))
fprintf(fp, "\t[surface is not mapped!]\n");
if (wl_list_empty(&view->layer_link.link)) {
if (!get_view_layer(view))
fprintf(fp, "\t[view is not part of any layer]\n");
else
fprintf(fp, "\t[view is under parent view layer]\n");
}
box = pixman_region32_extents(&view->transform.boundingbox);
fprintf(fp, "\t\tposition: (%d, %d) -> (%d, %d)\n",
box->x1, box->y1, box->x2, box->y2);
box = pixman_region32_extents(&view->transform.opaque);
if (weston_view_is_opaque(view, &view->transform.boundingbox)) {
fprintf(fp, "\t\t[fully opaque]\n");
} else if (!pixman_region32_not_empty(&view->transform.opaque)) {
fprintf(fp, "\t\t[not opaque]\n");
} else {
fprintf(fp, "\t\t[opaque: (%d, %d) -> (%d, %d)]\n",
box->x1, box->y1, box->x2, box->y2);
}
if (view->alpha < 1.0)
fprintf(fp, "\t\talpha: %f\n", view->alpha);
fputs(weston_cached_str_get(&view->scene_graph_record, view), fp);
if (view->output_mask != 0) {
fprintf(fp, "\t\tpaint nodes:\n");
@ -9708,6 +9784,8 @@ weston_compositor_print_scene_graph(struct weston_compositor *ec)
size_t len;
int err;
WESTON_TRACE_FUNC();
fp = open_memstream(&ret, &len);
assert(fp);
@ -10920,3 +10998,56 @@ weston_output_set_ready(struct weston_output *output)
weston_output_schedule_repaint(output);
}
}
/**
* Returns a cached string value held up by a pointer to a struct
* weston_cached_string or regenerates the string in case the cached value has
* been invalidated. This is useful for caching object's properties as a string
* value. Users should use weston_cached_str_invalidate in combination with
* this function to denote when it is time to regenerate the string cached
* value.
*
* Note that over-invalidation would actually hurt performance so this is ideal
* for (object) properties that do *not* change each frame.
*
* \param s a pointer to struct weston_cached_string
* \param data generic pointer to be passed to the regen function
* \sa weston_cached_str_invalidate
*
*/
WL_EXPORT const char *
weston_cached_str_get(struct weston_cached_string *s, void *data)
{
if (!s->is_up_to_date) {
char *str = NULL;
size_t size = 0;
FILE *fp;
free(s->cached_str);
fp = open_memstream(&str, &size);
abort_oom_if_null(fp);
s->regen(fp, data);
if (fclose(fp) == 0) {
s->cached_str = str;
} else {
free(str);
s->cached_str = xstrdup("[error]");
}
s->is_up_to_date = true;
}
return s->cached_str;
}
/** Invalidates the cached string value and forces weston_cached_str_get() to
* call the regen callback associated with that particular weston_cached_string
*
* \param s a pointer to struct weston_cached_string
*
*/
WL_EXPORT void
weston_cached_str_invalidate(struct weston_cached_string *s)
{
WESTON_TRACE_FUNC();
s->is_up_to_date = false;
}

View file

@ -750,6 +750,8 @@ struct weston_paint_node {
*/
bool need_hole;
uint32_t psf_flags; /* presentation-feedback flags */
struct weston_cached_string scene_graph_record;
};
struct weston_paint_node *

View file

@ -3398,6 +3398,7 @@ gl_renderer_fill_buffer_info(struct weston_compositor *ec,
buffer->renderer_private = gb;
gb->destroy_listener.notify = handle_buffer_destroy;
weston_cached_str_invalidate(&buffer->scene_graph_record);
wl_signal_add(&buffer->destroy_signal, &gb->destroy_listener);
return true;

View file

@ -130,12 +130,42 @@ str_printf(char **str_out, const char *fmt, ...)
}
/**
* Utility to print combination of enum values as string
* Utility to print combination of enum values as string. Use an opened FILE
* stream to write data.
*
* Only works for enum whose values are defined as power of two. Given a bitmask
* in which each bit represents an enum value and a function that maps each enum
* value to a string, this function returns a string (comma separated) with all
* the enum values that are present in the bitmask.
* \param bits The bitmask of enum values.
* \param map Function that maps enum values to string.
* \param fp an opened FILE stream
*/
static inline void
bits_to_str_stream(uint32_t bits, const char *(*map)(uint32_t), FILE *fp)
{
unsigned i;
const char *sep = "";
if (!fp)
return;
for (i = 0; bits; i++) {
uint32_t bitmask = 1u << i;
if (bits & bitmask) {
fprintf(fp, "%s%s", sep, map(bitmask));
sep = ", ";
}
bits &= ~bitmask;
}
}
/**
* Utility to print combination of enum values as string.
* Uses bits_to_str_stream() helper.
*
* Only works for enum whose values are defined as power of two. Given a
* bitmask in which each bit represents an enum value and a function that maps
* each enum value to a string, this function returns a string (comma
* separated) with all the enum values that are present in the bitmask.
*
* \param bits The bitmask of enum values.
* \param map Function that maps enum values to string.
@ -148,23 +178,13 @@ bits_to_str(uint32_t bits, const char *(*map)(uint32_t))
FILE *fp;
char *str = NULL;
size_t size = 0;
unsigned i;
const char *sep = "";
fp = open_memstream(&str, &size);
if (!fp)
return NULL;
for (i = 0; bits; i++) {
uint32_t bitmask = 1u << i;
bits_to_str_stream(bits, map, fp);
if (bits & bitmask) {
fprintf(fp, "%s%s", sep, map(bitmask));
sep = ", ";
}
bits &= ~bitmask;
}
fclose(fp);
return str;