1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-25 15:37:42 +00:00

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

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

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."));
}
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();

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"]:

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -0,0 +1,630 @@
/**************************************************************************/
/* wayland_embedder.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
#ifdef WAYLAND_ENABLED
#ifdef TOOLS_ENABLED
#include "core/templates/a_hash_map.h"
#include "core/templates/pooled_list.h"
#ifdef SOWRAP_ENABLED
#include "wayland/dynwrappers/wayland-client-core-so_wrap.h"
#else
#include <wayland-client-core.h>
#endif
#include "protocol/wayland.gen.h"
#include "protocol/linux_dmabuf_v1.gen.h"
#include "protocol/xdg_shell.gen.h"
#include "protocol/commit_timing_v1.gen.h"
#include "protocol/cursor_shape.gen.h"
#include "protocol/fifo_v1.gen.h"
#include "protocol/fractional_scale.gen.h"
#include "protocol/godot_embedding_compositor.gen.h"
#include "protocol/idle_inhibit.gen.h"
#include "protocol/linux_drm_syncobj_v1.gen.h"
#include "protocol/linux_explicit_synchronization_unstable_v1.gen.h"
#include "protocol/pointer_constraints.gen.h"
#include "protocol/pointer_gestures.gen.h"
#include "protocol/primary_selection.gen.h"
#include "protocol/relative_pointer.gen.h"
#include "protocol/tablet.gen.h"
#include "protocol/tearing_control_v1.gen.h"
#include "protocol/text_input.gen.h"
#include "protocol/viewporter.gen.h"
#include "protocol/wayland-drm.gen.h"
#include "protocol/xdg_activation.gen.h"
#include "protocol/xdg_decoration.gen.h"
#include "protocol/xdg_foreign_v1.gen.h"
#include "protocol/xdg_foreign_v2.gen.h"
#include "protocol/xdg_shell.gen.h"
#include "protocol/xdg_system_bell.gen.h"
#include "protocol/xdg_toplevel_icon.gen.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <poll.h>
#include "core/io/dir_access.h"
#include "core/os/thread.h"
// TODO: Consider resizing the ancillary buffer dynamically.
#define EMBED_ANCILLARY_BUF_SIZE 4096
class WaylandEmbedder {
enum class ProxyDirection {
CLIENT,
COMPOSITOR,
};
enum class MessageStatus {
HANDLED,
UNHANDLED,
INVALID,
ERROR,
};
struct msg_info {
uint32_t raw_id = 0;
uint16_t size = 0;
uint16_t opcode = 0;
pid_t pid = 0;
ProxyDirection direction = ProxyDirection::CLIENT;
constexpr size_t words() const { return (size / sizeof(uint32_t)); }
};
struct WaylandObjectData {
virtual ~WaylandObjectData() = default;
};
struct WaylandObject {
const struct wl_interface *interface = nullptr;
int version = 0;
// Inert, awaiting confirmation from server.
bool destroyed = false;
// Other objects might depend on it and must not be destroyed.
bool shared = false;
WaylandObjectData *data = nullptr;
};
struct WaylandDrmGlobalData : WaylandObjectData {
String device;
LocalVector<uint32_t> formats;
bool authenticated;
uint32_t capabilities;
};
struct WaylandShmGlobalData : WaylandObjectData {
LocalVector<uint32_t> formats;
};
struct Client {
struct GlobalIdInfo {
uint32_t id = INVALID_ID;
List<uint32_t>::Element *history_elem = nullptr;
GlobalIdInfo() = default;
GlobalIdInfo(uint32_t p_id, List<uint32_t>::Element *p_history_elem) :
id(p_id), history_elem(p_history_elem) {}
};
WaylandEmbedder *embedder = nullptr;
int socket = -1;
// NOTE: PIDs are not unique per client!
pid_t pid = 0;
// FIXME: Names suck.
AHashMap<uint32_t, HashSet<uint32_t>> registry_globals_instances;
HashSet<uint32_t> wl_registry_instances;
List<uint32_t> global_id_history;
AHashMap<uint32_t, GlobalIdInfo> global_ids;
AHashMap<uint32_t, uint32_t> local_ids;
// Objects with no equivalent on the real compositor.
AHashMap<uint32_t, WaylandObject> fake_objects;
// Objects which mirror events of a global object.
AHashMap<uint32_t, WaylandObject> global_instances;
uint32_t embedded_client_id = INVALID_ID;
uint32_t embedded_window_id = INVALID_ID;
List<int> fds;
// Clients obviously expect properly packed server IDs, so we need to allocate
// them somehow. This approach mimics the one used in PooledList.
uint32_t allocated_server_ids = INVALID_ID;
LocalVector<uint32_t> free_server_ids;
uint32_t get_global_id(uint32_t p_local_id) const { return global_ids.has(p_local_id) ? global_ids[p_local_id].id : INVALID_ID; }
uint32_t get_local_id(uint32_t p_global_id) const { return local_ids.has(p_global_id) ? local_ids[p_global_id] : INVALID_ID; }
uint32_t allocate_server_id();
WaylandObject *get_object(uint32_t p_local_id);
Error delete_object(uint32_t p_local_id);
Error bind_global_id(uint32_t p_global_id, uint32_t p_local_id);
uint32_t new_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr);
uint32_t new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr);
WaylandObject *new_fake_object(uint32_t p_local_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr);
WaylandObject *new_global_instance(uint32_t p_local_id, uint32_t p_global_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr);
Error send_wl_drm_state(uint32_t p_id, WaylandDrmGlobalData *p_state);
};
// Local IDs are a mess to handle as they strictly depend on their client of
// origin. This wrapper helps with that.
class LocalObjectHandle {
Client *client = nullptr;
uint32_t local_id = INVALID_ID;
public:
constexpr LocalObjectHandle() = default;
constexpr LocalObjectHandle(Client *p_client, uint32_t p_id) :
client(p_client), local_id(p_id) {}
void invalidate() {
client = nullptr;
local_id = INVALID_ID;
}
constexpr bool is_valid() const { return client != nullptr && local_id != INVALID_ID; }
WaylandObject *get() { return is_valid() ? client->get_object(local_id) : nullptr; }
constexpr Client *get_client() const { return client; }
constexpr uint32_t get_local_id() const { return local_id; }
uint32_t get_global_id() const { return (is_valid() && client->global_ids.has(local_id)) ? client->global_ids[local_id].id : INVALID_ID; }
};
struct WaylandSeatInstanceData : WaylandObjectData {
uint32_t wl_keyboard_id = INVALID_ID;
uint32_t wl_pointer_id = INVALID_ID;
};
struct WaylandSeatGlobalData : WaylandObjectData {
uint32_t capabilities = 0;
uint32_t pointed_surface_id = INVALID_ID;
uint32_t focused_surface_id = INVALID_ID;
};
struct WaylandKeyboardData : WaylandObjectData {
uint32_t wl_seat_id = INVALID_ID;
};
struct WaylandPointerData : WaylandObjectData {
uint32_t wl_seat_id = INVALID_ID;
};
struct WaylandSurfaceData : WaylandObjectData {
Client *client = nullptr;
LocalObjectHandle role_object_handle;
};
struct XdgSurfaceData : WaylandObjectData {
uint32_t wl_surface_id = INVALID_ID;
};
struct WaylandSubsurfaceData : WaylandObjectData {
Point2i position;
};
struct XdgToplevelData : WaylandObjectData {
LocalObjectHandle xdg_surface_handle;
LocalObjectHandle parent_handle;
uint32_t wl_subsurface_id = INVALID_ID;
Size2i size;
bool configured = false;
constexpr bool is_embedded() const { return wl_subsurface_id != INVALID_ID; }
};
struct XdgPopupData : WaylandObjectData {
LocalObjectHandle parent_handle;
};
struct XdgPositionerData : WaylandObjectData {
Rect2i anchor_rect;
};
struct EmbeddedClientData : WaylandObjectData {
Client *client = nullptr;
bool disconnected = false;
};
struct RegistryGlobalInfo {
const struct wl_interface *interface = nullptr;
uint32_t version = 0;
uint32_t compositor_name = 0;
// The specs requires for us to ignore requests for destroyed global
// objects until all instances are gone, to avoid races.
bool destroyed = false;
int instance_counter = 0;
// Key is version.
HashMap<uint32_t, uint32_t> reusable_objects;
WaylandObjectData *data = nullptr;
};
// These are the interfaces that the embedder understands and exposes. We do
// not implement handlers for all of them (that's the point), but we need to
// list them anyways to query their signatures at runtime, which include file
// descriptors count. Additionally, even if we could go without specifying
// them, having a "known good" list avoids unpleasant incompatibilities with
// future compositors.
const static constexpr struct wl_interface *interfaces[] = {
// wayland
&wl_buffer_interface,
&wl_callback_interface,
&wl_compositor_interface,
&wl_data_device_interface,
&wl_data_device_manager_interface,
&wl_data_offer_interface,
&wl_data_source_interface,
&wl_display_interface,
&wl_keyboard_interface,
&wl_output_interface,
&wl_pointer_interface,
&wl_region_interface,
&wl_registry_interface,
&wl_seat_interface,
//&wl_shell_interface, // Deprecated.
//&wl_shell_surface_interface, // Deprecated.
&wl_shm_interface,
&wl_shm_pool_interface,
&wl_subcompositor_interface,
&wl_subsurface_interface,
&wl_surface_interface,
//&wl_touch_interface, // Unused (at the moment).
// xdg-shell
&xdg_wm_base_interface,
&xdg_positioner_interface,
&xdg_surface_interface,
&xdg_toplevel_interface,
&xdg_popup_interface,
// linux-dmabuf-v1
&zwp_linux_dmabuf_v1_interface,
&zwp_linux_buffer_params_v1_interface,
&zwp_linux_dmabuf_feedback_v1_interface,
// linux-explicit-synchronization-unstable-v1
&zwp_linux_explicit_synchronization_v1_interface,
&zwp_linux_surface_synchronization_v1_interface,
&zwp_linux_buffer_release_v1_interface,
// fractional-scale
&wp_fractional_scale_manager_v1_interface,
&wp_fractional_scale_v1_interface,
// idle-inhibit
&zwp_idle_inhibit_manager_v1_interface,
&zwp_idle_inhibitor_v1_interface,
// pointer-constraints
&zwp_pointer_constraints_v1_interface,
&zwp_locked_pointer_v1_interface,
&zwp_confined_pointer_v1_interface,
// pointer-gestures
&zwp_pointer_gestures_v1_interface,
&zwp_pointer_gesture_swipe_v1_interface,
&zwp_pointer_gesture_pinch_v1_interface,
&zwp_pointer_gesture_hold_v1_interface,
// primary-selection
&zwp_primary_selection_device_manager_v1_interface,
&zwp_primary_selection_device_v1_interface,
&zwp_primary_selection_offer_v1_interface,
&zwp_primary_selection_source_v1_interface,
// relative-pointer
&zwp_relative_pointer_manager_v1_interface,
&zwp_relative_pointer_v1_interface,
// tablet
// TODO: Needs some extra work
//&zwp_tablet_manager_v2_interface,
//&zwp_tablet_seat_v2_interface,
//&zwp_tablet_tool_v2_interface,
//&zwp_tablet_v2_interface,
//&zwp_tablet_pad_ring_v2_interface,
//&zwp_tablet_pad_strip_v2_interface,
//&zwp_tablet_pad_group_v2_interface,
//&zwp_tablet_pad_v2_interface,
// text-input
&zwp_text_input_v3_interface,
&zwp_text_input_manager_v3_interface,
// viewporter
&wp_viewporter_interface,
&wp_viewport_interface,
// xdg-activation
&xdg_activation_v1_interface,
&xdg_activation_token_v1_interface,
// xdg-decoration
&zxdg_decoration_manager_v1_interface,
&zxdg_toplevel_decoration_v1_interface,
// xdg-foreign
&zxdg_exporter_v1_interface,
&zxdg_importer_v1_interface,
// xdg-foreign-v1
&zxdg_exporter_v1_interface,
&zxdg_importer_v1_interface,
// xdg-foreign-v2
&zxdg_exporter_v2_interface,
&zxdg_importer_v2_interface,
// xdg-shell
&xdg_wm_base_interface,
&xdg_positioner_interface,
&xdg_surface_interface,
&xdg_toplevel_interface,
&xdg_popup_interface,
// xdg-system-bell
&xdg_system_bell_v1_interface,
// xdg-toplevel-icon-v1
&xdg_toplevel_icon_manager_v1_interface,
&xdg_toplevel_icon_v1_interface,
// wp-cursor-shape-v1
&wp_cursor_shape_manager_v1_interface,
// wayland-drm
&wl_drm_interface,
// linux-drm-syncobj-v1
&wp_linux_drm_syncobj_manager_v1_interface,
&wp_linux_drm_syncobj_surface_v1_interface,
&wp_linux_drm_syncobj_timeline_v1_interface,
// fifo-v1
&wp_fifo_manager_v1_interface,
&wp_fifo_v1_interface,
// commit-timing-v1
&wp_commit_timing_manager_v1_interface,
&wp_commit_timer_v1_interface,
// tearing-control-v1
&wp_tearing_control_manager_v1_interface,
&wp_tearing_control_v1_interface,
// Our custom things.
&godot_embedding_compositor_interface,
&godot_embedded_client_interface,
};
// These interfaces will not be reported to embedded clients. This includes
// stuff that interacts with toplevels or other emulated objects that would
// have been filtered out manually anyways.
HashSet<const struct wl_interface *> embedded_interface_deny_list = HashSet({
&zxdg_decoration_manager_v1_interface,
&zxdg_decoration_manager_v1_interface,
&zxdg_exporter_v1_interface,
&zxdg_exporter_v2_interface,
&xdg_toplevel_icon_manager_v1_interface,
&godot_embedding_compositor_interface,
});
static constexpr uint32_t INVALID_ID = 0;
static constexpr uint32_t DISPLAY_ID = 1;
static constexpr uint32_t REGISTRY_ID = 2;
int proxy_socket = -1;
int compositor_socket = -1;
// NOTE: First element must be the listening socket! This allows us to process
// it last, cleaning up closed sockets before it reuses their handles.
LocalVector<struct pollfd> pollfds;
// Key is socket.
AHashMap<int, Client> clients;
Client *main_client = nullptr;
PooledList<WaylandObject> objects;
// Proxies allocated by the compositor. Their ID starts from 0xff000000.
LocalVector<WaylandObject> server_objects;
uint32_t wl_compositor_id = 0;
uint32_t wl_subcompositor_id = 0;
uint32_t main_toplevel_id = 0;
uint32_t xdg_wm_base_id = 0;
// Global id to name
HashMap<uint32_t, uint32_t> registry_globals_names;
HashMap<uint32_t, RegistryGlobalInfo> registry_globals;
uint32_t registry_globals_counter = 0;
uint32_t godot_embedding_compositor_name = 0;
LocalVector<uint32_t> wl_seat_names;
Thread proxy_thread;
List<int> client_fds;
List<int> compositor_fds;
uint32_t serial_counter = 0;
uint32_t configure_serial_counter = 0;
uint32_t sync_callback_id = 0;
Ref<DirAccess> runtime_dir;
int lock_fd = -1;
String socket_path;
String socket_lock_path;
LocalVector<uint32_t> msg_buf;
LocalVector<uint8_t> ancillary_buf;
SafeFlag thread_done;
static size_t wl_array_word_offset(uint32_t p_size);
const static struct wl_interface *wl_interface_from_string(const char *name, size_t size);
static int wl_interface_get_destructor_opcode(const struct wl_interface *p_iface, uint32_t p_version);
static Error send_raw_message(int p_socket, std::initializer_list<struct iovec> p_vecs, const LocalVector<int> &p_fds = LocalVector<int>());
static Error send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, const uint32_t *p_args, const size_t p_args_words);
static Error send_wayland_message(ProxyDirection p_direction, int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector<union wl_argument> &p_args);
// Utility aliases.
static Error send_wayland_message(int p_socket, uint32_t p_id, uint32_t p_opcode, std::initializer_list<uint32_t> p_args) {
return send_wayland_message(p_socket, p_id, p_opcode, p_args.begin(), p_args.size());
}
static Error send_wayland_method(int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector<union wl_argument> &p_args) {
return send_wayland_message(ProxyDirection::COMPOSITOR, p_socket, p_id, p_interface, p_opcode, p_args);
}
static Error send_wayland_event(int p_socket, uint32_t p_id, const struct wl_interface &p_interface, uint32_t p_opcode, const LocalVector<union wl_argument> &p_args) {
return send_wayland_message(ProxyDirection::CLIENT, p_socket, p_id, p_interface, p_opcode, p_args);
}
// Closes the socket.
static void socket_error(int p_socket, uint32_t p_object_id, uint32_t p_code, const String &p_message);
// NOTE: Yes, in our case object arguments are actually uints for now.
// Best way I found to reuse the Wayland stuff. Might need to make our
// own eventually.
static constexpr union wl_argument wl_arg_int(int32_t p_value) {
union wl_argument arg = {};
arg.i = p_value;
return arg;
}
static constexpr union wl_argument wl_arg_uint(uint32_t p_value) {
union wl_argument arg = {};
arg.u = p_value;
return arg;
}
static constexpr union wl_argument wl_arg_fixed(wl_fixed_t p_value) {
union wl_argument arg = {};
arg.f = p_value;
return arg;
}
static constexpr union wl_argument wl_arg_string(const char *p_value) {
union wl_argument arg = {};
arg.s = p_value;
return arg;
}
static constexpr union wl_argument wl_arg_object(uint32_t p_value) {
union wl_argument arg = {};
arg.u = p_value;
return arg;
}
static constexpr union wl_argument wl_arg_new_id(uint32_t p_value) {
union wl_argument arg = {};
arg.n = p_value;
return arg;
}
uint32_t new_object(const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr);
WaylandObject *new_server_object(uint32_t p_global_id, const struct wl_interface *p_interface, int p_version = 1, WaylandObjectData *p_data = nullptr);
void poll_sockets();
int allocate_global_id();
bool global_surface_is_window(uint32_t p_global_surface_id);
WaylandObject *get_object(uint32_t id);
Error delete_object(uint32_t id);
void cleanup_socket(int p_socket);
void sync();
uint32_t wl_registry_bind(uint32_t p_registry_id, uint32_t p_name, int p_version);
void seat_name_enter_surface(uint32_t p_seat_name, uint32_t p_global_surface_id);
void seat_name_leave_surface(uint32_t p_seat_name, uint32_t p_global_surface_id);
MessageStatus handle_request(LocalObjectHandle p_object, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len);
MessageStatus handle_event(uint32_t p_global_id, LocalObjectHandle p_local_handle, uint32_t p_opcode, const uint32_t *msg_data, size_t msg_len);
void shutdown();
bool handle_generic_msg(Client *client, const WaylandObject *p_object, const struct wl_message *message, const struct msg_info *info, uint32_t *buf, uint32_t instance_id = INVALID_ID);
Error handle_msg_info(Client *client, const struct msg_info *info, uint32_t *buf, int *fds_requested);
Error handle_sock(int p_fd);
void handle_fd(int p_fd, int p_revents);
static void _thread_loop(void *p_data);
public:
// Returns path to socket.
Error init();
String get_socket_path() const { return socket_path; }
~WaylandEmbedder();
};
#endif // TOOLS_ENABLED
#endif // WAYLAND_ENABLED

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();
#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,6 +4635,8 @@ 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.");
// 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.");
@@ -4473,6 +4645,11 @@ Error WaylandThread::init() {
#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) {
WARN_PRINT("Can't obtain the XDG activation global. Attention requesting won't work!");
}
@@ -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);
}

View File

@@ -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();
};

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

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>

View File

@@ -0,0 +1,5 @@
Linux DMA-BUF protocol
Maintainers:
Pekka Paalanen <pekka.paalanen@collabora.co.uk>
Daniel Stone <daniels@collabora.com>

View File

@@ -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

View File

@@ -0,0 +1,585 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="linux_dmabuf_v1">
<copyright>
Copyright © 2014, 2015 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_dmabuf_v1" version="5">
<description summary="factory for creating dmabuf-based wl_buffers">
Following the interfaces from:
https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
and the Linux DRM sub-system's AddFb2 ioctl.
This interface offers ways to create generic dmabuf-based wl_buffers.
Clients can use the get_surface_feedback request to get dmabuf feedback
for a particular surface. If the client wants to retrieve feedback not
tied to a surface, they can use the get_default_feedback request.
The following are required from clients:
- Clients must ensure that either all data in the dma-buf is
coherent for all subsequent read access or that coherency is
correctly handled by the underlying kernel-side dma-buf
implementation.
- Don't make any more attachments after sending the buffer to the
compositor. Making more attachments later increases the risk of
the compositor not being able to use (re-import) an existing
dmabuf-based wl_buffer.
The underlying graphics stack must ensure the following:
- The dmabuf file descriptors relayed to the server will stay valid
for the whole lifetime of the wl_buffer. This means the server may
at any time use those fds to import the dmabuf into any kernel
sub-system that might accept it.
However, when the underlying graphics stack fails to deliver the
promise, because of e.g. a device hot-unplug which raises internal
errors, after the wl_buffer has been successfully created the
compositor must not raise protocol errors to the client when dmabuf
import later fails.
To create a wl_buffer from one or more dmabufs, a client creates a
zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
request. All planes required by the intended format are added with
the 'add' request. Finally, a 'create' or 'create_immed' request is
issued, which has the following outcome depending on the import success.
The 'create' request,
- on success, triggers a 'created' event which provides the final
wl_buffer to the client.
- on failure, triggers a 'failed' event to convey that the server
cannot use the dmabufs received from the client.
For the 'create_immed' request,
- on success, the server immediately imports the added dmabufs to
create a wl_buffer. No event is sent from the server in this case.
- on failure, the server can choose to either:
- terminate the client by raising a fatal error.
- mark the wl_buffer as failed, and send a 'failed' event to the
client. If the client uses a failed wl_buffer as an argument to any
request, the behaviour is compositor implementation-defined.
For all DRM formats and unless specified in another protocol extension,
pre-multiplied alpha is used for pixel values.
Unless specified otherwise in another protocol extension, implicit
synchronization is used. In other words, compositors and clients must
wait and signal fences implicitly passed via the DMA-BUF's reservation
mechanism.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the factory">
Objects created through this interface, especially wl_buffers, will
remain valid.
</description>
</request>
<request name="create_params">
<description summary="create a temporary object for buffer parameters">
This temporary object is used to collect multiple dmabuf handles into
a single batch to create a wl_buffer. It can only be used once and
should be destroyed after a 'created' or 'failed' event has been
received.
</description>
<arg name="params_id" type="new_id" interface="zwp_linux_buffer_params_v1"
summary="the new temporary"/>
</request>
<event name="format">
<description summary="supported buffer format">
This event advertises one buffer format that the server supports.
All the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees
that the client has received all supported formats.
For the definition of the format codes, see the
zwp_linux_buffer_params_v1::create request.
Starting version 4, the format event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
</event>
<event name="modifier" since="3">
<description summary="supported buffer format modifier">
This event advertises the formats that the server supports, along with
the modifiers supported for each format. All the supported modifiers
for all the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees that
the client has received all supported format-modifier pairs.
For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi ==
0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event.
It indicates that the server can support the format with an implicit
modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
For the definition of the format and modifier codes, see the
zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add
requests.
Starting version 4, the modifier event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</event>
<!-- Version 4 additions -->
<request name="get_default_feedback" since="4">
<description summary="get default feedback">
This request creates a new wp_linux_dmabuf_feedback object not bound
to a particular surface. This object will deliver feedback about dmabuf
parameters to use if the client doesn't support per-surface feedback
(see get_surface_feedback).
</description>
<arg name="id" type="new_id" interface="zwp_linux_dmabuf_feedback_v1"/>
</request>
<request name="get_surface_feedback" since="4">
<description summary="get feedback for a surface">
This request creates a new wp_linux_dmabuf_feedback object for the
specified wl_surface. This object will deliver feedback about dmabuf
parameters to use for buffers attached to this surface.
If the surface is destroyed before the wp_linux_dmabuf_feedback object,
the feedback object becomes inert.
</description>
<arg name="id" type="new_id" interface="zwp_linux_dmabuf_feedback_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="zwp_linux_buffer_params_v1" version="5">
<description summary="parameters for creating a dmabuf-based wl_buffer">
This temporary object is a collection of dmabufs and other
parameters that together form a single logical buffer. The temporary
object may eventually create one wl_buffer unless cancelled by
destroying it before requesting 'create'.
Single-planar formats only require one dmabuf, however
multi-planar formats may require more than one dmabuf. For all
formats, an 'add' request must be called once per plane (even if the
underlying dmabuf fd is identical).
You must use consecutive plane indices ('plane_idx' argument for 'add')
from zero to the number of planes used by the drm_fourcc format code.
All planes required by the format must be given exactly once, but can
be given in any order. Each plane index can be set only once.
</description>
<enum name="error">
<entry name="already_used" value="0"
summary="the dmabuf_batch object has already been used to create a wl_buffer"/>
<entry name="plane_idx" value="1"
summary="plane index out of bounds"/>
<entry name="plane_set" value="2"
summary="the plane index was already set"/>
<entry name="incomplete" value="3"
summary="missing or too many planes to create a buffer"/>
<entry name="invalid_format" value="4"
summary="format not supported"/>
<entry name="invalid_dimensions" value="5"
summary="invalid width or height"/>
<entry name="out_of_bounds" value="6"
summary="offset + stride * height goes out of dmabuf bounds"/>
<entry name="invalid_wl_buffer" value="7"
summary="invalid wl_buffer resulted from importing dmabufs via
the create_immed request on given buffer_params"/>
</enum>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Cleans up the temporary data sent to the server for dmabuf-based
wl_buffer creation.
</description>
</request>
<request name="add">
<description summary="add a dmabuf to the temporary set">
This request adds one dmabuf to the set in this
zwp_linux_buffer_params_v1.
The 64-bit unsigned value combined from modifier_hi and modifier_lo
is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
fb modifier, which is defined in drm_mode.h of Linux UAPI.
This is an opaque token. Drivers use this token to express tiling,
compression, etc. driver-specific modifications to the base format
defined by the DRM fourcc code.
Starting from version 4, the invalid_format protocol error is sent if
the format + modifier pair was not advertised as supported.
Starting from version 5, the invalid_format protocol error is sent if
all planes don't use the same modifier.
This request raises the PLANE_IDX error if plane_idx is too large.
The error PLANE_SET is raised if attempting to set a plane that
was already set.
</description>
<arg name="fd" type="fd" summary="dmabuf fd"/>
<arg name="plane_idx" type="uint" summary="plane index"/>
<arg name="offset" type="uint" summary="offset in bytes"/>
<arg name="stride" type="uint" summary="stride in bytes"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</request>
<enum name="flags" bitfield="true">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
<entry name="interlaced" value="2" summary="content is interlaced"/>
<entry name="bottom_first" value="4" summary="bottom field first"/>
</enum>
<request name="create">
<description summary="create a wl_buffer from the given dmabufs">
This asks for creation of a wl_buffer from the added dmabuf
buffers. The wl_buffer is not created immediately but returned via
the 'created' event if the dmabuf sharing succeeds. The sharing
may fail at runtime for reasons a client cannot predict, in
which case the 'failed' event is triggered.
The 'format' argument is a DRM_FORMAT code, as defined by the
libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
authoritative source on how the format codes should work.
The 'flags' is a bitfield of the flags defined in enum "flags".
'y_invert' means the that the image needs to be y-flipped.
Flag 'interlaced' means that the frame in the buffer is not
progressive as usual, but interlaced. An interlaced buffer as
supported here must always contain both top and bottom fields.
The top field always begins on the first pixel row. The temporal
ordering between the two fields is top field first, unless
'bottom_first' is specified. It is undefined whether 'bottom_first'
is ignored if 'interlaced' is not set.
This protocol does not convey any information about field rate,
duration, or timing, other than the relative ordering between the
two fields in one buffer. A compositor may have to estimate the
intended field rate from the incoming buffer rate. It is undefined
whether the time of receiving wl_surface.commit with a new buffer
attached, applying the wl_surface state, wl_surface.frame callback
trigger, presentation, or any other point in the compositor cycle
is used to measure the frame or field times. There is no support
for detecting missed or late frames/fields/buffers either, and
there is no support whatsoever for cooperating with interlaced
compositor output.
The composited image quality resulting from the use of interlaced
buffers is explicitly undefined. A compositor may use elaborate
hardware features or software to deinterlace and create progressive
output frames from a sequence of interlaced input buffers, or it
may produce substandard image quality. However, compositors that
cannot guarantee reasonable image quality in all cases are recommended
to just reject all interlaced buffers.
Any argument errors, including non-positive width or height,
mismatch between the number of planes and the format, bad
format, bad offset or stride, may be indicated by fatal protocol
errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
OUT_OF_BOUNDS.
Dmabuf import errors in the server that are not obvious client
bugs are returned via the 'failed' event as non-fatal. This
allows attempting dmabuf sharing and falling back in the client
if it fails.
This request can be sent only once in the object's lifetime, after
which the only legal request is destroy. This object should be
destroyed after issuing a 'create' request. Attempting to use this
object after issuing 'create' raises ALREADY_USED protocol error.
It is not mandatory to issue 'create'. If a client wants to
cancel the buffer creation, it can just destroy this object.
</description>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" enum="flags" summary="see enum flags"/>
</request>
<event name="created">
<description summary="buffer creation succeeded">
This event indicates that the attempted buffer creation was
successful. It provides the new wl_buffer referencing the dmabuf(s).
Upon receiving this event, the client should destroy the
zwp_linux_buffer_params_v1 object.
</description>
<arg name="buffer" type="new_id" interface="wl_buffer"
summary="the newly created wl_buffer"/>
</event>
<event name="failed">
<description summary="buffer creation failed">
This event indicates that the attempted buffer creation has
failed. It usually means that one of the dmabuf constraints
has not been fulfilled.
Upon receiving this event, the client should destroy the
zwp_linux_buffer_params_v1 object.
</description>
</event>
<request name="create_immed" since="2">
<description summary="immediately create a wl_buffer from the given
dmabufs">
This asks for immediate creation of a wl_buffer by importing the
added dmabufs.
In case of import success, no event is sent from the server, and the
wl_buffer is ready to be used by the client.
Upon import failure, either of the following may happen, as seen fit
by the implementation:
- the client is terminated with one of the following fatal protocol
errors:
- INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
in case of argument errors such as mismatch between the number
of planes and the format, bad format, non-positive width or
height, or bad offset or stride.
- INVALID_WL_BUFFER, in case the cause for failure is unknown or
plaform specific.
- the server creates an invalid wl_buffer, marks it as failed and
sends a 'failed' event to the client. The result of using this
invalid wl_buffer as an argument in any request by the client is
defined by the compositor implementation.
This takes the same arguments as a 'create' request, and obeys the
same restrictions.
</description>
<arg name="buffer_id" type="new_id" interface="wl_buffer"
summary="id for the newly created wl_buffer"/>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" enum="flags" summary="see enum flags"/>
</request>
</interface>
<interface name="zwp_linux_dmabuf_feedback_v1" version="5">
<description summary="dmabuf feedback">
This object advertises dmabuf parameters feedback. This includes the
preferred devices and the supported formats/modifiers.
The parameters are sent once when this object is created and whenever they
change. The done event is always sent once after all parameters have been
sent. When a single parameter changes, all parameters are re-sent by the
compositor.
Compositors can re-send the parameters when the current client buffer
allocations are sub-optimal. Compositors should not re-send the
parameters if re-allocating the buffers would not result in a more optimal
configuration. In particular, compositors should avoid sending the exact
same parameters multiple times in a row.
The tranche_target_device and tranche_formats events are grouped by
tranches of preference. For each tranche, a tranche_target_device, one
tranche_flags and one or more tranche_formats events are sent, followed
by a tranche_done event finishing the list. The tranches are sent in
descending order of preference. All formats and modifiers in the same
tranche have the same preference.
To send parameters, the compositor sends one main_device event, tranches
(each consisting of one tranche_target_device event, one tranche_flags
event, tranche_formats events and then a tranche_done event), then one
done event.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the feedback object">
Using this request a client can tell the server that it is not going to
use the wp_linux_dmabuf_feedback object anymore.
</description>
</request>
<event name="done">
<description summary="all feedback has been sent">
This event is sent after all parameters of a wp_linux_dmabuf_feedback
object have been sent.
This allows changes to the wp_linux_dmabuf_feedback parameters to be
seen as atomic, even if they happen via multiple events.
</description>
</event>
<event name="format_table">
<description summary="format and modifier table">
This event provides a file descriptor which can be memory-mapped to
access the format and modifier table.
The table contains a tightly packed array of consecutive format +
modifier pairs. Each pair is 16 bytes wide. It contains a format as a
32-bit unsigned integer, followed by 4 bytes of unused padding, and a
modifier as a 64-bit unsigned integer. The native endianness is used.
The client must map the file descriptor in read-only private mode.
Compositors are not allowed to mutate the table file contents once this
event has been sent. Instead, compositors must create a new, separate
table file and re-send feedback parameters. Compositors are allowed to
store duplicate format + modifier pairs in the table.
</description>
<arg name="fd" type="fd" summary="table file descriptor"/>
<arg name="size" type="uint" summary="table size, in bytes"/>
</event>
<event name="main_device">
<description summary="preferred main device">
This event advertises the main device that the server prefers to use
when direct scan-out to the target device isn't possible. The
advertised main device may be different for each
wp_linux_dmabuf_feedback object, and may change over time.
There is exactly one main device. The compositor must send at least
one preference tranche with tranche_target_device equal to main_device.
Clients need to create buffers that the main device can import and
read from, otherwise creating the dmabuf wl_buffer will fail (see the
wp_linux_buffer_params.create and create_immed requests for details).
The main device will also likely be kept active by the compositor,
so clients can use it instead of waking up another device for power
savings.
In general the device is a DRM node. The DRM node type (primary vs.
render) is unspecified. Clients must not rely on the compositor sending
a particular node type. Clients cannot check two devices for equality
by comparing the dev_t value.
If explicit modifiers are not supported and the client performs buffer
allocations on a different device than the main device, then the client
must force the buffer to have a linear layout.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="tranche_done">
<description summary="a preference tranche has been sent">
This event splits tranche_target_device and tranche_formats events in
preference tranches. It is sent after a set of tranche_target_device
and tranche_formats events; it represents the end of a tranche. The
next tranche will have a lower preference.
</description>
</event>
<event name="tranche_target_device">
<description summary="target device">
This event advertises the target device that the server prefers to use
for a buffer created given this tranche. The advertised target device
may be different for each preference tranche, and may change over time.
There is exactly one target device per tranche.
The target device may be a scan-out device, for example if the
compositor prefers to directly scan-out a buffer created given this
tranche. The target device may be a rendering device, for example if
the compositor prefers to texture from said buffer.
The client can use this hint to allocate the buffer in a way that makes
it accessible from the target device, ideally directly. The buffer must
still be accessible from the main device, either through direct import
or through a potentially more expensive fallback path. If the buffer
can't be directly imported from the main device then clients must be
prepared for the compositor changing the tranche priority or making
wl_buffer creation fail (see the wp_linux_buffer_params.create and
create_immed requests for details).
If the device is a DRM node, the DRM node type (primary vs. render) is
unspecified. Clients must not rely on the compositor sending a
particular node type. Clients cannot check two devices for equality by
comparing the dev_t value.
This event is tied to a preference tranche, see the tranche_done event.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="tranche_formats">
<description summary="supported buffer format modifier">
This event advertises the format + modifier combinations that the
compositor supports.
It carries an array of indices, each referring to a format + modifier
pair in the last received format table (see the format_table event).
Each index is a 16-bit unsigned integer in native endianness.
For legacy support, DRM_FORMAT_MOD_INVALID is an allowed modifier.
It indicates that the server can support the format with an implicit
modifier. When a buffer has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
Compositors must not send duplicate format + modifier pairs within the
same tranche or across two different tranches with the same target
device and flags.
This event is tied to a preference tranche, see the tranche_done event.
For the definition of the format and modifier codes, see the
wp_linux_buffer_params.create request.
</description>
<arg name="indices" type="array" summary="array of 16-bit indexes"/>
</event>
<enum name="tranche_flags" bitfield="true">
<entry name="scanout" value="1" summary="direct scan-out tranche"/>
</enum>
<event name="tranche_flags">
<description summary="tranche flags">
This event sets tranche-specific flags.
The scanout flag is a hint that direct scan-out may be attempted by the
compositor on the target device if the client appropriately allocates a
buffer. How to allocate a buffer that can be scanned out on the target
device is implementation-defined.
This event is tied to a preference tranche, see the tranche_done event.
</description>
<arg name="flags" type="uint" enum="tranche_flags" summary="tranche flags"/>
</event>
</interface>
</protocol>

View File

@@ -0,0 +1,4 @@
Commit Timing Protocol
Maintainers:
Derek Foreman <derek.foreman@collabora.com> (@derekf)

View File

@@ -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>

View File

@@ -0,0 +1,4 @@
Fifo Protocol
Maintainers:
Derek Foreman <derek.foreman@collabora.com> (@derekf)

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>

View File

@@ -0,0 +1,4 @@
Linux DRM syncobj protocol
Maintainers:
Simon Ser <contact@emersion.fr> (@emersion)

View File

@@ -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>

View File

@@ -0,0 +1,4 @@
Tearing control protocol
Maintainers:
Xaver Hugl <xaver.hugl@gmail.com> (@Zamundaaa)

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
Linux explicit synchronization (dma-fence) protocol
Maintainers:
Daniel Stone <daniels@collabora.com>
Alexandros Frantzis <alexandros.frantzis@collabora.com>

View File

@@ -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>