/*
 * Copyright © 2025 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 "pixel-formats.h"
#include "shared/weston-drm-fourcc.h"
#include "shared/xalloc.h"
#include "weston-test-client-helper.h"
#include "weston-test-assert.h"
#include "xdg-client-helper.h"

struct setup_args {
	struct fixture_metadata meta;
	enum weston_renderer_type renderer;
};

static const struct setup_args my_setup_args[] = {
	{
		.meta.name = "GL",
		.renderer = WESTON_RENDERER_GL,
	},
	{
		.meta.name = "Vulkan",
		.renderer = WESTON_RENDERER_VULKAN,
	},
};

static enum test_result_code
fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg)
{
	struct compositor_setup setup;

	compositor_setup_defaults(&setup);
	setup.backend = WESTON_BACKEND_DRM;
	setup.renderer = arg->renderer;
	setup.logging_scopes = "log,drm-backend";
	setup.width = 1024;
	setup.height = 768;

	return weston_test_harness_execute_as_client(harness, &setup);
}
DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta);

static void
buffer_release(void *data, struct wl_buffer *wl_buffer)
{
	struct client_buffer *buffer = data;

	client_buffer_util_destroy_buffer(buffer);
}

static const struct wl_buffer_listener buffer_listener = {
	buffer_release
};

enum feedback_result {
	FB_PENDING = 0,
	FB_PRESENTED,
	FB_PRESENTED_ZERO_COPY,
	FB_DISCARDED
};

static void
presentation_feedback_handle_sync_output(void *data,
					 struct wp_presentation_feedback *feedback,
					 struct wl_output *output)
{
}

static void
presentation_feedback_handle_presented(void *data,
				       struct wp_presentation_feedback *feedback,
				       uint32_t tv_sec_hi,
				       uint32_t tv_sec_lo,
				       uint32_t tv_nsec,
				       uint32_t refresh,
				       uint32_t seq_hi,
				       uint32_t seq_lo,
				       uint32_t flags)
{
	enum feedback_result *result = data;
	bool zero_copy = flags & WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY;

	if (zero_copy)
		*result = FB_PRESENTED_ZERO_COPY;
	else
		*result = FB_PRESENTED;

	wp_presentation_feedback_destroy(feedback);
}

static void
presentation_feedback_handle_discarded(void *data,
				       struct wp_presentation_feedback *feedback)
{
	enum feedback_result *result = data;

	*result = FB_DISCARDED;
	wp_presentation_feedback_destroy(feedback);
}

static const struct wp_presentation_feedback_listener presentation_feedback_listener = {
	.sync_output = presentation_feedback_handle_sync_output,
	.presented = presentation_feedback_handle_presented,
	.discarded = presentation_feedback_handle_discarded,
};

static void
presentation_wait_nofail(struct client *client, enum feedback_result *result)
{
	while (*result == FB_PENDING) {
		if (wl_display_dispatch(client->wl_display) < 0)
			test_assert_not_reached("Connection error");
	}
}

static void
overlay_buffer_release(void *data, struct wl_buffer *buffer)
{
	wl_buffer_destroy(buffer);
}

static const struct wl_buffer_listener overlay_buffer_listener = {
	overlay_buffer_release
};

/*
 * All following tests assume the vkms default configuration of a single
 * 1024x768 pixel output with a primary plane and one cursor plane (limited to
 * 512x512 pixels).
 */

/*
 * Test that a fullscreen client with fullscreen-sized buffer is presented via
 * direct-scanout.
 */
