ui/dbus: use shared memory when possible on win32
When the display surface has an associated HANDLE, we can duplicate it to the client process and let it map the memory to avoid expensive copies. Introduce two new win32-specific methods ScanoutMap and UpdateMap. The first is used to inform the listener about the a shared map availability, and the second for display updates. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-Id: <20230606115658.677673-12-marcandre.lureau@redhat.com>
This commit is contained in:
parent
9462ff4695
commit
48dddba176
@ -370,9 +370,7 @@
|
||||
</arg>
|
||||
</method>
|
||||
|
||||
<?if $(env.TARGETOS) == windows?>
|
||||
<!-- Add shared memory/texture support -->
|
||||
<?else?>
|
||||
<?if $(env.TARGETOS) != windows?>
|
||||
<!--
|
||||
ScanoutDMABUF:
|
||||
@dmabuf: the DMABUF file descriptor.
|
||||
@ -471,6 +469,50 @@
|
||||
<property name="Interfaces" type="as" access="read"/>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Listener.Win32.Map:
|
||||
|
||||
This client-side interface can complement org.qemu.Display1.Listener on
|
||||
``/org/qemu/Display1/Listener`` for Windows specific methods.
|
||||
-->
|
||||
<interface name="org.qemu.Display1.Listener.Win32.Map">
|
||||
<!--
|
||||
ScanoutMap:
|
||||
@handle: the shared map handle value.
|
||||
@offset: mapping offset.
|
||||
@width: display width, in pixels.
|
||||
@height: display height, in pixels.
|
||||
@stride: stride, in bytes.
|
||||
@pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``).
|
||||
|
||||
Resize and update the display content with a shared map.
|
||||
-->
|
||||
<method name="ScanoutMap">
|
||||
<arg type="t" name="handle" direction="in"/>
|
||||
<arg type="u" name="offset" direction="in"/>
|
||||
<arg type="u" name="width" direction="in"/>
|
||||
<arg type="u" name="height" direction="in"/>
|
||||
<arg type="u" name="stride" direction="in"/>
|
||||
<arg type="u" name="pixman_format" direction="in"/>
|
||||
</method>
|
||||
|
||||
<!--
|
||||
UpdateMap:
|
||||
@x: the X update position, in pixels.
|
||||
@y: the Y update position, in pixels.
|
||||
@width: the update width, in pixels.
|
||||
@height: the update height, in pixels.
|
||||
|
||||
Update the display content with the current shared map and the given region.
|
||||
-->
|
||||
<method name="UpdateMap">
|
||||
<arg type="i" name="x" direction="in"/>
|
||||
<arg type="i" name="y" direction="in"/>
|
||||
<arg type="i" name="width" direction="in"/>
|
||||
<arg type="i" name="height" direction="in"/>
|
||||
</method>
|
||||
</interface>
|
||||
|
||||
<!--
|
||||
org.qemu.Display1.Clipboard:
|
||||
|
||||
|
@ -48,6 +48,14 @@ struct _DBusDisplayListener {
|
||||
DisplayChangeListener dcl;
|
||||
DisplaySurface *ds;
|
||||
int gl_updates;
|
||||
|
||||
bool ds_mapped;
|
||||
bool can_share_map;
|
||||
|
||||
#ifdef WIN32
|
||||
QemuDBusDisplay1ListenerWin32Map *map_proxy;
|
||||
HANDLE peer_process;
|
||||
#endif
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT)
|
||||
@ -119,7 +127,61 @@ static void dbus_scanout_dmabuf(DisplayChangeListener *dcl,
|
||||
fd_list,
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
#endif /* OPENGL & GBM */
|
||||
|
||||
#ifdef WIN32
|
||||
static bool dbus_scanout_map(DBusDisplayListener *ddl)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
BOOL success;
|
||||
HANDLE target_handle;
|
||||
|
||||
if (ddl->ds_mapped) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ddl->can_share_map || !ddl->ds->handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
success = DuplicateHandle(
|
||||
GetCurrentProcess(),
|
||||
ddl->ds->handle,
|
||||
ddl->peer_process,
|
||||
&target_handle,
|
||||
FILE_MAP_READ | SECTION_QUERY,
|
||||
FALSE, 0);
|
||||
if (!success) {
|
||||
g_autofree char *msg = g_win32_error_message(GetLastError());
|
||||
g_debug("Failed to DuplicateHandle: %s", msg);
|
||||
ddl->can_share_map = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qemu_dbus_display1_listener_win32_map_call_scanout_map_sync(
|
||||
ddl->map_proxy,
|
||||
GPOINTER_TO_UINT(target_handle),
|
||||
ddl->ds->handle_offset,
|
||||
surface_width(ddl->ds),
|
||||
surface_height(ddl->ds),
|
||||
surface_stride(ddl->ds),
|
||||
surface_format(ddl->ds),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
DBUS_DEFAULT_TIMEOUT,
|
||||
NULL,
|
||||
&err)) {
|
||||
g_debug("Failed to call ScanoutMap: %s", err->message);
|
||||
ddl->can_share_map = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ddl->ds_mapped = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM)
|
||||
static void dbus_scanout_texture(DisplayChangeListener *dcl,
|
||||
uint32_t tex_id,
|
||||
bool backing_y_0_top,
|
||||
@ -239,7 +301,7 @@ static void dbus_gl_refresh(DisplayChangeListener *dcl)
|
||||
ddl->gl_updates = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif /* OPENGL & GBM */
|
||||
|
||||
static void dbus_refresh(DisplayChangeListener *dcl)
|
||||
{
|
||||
@ -265,10 +327,20 @@ static void dbus_gfx_update(DisplayChangeListener *dcl,
|
||||
size_t stride;
|
||||
|
||||
assert(ddl->ds);
|
||||
stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
|
||||
|
||||
trace_dbus_update(x, y, w, h);
|
||||
|
||||
#ifdef WIN32
|
||||
if (dbus_scanout_map(ddl)) {
|
||||
qemu_dbus_display1_listener_win32_map_call_update_map(
|
||||
ddl->map_proxy,
|
||||
x, y, w, h,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (x == 0 && y == 0 && w == surface_width(ddl->ds) && h == surface_height(ddl->ds)) {
|
||||
v_data = g_variant_new_from_data(
|
||||
G_VARIANT_TYPE("ay"),
|
||||
@ -290,6 +362,7 @@ static void dbus_gfx_update(DisplayChangeListener *dcl,
|
||||
}
|
||||
|
||||
/* make a copy, since gvariant only handles linear data */
|
||||
stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8);
|
||||
img = pixman_image_create_bits(surface_format(ddl->ds),
|
||||
w, h, NULL, stride);
|
||||
pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img,
|
||||
@ -333,6 +406,9 @@ static void dbus_gfx_switch(DisplayChangeListener *dcl,
|
||||
DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl);
|
||||
|
||||
ddl->ds = new_surface;
|
||||
#ifdef WIN32
|
||||
ddl->ds_mapped = false;
|
||||
#endif
|
||||
if (!ddl->ds) {
|
||||
/* why not call disable instead? */
|
||||
return;
|
||||
@ -414,6 +490,10 @@ dbus_display_listener_dispose(GObject *object)
|
||||
g_clear_object(&ddl->conn);
|
||||
g_clear_pointer(&ddl->bus_name, g_free);
|
||||
g_clear_object(&ddl->proxy);
|
||||
#ifdef WIN32
|
||||
g_clear_object(&ddl->map_proxy);
|
||||
g_clear_pointer(&ddl->peer_process, CloseHandle);
|
||||
#endif
|
||||
|
||||
G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object);
|
||||
}
|
||||
@ -459,6 +539,85 @@ dbus_display_listener_get_console(DBusDisplayListener *ddl)
|
||||
return ddl->console;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static bool
|
||||
dbus_display_listener_implements(DBusDisplayListener *ddl, const char *iface)
|
||||
{
|
||||
QemuDBusDisplay1Listener *l = QEMU_DBUS_DISPLAY1_LISTENER(ddl->proxy);
|
||||
bool implements;
|
||||
|
||||
implements = g_strv_contains(qemu_dbus_display1_listener_get_interfaces(l), iface);
|
||||
if (!implements) {
|
||||
g_debug("Display listener does not implement: `%s`", iface);
|
||||
}
|
||||
|
||||
return implements;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
dbus_display_listener_setup_shared_map(DBusDisplayListener *ddl)
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_autoptr(GError) err = NULL;
|
||||
GDBusConnection *conn;
|
||||
GIOStream *stream;
|
||||
GSocket *sock;
|
||||
g_autoptr(GCredentials) creds = NULL;
|
||||
DWORD *pid;
|
||||
|
||||
if (!dbus_display_listener_implements(ddl, "org.qemu.Display1.Listener.Win32.Map")) {
|
||||
return;
|
||||
}
|
||||
|
||||
conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(ddl->proxy));
|
||||
stream = g_dbus_connection_get_stream(conn);
|
||||
|
||||
if (!G_IS_UNIX_CONNECTION(stream)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sock = g_socket_connection_get_socket(G_SOCKET_CONNECTION(stream));
|
||||
creds = g_socket_get_credentials(sock, &err);
|
||||
|
||||
if (!creds) {
|
||||
g_debug("Failed to get peer credentials: %s", err->message);
|
||||
return;
|
||||
}
|
||||
|
||||
pid = g_credentials_get_native(creds, G_CREDENTIALS_TYPE_WIN32_PID);
|
||||
|
||||
if (pid == NULL) {
|
||||
g_debug("Failed to get peer PID");
|
||||
return;
|
||||
}
|
||||
|
||||
ddl->peer_process = OpenProcess(
|
||||
PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION,
|
||||
false, *pid);
|
||||
|
||||
if (!ddl->peer_process) {
|
||||
g_autofree char *msg = g_win32_error_message(GetLastError());
|
||||
g_debug("Failed to OpenProcess: %s", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
ddl->map_proxy =
|
||||
qemu_dbus_display1_listener_win32_map_proxy_new_sync(conn,
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
||||
NULL,
|
||||
"/org/qemu/Display1/Listener",
|
||||
NULL,
|
||||
&err);
|
||||
if (!ddl->map_proxy) {
|
||||
g_debug("Failed to setup win32 map proxy: %s", err->message);
|
||||
return;
|
||||
}
|
||||
|
||||
ddl->can_share_map = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
DBusDisplayListener *
|
||||
dbus_display_listener_new(const char *bus_name,
|
||||
GDBusConnection *conn,
|
||||
@ -487,6 +646,8 @@ dbus_display_listener_new(const char *bus_name,
|
||||
ddl->conn = conn;
|
||||
ddl->console = console;
|
||||
|
||||
dbus_display_listener_setup_shared_map(ddl);
|
||||
|
||||
con = qemu_console_lookup_by_index(dbus_display_console_get_index(console));
|
||||
assert(con);
|
||||
ddl->dcl.con = con;
|
||||
|
Loading…
Reference in New Issue
Block a user