Skip to content
Commits on Source (5)
......@@ -2970,6 +2970,8 @@ load_headless_backend(struct weston_compositor *c,
false);
weston_config_section_get_bool(section, "use-gl", &config.use_gl,
false);
weston_config_section_get_bool(section, "output-decorations", &config.decorate,
false);
const struct weston_option options[] = {
{ WESTON_OPTION_INTEGER, "width", 0, &parsed_options->width },
......
......@@ -44,6 +44,9 @@ struct weston_headless_backend_config {
/** Whether to use the GL renderer, conflicts with use_pixman */
bool use_gl;
/** Use output decorations, requires use_gl = true */
bool decorate;
};
#ifdef __cplusplus
......
/*
* Copyright © 2010-2011 Benjamin Franzke
* Copyright © 2012 Intel Corporation
* Copyright © 2013 Jason Ekstrand
* Copyright 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
......@@ -42,6 +44,7 @@
#include "renderer-gl/gl-renderer.h"
#include "shared/weston-drm-fourcc.h"
#include "shared/weston-egl-ext.h"
#include "shared/cairo-util.h"
#include "linux-dmabuf.h"
#include "presentation-time-server-protocol.h"
#include <libweston/windowed-output-api.h>
......@@ -60,6 +63,8 @@ struct headless_backend {
enum headless_renderer_type renderer_type;
struct gl_renderer_interface *glri;
bool decorate;
struct theme *theme;
};
struct headless_head {
......@@ -72,6 +77,16 @@ struct headless_output {
struct weston_mode mode;
struct wl_event_source *finish_frame_timer;
pixman_image_t *image;
struct frame *frame;
struct {
struct {
cairo_surface_t *top;
cairo_surface_t *left;
cairo_surface_t *right;
cairo_surface_t *bottom;
} border;
} gl;
};
static const uint32_t headless_formats[] = {
......@@ -130,6 +145,78 @@ finish_frame_handler(void *data)
return 1;
}
static void
headless_output_update_gl_border(struct headless_output *output)
{
struct headless_backend *backend = to_headless_backend(output->base.compositor);
struct gl_renderer_interface *glri = backend->glri;
int32_t ix, iy, iwidth, iheight, fwidth, fheight;
cairo_t *cr;
if (!output->frame)
return;
if (!(frame_status(output->frame) & FRAME_STATUS_REPAINT))
return;
fwidth = frame_width(output->frame);
fheight = frame_height(output->frame);
frame_interior(output->frame, &ix, &iy, &iwidth, &iheight);
if (!output->gl.border.top)
output->gl.border.top =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
fwidth, iy);
cr = cairo_create(output->gl.border.top);
frame_repaint(output->frame, cr);
cairo_destroy(cr);
glri->output_set_border(&output->base, GL_RENDERER_BORDER_TOP,
fwidth, iy,
cairo_image_surface_get_stride(output->gl.border.top) / 4,
cairo_image_surface_get_data(output->gl.border.top));
if (!output->gl.border.left)
output->gl.border.left =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
ix, 1);
cr = cairo_create(output->gl.border.left);
cairo_translate(cr, 0, -iy);
frame_repaint(output->frame, cr);
cairo_destroy(cr);
glri->output_set_border(&output->base, GL_RENDERER_BORDER_LEFT,
ix, 1,
cairo_image_surface_get_stride(output->gl.border.left) / 4,
cairo_image_surface_get_data(output->gl.border.left));
if (!output->gl.border.right)
output->gl.border.right =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
fwidth - (ix + iwidth), 1);
cr = cairo_create(output->gl.border.right);
cairo_translate(cr, -(iwidth + ix), -iy);
frame_repaint(output->frame, cr);
cairo_destroy(cr);
glri->output_set_border(&output->base, GL_RENDERER_BORDER_RIGHT,
fwidth - (ix + iwidth), 1,
cairo_image_surface_get_stride(output->gl.border.right) / 4,
cairo_image_surface_get_data(output->gl.border.right));
if (!output->gl.border.bottom)
output->gl.border.bottom =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
fwidth, fheight - (iy + iheight));
cr = cairo_create(output->gl.border.bottom);
cairo_translate(cr, 0, -(iy + iheight));
frame_repaint(output->frame, cr);
cairo_destroy(cr);
glri->output_set_border(&output->base, GL_RENDERER_BORDER_BOTTOM,
fwidth, fheight - (iy + iheight),
cairo_image_surface_get_stride(output->gl.border.bottom) / 4,
cairo_image_surface_get_data(output->gl.border.bottom));
}
static int
headless_output_repaint(struct weston_output *output_base,
pixman_region32_t *damage)
......@@ -141,6 +228,8 @@ headless_output_repaint(struct weston_output *output_base,
ec = output->base.compositor;
headless_output_update_gl_border(output);
ec->renderer->repaint_output(&output->base, damage);
pixman_region32_subtract(&ec->primary_plane.damage,
......@@ -158,6 +247,20 @@ headless_output_disable_gl(struct headless_output *output)
struct headless_backend *b = to_headless_backend(compositor);
b->glri->output_destroy(&output->base);
if (output->frame) {
frame_destroy(output->frame);
output->frame = NULL;
}
cairo_surface_destroy(output->gl.border.top);
cairo_surface_destroy(output->gl.border.left);
cairo_surface_destroy(output->gl.border.right);
cairo_surface_destroy(output->gl.border.bottom);
output->gl.border.top = NULL;
output->gl.border.left = NULL;
output->gl.border.right = NULL;
output->gl.border.bottom = NULL;
}
static void
......@@ -206,6 +309,7 @@ headless_output_destroy(struct weston_output *base)
headless_output_disable(&output->base);
weston_output_release(&output->base);
assert(!output->frame);
free(output);
}
......@@ -215,19 +319,43 @@ headless_output_enable_gl(struct headless_output *output)
struct weston_compositor *compositor = output->base.compositor;
struct headless_backend *b = to_headless_backend(compositor);
const struct weston_mode *mode = output->base.current_mode;
const struct gl_renderer_pbuffer_options options = {
struct gl_renderer_pbuffer_options options = {
.drm_formats = headless_formats,
.drm_formats_count = ARRAY_LENGTH(headless_formats),
.area.x = 0,
.area.y = 0,
.area.width = mode->width,
.area.height = mode->height,
.fb_size.width = mode->width,
.fb_size.height = mode->height,
};
if (b->decorate) {
/*
* Start with a dummy exterior size and then resize, because
* there is no frame_create() with interior size.
*/
output->frame = frame_create(b->theme, 100, 100,
FRAME_BUTTON_CLOSE, NULL, NULL);
if (!output->frame) {
weston_log("failed to create frame for output\n");
return -1;
}
frame_resize_inside(output->frame, mode->width, mode->height);
options.fb_size.width = frame_width(output->frame);
options.fb_size.height = frame_height(output->frame);
frame_interior(output->frame, &options.area.x, &options.area.y,
&options.area.width, &options.area.height);
} else {
options.area.x = 0;
options.area.y = 0;
options.area.width = mode->width;
options.area.height = mode->height;
options.fb_size.width = mode->width;
options.fb_size.height = mode->height;
}
if (b->glri->output_pbuffer_create(&output->base, &options) < 0) {
weston_log("failed to create gl renderer output state\n");
if (output->frame) {
frame_destroy(output->frame);
output->frame = NULL;
}
return -1;
}
......@@ -435,6 +563,9 @@ headless_destroy(struct weston_compositor *ec)
headless_head_destroy(base);
}
if (b->theme)
theme_destroy(b->theme);
free(b);
}
......@@ -488,6 +619,19 @@ headless_backend_create(struct weston_compositor *compositor,
goto err_free;
}
if (config->decorate && !config->use_gl) {
weston_log("Error: headless-backend decorations require GL renderer.\n");
goto err_free;
}
b->decorate = config->decorate;
if (b->decorate) {
b->theme = theme_create();
if (!b->theme) {
weston_log("Error: could not load decorations theme.\n");
goto err_free;
}
}
if (config->use_gl)
b->renderer_type = HEADLESS_GL;
else if (config->use_pixman)
......@@ -536,6 +680,9 @@ headless_backend_create(struct weston_compositor *compositor,
return b;
err_input:
if (b->theme)
theme_destroy(b->theme);
weston_compositor_shutdown(compositor);
err_free:
free(b);
......
......@@ -12,7 +12,11 @@ plugin_headless = shared_library(
'headless-backend',
srcs_headless,
include_directories: common_inc,
dependencies: [ dep_libweston_private, dep_libdrm_headers ],
dependencies: [
dep_libweston_private,
dep_libdrm_headers,
dep_lib_cairo_shared,
],
name_prefix: '',
install: true,
install_dir: dir_module_libweston,
......
......@@ -204,6 +204,14 @@ spaces and perform monitor profiling, and tone mapping required to enable HDR
video modes. This extended functionality comes at the cost of heavier image
processing and sometimes a loss of some hardware off-loading features like
composite-bypass.
.TP 7
.BI "output-decorations=" true
For headless-backend with GL-renderer only: draws output window decorations,
similar to what wayland-backend does for floating output windows.
Boolean, defaults to
.BR false .
These decorations cannot normally be screenshot. This option is useful for
the Weston test suite only.
.\"---------------------------------------------------------------------
.SH "LIBINPUT SECTION"
The
......
......@@ -348,7 +348,7 @@ TEST(alpha_blend)
shot = capture_screenshot_of_output(client);
assert(shot);
match = verify_image(shot, "alpha_blend", seq_no, NULL, seq_no);
match = verify_image(shot->image, "alpha_blend", seq_no, NULL, seq_no);
assert(check_blend_pattern(bg, fg, shot, space));
assert(match);
......
......@@ -450,6 +450,7 @@ fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg)
weston_ini_setup(&setup,
cfgln("[core]"),
cfgln("output-decorations=true"),
cfgln("color-management=true"),
cfgln("[output]"),
cfgln("name=headless"),
......@@ -587,6 +588,9 @@ process_pipeline_comparison(const struct buffer *src_buf,
* The groundtruth conversion comes from the struct lcms_pipeline definitions.
* The first error source is converting those to ICC files. The second error
* source is Weston.
*
* This tests particularly the chain of input-to-blend followed by
* blend-to-output categories of color transformations.
*/
TEST(opaque_pixel_conversion)
{
......@@ -617,7 +621,7 @@ TEST(opaque_pixel_conversion)
shot = capture_screenshot_of_output(client);
assert(shot);
match = verify_image(shot, "shaper_matrix", arg->ref_image_index,
match = verify_image(shot->image, "shaper_matrix", arg->ref_image_index,
NULL, seq_no);
assert(process_pipeline_comparison(buf, shot, arg));
assert(match);
......@@ -832,7 +836,7 @@ TEST(output_icc_alpha_blend)
shot = capture_screenshot_of_output(client);
assert(shot);
match = verify_image(shot, "output_icc_alpha_blend", arg->ref_image_index,
match = verify_image(shot->image, "output_icc_alpha_blend", arg->ref_image_index,
NULL, seq_no);
assert(check_blend_pattern(bg, fg, shot, arg));
assert(match);
......@@ -845,3 +849,39 @@ TEST(output_icc_alpha_blend)
wl_subcompositor_destroy(subco);
client_destroy(client); /* destroys bg */
}
/*
* Test that output decorations have the expected colors.
*
* This is the only way to test input-to-output category of color
* transformations. They are used only for output decorations and some other
* debug-like features. The input color space is hardcoded to sRGB in the
* compositor.
*
* Because the output decorations are drawn with Cairo, we do not have an
* easy access to the ground-truth image and so do not check the results
* against a reference formula.
*/
TEST(output_icc_decorations)
{
int seq_no = get_test_fixture_index();
const struct setup_args *arg = &my_setup_args[seq_no];
struct client *client;
struct buffer *shot;
pixman_image_t *img;
bool match;
client = create_client();
shot = client_capture_output(client, client->output,
WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER);
img = image_convert_to_a8r8g8b8(shot->image);
match = verify_image(img, "output-icc-decorations",
arg->ref_image_index, NULL, seq_no);
assert(match);
pixman_image_unref(img);
buffer_destroy(shot);
client_destroy(client);
}
......@@ -193,6 +193,7 @@ tests = [
'dep_objs': [ dep_libdrm_headers ],
},
{ 'name': 'output-damage', },
{ 'name': 'output-decorations', },
{ 'name': 'output-transforms', },
{ 'name': 'plugin-registry', },
{
......
/*
* Copyright 2022 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include "weston-test-client-helper.h"
#include "weston-test-fixture-compositor.h"
static enum test_result_code
fixture_setup(struct weston_test_harness *harness)
{
struct compositor_setup setup;
compositor_setup_defaults(&setup);
setup.renderer = RENDERER_GL;
setup.width = 300;
setup.height = 150;
setup.shell = SHELL_TEST_DESKTOP;
weston_ini_setup(&setup,
cfgln("[core]"),
cfgln("output-decorations=true"));
return weston_test_harness_execute_as_client(harness, &setup);
}
DECLARE_FIXTURE_SETUP(fixture_setup);
/*
* Basic screenshot test for output decorations
*
* Tests that the cairo-util code for drawing window decorations works at all
* through headless-backend. The window decorations are normally used as output
* decorations by wayland-backend when the outputs are windows in a parent
* compositor.
*
* This works only with GL-renderer. Pixman-renderer has no code for blitting
* output decorations and does not even know they exist.
*
* Headless-backend sets window title string to NULL because it might be
* difficult to ensure text rendering is pixel-precise between different
* systems.
*/
TEST(output_decorations)
{
struct client *client;
struct buffer *shot;
pixman_image_t *img;
bool match;
client = create_client();
shot = client_capture_output(client, client->output,
WESTON_CAPTURE_V1_SOURCE_FULL_FRAMEBUFFER);
img = image_convert_to_a8r8g8b8(shot->image);
match = verify_image(img, "output-decorations", 0, NULL, 0);
assert(match);
pixman_image_unref(img);
buffer_destroy(shot);
client_destroy(client);
}
......@@ -1537,7 +1537,7 @@ write_image_as_png(pixman_image_t *image, const char *fname)
return true;
}
static pixman_image_t *
pixman_image_t *
image_convert_to_a8r8g8b8(pixman_image_t *image)
{
pixman_image_t *ret;
......@@ -1685,7 +1685,7 @@ static const struct weston_capture_source_v1_listener output_capturer_source_han
.failed = output_capturer_handle_failed,
};
static struct buffer *
struct buffer *
client_capture_output(struct client *client,
struct output *output,
enum weston_capture_v1_source src)
......@@ -1758,7 +1758,7 @@ capture_screenshot_of_output(struct client *client)
static void
write_visual_diff(pixman_image_t *ref_image,
struct buffer *shot,
pixman_image_t *shot,
const struct rectangle *clip,
const char *test_name,
int seq_no,
......@@ -1773,7 +1773,7 @@ write_visual_diff(pixman_image_t *ref_image,
assert(ret >= 0);
fname = screenshot_output_filename(ext_test_name, seq_no);
diff = visualize_image_difference(ref_image, shot->image, clip, fuzz);
diff = visualize_image_difference(ref_image, shot, clip, fuzz);
write_image_as_png(diff, fname);
pixman_image_unref(diff);
......@@ -1812,7 +1812,7 @@ write_visual_diff(pixman_image_t *ref_image,
* \sa verify_screen_content
*/
bool
verify_image(struct buffer *shot,
verify_image(pixman_image_t *shot,
const char *ref_image,
int ref_seq_no,
const struct rectangle *clip,
......@@ -1833,7 +1833,7 @@ verify_image(struct buffer *shot,
}
if (ref) {
match = check_images_match(ref, shot->image, clip, &gl_fuzz);
match = check_images_match(ref, shot, clip, &gl_fuzz);
testlog("Verify reference image %s vs. shot %s: %s\n",
ref_fname, shot_fname, match ? "PASS" : "FAIL");
......@@ -1848,7 +1848,7 @@ verify_image(struct buffer *shot,
}
if (!match)
write_image_as_png(shot->image, shot_fname);
write_image_as_png(shot, shot_fname);
free(ref_fname);
free(shot_fname);
......@@ -1881,7 +1881,7 @@ verify_screen_content(struct client *client,
shot = capture_screenshot_of_output(client);
assert(shot);
match = verify_image(shot, ref_image, ref_seq_no, clip, seq_no);
match = verify_image(shot->image, ref_image, ref_seq_no, clip, seq_no);
buffer_destroy(shot);
return match;
......
......@@ -38,6 +38,7 @@
#include "weston-test-runner.h"
#include "weston-test-client-protocol.h"
#include "viewporter-client-protocol.h"
#include "weston-output-capture-client-protocol.h"
struct client {
struct wl_display *wl_display;
......@@ -275,8 +276,16 @@ load_image_from_png(const char *fname);
struct buffer *
capture_screenshot_of_output(struct client *client);
struct buffer *
client_capture_output(struct client *client,
struct output *output,
enum weston_capture_v1_source src);
pixman_image_t *
image_convert_to_a8r8g8b8(pixman_image_t *image);
bool
verify_image(struct buffer *shot,
verify_image(pixman_image_t *shot,
const char *ref_image,
int ref_seq_no,
const struct rectangle *clip,
......