static enum test_result_code
drm_offload_fullscreen(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct client_buffer *buffer;
	struct wl_surface *surface;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	buffer = client_buffer_util_create_dmabuf_buffer(client->wl_display,
							 client->dmabuf,
							 fmt_info,
							 xdg_surface->configure.width,
							 xdg_surface->configure.height);
	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
	wl_surface_damage(surface, 0, 0, INT32_MAX, INT32_MAX);
	xdg_surface_maybe_ack_configure(xdg_surface);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED_ZERO_COPY);

	client_buffer_util_destroy_buffer(buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a fullscreen client with fullscreen-sized buffer and a fully
 * transparent overlay surface is presented via direct-scanout.
 */
static enum test_result_code
drm_offload_fullscreen_transparent_overlay(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct client_buffer *buffer;
	struct wl_surface *surface;
	struct wl_surface *overlay_surface;
	struct wl_subsurface *overlay_subsurface;
	struct wl_buffer *overlay_buffer;
	struct wp_viewport *overlay_viewport;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	buffer = client_buffer_util_create_dmabuf_buffer(client->wl_display,
							 client->dmabuf,
							 fmt_info,
							 xdg_surface->configure.width,
							 xdg_surface->configure.height);
	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
	wl_surface_damage(surface, 0, 0, INT32_MAX, INT32_MAX);
	xdg_surface_maybe_ack_configure(xdg_surface);

	overlay_surface = wl_compositor_create_surface(client->wl_compositor);
	overlay_subsurface =
		wl_subcompositor_get_subsurface(client->wl_subcompositor,
						overlay_surface,
						surface);
	overlay_viewport = wp_viewporter_get_viewport(client->viewporter,
						      overlay_surface);
	wp_viewport_set_destination(overlay_viewport, 100, 100);
	overlay_buffer =
		wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(client->single_pixel_manager,
									 0x0, 0x0, 0x0, 0x0);
	wl_surface_attach(overlay_surface, overlay_buffer, 0, 0);
	wl_buffer_add_listener(overlay_buffer, &overlay_buffer_listener, NULL);
	wl_surface_damage_buffer(overlay_surface, 0, 0, 1, 1);
	wl_surface_commit(overlay_surface);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED_ZERO_COPY);

	wp_viewport_destroy(overlay_viewport);
	wl_subsurface_destroy(overlay_subsurface);
	wl_surface_destroy(overlay_surface);

	client_buffer_util_destroy_buffer(buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a fullscreen client with smaller-than-fullscreen-sized buffer is
 * presented via direct-scanout.
 */
static enum test_result_code
drm_offload_fullscreen_black_background(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct client_buffer *buffer;
	struct wl_surface *surface;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	buffer = client_buffer_util_create_dmabuf_buffer(client->wl_display,
							 client->dmabuf,
							 fmt_info,
							 xdg_surface->configure.width - 100,
							 xdg_surface->configure.height - 100);
	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
	xdg_surface_maybe_ack_configure(xdg_surface);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED_ZERO_COPY);

	client_buffer_util_destroy_buffer(buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a fullscreen client with opaque-black single-pixel-buffer with a
 * smaller-than-fullscreen-sized dmabuf subsurface above is presented via
 * direct-scanout.
 */
static enum test_result_code
drm_offload_fullscreen_semi_transparent_black_background(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct wl_buffer *buffer;
	struct wl_surface *surface;
	struct wl_surface *overlay_surface;
	struct wl_subsurface *overlay_subsurface;
	struct client_buffer *overlay_buffer;
	struct wp_viewport *viewport;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_ARGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	viewport = wp_viewporter_get_viewport(client->viewporter, surface);
	wp_viewport_set_destination(viewport,
				    xdg_surface->configure.width,
				    xdg_surface->configure.height);
	buffer =
		wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(client->single_pixel_manager,
									 0x0, 0x0, 0x0, 0xffffffff);
	wl_surface_attach(surface, buffer, 0, 0);
	wl_buffer_add_listener(buffer, &overlay_buffer_listener, NULL);
	wl_surface_damage_buffer(surface, 0, 0, 1, 1);
	xdg_surface_maybe_ack_configure(xdg_surface);

	overlay_surface = wl_compositor_create_surface(client->wl_compositor);
	overlay_subsurface =
		wl_subcompositor_get_subsurface(client->wl_subcompositor,
						overlay_surface,
						surface);
	overlay_buffer =
		client_buffer_util_create_dmabuf_buffer(client->wl_display,
							client->dmabuf,
							fmt_info,
							100,
							100);
	wl_buffer_add_listener(overlay_buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(overlay_surface, overlay_buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(overlay_surface, 0, 0, INT32_MAX, INT32_MAX);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 overlay_surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(overlay_surface);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED_ZERO_COPY);

	wp_viewport_destroy(viewport);
	wl_subsurface_destroy(overlay_subsurface);
	wl_surface_destroy(overlay_surface);

	client_buffer_util_destroy_buffer(overlay_buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a fullscreen client with opaque-white single-pixel-buffer with a
 * smaller-than-fullscreen-sized dmabuf subsurface above is presented via
 * direct-scanout.
 */
static enum test_result_code
drm_offload_fullscreen_semi_transparent_white_background(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct wl_buffer *buffer;
	struct wl_surface *surface;
	struct wl_surface *overlay_surface;
	struct wl_subsurface *overlay_subsurface;
	struct client_buffer *overlay_buffer;
	struct wp_viewport *viewport;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_ARGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	viewport = wp_viewporter_get_viewport(client->viewporter, surface);
	wp_viewport_set_destination(viewport,
				    xdg_surface->configure.width,
				    xdg_surface->configure.height);
	buffer =
		wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(client->single_pixel_manager,
									 0xffffffff, 0xffffffff,
									 0xffffffff, 0xffffffff);
	wl_surface_attach(surface, buffer, 0, 0);
	wl_buffer_add_listener(buffer, &overlay_buffer_listener, NULL);
	wl_surface_damage_buffer(surface, 0, 0, 1, 1);
	xdg_surface_maybe_ack_configure(xdg_surface);

	overlay_surface = wl_compositor_create_surface(client->wl_compositor);
	overlay_subsurface =
		wl_subcompositor_get_subsurface(client->wl_subcompositor,
						overlay_surface,
						surface);
	overlay_buffer =
		client_buffer_util_create_dmabuf_buffer(client->wl_display,
							client->dmabuf,
							fmt_info,
							100,
							100);
	wl_buffer_add_listener(overlay_buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(overlay_surface, overlay_buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(overlay_surface, 0, 0, INT32_MAX, INT32_MAX);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 overlay_surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(overlay_surface);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);

	wp_viewport_destroy(viewport);
	wl_subsurface_destroy(overlay_subsurface);
	wl_surface_destroy(overlay_surface);

	client_buffer_util_destroy_buffer(overlay_buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	switch (result) {
	case FB_PRESENTED_ZERO_COPY:
		return RESULT_OK;
	case FB_PRESENTED:
		return RESULT_SKIP;
	default:
		return RESULT_FAIL;
	}
}

/*
 * Test that a fullscreen client with smaller-than-fullscreen-sized opaque-black
 * single-pixel-buffer with an even smaller dmabuf subsurface above is presented
 * via direct-scanout.
 */
static enum test_result_code
drm_offload_fullscreen_black_background_black_subsurface_underlay(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct wl_buffer *buffer;
	struct wl_surface *surface;
	struct wl_surface *overlay_surface;
	struct wl_subsurface *overlay_subsurface;
	struct client_buffer *overlay_buffer;
	struct wp_viewport *viewport;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	viewport = wp_viewporter_get_viewport(client->viewporter, surface);
	wp_viewport_set_destination(viewport,
				    xdg_surface->configure.width - 100,
				    xdg_surface->configure.height - 100);
	buffer =
		wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(client->single_pixel_manager,
									 0x0, 0x0, 0x0, 0xffffffff);
	wl_surface_attach(surface, buffer, 0, 0);
	wl_buffer_add_listener(buffer, &overlay_buffer_listener, NULL);
	wl_surface_damage_buffer(surface, 0, 0, 1, 1);
	xdg_surface_maybe_ack_configure(xdg_surface);

	overlay_surface = wl_compositor_create_surface(client->wl_compositor);
	overlay_subsurface =
		wl_subcompositor_get_subsurface(client->wl_subcompositor,
						overlay_surface,
						surface);
	overlay_buffer =
		client_buffer_util_create_dmabuf_buffer(client->wl_display,
							client->dmabuf,
							fmt_info,
							100,
							100);
	wl_buffer_add_listener(overlay_buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(overlay_surface, overlay_buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(overlay_surface, 0, 0, INT32_MAX, INT32_MAX);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 overlay_surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(overlay_surface);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED_ZERO_COPY);

	//TODO: check the spb surface for FB_PRESENTED_ZERO_COPY, too (does not yet work).

	wp_viewport_destroy(viewport);
	wl_subsurface_destroy(overlay_subsurface);
	wl_surface_destroy(overlay_surface);

	client_buffer_util_destroy_buffer(overlay_buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a fullscreen client with smaller-than-fullscreen-sized buffer
 * with an overlapping opaque-black single-pixel-buffer subsurface above is
 * *not* presented via direct-scanout. This test is meant to ensure that future
 * optimizations for the above tests don't overreach.
 */
static enum test_result_code
drm_offload_fullscreen_black_background_black_subsurface_overlay(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct client_buffer *buffer;
	struct wl_surface *surface;
	struct wl_surface *overlay_surface;
	struct wl_subsurface *overlay_subsurface;
	struct wl_buffer *overlay_buffer;
	struct wp_viewport *overlay_viewport;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	buffer = client_buffer_util_create_dmabuf_buffer(client->wl_display,
							 client->dmabuf,
							 fmt_info,
							 xdg_surface->configure.width - 100,
							 xdg_surface->configure.height - 100);
	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
	xdg_surface_maybe_ack_configure(xdg_surface);

	overlay_surface = wl_compositor_create_surface(client->wl_compositor);
	overlay_subsurface =
		wl_subcompositor_get_subsurface(client->wl_subcompositor,
						overlay_surface,
						surface);
	overlay_viewport = wp_viewporter_get_viewport(client->viewporter,
						      overlay_surface);
	wp_viewport_set_destination(overlay_viewport, 100, 100);
	overlay_buffer =
		wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(client->single_pixel_manager,
									 0x0, 0x0, 0x0, 0xffffffff);
	wl_surface_attach(overlay_surface, overlay_buffer, 0, 0);
	wl_buffer_add_listener(overlay_buffer, &overlay_buffer_listener, NULL);
	wl_surface_damage_buffer(overlay_surface, 0, 0, 1, 1);
	wl_surface_commit(overlay_surface);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED);

	wp_viewport_destroy(overlay_viewport);
	wl_subsurface_destroy(overlay_subsurface);
	wl_surface_destroy(overlay_surface);

	client_buffer_util_destroy_buffer(buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a fullscreen client with smaller-than-fullscreen-sized opaque-red
 * single-pixel-buffer with an even smaller dmabuf subsurface above is *not*
 * presented via direct-scanout.
 *
 * This *might* be possible to get optimized in the future once the backend can
 * support multiple solid color planes, see
 * https://lore.kernel.org/dri-devel/20231027-solid-fill-v7-0-780188bfa7b2@quicinc.com/
 */
static enum test_result_code
drm_offload_fullscreen_black_background_red_subsurface_underlay(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct wl_buffer *buffer;
	struct wl_surface *surface;
	struct wl_surface *overlay_surface;
	struct wl_subsurface *overlay_subsurface;
	struct client_buffer *overlay_buffer;
	struct wp_viewport *viewport;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_toplevel_set_fullscreen(xdg_surface->xdg_toplevel, NULL);
	xdg_surface_wait_configure(xdg_surface);

	test_assert_true(xdg_surface->configure.fullscreen);
	test_assert_int_gt(xdg_surface->configure.width, 0);
	test_assert_int_gt(xdg_surface->configure.height, 0);

	viewport = wp_viewporter_get_viewport(client->viewporter, surface);
	wp_viewport_set_destination(viewport,
				    xdg_surface->configure.width - 100,
				    xdg_surface->configure.height - 100);
	buffer =
		wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer(client->single_pixel_manager,
									 0xffffffff, 0x0, 0x0, 0xffffffff);
	wl_surface_attach(surface, buffer, 0, 0);
	wl_buffer_add_listener(buffer, &overlay_buffer_listener, NULL);
	wl_surface_damage_buffer(surface, 0, 0, 1, 1);
	xdg_surface_maybe_ack_configure(xdg_surface);

	overlay_surface = wl_compositor_create_surface(client->wl_compositor);
	overlay_subsurface =
		wl_subcompositor_get_subsurface(client->wl_subcompositor,
						overlay_surface,
						surface);
	overlay_buffer =
		client_buffer_util_create_dmabuf_buffer(client->wl_display,
							client->dmabuf,
							fmt_info,
							100,
							100);
	wl_buffer_add_listener(overlay_buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(overlay_surface, overlay_buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(overlay_surface, 0, 0, INT32_MAX, INT32_MAX);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 overlay_surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(overlay_surface);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED);

	wp_viewport_destroy(viewport);
	wl_subsurface_destroy(overlay_subsurface);
	wl_surface_destroy(overlay_surface);

	client_buffer_util_destroy_buffer(overlay_buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

/*
 * Test that a windowed / not-fullscreen client on top of a solid background is
 * presented via direct-scanout.
 */
static enum test_result_code
drm_offload_windowed(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct client_buffer *buffer;
	struct wl_surface *surface;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_surface_wait_configure(xdg_surface);

	test_assert_false(xdg_surface->configure.fullscreen);
	test_assert_int_eq(xdg_surface->configure.width, 0);
	test_assert_int_eq(xdg_surface->configure.height, 0);

	buffer = client_buffer_util_create_dmabuf_buffer(client->wl_display,
							 client->dmabuf,
							 fmt_info,
							 100,
							 100);
	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
	xdg_surface_maybe_ack_configure(xdg_surface);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);

	client_buffer_util_destroy_buffer(buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	switch (result) {
	case FB_PRESENTED_ZERO_COPY:
		return RESULT_OK;
	case FB_PRESENTED:
		return RESULT_SKIP;
	default:
		return RESULT_FAIL;
	}
}

/*
 * Test that a windowed / not-fullscreen client with a wl_shm buffer is *not*
 * presented via direct-scanout. This is mainly a sanity check for the tests
 * above.
 */
static enum test_result_code
drm_offload_windowed_shm(struct wet_testsuite_data *suite_data)
{
	struct xdg_client *xdg_client;
	struct xdg_surface_data *xdg_surface;
	struct client *client;
	struct client_buffer *buffer;
	struct wl_surface *surface;
	const struct pixel_format_info *fmt_info;
	struct wp_presentation_feedback *presentation_feedback;
	enum feedback_result result;

	fmt_info = pixel_format_get_info(DRM_FORMAT_XRGB8888);

	xdg_client = create_xdg_client();
	client = xdg_client->client;
	xdg_surface = create_xdg_surface(xdg_client);
	surface = xdg_surface->surface->wl_surface;

	xdg_surface_make_toplevel(xdg_surface, "weston.test.drm-offload", "one");
	xdg_surface_wait_configure(xdg_surface);

	test_assert_false(xdg_surface->configure.fullscreen);
	test_assert_int_eq(xdg_surface->configure.width, 0);
	test_assert_int_eq(xdg_surface->configure.height, 0);

	buffer = client_buffer_util_create_shm_buffer(client->wl_shm,
						      fmt_info,
						      100,
						      100);
	wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
	wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
	wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
	xdg_surface_maybe_ack_configure(xdg_surface);

	result = FB_PENDING;
	presentation_feedback = wp_presentation_feedback(client->presentation,
							 surface);
	wp_presentation_feedback_add_listener(presentation_feedback,
					      &presentation_feedback_listener,
					      &result);
	wl_surface_commit(surface);
	presentation_wait_nofail(client, &result);
	test_assert_enum_eq(result, FB_PRESENTED);

	client_buffer_util_destroy_buffer(buffer);
	destroy_xdg_surface(xdg_surface);
	xdg_client_destroy(xdg_client);

	return RESULT_OK;
}

DECLARE_TEST_LIST(
	TESTFN(drm_offload_fullscreen),
	TESTFN(drm_offload_fullscreen_transparent_overlay),
	TESTFN(drm_offload_fullscreen_black_background),
	TESTFN(drm_offload_fullscreen_semi_transparent_black_background),
	TESTFN(drm_offload_fullscreen_semi_transparent_white_background),
	TESTFN(drm_offload_fullscreen_black_background_black_subsurface_underlay),
	TESTFN(drm_offload_fullscreen_black_background_black_subsurface_overlay),
	TESTFN(drm_offload_fullscreen_black_background_red_subsurface_underlay),
	TESTFN(drm_offload_windowed),
	TESTFN(drm_offload_windowed_shm),
);
