scene: Optimize rendering of single-pixel buffers

The single-pixel buffer protocol is used to allow wayland clients to
easily draw solid-color rectangles by presenting a 1x1-pixel buffer and
scaling it to the desired size.  This patch improves how these buffers
are then handled in the scene-tree renderer.

We already ignore opaque black rectangles at the very bottom (and
anything under them) because we assume we'll be rendering on a black
background.  This patch detects black opaque single-pixel buffers and
handles them in the same way as black opaque rectangles.  It also
renders single-pixel buffers as rectangles rather than buffers because
this is probably more efficient in the underlying renderer.

In wlr_scene_surface we cache whether the attached buffer is a
single-pixel buffer.  This is done because the
wlr_single_pixel_buffer_v1 will be destroyed after texture upload, after
which it becomes much more annoying to check if the buffer is a
single-pixel buffer.
This commit is contained in:
David Turner 2025-04-07 13:28:51 +01:00
parent 5563d23b81
commit 792bee9657
3 changed files with 65 additions and 0 deletions

View File

@ -132,6 +132,12 @@ struct wlr_scene_surface {
struct wl_listener frame_done;
struct wl_listener surface_destroy;
struct wl_listener surface_commit;
// True if the underlying buffer is a wlr_single_pixel_buffer_v1
bool is_single_pixel_buffer;
// If is_single_pixel_buffer is set, contains the color of the buffer
// as {R, G, B, A} where the max value of each component is UINT32_MAX
uint32_t single_pixel_buffer_color[4];
} WLR_PRIVATE;
};

View File

@ -7,6 +7,7 @@
#include <wlr/types/wlr_linux_drm_syncobj_v1.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_presentation_time.h>
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
#include <wlr/util/transform.h>
#include "types/wlr_scene.h"
@ -164,6 +165,24 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) {
scene_buffer_unmark_client_buffer(scene_buffer);
if (surface->buffer) {
// If this is a buffer change, check if it's a single pixel buffer.
// Cache that so we can still apply rendering optimisations even when
// the original buffer has been freed after texture upload.
if (&surface->buffer->base != scene_buffer->buffer) {
scene_surface->is_single_pixel_buffer = false;
if (surface->buffer->source != NULL) {
struct wlr_single_pixel_buffer_v1 *single_pixel_buffer =
wlr_single_pixel_buffer_v1_try_from_buffer(surface->buffer->source);
if (single_pixel_buffer != NULL) {
scene_surface->is_single_pixel_buffer = true;
scene_surface->single_pixel_buffer_color[0] = single_pixel_buffer->r;
scene_surface->single_pixel_buffer_color[1] = single_pixel_buffer->g;
scene_surface->single_pixel_buffer_color[2] = single_pixel_buffer->b;
scene_surface->single_pixel_buffer_color[3] = single_pixel_buffer->a;
}
}
}
client_buffer_mark_next_can_damage(surface->buffer);
struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state =

View File

@ -1386,6 +1386,24 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren
case WLR_SCENE_NODE_BUFFER:;
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
struct wlr_scene_surface *scene_surface =
wlr_scene_surface_try_from_buffer(scene_buffer);
if (scene_surface != NULL && scene_surface->is_single_pixel_buffer) {
// Render the buffer as a rect, this is likely to be more efficient
wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){
.box = dst_box,
.color = {
.r = (float)scene_surface->single_pixel_buffer_color[0] / (float)UINT32_MAX,
.g = (float)scene_surface->single_pixel_buffer_color[1] / (float)UINT32_MAX,
.b = (float)scene_surface->single_pixel_buffer_color[2] / (float)UINT32_MAX,
.a = (float)scene_surface->single_pixel_buffer_color[3] /
(float)UINT32_MAX * scene_buffer->opacity,
},
.clip = &render_region,
});
break;
}
struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer,
data->output->output->renderer);
if (texture == NULL) {
@ -1736,6 +1754,18 @@ struct render_list_constructor_data {
bool fractional_scale;
};
static bool scene_buffer_is_black_opaque(struct wlr_scene_buffer *scene_buffer) {
struct wlr_scene_surface *scene_surface =
wlr_scene_surface_try_from_buffer(scene_buffer);
return scene_surface != NULL &&
scene_surface->is_single_pixel_buffer &&
scene_surface->single_pixel_buffer_color[0] == 0 &&
scene_surface->single_pixel_buffer_color[1] == 0 &&
scene_surface->single_pixel_buffer_color[2] == 0 &&
scene_surface->single_pixel_buffer_color[3] == UINT32_MAX &&
scene_buffer->opacity == 1.0;
}
static bool construct_render_list_iterator(struct wlr_scene_node *node,
int lx, int ly, void *_data) {
struct render_list_constructor_data *data = _data;
@ -1758,6 +1788,16 @@ static bool construct_render_list_iterator(struct wlr_scene_node *node,
}
}
// Apply the same special-case to black opaque single-pixel buffers
if (node->type == WLR_SCENE_NODE_BUFFER && data->calculate_visibility &&
(!data->fractional_scale || data->render_list->size == 0)) {
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
if (scene_buffer_is_black_opaque(scene_buffer)) {
return false;
}
}
pixman_region32_t intersection;
pixman_region32_init(&intersection);
pixman_region32_intersect_rect(&intersection, &node->visible,