Wayland: Implement game embedding

This patch introduces a new protocol proxy, which multiplxes Wayland
clients into a single connection, allowing us to redirect calls (e.g.
create toplevel -> create subsurface). Mixed with some state tracking
and emulation, we can embed a full-featured client into the editor.
This commit is contained in:
Dery Almas
2025-11-15 23:38:41 +01:00
parent ef34c3d534
commit bbf65ae72f
25 changed files with 6053 additions and 23 deletions
+28
View File
@@ -643,6 +643,12 @@ Copyright: 2008-2013, Kristian Høgsberg
2015, Red Hat Inc.
License: Expat
Files: thirdparty/wayland-protocols/mesa/wayland-drm.xml
Comment: Mesa Wayland protocols
Copyright: 2008-2011, Kristian Høgsberg
2010-2011, Intel Corporation
License: X11
Files: thirdparty/wslay/*
Comment: Wslay
Copyright: 2011, 2012, 2015, Tatsuhiro Tsujikawa
@@ -2305,6 +2311,28 @@ License: WOL
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
ANY KIND. See https://dspguru.com/wide-open-license/ for more information.
License: X11
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that\n the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
License: Zlib
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
+31 -6
View File
@@ -668,7 +668,7 @@ GameView::EmbedAvailability GameView::_get_embed_available() {
return EMBED_NOT_AVAILABLE_SINGLE_WINDOW_MODE;
}
String display_driver = GLOBAL_GET("display/display_server/driver");
if (display_driver == "headless" || display_driver == "wayland") {
if (display_driver == "headless") {
return EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER;
}
@@ -714,11 +714,7 @@ void GameView::_update_ui() {
}
break;
case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED:
if (DisplayServer::get_singleton()->get_name() == "Wayland") {
state_label->set_text(TTRC("Game embedding not available on Wayland.\nWayland can be disabled in the Editor Settings (Run > Platforms > Linux/*BSD > Prefer Wayland)."));
} else {
state_label->set_text(TTRC("Game embedding not available on your OS."));
}
state_label->set_text(TTRC("Game embedding not available on your OS."));
break;
case EMBED_NOT_AVAILABLE_PROJECT_DISPLAY_DRIVER:
state_label->set_text(vformat(TTR("Game embedding not available for the Display Server: '%s'.\nDisplay Server can be modified in the Project Settings (Display > Display Server > Driver)."), GLOBAL_GET("display/display_server/driver")));
@@ -991,6 +987,21 @@ void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_argumen
// macOS requires the embedded display driver.
remove_args.insert("--display-driver");
#endif
#ifdef WAYLAND_ENABLED
// Wayland requires its display driver.
if (DisplayServer::get_singleton()->get_name() == "Wayland") {
remove_args.insert("--display-driver");
}
#endif
#ifdef X11_ENABLED
// X11 requires its display driver.
if (DisplayServer::get_singleton()->get_name() == "X11") {
remove_args.insert("--display-driver");
}
#endif
while (E) {
List<String>::Element *N = E->next();
@@ -1020,6 +1031,20 @@ void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_argumen
N = r_arguments.insert_after(N, "--embedded");
#endif
#ifdef WAYLAND_ENABLED
if (DisplayServer::get_singleton()->get_name() == "Wayland") {
N = r_arguments.insert_after(N, "--display-driver");
N = r_arguments.insert_after(N, "wayland");
}
#endif
#ifdef X11_ENABLED
if (DisplayServer::get_singleton()->get_name() == "X11") {
N = r_arguments.insert_after(N, "--display-driver");
N = r_arguments.insert_after(N, "x11");
}
#endif
// Be sure to have the correct window size in the embedded_process control.
_update_embed_window_size();
Rect2i rect = embedded_process->get_screen_embedded_window_rect();
+16
View File
@@ -97,6 +97,21 @@ generated_sources = [
generate_from_xml(
"xdg_foreign_v2", "#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml"
),
generate_from_xml("linux_dmabuf_v1", "#thirdparty/wayland-protocols/stable/linux-dmabuf/linux-dmabuf-v1.xml"),
generate_from_xml(
"linux_explicit_synchronization_unstable_v1",
"#thirdparty/wayland-protocols/unstable/linux-explicit-synchronization/linux-explicit-synchronization-unstable-v1.xml",
),
generate_from_xml("fifo_v1", "#thirdparty/wayland-protocols/staging/fifo/fifo-v1.xml"),
generate_from_xml("commit_timing_v1", "#thirdparty/wayland-protocols/staging/commit-timing/commit-timing-v1.xml"),
generate_from_xml(
"linux_drm_syncobj_v1", "#thirdparty/wayland-protocols/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml"
),
generate_from_xml(
"tearing_control_v1", "#thirdparty/wayland-protocols/staging/tearing-control/tearing-control-v1.xml"
),
generate_from_xml("wayland-drm", "#thirdparty/wayland-protocols/mesa/wayland-drm.xml"),
generate_from_xml("godot_embedding_compositor", "godot-embedding-compositor.xml"),
]
source_files = generated_sources + [
@@ -104,6 +119,7 @@ source_files = generated_sources + [
File("display_server_wayland.cpp"),
File("key_mapping_xkb.cpp"),
File("wayland_thread.cpp"),
File("wayland_embedder.cpp"),
]
if env["use_sowrap"]:
@@ -195,6 +195,7 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const {
case FEATURE_WINDOW_DRAG:
case FEATURE_CLIPBOARD_PRIMARY:
case FEATURE_SUBWINDOWS:
case FEATURE_WINDOW_EMBEDDING:
case FEATURE_SELF_FITTING_WINDOWS: {
return true;
} break;
@@ -1298,6 +1299,8 @@ void DisplayServerWayland::window_move_to_foreground(DisplayServer::WindowID p_w
}
bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const {
MutexLock mutex_lock(wayland_thread.mutex);
return wayland_thread.pointer_get_pointed_window_id() == p_window_id;
}
@@ -1505,6 +1508,94 @@ bool DisplayServerWayland::get_swap_cancel_ok() {
return swap_cancel_ok;
}
Error DisplayServerWayland::embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) {
MutexLock mutex_lock(wayland_thread.mutex);
struct godot_embedding_compositor *ec = wayland_thread.get_embedding_compositor();
ERR_FAIL_NULL_V_MSG(ec, ERR_BUG, "Missing embedded compositor interface");
struct WaylandThread::EmbeddingCompositorState *ecs = WaylandThread::godot_embedding_compositor_get_state(ec);
ERR_FAIL_NULL_V(ecs, ERR_BUG);
if (!ecs->mapped_clients.has(p_pid)) {
return ERR_DOES_NOT_EXIST;
}
struct godot_embedded_client *embedded_client = ecs->mapped_clients[p_pid];
WaylandThread::EmbeddedClientState *client_data = (WaylandThread::EmbeddedClientState *)godot_embedded_client_get_user_data(embedded_client);
ERR_FAIL_NULL_V(client_data, ERR_BUG);
if (p_grab_focus) {
godot_embedded_client_focus_window(embedded_client);
}
if (p_visible) {
WaylandThread::WindowState *ws = wayland_thread.window_get_state(p_window);
ERR_FAIL_NULL_V(ws, ERR_BUG);
struct xdg_toplevel *toplevel = ws->xdg_toplevel;
if (toplevel == nullptr && ws->libdecor_frame) {
toplevel = libdecor_frame_get_xdg_toplevel(ws->libdecor_frame);
}
ERR_FAIL_NULL_V(toplevel, ERR_CANT_CREATE);
godot_embedded_client_set_embedded_window_parent(embedded_client, toplevel);
double window_scale = WaylandThread::window_state_get_scale_factor(ws);
Rect2i scaled_rect = p_rect;
scaled_rect.position = WaylandThread::scale_vector2i(scaled_rect.position, 1 / window_scale);
scaled_rect.size = WaylandThread::scale_vector2i(scaled_rect.size, 1 / window_scale);
print_verbose(vformat("Scaling embedded rect down by %f from %s to %s.", window_scale, p_rect, scaled_rect));
godot_embedded_client_set_embedded_window_rect(embedded_client, scaled_rect.position.x, scaled_rect.position.y, scaled_rect.size.width, scaled_rect.size.height);
} else {
godot_embedded_client_set_embedded_window_parent(embedded_client, nullptr);
}
return OK;
}
Error DisplayServerWayland::request_close_embedded_process(OS::ProcessID p_pid) {
MutexLock mutex_lock(wayland_thread.mutex);
struct godot_embedding_compositor *ec = wayland_thread.get_embedding_compositor();
ERR_FAIL_NULL_V_MSG(ec, ERR_BUG, "Missing embedded compositor interface");
struct WaylandThread::EmbeddingCompositorState *ecs = WaylandThread::godot_embedding_compositor_get_state(ec);
ERR_FAIL_NULL_V(ecs, ERR_BUG);
if (!ecs->mapped_clients.has(p_pid)) {
return ERR_DOES_NOT_EXIST;
}
struct godot_embedded_client *embedded_client = ecs->mapped_clients[p_pid];
WaylandThread::EmbeddedClientState *client_data = (WaylandThread::EmbeddedClientState *)godot_embedded_client_get_user_data(embedded_client);
ERR_FAIL_NULL_V(client_data, ERR_BUG);
godot_embedded_client_embedded_window_request_close(embedded_client);
return OK;
}
Error DisplayServerWayland::remove_embedded_process(OS::ProcessID p_pid) {
return request_close_embedded_process(p_pid);
}
OS::ProcessID DisplayServerWayland::get_focused_process_id() {
MutexLock mutex_lock(wayland_thread.mutex);
OS::ProcessID embedded_pid = wayland_thread.embedded_compositor_get_focused_pid();
if (embedded_pid < 0) {
return OS::get_singleton()->get_process_id();
}
return embedded_pid;
}
int DisplayServerWayland::keyboard_get_layout_count() const {
MutexLock mutex_lock(wayland_thread.mutex);
@@ -328,6 +328,11 @@ public:
virtual bool get_swap_cancel_ok() override;
virtual Error embed_process(WindowID p_window, OS::ProcessID p_pid, const Rect2i &p_rect, bool p_visible, bool p_grab_focus) override;
virtual Error request_close_embedded_process(OS::ProcessID p_pid) override;
virtual Error remove_embedded_process(OS::ProcessID p_pid) override;
virtual OS::ProcessID get_focused_process_id() override;
virtual int keyboard_get_layout_count() const override;
virtual int keyboard_get_current_layout() const override;
virtual void keyboard_set_current_layout(int p_index) override;
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="godot_embedding_compositor">
<copyright>
Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
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 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.
</copyright>
<interface name="godot_embedding_compositor" version="1">
<description summary="Main control interface for embedding compositor"/>
<event name="client">
<description summary="A new client connected to the compositor"/>
<arg name="client" type="new_id" interface="godot_embedded_client"/>
<arg name="pid" type="int"/>
</event>
</interface>
<interface name="godot_embedded_client" version="1">
<description summary="A client connected to the embedded compositor.">
Clients have only a single embedded window at a time, subject to change.
</description>
<request name="destroy" type="destructor"/>
<request name="set_embedded_window_rect">
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<request name="set_embedded_window_parent">
<description summary="(Re)map onto an xdg_toplevel"/>
<arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
</request>
<request name="focus_window"/>
<request name="embedded_window_request_close"/>
<event name="disconnected">
<description summary="The client got disconnected from the compositor">
This instance is no longer valid. The compositor shall ignore any
further request except destroy and stop emitting events for this object.
After this event, the client can safely destroy this object.
</description>
</event>
<event name="window_embedded"/>
<event name="window_focus_in"/>
<event name="window_focus_out"/>
</interface>
</protocol>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+223 -17
View File
@@ -662,6 +662,13 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re
if (strcmp(interface, FIFO_INTERFACE_NAME) == 0) {
registry->wp_fifo_manager_name = name;
}
if (strcmp(interface, godot_embedding_compositor_interface.name) == 0) {
registry->godot_embedding_compositor = (struct godot_embedding_compositor *)wl_registry_bind(wl_registry, name, &godot_embedding_compositor_interface, 1);
registry->godot_embedding_compositor_name = name;
godot_embedding_compositor_add_listener(registry->godot_embedding_compositor, &godot_embedding_compositor_listener, memnew(EmbeddingCompositorState));
}
}
void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {
@@ -1092,6 +1099,25 @@ void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry
if (name == registry->wp_fifo_manager_name) {
registry->wp_fifo_manager_name = 0;
}
if (name == registry->godot_embedding_compositor_name) {
registry->godot_embedding_compositor_name = 0;
EmbeddingCompositorState *es = godot_embedding_compositor_get_state(registry->godot_embedding_compositor);
ERR_FAIL_NULL(es);
es->mapped_clients.clear();
for (struct godot_embedded_client *client : es->clients) {
godot_embedded_client_destroy(client);
}
es->clients.clear();
memdelete(es);
godot_embedding_compositor_destroy(registry->godot_embedding_compositor);
registry->godot_embedding_compositor = nullptr;
}
}
void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
@@ -2064,6 +2090,8 @@ void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_ke
xkb_state_unref(ss->xkb_state);
ss->xkb_state = xkb_state_new(ss->xkb_keymap);
xkb_state_update_mask(ss->xkb_state, ss->mods_depressed, ss->mods_latched, ss->mods_locked, 0, 0, ss->current_layout_index);
}
void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
@@ -2124,6 +2152,15 @@ void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_key
msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;
wayland_thread->push_message(msg);
ss->shift_pressed = false;
ss->ctrl_pressed = false;
ss->alt_pressed = false;
ss->meta_pressed = false;
if (ss->xkb_state != nullptr) {
xkb_state_update_mask(ss->xkb_state, 0, 0, 0, 0, 0, 0);
}
DEBUG_LOG_WAYLAND_THREAD(vformat("Keyboard unfocused window %d.", ws->id));
}
@@ -2177,14 +2214,21 @@ void WaylandThread::_wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl
SeatState *ss = (SeatState *)data;
ERR_FAIL_NULL(ss);
xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, ss->current_layout_index, ss->current_layout_index, group);
ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED);
ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);
ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED);
ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED);
ss->mods_depressed = mods_depressed;
ss->mods_latched = mods_latched;
ss->mods_locked = mods_locked;
ss->current_layout_index = group;
if (ss->xkb_state == nullptr) {
return;
}
xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_EFFECTIVE);
ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE);
ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_EFFECTIVE);
ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE);
}
void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {
@@ -3036,15 +3080,82 @@ void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activat
DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation."));
}
void WaylandThread::_godot_embedding_compositor_on_client(void *data, struct godot_embedding_compositor *godot_embedding_compositor, struct godot_embedded_client *godot_embedded_client, int32_t pid) {
EmbeddingCompositorState *state = (EmbeddingCompositorState *)data;
ERR_FAIL_NULL(state);
EmbeddedClientState *client_state = memnew(EmbeddedClientState);
client_state->embedding_compositor = godot_embedding_compositor;
client_state->pid = pid;
godot_embedded_client_add_listener(godot_embedded_client, &godot_embedded_client_listener, client_state);
DEBUG_LOG_WAYLAND_THREAD(vformat("New client %d.", pid));
state->clients.push_back(godot_embedded_client);
}
void WaylandThread::_godot_embedded_client_on_disconnected(void *data, struct godot_embedded_client *godot_embedded_client) {
EmbeddedClientState *state = (EmbeddedClientState *)data;
ERR_FAIL_NULL(state);
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
ERR_FAIL_NULL(ecomp_state);
ecomp_state->clients.erase_unordered(godot_embedded_client);
ecomp_state->mapped_clients.erase(state->pid);
memfree(state);
godot_embedded_client_destroy(godot_embedded_client);
DEBUG_LOG_WAYLAND_THREAD(vformat("Client %d disconnected.", state->pid));
}
void WaylandThread::_godot_embedded_client_on_window_embedded(void *data, struct godot_embedded_client *godot_embedded_client) {
EmbeddedClientState *state = (EmbeddedClientState *)data;
ERR_FAIL_NULL(state);
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
ERR_FAIL_NULL(ecomp_state);
state->window_mapped = true;
ERR_FAIL_COND_MSG(ecomp_state->mapped_clients.has(state->pid), "More than one Wayland client per PID tried to create a window.");
ecomp_state->mapped_clients[state->pid] = godot_embedded_client;
}
void WaylandThread::_godot_embedded_client_on_window_focus_in(void *data, struct godot_embedded_client *godot_embedded_client) {
EmbeddedClientState *state = (EmbeddedClientState *)data;
ERR_FAIL_NULL(state);
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
ERR_FAIL_NULL(ecomp_state);
ecomp_state->focused_pid = state->pid;
DEBUG_LOG_WAYLAND_THREAD(vformat("Embedded client pid %d focus in", state->pid));
}
void WaylandThread::_godot_embedded_client_on_window_focus_out(void *data, struct godot_embedded_client *godot_embedded_client) {
EmbeddedClientState *state = (EmbeddedClientState *)data;
ERR_FAIL_NULL(state);
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(state->embedding_compositor);
ERR_FAIL_NULL(ecomp_state);
ecomp_state->focused_pid = -1;
DEBUG_LOG_WAYLAND_THREAD(vformat("Embedded client pid %d focus out", state->pid));
}
// NOTE: This must be started after a valid wl_display is loaded.
void WaylandThread::_poll_events_thread(void *p_data) {
Thread::set_name("Wayland Events");
ThreadData *data = (ThreadData *)p_data;
ERR_FAIL_NULL(data);
ERR_FAIL_NULL(data->wl_display);
struct pollfd poll_fd;
struct pollfd poll_fd = {};
poll_fd.fd = wl_display_get_fd(data->wl_display);
poll_fd.events = POLLIN | POLLHUP;
poll_fd.events = POLLIN;
while (true) {
// Empty the event queue while it's full.
@@ -3188,6 +3299,15 @@ WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_s
return nullptr;
}
WaylandThread::EmbeddingCompositorState *WaylandThread::godot_embedding_compositor_get_state(struct godot_embedding_compositor *p_compositor) {
// NOTE: No need for tag check as it's a "fake" interface - nothing else exposes it.
if (p_compositor) {
return (EmbeddingCompositorState *)godot_embedding_compositor_get_user_data(p_compositor);
}
return nullptr;
}
// This is implemented as a method because this is the simplest way of
// accounting for dynamic output scale changes.
int WaylandThread::window_state_get_preferred_buffer_scale(WindowState *p_ws) {
@@ -3356,15 +3476,20 @@ void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
ERR_FAIL_NULL(p_ss);
if (p_ss->wl_pointer == nullptr) {
WARN_PRINT("Can't lock - no pointer?");
return;
}
if (registry.wp_pointer_constraints == nullptr) {
WARN_PRINT("Can't lock - no constraints global.");
return;
}
if (p_ss->wp_locked_pointer == nullptr) {
struct wl_surface *locked_surface = window_get_wl_surface(p_ss->pointer_data.last_pointed_id);
if (locked_surface == nullptr) {
locked_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
}
ERR_FAIL_NULL(locked_surface);
p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, nullptr, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
@@ -4445,7 +4570,52 @@ Error WaylandThread::init() {
KeyMappingXKB::initialize();
wl_display = wl_display_connect(nullptr);
#ifdef TOOLS_ENABLED
String embedder_socket_path;
bool embedder_enabled = true;
if (OS::get_singleton()->get_environment("GODOT_WAYLAND_DISABLE_EMBEDDER") == "1") {
print_verbose("Disabling Wayland embedder as per GODOT_WAYLAND_DISABLE_EMBEDDER.");
embedder_enabled = false;
}
if (embedder_enabled && Engine::get_singleton()->is_editor_hint() && !Engine::get_singleton()->is_project_manager_hint()) {
print_verbose("Initializing Wayland embedder.");
Error embedder_status = embedder.init();
ERR_FAIL_COND_V_MSG(embedder_status != OK, ERR_CANT_CREATE, "Can't initialize Wayland embedder.");
embedder_socket_path = embedder.get_socket_path();
ERR_FAIL_COND_V_MSG(embedder_socket_path.is_empty(), ERR_CANT_CREATE, "Wayland embedder returned invalid path.");
OS::get_singleton()->set_environment("GODOT_WAYLAND_DISPLAY", embedder_socket_path);
// Debug
if (OS::get_singleton()->get_environment("GODOT_DEBUG_EMBEDDER_SINGLE_INSTANCE") == "1") {
print_line("Pausing as per GODOT_DEBUG_EMBEDDER_SINGLE_INSTANCE.");
pause();
}
} else if (Engine::get_singleton()->is_embedded_in_editor()) {
embedder_socket_path = OS::get_singleton()->get_environment("GODOT_WAYLAND_DISPLAY");
#if 0
// Debug
OS::get_singleton()->set_environment("WAYLAND_DEBUG", "1");
int fd = open("/tmp/gdembedded.log", O_CREAT | O_RDWR, 0666);
dup2(fd, 1);
dup2(fd, 2);
#endif
}
if (embedder_socket_path.is_empty()) {
print_verbose("Connecting to the default Wayland display.");
wl_display = wl_display_connect(nullptr);
} else {
print_verbose("Connecting to the Wayland embedder display.");
wl_display = wl_display_connect(embedder_socket_path.utf8().get_data());
}
#endif // TOOLS_ENABLED
ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display.");
thread_data.wl_display = wl_display;
@@ -4465,12 +4635,19 @@ Error WaylandThread::init() {
ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global.");
ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global.");
if (!registry.xdg_decoration_manager) {
// Embedded games can't access the decoration and icon protocol.
if (!Engine::get_singleton()->is_embedded_in_editor()) {
if (!registry.xdg_decoration_manager) {
#ifdef LIBDECOR_ENABLED
WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available.");
WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available.");
#else
WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up.");
WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up.");
#endif // LIBDECOR_ENABLED
}
if (!registry.xdg_toplevel_icon_manager_name) {
WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon.");
}
}
if (!registry.xdg_activation) {
@@ -4487,10 +4664,6 @@ Error WaylandThread::init() {
WARN_PRINT("FIFO protocol not found! Frame pacing will be degraded.");
}
if (!registry.xdg_toplevel_icon_manager_name) {
WARN_PRINT("xdg-toplevel-icon protocol not found! Cannot set window icon.");
}
// Wait for seat capabilities.
wl_display_roundtrip(wl_display);
@@ -5010,6 +5183,17 @@ bool WaylandThread::is_suspended() const {
return true;
}
struct godot_embedding_compositor *WaylandThread::get_embedding_compositor() {
return registry.godot_embedding_compositor;
}
OS::ProcessID WaylandThread::embedded_compositor_get_focused_pid() {
EmbeddingCompositorState *ecomp_state = godot_embedding_compositor_get_state(registry.godot_embedding_compositor);
ERR_FAIL_NULL_V(ecomp_state, -1);
return ecomp_state->focused_pid;
}
void WaylandThread::destroy() {
if (!initialized) {
return;
@@ -5045,6 +5229,10 @@ void WaylandThread::destroy() {
}
#endif // LIBDECOR_ENABLED
if (ws.xdg_toplevel_decoration) {
zxdg_toplevel_decoration_v1_destroy(ws.xdg_toplevel_decoration);
}
if (ws.xdg_toplevel) {
xdg_toplevel_destroy(ws.xdg_toplevel);
}
@@ -5132,6 +5320,22 @@ void WaylandThread::destroy() {
wl_output_destroy(wl_output);
}
if (registry.godot_embedding_compositor) {
EmbeddingCompositorState *es = godot_embedding_compositor_get_state(registry.godot_embedding_compositor);
ERR_FAIL_NULL(es);
es->mapped_clients.clear();
for (struct godot_embedded_client *client : es->clients) {
godot_embedded_client_destroy(client);
}
es->clients.clear();
memdelete(es);
godot_embedding_compositor_destroy(registry.godot_embedding_compositor);
}
if (wl_cursor_theme) {
wl_cursor_theme_destroy(wl_cursor_theme);
}
@@ -5212,6 +5416,8 @@ void WaylandThread::destroy() {
wl_registry_destroy(wl_registry);
}
wl_display_roundtrip(wl_display);
if (wl_display) {
wl_display_disconnect(wl_display);
}
@@ -72,6 +72,8 @@
#include "wayland/protocol/xdg_system_bell.gen.h"
#include "wayland/protocol/xdg_toplevel_icon.gen.h"
#include "wayland/protocol/godot_embedding_compositor.gen.h"
// NOTE: Deprecated.
#include "wayland/protocol/xdg_foreign_v1.gen.h"
@@ -86,6 +88,8 @@
#include "core/os/thread.h"
#include "servers/display/display_server.h"
#include "wayland_embedder.h"
class WaylandThread {
public:
// Messages used for exchanging information between Godot's and Wayland's thread.
@@ -228,6 +232,9 @@ public:
// We're really not meant to use this one directly but we still need to know
// whether it's available.
uint32_t wp_fifo_manager_name = 0;
struct godot_embedding_compositor *godot_embedding_compositor = nullptr;
uint32_t godot_embedding_compositor_name = 0;
};
// General Wayland-specific states. Shouldn't be accessed directly.
@@ -477,6 +484,10 @@ public:
uint64_t last_repeat_start_msec = 0;
uint64_t last_repeat_msec = 0;
uint32_t mods_depressed = 0;
uint32_t mods_latched = 0;
uint32_t mods_locked = 0;
bool shift_pressed = false;
bool ctrl_pressed = false;
bool alt_pressed = false;
@@ -529,6 +540,22 @@ public:
Point2i hotspot;
};
struct EmbeddingCompositorState {
LocalVector<struct godot_embedded_client *> clients;
// Only a client per PID can create a window.
HashMap<int, struct godot_embedded_client *> mapped_clients;
OS::ProcessID focused_pid = -1;
};
struct EmbeddedClientState {
struct godot_embedding_compositor *embedding_compositor = nullptr;
uint32_t pid = 0;
bool window_mapped = false;
};
private:
struct ThreadData {
SafeFlag thread_done;
@@ -604,6 +631,10 @@ private:
bool initialized = false;
#ifdef TOOLS_ENABLED
WaylandEmbedder embedder;
#endif
#ifdef LIBDECOR_ENABLED
struct libdecor *libdecor_context = nullptr;
#endif // LIBDECOR_ENABLED
@@ -742,6 +773,13 @@ private:
static void _xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token);
static void _godot_embedding_compositor_on_client(void *data, struct godot_embedding_compositor *godot_embedding_compositor, struct godot_embedded_client *godot_embedded_client, int32_t pid);
static void _godot_embedded_client_on_disconnected(void *data, struct godot_embedded_client *godot_embedded_client);
static void _godot_embedded_client_on_window_embedded(void *data, struct godot_embedded_client *godot_embedded_client);
static void _godot_embedded_client_on_window_focus_in(void *data, struct godot_embedded_client *godot_embedded_client);
static void _godot_embedded_client_on_window_focus_out(void *data, struct godot_embedded_client *godot_embedded_client);
// Core Wayland event listeners.
static constexpr struct wl_registry_listener wl_registry_listener = {
.global = _wl_registry_on_global,
@@ -929,6 +967,18 @@ private:
.done = _xdg_activation_token_on_done,
};
// Godot interfaces.
static constexpr struct godot_embedding_compositor_listener godot_embedding_compositor_listener = {
.client = _godot_embedding_compositor_on_client,
};
static constexpr struct godot_embedded_client_listener godot_embedded_client_listener = {
.disconnected = _godot_embedded_client_on_disconnected,
.window_embedded = _godot_embedded_client_on_window_embedded,
.window_focus_in = _godot_embedded_client_on_window_focus_in,
.window_focus_out = _godot_embedded_client_on_window_focus_out,
};
#ifdef LIBDECOR_ENABLED
// libdecor event handlers.
static void libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message);
@@ -1009,6 +1059,8 @@ public:
static OfferState *wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer);
static EmbeddingCompositorState *godot_embedding_compositor_get_state(struct godot_embedding_compositor *p_compositor);
void seat_state_unlock_pointer(SeatState *p_ss);
void seat_state_lock_pointer(SeatState *p_ss);
void seat_state_set_hint(SeatState *p_ss, int p_x, int p_y);
@@ -1116,6 +1168,10 @@ public:
bool window_is_suspended(DisplayServer::WindowID p_window_id) const;
bool is_suspended() const;
struct godot_embedding_compositor *get_embedding_compositor();
OS::ProcessID embedded_compositor_get_focused_pid();
Error init();
void destroy();
};
+4
View File
@@ -1192,6 +1192,10 @@ Files extracted from upstream source:
- `unstable/xdg-foreign/xdg-foreign-unstable-v1.xml`
- `COPYING`
The following files are extracted from thirdparty sources:
- `mesa/wayland-drm.xml`: https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-25.3.0/src/egl/wayland/wayland-drm/wayland-drm.xml
## wslay
+189
View File
@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="drm">
<copyright>
Copyright © 2008-2011 Kristian Høgsberg
Copyright © 2010-2011 Intel Corporation
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that\n the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<!-- drm support. This object is created by the server and published
using the display's global event. -->
<interface name="wl_drm" version="2">
<enum name="error">
<entry name="authenticate_fail" value="0"/>
<entry name="invalid_format" value="1"/>
<entry name="invalid_name" value="2"/>
</enum>
<enum name="format">
<!-- The drm format codes match the #defines in drm_fourcc.h.
The formats actually supported by the compositor will be
reported by the format event. New codes must not be added,
unless directly taken from drm_fourcc.h. -->
<entry name="c8" value="0x20203843"/>
<entry name="rgb332" value="0x38424752"/>
<entry name="bgr233" value="0x38524742"/>
<entry name="xrgb4444" value="0x32315258"/>
<entry name="xbgr4444" value="0x32314258"/>
<entry name="rgbx4444" value="0x32315852"/>
<entry name="bgrx4444" value="0x32315842"/>
<entry name="argb4444" value="0x32315241"/>
<entry name="abgr4444" value="0x32314241"/>
<entry name="rgba4444" value="0x32314152"/>
<entry name="bgra4444" value="0x32314142"/>
<entry name="xrgb1555" value="0x35315258"/>
<entry name="xbgr1555" value="0x35314258"/>
<entry name="rgbx5551" value="0x35315852"/>
<entry name="bgrx5551" value="0x35315842"/>
<entry name="argb1555" value="0x35315241"/>
<entry name="abgr1555" value="0x35314241"/>
<entry name="rgba5551" value="0x35314152"/>
<entry name="bgra5551" value="0x35314142"/>
<entry name="rgb565" value="0x36314752"/>
<entry name="bgr565" value="0x36314742"/>
<entry name="rgb888" value="0x34324752"/>
<entry name="bgr888" value="0x34324742"/>
<entry name="xrgb8888" value="0x34325258"/>
<entry name="xbgr8888" value="0x34324258"/>
<entry name="rgbx8888" value="0x34325852"/>
<entry name="bgrx8888" value="0x34325842"/>
<entry name="argb8888" value="0x34325241"/>
<entry name="abgr8888" value="0x34324241"/>
<entry name="rgba8888" value="0x34324152"/>
<entry name="bgra8888" value="0x34324142"/>
<entry name="xrgb2101010" value="0x30335258"/>
<entry name="xbgr2101010" value="0x30334258"/>
<entry name="rgbx1010102" value="0x30335852"/>
<entry name="bgrx1010102" value="0x30335842"/>
<entry name="argb2101010" value="0x30335241"/>
<entry name="abgr2101010" value="0x30334241"/>
<entry name="rgba1010102" value="0x30334152"/>
<entry name="bgra1010102" value="0x30334142"/>
<entry name="yuyv" value="0x56595559"/>
<entry name="yvyu" value="0x55595659"/>
<entry name="uyvy" value="0x59565955"/>
<entry name="vyuy" value="0x59555956"/>
<entry name="ayuv" value="0x56555941"/>
<entry name="xyuv8888" value="0x56555958"/>
<entry name="nv12" value="0x3231564e"/>
<entry name="nv21" value="0x3132564e"/>
<entry name="nv16" value="0x3631564e"/>
<entry name="nv61" value="0x3136564e"/>
<entry name="yuv410" value="0x39565559"/>
<entry name="yvu410" value="0x39555659"/>
<entry name="yuv411" value="0x31315559"/>
<entry name="yvu411" value="0x31315659"/>
<entry name="yuv420" value="0x32315559"/>
<entry name="yvu420" value="0x32315659"/>
<entry name="yuv422" value="0x36315559"/>
<entry name="yvu422" value="0x36315659"/>
<entry name="yuv444" value="0x34325559"/>
<entry name="yvu444" value="0x34325659"/>
<entry name="abgr16f" value="0x48344241"/>
<entry name="xbgr16f" value="0x48344258"/>
</enum>
<!-- Call this request with the magic received from drmGetMagic().
It will be passed on to the drmAuthMagic() or
DRIAuthConnection() call. This authentication must be
completed before create_buffer could be used. -->
<request name="authenticate">
<arg name="id" type="uint"/>
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_buffer">
<arg name="id" type="new_id" interface="wl_buffer"/>
<arg name="name" type="uint"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
<arg name="stride" type="uint"/>
<arg name="format" type="uint"/>
</request>
<!-- Create a wayland buffer for the named DRM buffer. The DRM
surface must have a name using the flink ioctl -->
<request name="create_planar_buffer">
<arg name="id" type="new_id" interface="wl_buffer"/>
<arg name="name" type="uint"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
<arg name="format" type="uint"/>
<arg name="offset0" type="int"/>
<arg name="stride0" type="int"/>
<arg name="offset1" type="int"/>
<arg name="stride1" type="int"/>
<arg name="offset2" type="int"/>
<arg name="stride2" type="int"/>
</request>
<!-- Notification of the path of the drm device which is used by
the server. The client should use this device for creating
local buffers. Only buffers created from this device should
be be passed to the server using this drm object's
create_buffer request. -->
<event name="device">
<arg name="name" type="string"/>
</event>
<event name="format">
<arg name="format" type="uint"/>
</event>
<!-- Raised if the authenticate request succeeded -->
<event name="authenticated"/>
<enum name="capability" since="2">
<description summary="wl_drm capability bitmask">
Bitmask of capabilities.
</description>
<entry name="prime" value="1" summary="wl_drm prime available"/>
</enum>
<event name="capabilities">
<arg name="value" type="uint"/>
</event>
<!-- Version 2 additions -->
<!-- Create a wayland buffer for the prime fd. Use for regular and planar
buffers. Pass 0 for offset and stride for unused planes. -->
<request name="create_prime_buffer" since="2">
<arg name="id" type="new_id" interface="wl_buffer"/>
<arg name="name" type="fd"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
<arg name="format" type="uint"/>
<arg name="offset0" type="int"/>
<arg name="stride0" type="int"/>
<arg name="offset1" type="int"/>
<arg name="stride1" type="int"/>
<arg name="offset2" type="int"/>
<arg name="stride2" type="int"/>
</request>
</interface>
</protocol>
@@ -0,0 +1,5 @@
Linux DMA-BUF protocol
Maintainers:
Pekka Paalanen <pekka.paalanen@collabora.co.uk>
Daniel Stone <daniels@collabora.com>
@@ -0,0 +1,218 @@
.. Copyright 2021 Simon Ser
.. contents::
linux-dmabuf feedback introduction
==================================
linux-dmabuf feedback allows compositors and clients to negotiate optimal buffer
allocation parameters. This document will assume that the compositor is using a
rendering API such as OpenGL or Vulkan and KMS as the presentation API: even if
linux-dmabuf feedback isn't restricted to this use-case, it's the most common.
linux-dmabuf feedback introduces the following concepts:
1. A main device. This is the render device that the compositor is using to
perform composition. Compositors should always be able to display a buffer
submitted by a client, so this device can be used as a fallback in case none
of the more optimized code-paths work. Clients should allocate buffers such
that they can be imported and textured from the main device.
2. One or more tranches. Each tranche consists of a target device, allocation
flags and a set of format/modifier pairs. A tranche can be seen as a set of
formats/modifier pairs that are compatible with the target device.
A tranche can have the ``scanout`` flag. It means that the target device is
a KMS device, and that buffers allocated with one of the format/modifier
pairs in the tranche are eligible for direct scanout.
Clients should use the tranches in order to allocate buffers with the most
appropriate format/modifier and also to avoid allocating in private device
memory when cross-device operations are going to happen.
linux-dmabuf feedback implementation notes
==========================================
This section contains recommendations for client and compositor implementations.
For clients
-----------
Clients are expected to either pick a fixed DRM format beforehand, or
perform the following steps repeatedly until they find a suitable format.
Basic clients may only support static buffer allocation on startup. These
clients should do the following:
1. Send a ``get_default_feedback`` request to get global feedback.
2. Select the device indicated by ``main_device`` for allocation.
3. For each tranche:
1. If ``tranche_target_device`` doesn't match the allocation device, ignore
the tranche.
2. Accumulate allocation flags from ``tranche_flags``.
3. Accumulate format/modifier pairs received via ``tranche_formats`` in a
list.
4. When the ``tranche_done`` event is received, try to allocate the buffer
with the accumulated list of modifiers and allocation flags. If that
fails, proceed with the next tranche. If that succeeds, stop the loop.
4. Destroy the feedback object.
Tranches are ordered by preference: the more optimized tranches come first. As
such, clients should use the first tranche that happens to work.
Some clients may have already selected the device they want to use beforehand.
These clients can ignore the ``main_device`` event, and ignore tranches whose
``tranche_target_device`` doesn't match the selected device. Such clients need
to be prepared for the ``wp_linux_buffer_params.create`` request to potentially
fail.
If the client allocates a buffer without specifying explicit modifiers on a
device different from the one indicated by ``main_device``, then the client
must force a linear layout.
Some clients might support re-negotiating the buffer format/modifier on the
fly. These clients should send a ``get_surface_feedback`` request and keep the
feedback object alive after the initial allocation. Each time a new set of
feedback parameters is received (ended by the ``done`` event), they should
perform the same steps as basic clients described above. They should detect
when the optimal allocation parameters didn't change (same
format/modifier/flags) to avoid needlessly re-allocating their buffers.
Some clients might additionally support switching the device used for
allocations on the fly. Such clients should send a ``get_surface_feedback``
request. For each tranche, select the device indicated by
``tranche_target_device`` for allocation. Accumulate allocation flags (received
via ``tranche_flags``) and format/modifier pairs (received via
``tranche_formats``) as usual. When the ``tranche_done`` event is received, try
to allocate the buffer with the accumulated list of modifiers and the
allocation flags. Try to import the resulting buffer by sending a
``wp_linux_buffer_params.create`` request (this might fail). Repeat with each
tranche until an allocation and import succeeds. Each time a new set of
feedback parameters is received, they should perform these steps again. They
should detect when the optimal allocation parameters didn't change (same
device/format/modifier/flags) to avoid needlessly re-allocating their buffers.
For compositors
---------------
Basic compositors may only support texturing the DMA-BUFs via a rendering API
such as OpenGL or Vulkan. Such compositors can send a single tranche as a reply
to both ``get_default_feedback`` and ``get_surface_feedback``. Set the
``main_device`` to the rendering device. Send the tranche with
``tranche_target_device`` set to the rendering device and all of the DRM
format/modifier pairs supported by the rendering API. Do not set the
``scanout`` flag in the ``tranche_flags`` event.
Some compositors may support direct scan-out for full-screen surfaces. These
compositors can re-send the feedback parameters when a surface becomes
full-screen or leaves full-screen mode if the client has used the
``get_surface_feedback`` request. The non-full-screen feedback parameters are
the same as basic compositors described above. The full-screen feedback
parameters have two tranches: one with the format/modifier pairs supported by
the KMS plane, with the ``scanout`` flag set in the ``tranche_flags`` event and
with ``tranche_target_device`` set to the KMS scan-out device; the other with
the rest of the format/modifier pairs (supported for texturing, but not for
scan-out), without the ``scanout`` flag set in the ``tranche_flags`` event, and
with the ``tranche_target_device`` set to the rendering device.
Some compositors may support direct scan-out for all surfaces. These
compositors can send two tranches for surfaces that become candidates for
direct scan-out, similarly to compositors supporting direct scan-out for
fullscreen surfaces. When a surface stops being a candidate for direct
scan-out, compositors should re-send the feedback parameters optimized for
texturing only. The way candidates for direct scan-out are selected is
compositor policy, a possible implementation is to select as many surfaces as
there are available hardware planes, starting from surfaces closer to the eye.
Some compositors may support multiple devices at the same time. If the
compositor supports rendering with a fixed device and direct scan-out on a
secondary device, it may send a separate tranche for surfaces displayed on
the secondary device that are candidates for direct scan-out. The
``tranche_target_device`` for this tranche will be the secondary device and
will not match the ``main_device``.
Some compositors may support switching their rendering device at runtime or
changing their rendering device depending on the surface. When the rendering
device changes for a surface, such compositors may re-send the feedback
parameters with a different ``main_device``. However there is a risk that
clients don't support switching their device at runtime and continue using the
previous device. For this reason, compositors should always have a fallback
rendering device that they initially send as ``main_device``, such that these
clients use said fallback device.
Compositors should not change the ``main_device`` on-the-fly when explicit
modifiers are not supported, because there's a risk of importing buffers
with an implicit non-linear modifier as a linear buffer, resulting in
misinterpreted buffer contents.
Compositors should not send feedback parameters if they don't have a fallback
path. For instance, compositors shouldn't send a format/modifier supported for
direct scan-out but not supported by the rendering API for texturing.
Compositors can decide to use multiple tranches to describe the allocation
parameters optimized for texturing. For example, if there are formats which
have a fast texturing path and formats which have a slower texturing path, the
compositor can decide to expose two separate tranches.
Compositors can decide to use intermediate tranches to describe code-paths
slower than direct scan-out but faster than texturing. For instance, a
compositor could insert an intermediate tranche if it's possible to use a
mem2mem device to convert buffers to be able to use scan-out.
``dev_t`` encoding
==================
The protocol carries ``dev_t`` values on the wire using arrays. A compositor
written in C can encode the values as follows:
.. code-block:: c
struct stat drm_node_stat;
struct wl_array dev_array = {
.size = sizeof(drm_node_stat.st_rdev),
.data = &drm_node_stat.st_rdev,
};
A client can decode the values as follows:
.. code-block:: c
dev_t dev;
assert(dev_array->size == sizeof(dev));
memcpy(&dev, dev_array->data, sizeof(dev));
Because two DRM nodes can refer to the same DRM device while having different
``dev_t`` values, clients should use ``drmDevicesEqual`` to compare two
devices.
``format_table`` encoding
=========================
The ``format_table`` event carries a file descriptor containing a list of
format + modifier pairs. The list is an array of pairs which can be accessed
with this C structure definition:
.. code-block:: c
struct dmabuf_format_modifier {
uint32_t format;
uint32_t pad; /* unused */
uint64_t modifier;
};
Integration with other APIs
===========================
- libdrm: ``drmGetDeviceFromDevId`` returns a ``drmDevice`` from a device ID.
- EGL: the `EGL_EXT_device_drm_render_node`_ extension may be used to query the
DRM device render node used by a given EGL display. When unavailable, the
older `EGL_EXT_device_drm`_ extension may be used as a fallback.
- Vulkan: the `VK_EXT_physical_device_drm`_ extension may be used to query the
DRM device used by a given ``VkPhysicalDevice``.
.. _EGL_EXT_device_drm: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm.txt
.. _EGL_EXT_device_drm_render_node: https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt
.. _VK_EXT_physical_device_drm: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_EXT_physical_device_drm.html
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,4 @@
Commit Timing Protocol
Maintainers:
Derek Foreman <derek.foreman@collabora.com> (@derekf)
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="commit_timing_v1">
<copyright>
Copyright © 2023 Valve Corporation
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.
</copyright>
<interface name="wp_commit_timing_manager_v1" version="1">
<description summary="commit timing">
When a compositor latches on to new content updates it will check for
any number of requirements of the available content updates (such as
fences of all buffers being signalled) to consider the update ready.
This protocol provides a method for adding a time constraint to surface
content. This constraint indicates to the compositor that a content
update should be presented as closely as possible to, but not before,
a specified time.
This protocol does not change the Wayland property that content
updates are applied in the order they are received, even when some
content updates contain timestamps and others do not.
To provide timestamps, this global factory interface must be used to
acquire a wp_commit_timing_v1 object for a surface, which may then be
used to provide timestamp information for commits.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<enum name="error">
<entry name="commit_timer_exists" value="0"
summary="commit timer already exists for surface"/>
</enum>
<request name="destroy" type="destructor">
<description summary="unbind from the commit timing interface">
Informs the server that the client will no longer be using
this protocol object. Existing objects created by this object
are not affected.
</description>
</request>
<request name="get_timer">
<description summary="request commit timer interface for surface">
Establish a timing controller for a surface.
Only one commit timer can be created for a surface, or a
commit_timer_exists protocol error will be generated.
</description>
<arg name="id" type="new_id" interface="wp_commit_timer_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="wp_commit_timer_v1" version="1">
<description summary="Surface commit timer">
An object to set a time constraint for a content update on a surface.
</description>
<enum name="error">
<entry name="invalid_timestamp" value="0"
summary="timestamp contains an invalid value"/>
<entry name="timestamp_exists" value="1"
summary="timestamp exists"/>
<entry name="surface_destroyed" value="2"
summary="the associated surface no longer exists"/>
</enum>
<request name="set_timestamp">
<description summary="Specify time the following commit takes effect">
Provide a timing constraint for a surface content update.
A set_timestamp request may be made before a wl_surface.commit to
tell the compositor that the content is intended to be presented
as closely as possible to, but not before, the specified time.
The time is in the domain of the compositor's presentation clock.
An invalid_timestamp error will be generated for invalid tv_nsec.
If a timestamp already exists on the surface, a timestamp_exists
error is generated.
Requesting set_timestamp after the commit_timer object's surface is
destroyed will generate a "surface_destroyed" error.
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of target time"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of target time"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of target time"/>
</request>
<request name="destroy" type="destructor">
<description summary="Destroy the timer">
Informs the server that the client will no longer be using
this protocol object.
Existing timing constraints are not affected by the destruction.
</description>
</request>
</interface>
</protocol>
+4
View File
@@ -0,0 +1,4 @@
Fifo Protocol
Maintainers:
Derek Foreman <derek.foreman@collabora.com> (@derekf)
+143
View File
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="fifo_v1">
<copyright>
Copyright © 2023 Valve Corporation
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.
</copyright>
<interface name="wp_fifo_manager_v1" version="1">
<description summary="protocol for fifo constraints">
When a Wayland compositor considers applying a content update,
it must ensure all the update's readiness constraints (fences, etc)
are met.
This protocol provides a way to use the completion of a display refresh
cycle as an additional readiness constraint.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<enum name="error">
<description summary="fatal presentation error">
These fatal protocol errors may be emitted in response to
illegal requests.
</description>
<entry name="already_exists" value="0"
summary="fifo manager already exists for surface"/>
</enum>
<request name="destroy" type="destructor">
<description summary="unbind from the manager interface">
Informs the server that the client will no longer be using
this protocol object. Existing objects created by this object
are not affected.
</description>
</request>
<request name="get_fifo">
<description summary="request fifo interface for surface">
Establish a fifo object for a surface that may be used to add
display refresh constraints to content updates.
Only one such object may exist for a surface and attempting
to create more than one will result in an already_exists
protocol error. If a surface is acted on by multiple software
components, general best practice is that only the component
performing wl_surface.attach operations should use this protocol.
</description>
<arg name="id" type="new_id" interface="wp_fifo_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="wp_fifo_v1" version="1">
<description summary="fifo interface">
A fifo object for a surface that may be used to add
display refresh constraints to content updates.
</description>
<enum name="error">
<description summary="fatal error">
These fatal protocol errors may be emitted in response to
illegal requests.
</description>
<entry name="surface_destroyed" value="0"
summary="the associated surface no longer exists"/>
</enum>
<request name="set_barrier">
<description summary="sets the start point for a fifo constraint">
When the content update containing the "set_barrier" is applied,
it sets a "fifo_barrier" condition on the surface associated with
the fifo object. The condition is cleared immediately after the
following latching deadline for non-tearing presentation.
The compositor may clear the condition early if it must do so to
ensure client forward progress assumptions.
To wait for this condition to clear, use the "wait_barrier" request.
"set_barrier" is double-buffered state, see wl_surface.commit.
Requesting set_barrier after the fifo object's surface is
destroyed will generate a "surface_destroyed" error.
</description>
</request>
<request name="wait_barrier">
<description summary="adds a fifo constraint to a content update">
Indicate that this content update is not ready while a
"fifo_barrier" condition is present on the surface.
This means that when the content update containing "set_barrier"
was made active at a latching deadline, it will be active for
at least one refresh cycle. A content update which is allowed to
tear might become active after a latching deadline if no content
update became active at the deadline.
The constraint must be ignored if the surface is a subsurface in
synchronized mode. If the surface is not being updated by the
compositor (off-screen, occluded) the compositor may ignore the
constraint. Clients must use an additional mechanism such as
frame callbacks or timestamps to ensure throttling occurs under
all conditions.
"wait_barrier" is double-buffered state, see wl_surface.commit.
Requesting "wait_barrier" after the fifo object's surface is
destroyed will generate a "surface_destroyed" error.
</description>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the fifo interface">
Informs the server that the client will no longer be using
this protocol object.
Surface state changes previously made by this protocol are
unaffected by this object's destruction.
</description>
</request>
</interface>
</protocol>
@@ -0,0 +1,4 @@
Linux DRM syncobj protocol
Maintainers:
Simon Ser <contact@emersion.fr> (@emersion)
@@ -0,0 +1,261 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="linux_drm_syncobj_v1">
<copyright>
Copyright 2016 The Chromium Authors.
Copyright 2017 Intel Corporation
Copyright 2018 Collabora, Ltd
Copyright 2021 Simon Ser
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.
</copyright>
<description summary="protocol for providing explicit synchronization">
This protocol allows clients to request explicit synchronization for
buffers. It is tied to the Linux DRM synchronization object framework.
Synchronization refers to co-ordination of pipelined operations performed
on buffers. Most GPU clients will schedule an asynchronous operation to
render to the buffer, then immediately send the buffer to the compositor
to be attached to a surface.
With implicit synchronization, ensuring that the rendering operation is
complete before the compositor displays the buffer is an implementation
detail handled by either the kernel or userspace graphics driver.
By contrast, with explicit synchronization, DRM synchronization object
timeline points mark when the asynchronous operations are complete. When
submitting a buffer, the client provides a timeline point which will be
waited on before the compositor accesses the buffer, and another timeline
point that the compositor will signal when it no longer needs to access the
buffer contents for the purposes of the surface commit.
Linux DRM synchronization objects are documented at:
https://dri.freedesktop.org/docs/drm/gpu/drm-mm.html#drm-sync-objects
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<interface name="wp_linux_drm_syncobj_manager_v1" version="1">
<description summary="global for providing explicit synchronization">
This global is a factory interface, allowing clients to request
explicit synchronization for buffers on a per-surface basis.
See wp_linux_drm_syncobj_surface_v1 for more information.
</description>
<request name="destroy" type="destructor">
<description summary="destroy explicit synchronization factory object">
Destroy this explicit synchronization factory object. Other objects
shall not be affected by this request.
</description>
</request>
<enum name="error">
<entry name="surface_exists" value="0"
summary="the surface already has a synchronization object associated"/>
<entry name="invalid_timeline" value="1"
summary="the timeline object could not be imported"/>
</enum>
<request name="get_surface">
<description summary="extend surface interface for explicit synchronization">
Instantiate an interface extension for the given wl_surface to provide
explicit synchronization.
If the given wl_surface already has an explicit synchronization object
associated, the surface_exists protocol error is raised.
Graphics APIs, like EGL or Vulkan, that manage the buffer queue and
commits of a wl_surface themselves, are likely to be using this
extension internally. If a client is using such an API for a
wl_surface, it should not directly use this extension on that surface,
to avoid raising a surface_exists protocol error.
</description>
<arg name="id" type="new_id" interface="wp_linux_drm_syncobj_surface_v1"
summary="the new synchronization surface object id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
<request name="import_timeline">
<description summary="import a DRM syncobj timeline">
Import a DRM synchronization object timeline.
If the FD cannot be imported, the invalid_timeline error is raised.
</description>
<arg name="id" type="new_id" interface="wp_linux_drm_syncobj_timeline_v1"/>
<arg name="fd" type="fd" summary="drm_syncobj file descriptor"/>
</request>
</interface>
<interface name="wp_linux_drm_syncobj_timeline_v1" version="1">
<description summary="synchronization object timeline">
This object represents an explicit synchronization object timeline
imported by the client to the compositor.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the timeline">
Destroy the synchronization object timeline. Other objects are not
affected by this request, in particular timeline points set by
set_acquire_point and set_release_point are not unset.
</description>
</request>
</interface>
<interface name="wp_linux_drm_syncobj_surface_v1" version="1">
<description summary="per-surface explicit synchronization">
This object is an add-on interface for wl_surface to enable explicit
synchronization.
Each surface can be associated with only one object of this interface at
any time.
Explicit synchronization is guaranteed to be supported for buffers
created with any version of the linux-dmabuf protocol. Compositors are
free to support explicit synchronization for additional buffer types.
If at surface commit time the attached buffer does not support explicit
synchronization, an unsupported_buffer error is raised.
As long as the wp_linux_drm_syncobj_surface_v1 object is alive, the
compositor may ignore implicit synchronization for buffers attached and
committed to the wl_surface. The delivery of wl_buffer.release events
for buffers attached to the surface becomes undefined.
Clients must set both acquire and release points if and only if a
non-null buffer is attached in the same surface commit. See the
no_buffer, no_acquire_point and no_release_point protocol errors.
If at surface commit time the acquire and release DRM syncobj timelines
are identical, the acquire point value must be strictly less than the
release point value, or else the conflicting_points protocol error is
raised.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the surface synchronization object">
Destroy this surface synchronization object.
Any timeline point set by this object with set_acquire_point or
set_release_point since the last commit may be discarded by the
compositor. Any timeline point set by this object before the last
commit will not be affected.
</description>
</request>
<enum name="error">
<entry name="no_surface" value="1"
summary="the associated wl_surface was destroyed"/>
<entry name="unsupported_buffer" value="2"
summary="the buffer does not support explicit synchronization"/>
<entry name="no_buffer" value="3" summary="no buffer was attached"/>
<entry name="no_acquire_point" value="4"
summary="no acquire timeline point was set"/>
<entry name="no_release_point" value="5"
summary="no release timeline point was set"/>
<entry name="conflicting_points" value="6"
summary="acquire and release timeline points are in conflict"/>
</enum>
<request name="set_acquire_point">
<description summary="set the acquire timeline point">
Set the timeline point that must be signalled before the compositor may
sample from the buffer attached with wl_surface.attach.
The 64-bit unsigned value combined from point_hi and point_lo is the
point value.
The acquire point is double-buffered state, and will be applied on the
next wl_surface.commit request for the associated surface. Thus, it
applies only to the buffer that is attached to the surface at commit
time.
If an acquire point has already been attached during the same commit
cycle, the new point replaces the old one.
If the associated wl_surface was destroyed, a no_surface error is
raised.
If at surface commit time there is a pending acquire timeline point set
but no pending buffer attached, a no_buffer error is raised. If at
surface commit time there is a pending buffer attached but no pending
acquire timeline point set, the no_acquire_point protocol error is
raised.
</description>
<arg name="timeline" type="object" interface="wp_linux_drm_syncobj_timeline_v1"/>
<arg name="point_hi" type="uint" summary="high 32 bits of the point value"/>
<arg name="point_lo" type="uint" summary="low 32 bits of the point value"/>
</request>
<request name="set_release_point">
<description summary="set the release timeline point">
Set the timeline point that must be signalled by the compositor when it
has finished its usage of the buffer attached with wl_surface.attach
for the relevant commit.
Once the timeline point is signaled, and assuming the associated buffer
is not pending release from other wl_surface.commit requests, no
additional explicit or implicit synchronization with the compositor is
required to safely re-use the buffer.
Note that clients cannot rely on the release point being always
signaled after the acquire point: compositors may release buffers
without ever reading from them. In addition, the compositor may use
different presentation paths for different commits, which may have
different release behavior. As a result, the compositor may signal the
release points in a different order than the client committed them.
Because signaling a timeline point also signals every previous point,
it is generally not safe to use the same timeline object for the
release points of multiple buffers. The out-of-order signaling
described above may lead to a release point being signaled before the
compositor has finished reading. To avoid this, it is strongly
recommended that each buffer should use a separate timeline for its
release points.
The 64-bit unsigned value combined from point_hi and point_lo is the
point value.
The release point is double-buffered state, and will be applied on the
next wl_surface.commit request for the associated surface. Thus, it
applies only to the buffer that is attached to the surface at commit
time.
If a release point has already been attached during the same commit
cycle, the new point replaces the old one.
If the associated wl_surface was destroyed, a no_surface error is
raised.
If at surface commit time there is a pending release timeline point set
but no pending buffer attached, a no_buffer error is raised. If at
surface commit time there is a pending buffer attached but no pending
release timeline point set, the no_release_point protocol error is
raised.
</description>
<arg name="timeline" type="object" interface="wp_linux_drm_syncobj_timeline_v1"/>
<arg name="point_hi" type="uint" summary="high 32 bits of the point value"/>
<arg name="point_lo" type="uint" summary="low 32 bits of the point value"/>
</request>
</interface>
</protocol>
@@ -0,0 +1,4 @@
Tearing control protocol
Maintainers:
Xaver Hugl <xaver.hugl@gmail.com> (@Zamundaaa)
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="tearing_control_v1">
<copyright>
Copyright © 2021 Xaver Hugl
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.
</copyright>
<interface name="wp_tearing_control_manager_v1" version="1">
<description summary="protocol for tearing control">
For some use cases like games or drawing tablets it can make sense to
reduce latency by accepting tearing with the use of asynchronous page
flips. This global is a factory interface, allowing clients to inform
which type of presentation the content of their surfaces is suitable for.
Graphics APIs like EGL or Vulkan, that manage the buffer queue and commits
of a wl_surface themselves, are likely to be using this extension
internally. If a client is using such an API for a wl_surface, it should
not directly use this extension on that surface, to avoid raising a
tearing_control_exists protocol error.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
</description>
<request name="destroy" type="destructor">
<description summary="destroy tearing control factory object">
Destroy this tearing control factory object. Other objects, including
wp_tearing_control_v1 objects created by this factory, are not affected
by this request.
</description>
</request>
<enum name="error">
<entry name="tearing_control_exists" value="0"
summary="the surface already has a tearing object associated"/>
</enum>
<request name="get_tearing_control">
<description summary="extend surface interface for tearing control">
Instantiate an interface extension for the given wl_surface to request
asynchronous page flips for presentation.
If the given wl_surface already has a wp_tearing_control_v1 object
associated, the tearing_control_exists protocol error is raised.
</description>
<arg name="id" type="new_id" interface="wp_tearing_control_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="wp_tearing_control_v1" version="1">
<description summary="per-surface tearing control interface">
An additional interface to a wl_surface object, which allows the client
to hint to the compositor if the content on the surface is suitable for
presentation with tearing.
The default presentation hint is vsync. See presentation_hint for more
details.
If the associated wl_surface is destroyed, this object becomes inert and
should be destroyed.
</description>
<enum name="presentation_hint">
<description summary="presentation hint values">
This enum provides information for if submitted frames from the client
may be presented with tearing.
</description>
<entry name="vsync" value="0">
<description summary="tearing-free presentation">
The content of this surface is meant to be synchronized to the
vertical blanking period. This should not result in visible tearing
and may result in a delay before a surface commit is presented.
</description>
</entry>
<entry name="async" value="1">
<description summary="asynchronous presentation">
The content of this surface is meant to be presented with minimal
latency and tearing is acceptable.
</description>
</entry>
</enum>
<request name="set_presentation_hint">
<description summary="set presentation hint">
Set the presentation hint for the associated wl_surface. This state is
double-buffered, see wl_surface.commit.
The compositor is free to dynamically respect or ignore this hint based
on various conditions like hardware capabilities, surface state and
user preferences.
</description>
<arg name="hint" type="uint" enum="presentation_hint"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy tearing control object">
Destroy this surface tearing object and revert the presentation hint to
vsync. The change will be applied on the next wl_surface.commit.
</description>
</request>
</interface>
</protocol>
@@ -0,0 +1,5 @@
Linux explicit synchronization (dma-fence) protocol
Maintainers:
Daniel Stone <daniels@collabora.com>
Alexandros Frantzis <alexandros.frantzis@collabora.com>
@@ -0,0 +1,256 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="zwp_linux_explicit_synchronization_unstable_v1">
<copyright>
Copyright 2016 The Chromium Authors.
Copyright 2017 Intel Corporation
Copyright 2018 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.
</copyright>
<interface name="zwp_linux_explicit_synchronization_v1" version="2">
<description summary="protocol for providing explicit synchronization">
This global is a factory interface, allowing clients to request
explicit synchronization for buffers on a per-surface basis.
See zwp_linux_surface_synchronization_v1 for more information.
This interface is derived from Chromium's
zcr_linux_explicit_synchronization_v1.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<request name="destroy" type="destructor">
<description summary="destroy explicit synchronization factory object">
Destroy this explicit synchronization factory object. Other objects,
including zwp_linux_surface_synchronization_v1 objects created by this
factory, shall not be affected by this request.
</description>
</request>
<enum name="error">
<entry name="synchronization_exists" value="0"
summary="the surface already has a synchronization object associated"/>
</enum>
<request name="get_synchronization">
<description summary="extend surface interface for explicit synchronization">
Instantiate an interface extension for the given wl_surface to provide
explicit synchronization.
If the given wl_surface already has an explicit synchronization object
associated, the synchronization_exists protocol error is raised.
Graphics APIs, like EGL or Vulkan, that manage the buffer queue and
commits of a wl_surface themselves, are likely to be using this
extension internally. If a client is using such an API for a
wl_surface, it should not directly use this extension on that surface,
to avoid raising a synchronization_exists protocol error.
</description>
<arg name="id" type="new_id"
interface="zwp_linux_surface_synchronization_v1"
summary="the new synchronization interface id"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface"/>
</request>
</interface>
<interface name="zwp_linux_surface_synchronization_v1" version="2">
<description summary="per-surface explicit synchronization support">
This object implements per-surface explicit synchronization.
Synchronization refers to co-ordination of pipelined operations performed
on buffers. Most GPU clients will schedule an asynchronous operation to
render to the buffer, then immediately send the buffer to the compositor
to be attached to a surface.
In implicit synchronization, ensuring that the rendering operation is
complete before the compositor displays the buffer is an implementation
detail handled by either the kernel or userspace graphics driver.
By contrast, in explicit synchronization, dma_fence objects mark when the
asynchronous operations are complete. When submitting a buffer, the
client provides an acquire fence which will be waited on before the
compositor accesses the buffer. The Wayland server, through a
zwp_linux_buffer_release_v1 object, will inform the client with an event
which may be accompanied by a release fence, when the compositor will no
longer access the buffer contents due to the specific commit that
requested the release event.
Each surface can be associated with only one object of this interface at
any time.
In version 1 of this interface, explicit synchronization is only
guaranteed to be supported for buffers created with any version of the
wp_linux_dmabuf buffer factory. Version 2 additionally guarantees
explicit synchronization support for opaque EGL buffers, which is a type
of platform specific buffers described in the EGL_WL_bind_wayland_display
extension. Compositors are free to support explicit synchronization for
additional buffer types.
</description>
<request name="destroy" type="destructor">
<description summary="destroy synchronization object">
Destroy this explicit synchronization object.
Any fence set by this object with set_acquire_fence since the last
commit will be discarded by the server. Any fences set by this object
before the last commit are not affected.
zwp_linux_buffer_release_v1 objects created by this object are not
affected by this request.
</description>
</request>
<enum name="error">
<entry name="invalid_fence" value="0"
summary="the fence specified by the client could not be imported"/>
<entry name="duplicate_fence" value="1"
summary="multiple fences added for a single surface commit"/>
<entry name="duplicate_release" value="2"
summary="multiple releases added for a single surface commit"/>
<entry name="no_surface" value="3"
summary="the associated wl_surface was destroyed"/>
<entry name="unsupported_buffer" value="4"
summary="the buffer does not support explicit synchronization"/>
<entry name="no_buffer" value="5"
summary="no buffer was attached"/>
</enum>
<request name="set_acquire_fence">
<description summary="set the acquire fence">
Set the acquire fence that must be signaled before the compositor
may sample from the buffer attached with wl_surface.attach. The fence
is a dma_fence kernel object.
The acquire fence is double-buffered state, and will be applied on the
next wl_surface.commit request for the associated surface. Thus, it
applies only to the buffer that is attached to the surface at commit
time.
If the provided fd is not a valid dma_fence fd, then an INVALID_FENCE
error is raised.
If a fence has already been attached during the same commit cycle, a
DUPLICATE_FENCE error is raised.
If the associated wl_surface was destroyed, a NO_SURFACE error is
raised.
If at surface commit time the attached buffer does not support explicit
synchronization, an UNSUPPORTED_BUFFER error is raised.
If at surface commit time there is no buffer attached, a NO_BUFFER
error is raised.
</description>
<arg name="fd" type="fd" summary="acquire fence fd"/>
</request>
<request name="get_release">
<description summary="release fence for last-attached buffer">
Create a listener for the release of the buffer attached by the
client with wl_surface.attach. See zwp_linux_buffer_release_v1
documentation for more information.
The release object is double-buffered state, and will be associated
with the buffer that is attached to the surface at wl_surface.commit
time.
If a zwp_linux_buffer_release_v1 object has already been requested for
the surface in the same commit cycle, a DUPLICATE_RELEASE error is
raised.
If the associated wl_surface was destroyed, a NO_SURFACE error
is raised.
If at surface commit time there is no buffer attached, a NO_BUFFER
error is raised.
</description>
<arg name="release" type="new_id" interface="zwp_linux_buffer_release_v1"
summary="new zwp_linux_buffer_release_v1 object"/>
</request>
</interface>
<interface name="zwp_linux_buffer_release_v1" version="1">
<description summary="buffer release explicit synchronization">
This object is instantiated in response to a
zwp_linux_surface_synchronization_v1.get_release request.
It provides an alternative to wl_buffer.release events, providing a
unique release from a single wl_surface.commit request. The release event
also supports explicit synchronization, providing a fence FD for the
client to synchronize against.
Exactly one event, either a fenced_release or an immediate_release, will
be emitted for the wl_surface.commit request. The compositor can choose
release by release which event it uses.
This event does not replace wl_buffer.release events; servers are still
required to send those events.
Once a buffer release object has delivered a 'fenced_release' or an
'immediate_release' event it is automatically destroyed.
</description>
<event name="fenced_release" type="destructor">
<description summary="release buffer with fence">
Sent when the compositor has finalised its usage of the associated
buffer for the relevant commit, providing a dma_fence which will be
signaled when all operations by the compositor on that buffer for that
commit have finished.
Once the fence has signaled, and assuming the associated buffer is not
pending release from other wl_surface.commit requests, no additional
explicit or implicit synchronization is required to safely reuse or
destroy the buffer.
This event destroys the zwp_linux_buffer_release_v1 object.
</description>
<arg name="fence" type="fd" summary="fence for last operation on buffer"/>
</event>
<event name="immediate_release" type="destructor">
<description summary="release buffer immediately">
Sent when the compositor has finalised its usage of the associated
buffer for the relevant commit, and either performed no operations
using it, or has a guarantee that all its operations on that buffer for
that commit have finished.
Once this event is received, and assuming the associated buffer is not
pending release from other wl_surface.commit requests, no additional
explicit or implicit synchronization is required to safely reuse or
destroy the buffer.
This event destroys the zwp_linux_buffer_release_v1 object.
</description>
</event>
</interface>
</protocol>