UI-related for 10.1

- [PATCH v3 0/2] ui/vnc: Do not copy z_stream
 - [PATCH v6 0/7] ui/spice: Enable gl=on option for non-local or remote clients
 - [PATCH v6 0/1] Allow injection of virtio-gpu EDID name
 - [PATCH 0/2] ui/gtk: Add keep-aspect-ratio and scale option
 -----BEGIN PGP SIGNATURE-----
 
 iQJQBAABCgA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmh19eYcHG1hcmNhbmRy
 ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5cLsEAC1NV4DFQmb0TjuK/Bb
 81dDED9DGHsYybVy5x3xSqVkJtAoHTC4FmCm8x9T8wwg+utDvCGFfRM1GeMFR/yI
 IzM+2xs9PcG/+7j/HhVLWr9QhoWV/yoKHcjJScfkTrTtZxAQRA3suUdQT1RjvwUY
 NEuKaOx42dEpV7E+OHp8172eG8CWBzFMjH+cx2b6yKoxF1kVsB7kgVb+kCMYBEQi
 1YHf34G+HGTev+IzzpxnO+P7p2lJ1ud93kCp1Yz8ua5zOUEPiaHkbClFj4M9mdsn
 xvaxby+zJqe33rh8pVr3qD/4R2j35OW7F5uiAQ8C96KF5Eviia8Cno1s4QInpcw/
 sqtorkaP+OLO6sCnvBQqo99iMH2KloCV7b5sUzfxlUkS+3txD1AKRbodz+vhBqMN
 dbESdd1veUFEvi00DGbxfJbbkzVIhxAwad8CNnSjCdsvJdfYLA7TuSEuBtf1lQPF
 lqpVZFB6C3LQMbmTwT9YrOzMtMXQcT+GFpJLOBk0Cxv4rCSil+TeDpEUNXHurYjI
 qWZT+vyGDqyhoZHyQMPsBwAywKgtMC3IwnkKgJdTHroJ57Am86BvZqELRzh8Tffl
 nkdu1uHdNQXT/u8ybU3mStaQ7xMJALL4tlMuIZ5TIkvMeQm4CiViGb/i5LSn/GMk
 lx2JmBwXXf/imsXeBUfxktJFrw==
 =QQ/7
 -----END PGP SIGNATURE-----

Merge tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging

UI-related for 10.1

- [PATCH v3 0/2] ui/vnc: Do not copy z_stream
- [PATCH v6 0/7] ui/spice: Enable gl=on option for non-local or remote clients
- [PATCH v6 0/1] Allow injection of virtio-gpu EDID name
- [PATCH 0/2] ui/gtk: Add keep-aspect-ratio and scale option

# -----BEGIN PGP SIGNATURE-----
#
# iQJQBAABCgA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAmh19eYcHG1hcmNhbmRy
# ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5cLsEAC1NV4DFQmb0TjuK/Bb
# 81dDED9DGHsYybVy5x3xSqVkJtAoHTC4FmCm8x9T8wwg+utDvCGFfRM1GeMFR/yI
# IzM+2xs9PcG/+7j/HhVLWr9QhoWV/yoKHcjJScfkTrTtZxAQRA3suUdQT1RjvwUY
# NEuKaOx42dEpV7E+OHp8172eG8CWBzFMjH+cx2b6yKoxF1kVsB7kgVb+kCMYBEQi
# 1YHf34G+HGTev+IzzpxnO+P7p2lJ1ud93kCp1Yz8ua5zOUEPiaHkbClFj4M9mdsn
# xvaxby+zJqe33rh8pVr3qD/4R2j35OW7F5uiAQ8C96KF5Eviia8Cno1s4QInpcw/
# sqtorkaP+OLO6sCnvBQqo99iMH2KloCV7b5sUzfxlUkS+3txD1AKRbodz+vhBqMN
# dbESdd1veUFEvi00DGbxfJbbkzVIhxAwad8CNnSjCdsvJdfYLA7TuSEuBtf1lQPF
# lqpVZFB6C3LQMbmTwT9YrOzMtMXQcT+GFpJLOBk0Cxv4rCSil+TeDpEUNXHurYjI
# qWZT+vyGDqyhoZHyQMPsBwAywKgtMC3IwnkKgJdTHroJ57Am86BvZqELRzh8Tffl
# nkdu1uHdNQXT/u8ybU3mStaQ7xMJALL4tlMuIZ5TIkvMeQm4CiViGb/i5LSn/GMk
# lx2JmBwXXf/imsXeBUfxktJFrw==
# =QQ/7
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 15 Jul 2025 02:32:06 EDT
# gpg:                using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5
# gpg:                issuer "marcandre.lureau@redhat.com"
# gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [full]
# gpg:                 aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [full]
# Primary key fingerprint: 87A9 BD93 3F87 C606 D276  F62D DAE8 E109 7596 9CE5

* tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu:
  tpm: "qemu -tpmdev help" should return success
  ui/gtk: Add scale option
  ui/gtk: Add keep-aspect-ratio option
  hw/display: Allow injection of virtio-gpu EDID name
  ui/spice: Blit the scanout texture if its memory layout is not linear
  ui/spice: Create a new texture with linear layout when gl=on is specified
  ui/console-gl: Add a helper to create a texture with linear memory layout
  ui/spice: Add an option to submit gl_draw requests at fixed rate
  ui/spice: Add an option for users to provide a preferred video codec
  ui/spice: Enable gl=on option for non-local or remote clients
  ui/egl-helpers: Error check the fds in egl_dmabuf_export_texture()
  ui/vnc: Introduce the VncWorker type
  ui/vnc: Do not copy z_stream

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2025-07-16 07:07:05 -04:00
commit 1c37425423
25 changed files with 881 additions and 424 deletions

View File

@ -1299,3 +1299,47 @@ const PropertyInfo qdev_prop_vmapple_virtio_blk_variant = {
.set = qdev_propinfo_set_enum,
.set_default_value = qdev_propinfo_set_default_value_enum,
};
/* --- VirtIOGPUOutputList --- */
static void get_virtio_gpu_output_list(Object *obj, Visitor *v,
const char *name, void *opaque, Error **errp)
{
VirtIOGPUOutputList **prop_ptr =
object_field_prop_ptr(obj, opaque);
visit_type_VirtIOGPUOutputList(v, name, prop_ptr, errp);
}
static void set_virtio_gpu_output_list(Object *obj, Visitor *v,
const char *name, void *opaque, Error **errp)
{
VirtIOGPUOutputList **prop_ptr =
object_field_prop_ptr(obj, opaque);
VirtIOGPUOutputList *list;
if (!visit_type_VirtIOGPUOutputList(v, name, &list, errp)) {
return;
}
qapi_free_VirtIOGPUOutputList(*prop_ptr);
*prop_ptr = list;
}
static void release_virtio_gpu_output_list(Object *obj,
const char *name, void *opaque)
{
VirtIOGPUOutputList **prop_ptr =
object_field_prop_ptr(obj, opaque);
qapi_free_VirtIOGPUOutputList(*prop_ptr);
*prop_ptr = NULL;
}
const PropertyInfo qdev_prop_virtio_gpu_output_list = {
.type = "VirtIOGPUOutputList",
.description = "VirtIO GPU output list [{\"name\":\"<name>\"},...]",
.get = get_virtio_gpu_output_list,
.set = set_virtio_gpu_output_list,
.release = release_virtio_gpu_output_list,
};

View File

@ -19,6 +19,7 @@
#include "qemu/error-report.h"
#include "hw/display/edid.h"
#include "trace.h"
#include "qapi/qapi-types-virtio.h"
void
virtio_gpu_base_reset(VirtIOGPUBase *g)
@ -56,6 +57,8 @@ void
virtio_gpu_base_generate_edid(VirtIOGPUBase *g, int scanout,
struct virtio_gpu_resp_edid *edid)
{
size_t output_idx;
VirtIOGPUOutputList *node;
qemu_edid_info info = {
.width_mm = g->req_state[scanout].width_mm,
.height_mm = g->req_state[scanout].height_mm,
@ -64,6 +67,14 @@ virtio_gpu_base_generate_edid(VirtIOGPUBase *g, int scanout,
.refresh_rate = g->req_state[scanout].refresh_rate,
};
for (output_idx = 0, node = g->conf.outputs;
output_idx <= scanout && node; output_idx++, node = node->next) {
if (output_idx == scanout && node->value && node->value->name) {
info.name = node->value->name;
break;
}
}
edid->size = cpu_to_le32(sizeof(edid->edid));
qemu_edid_generate(edid->edid, sizeof(edid->edid), &info);
}
@ -172,6 +183,8 @@ virtio_gpu_base_device_realize(DeviceState *qdev,
VirtIOHandleOutput cursor_cb,
Error **errp)
{
size_t output_idx;
VirtIOGPUOutputList *node;
VirtIODevice *vdev = VIRTIO_DEVICE(qdev);
VirtIOGPUBase *g = VIRTIO_GPU_BASE(qdev);
int i;
@ -181,6 +194,20 @@ virtio_gpu_base_device_realize(DeviceState *qdev,
return false;
}
for (output_idx = 0, node = g->conf.outputs;
node; output_idx++, node = node->next) {
if (output_idx == g->conf.max_outputs) {
error_setg(errp, "invalid outputs > %d", g->conf.max_outputs);
return false;
}
if (node->value && node->value->name &&
strlen(node->value->name) > EDID_NAME_MAX_LENGTH) {
error_setg(errp, "invalid output name '%s' > %d",
node->value->name, EDID_NAME_MAX_LENGTH);
return false;
}
}
if (virtio_gpu_virgl_enabled(g->conf)) {
error_setg(&g->migration_blocker, "virgl is not yet migratable");
if (migrate_add_blocker(&g->migration_blocker, errp) < 0) {

View File

@ -1,6 +1,8 @@
#ifndef EDID_H
#define EDID_H
#define EDID_NAME_MAX_LENGTH 12
typedef struct qemu_edid_info {
const char *vendor; /* http://www.uefi.org/pnp_id_list */
const char *name;

View File

@ -32,6 +32,7 @@ extern const PropertyInfo qdev_prop_cpus390entitlement;
extern const PropertyInfo qdev_prop_iothread_vq_mapping_list;
extern const PropertyInfo qdev_prop_endian_mode;
extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant;
extern const PropertyInfo qdev_prop_virtio_gpu_output_list;
#define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \
DEFINE_PROP_SIGNED(_n, _s, _f, _d, qdev_prop_pci_devfn, int32_t)
@ -110,4 +111,8 @@ extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant;
qdev_prop_vmapple_virtio_blk_variant, \
VMAppleVirtioBlkVariant)
#define DEFINE_PROP_VIRTIO_GPU_OUTPUT_LIST(_name, _state, _field) \
DEFINE_PROP(_name, _state, _field, qdev_prop_virtio_gpu_output_list, \
VirtIOGPUOutputList *)
#endif

View File

@ -20,6 +20,7 @@
#include "hw/virtio/virtio.h"
#include "qemu/log.h"
#include "system/vhost-user-backend.h"
#include "qapi/qapi-types-virtio.h"
#include "standard-headers/linux/virtio_gpu.h"
#include "standard-headers/linux/virtio_ids.h"
@ -128,6 +129,7 @@ struct virtio_gpu_base_conf {
uint32_t xres;
uint32_t yres;
uint64_t hostmem;
VirtIOGPUOutputList *outputs;
};
struct virtio_gpu_ctrl_command {
@ -167,6 +169,7 @@ struct VirtIOGPUBaseClass {
#define VIRTIO_GPU_BASE_PROPERTIES(_state, _conf) \
DEFINE_PROP_UINT32("max_outputs", _state, _conf.max_outputs, 1), \
DEFINE_PROP_VIRTIO_GPU_OUTPUT_LIST("outputs", _state, _conf.outputs), \
DEFINE_PROP_BIT("edid", _state, _conf.flags, \
VIRTIO_GPU_FLAG_EDID_ENABLED, true), \
DEFINE_PROP_UINT32("xres", _state, _conf.xres, 1280), \

View File

@ -422,6 +422,9 @@ bool console_gl_check_format(DisplayChangeListener *dcl,
pixman_format_code_t format);
void surface_gl_create_texture(QemuGLShader *gls,
DisplaySurface *surface);
bool surface_gl_create_texture_from_fd(DisplaySurface *surface,
int fd, GLuint *texture,
GLuint *mem_obj);
void surface_gl_update_texture(QemuGLShader *gls,
DisplaySurface *surface,
int x, int y, int w, int h);

View File

@ -41,6 +41,7 @@ typedef struct VirtualGfxConsole {
DisplaySurface *ds;
pixman_image_t *convert;
cairo_surface_t *surface;
double preferred_scale;
double scale_x;
double scale_y;
#if defined(CONFIG_OPENGL)
@ -140,6 +141,7 @@ struct GtkDisplayState {
GdkCursor *null_cursor;
Notifier mouse_mode_notifier;
gboolean free_scale;
gboolean keep_aspect_ratio;
bool external_pause_update;

View File

@ -132,6 +132,9 @@ struct SimpleSpiceDisplay {
egl_fb guest_fb;
egl_fb blit_fb;
egl_fb cursor_fb;
bool backing_y_0_top;
bool blit_scanout_texture;
bool new_scanout_texture;
bool have_hot;
#endif
};
@ -151,6 +154,8 @@ struct SimpleSpiceCursor {
};
extern bool spice_opengl;
extern bool spice_remote_client;
extern int spice_max_refresh_rate;
int qemu_spice_rect_is_empty(const QXLRect* r);
void qemu_spice_rect_union(QXLRect *dest, const QXLRect *r);

View File

@ -22,6 +22,7 @@ typedef struct DisplaySurface {
GLenum glformat;
GLenum gltype;
GLuint texture;
GLuint mem_obj;
#endif
qemu_pixman_shareable share_handle;
uint32_t share_handle_offset;

View File

@ -1335,13 +1335,20 @@
# @show-menubar: Display the main window menubar. Defaults to "on".
# (Since 8.0)
#
# @keep-aspect-ratio: Keep width/height aspect ratio of guest content when
# resizing host window. Defaults to "on". (Since 10.1)
#
# @scale: Set preferred scale of the display. Defaults to 1.0. (Since 10.1)
#
# Since: 2.12
##
{ 'struct' : 'DisplayGTK',
'data' : { '*grab-on-hover' : 'bool',
'*zoom-to-fit' : 'bool',
'*show-tabs' : 'bool',
'*show-menubar' : 'bool' } }
'data' : { '*grab-on-hover' : 'bool',
'*zoom-to-fit' : 'bool',
'*show-tabs' : 'bool',
'*show-menubar' : 'bool',
'*keep-aspect-ratio' : 'bool',
'*scale' : 'number' } }
##
# @DisplayEGLHeadless:

View File

@ -963,17 +963,31 @@
{ 'struct': 'IOThreadVirtQueueMapping',
'data': { 'iothread': 'str', '*vqs': ['uint16'] } }
##
# @VirtIOGPUOutput:
#
# Describes configuration of a VirtIO GPU output.
#
# @name: the name of the output
#
# Since: 10.1
##
{ 'struct': 'VirtIOGPUOutput',
'data': { 'name': 'str' } }
##
# @DummyVirtioForceArrays:
#
# Not used by QMP; hack to let us use IOThreadVirtQueueMappingList
# internally
# and VirtIOGPUOutputList internally
#
# Since: 9.0
##
{ 'struct': 'DummyVirtioForceArrays',
'data': { 'unused-iothread-vq-mapping': ['IOThreadVirtQueueMapping'] } }
'data': { 'unused-iothread-vq-mapping': ['IOThreadVirtQueueMapping'],
'unused-virtio-gpu-output': ['VirtIOGPUOutput'] } }
##
# @GranuleMode:

View File

@ -2286,6 +2286,8 @@ DEF("spice", HAS_ARG, QEMU_OPTION_spice,
" [,streaming-video=[off|all|filter]][,disable-copy-paste=on|off]\n"
" [,disable-agent-file-xfer=on|off][,agent-mouse=[on|off]]\n"
" [,playback-compression=[on|off]][,seamless-migration=[on|off]]\n"
" [,video-codec=<codec>\n"
" [,max-refresh-rate=rate\n"
" [,gl=[on|off]][,rendernode=<file>]\n"
" enable spice\n"
" at least one of {port, tls-port} is mandatory\n",
@ -2374,6 +2376,17 @@ SRST
``seamless-migration=[on|off]``
Enable/disable spice seamless migration. Default is off.
``video-codec=<codec>``
Provide the preferred codec the Spice server should use with the
Gstreamer encoder. This option is only relevant when gl=on is
specified. If no codec is provided, then the codec gstreamer:h264
would be used as default. And, for the case where gl=off, the
default codec to be used is determined by the Spice server.
``max-refresh-rate=rate``
Provide the maximum refresh rate (or FPS) at which the encoding
requests should be sent to the Spice server. Default would be 30.
``gl=[on|off]``
Enable/disable OpenGL context. Default is off.

View File

@ -21,6 +21,7 @@
#include "system/tpm.h"
#include "qemu/config-file.h"
#include "qemu/error-report.h"
#include "qemu/help_option.h"
static QLIST_HEAD(, TPMBackend) tpm_backends =
QLIST_HEAD_INITIALIZER(tpm_backends);
@ -179,9 +180,9 @@ int tpm_config_parse(QemuOptsList *opts_list, const char *optstr)
{
QemuOpts *opts;
if (!strcmp(optstr, "help")) {
if (is_help_option(optstr)) {
tpm_display_backend_drivers();
return -1;
exit(EXIT_SUCCESS);
}
opts = qemu_opts_parse_noisily(opts_list, optstr, true);
if (!opts) {

View File

@ -25,6 +25,7 @@
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "ui/console.h"
#include "ui/shader.h"
@ -96,6 +97,53 @@ void surface_gl_create_texture(QemuGLShader *gls,
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
bool surface_gl_create_texture_from_fd(DisplaySurface *surface,
int fd, GLuint *texture,
GLuint *mem_obj)
{
unsigned long size = surface_stride(surface) * surface_height(surface);
GLenum err = glGetError();
*texture = 0;
*mem_obj = 0;
if (!epoxy_has_gl_extension("GL_EXT_memory_object") ||
!epoxy_has_gl_extension("GL_EXT_memory_object_fd")) {
error_report("spice: required OpenGL extensions not supported: "
"GL_EXT_memory_object and GL_EXT_memory_object_fd");
return false;
}
#ifdef GL_EXT_memory_object_fd
glCreateMemoryObjectsEXT(1, mem_obj);
glImportMemoryFdEXT(*mem_obj, size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd);
err = glGetError();
if (err != GL_NO_ERROR) {
error_report("spice: cannot import memory object from fd");
goto cleanup_mem;
}
glGenTextures(1, texture);
glBindTexture(GL_TEXTURE_2D, *texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT);
glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, surface_width(surface),
surface_height(surface), *mem_obj, 0);
err = glGetError();
if (err != GL_NO_ERROR) {
error_report("spice: cannot create texture from memory object");
goto cleanup_tex_and_mem;
}
return true;
cleanup_tex_and_mem:
glDeleteTextures(1, texture);
cleanup_mem:
glDeleteMemoryObjectsEXT(1, mem_obj);
#endif
return false;
}
void surface_gl_update_texture(QemuGLShader *gls,
DisplaySurface *surface,
int x, int y, int w, int h)
@ -136,6 +184,12 @@ void surface_gl_destroy_texture(QemuGLShader *gls,
}
glDeleteTextures(1, &surface->texture);
surface->texture = 0;
#ifdef GL_EXT_memory_object_fd
if (surface->mem_obj) {
glDeleteMemoryObjectsEXT(1, &surface->mem_obj);
surface->mem_obj = 0;
}
#endif
}
void surface_gl_setup_viewport(QemuGLShader *gls,

View File

@ -295,6 +295,7 @@ bool egl_dmabuf_export_texture(uint32_t tex_id, int *fd, EGLint *offset,
{
EGLImageKHR image;
EGLuint64KHR modifiers[DMABUF_MAX_PLANES];
int i;
image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(),
EGL_GL_TEXTURE_2D_KHR,
@ -314,6 +315,11 @@ bool egl_dmabuf_export_texture(uint32_t tex_id, int *fd, EGLint *offset,
*modifier = modifiers[0];
}
for (i = 0; i < *num_planes; i++) {
if (fd[i] < 0) {
return false;
}
}
return true;
}

View File

@ -67,6 +67,7 @@
#define VC_TERM_X_MIN 80
#define VC_TERM_Y_MIN 25
#define VC_SCALE_MIN 0.25
#define VC_SCALE_MAX 4
#define VC_SCALE_STEP 0.25
#ifdef GDK_WINDOWING_X11
@ -272,15 +273,11 @@ static void gd_update_geometry_hints(VirtualConsole *vc)
if (!vc->gfx.ds) {
return;
}
if (s->free_scale) {
geo.min_width = surface_width(vc->gfx.ds) * VC_SCALE_MIN;
geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN;
mask |= GDK_HINT_MIN_SIZE;
} else {
geo.min_width = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
mask |= GDK_HINT_MIN_SIZE;
}
double scale_x = s->free_scale ? VC_SCALE_MIN : vc->gfx.scale_x;
double scale_y = s->free_scale ? VC_SCALE_MIN : vc->gfx.scale_y;
geo.min_width = surface_width(vc->gfx.ds) * scale_x;
geo.min_height = surface_height(vc->gfx.ds) * scale_y;
mask |= GDK_HINT_MIN_SIZE;
geo_widget = vc->gfx.drawing_area;
gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height);
@ -828,8 +825,12 @@ void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh)
sx = (double)ww / fbw;
sy = (double)wh / fbh;
vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
if (vc->s->keep_aspect_ratio) {
vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
} else {
vc->gfx.scale_x = sx;
vc->gfx.scale_y = sy;
}
}
}
/**
@ -1575,8 +1576,8 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
}
s->full_screen = FALSE;
if (vc->type == GD_VC_GFX) {
vc->gfx.scale_x = 1.0;
vc->gfx.scale_y = 1.0;
vc->gfx.scale_x = vc->gfx.preferred_scale;
vc->gfx.scale_y = vc->gfx.preferred_scale;
gd_update_windowsize(vc);
}
}
@ -1632,8 +1633,8 @@ static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
GtkDisplayState *s = opaque;
VirtualConsole *vc = gd_vc_find_current(s);
vc->gfx.scale_x = 1.0;
vc->gfx.scale_y = 1.0;
vc->gfx.scale_x = vc->gfx.preferred_scale;
vc->gfx.scale_y = vc->gfx.preferred_scale;
gd_update_windowsize(vc);
}
@ -1647,8 +1648,8 @@ static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
s->free_scale = TRUE;
} else {
s->free_scale = FALSE;
vc->gfx.scale_x = 1.0;
vc->gfx.scale_y = 1.0;
vc->gfx.scale_x = vc->gfx.preferred_scale;
vc->gfx.scale_y = vc->gfx.preferred_scale;
}
gd_update_windowsize(vc);
@ -2239,6 +2240,11 @@ static void gl_area_realize(GtkGLArea *area, VirtualConsole *vc)
}
#endif
static bool gd_scale_valid(double scale)
{
return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX;
}
static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
QemuConsole *con, int idx,
GSList *group, GtkWidget *view_menu)
@ -2248,8 +2254,18 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
vc->label = qemu_console_get_label(con);
vc->s = s;
vc->gfx.scale_x = 1.0;
vc->gfx.scale_y = 1.0;
vc->gfx.preferred_scale = 1.0;
if (s->opts->u.gtk.has_scale) {
if (gd_scale_valid(s->opts->u.gtk.scale)) {
vc->gfx.preferred_scale = s->opts->u.gtk.scale;
} else {
error_report("Invalid scale value %lf given, being ignored",
s->opts->u.gtk.scale);
s->opts->u.gtk.has_scale = false;
}
}
vc->gfx.scale_x = vc->gfx.preferred_scale;
vc->gfx.scale_y = vc->gfx.preferred_scale;
#if defined(CONFIG_OPENGL)
if (display_opengl) {
@ -2328,6 +2344,10 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
s->free_scale = true;
}
s->keep_aspect_ratio = true;
if (s->opts->u.gtk.has_keep_aspect_ratio)
s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio;
for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
struct touch_slot *slot = &touch_slots[i];
slot->tracking_id = -1;

View File

@ -56,6 +56,8 @@ struct SpiceTimer {
QEMUTimer *timer;
};
#define DEFAULT_MAX_REFRESH_RATE 30
static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
{
SpiceTimer *timer;
@ -488,6 +490,12 @@ static QemuOptsList qemu_spice_opts = {
},{
.name = "streaming-video",
.type = QEMU_OPT_STRING,
},{
.name = "video-codec",
.type = QEMU_OPT_STRING,
},{
.name = "max-refresh-rate",
.type = QEMU_OPT_NUMBER,
},{
.name = "agent-mouse",
.type = QEMU_OPT_BOOL,
@ -801,6 +809,13 @@ static void qemu_spice_init(void)
spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF);
}
spice_max_refresh_rate = qemu_opt_get_number(opts, "max-refresh-rate",
DEFAULT_MAX_REFRESH_RATE);
if (spice_max_refresh_rate <= 0) {
error_report("max refresh rate/fps is invalid");
exit(1);
}
spice_server_set_agent_mouse
(spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1));
spice_server_set_playback_compression
@ -836,9 +851,26 @@ static void qemu_spice_init(void)
#ifdef HAVE_SPICE_GL
if (qemu_opt_get_bool(opts, "gl", 0)) {
if ((port != 0) || (tls_port != 0)) {
#if SPICE_SERVER_VERSION >= 0x000f03 /* release 0.15.3 */
const char *video_codec = NULL;
g_autofree char *enc_codec = NULL;
spice_remote_client = 1;
video_codec = qemu_opt_get(opts, "video-codec");
if (video_codec) {
enc_codec = g_strconcat("gstreamer:", video_codec, NULL);
}
if (spice_server_set_video_codecs(spice_server,
enc_codec ?: "gstreamer:h264")) {
error_report("invalid video codec");
exit(1);
}
#else
error_report("SPICE GL support is local-only for now and "
"incompatible with -spice port/tls-port");
exit(1);
#endif
}
egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal);
spice_opengl = 1;

View File

@ -31,6 +31,8 @@
#include "standard-headers/drm/drm_fourcc.h"
bool spice_opengl;
bool spice_remote_client;
int spice_max_refresh_rate;
int qemu_spice_rect_is_empty(const QXLRect* r)
{
@ -843,12 +845,32 @@ static void qemu_spice_gl_block_timer(void *opaque)
warn_report("spice: no gl-draw-done within one second");
}
static void spice_gl_draw(SimpleSpiceDisplay *ssd,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
uint64_t cookie;
cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie);
}
static void spice_gl_refresh(DisplayChangeListener *dcl)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
uint64_t cookie;
if (!ssd->ds || qemu_console_is_gl_blocked(ssd->dcl.con)) {
if (!ssd->ds) {
return;
}
if (qemu_console_is_gl_blocked(ssd->dcl.con)) {
if (spice_remote_client && ssd->gl_updates && ssd->have_scanout) {
glFlush();
spice_gl_draw(ssd, 0, 0,
surface_width(ssd->ds), surface_height(ssd->ds));
ssd->gl_updates = 0;
/* E.g, to achieve 60 FPS, update_interval needs to be ~16.66 ms */
dcl->update_interval = 1000 / spice_max_refresh_rate;
}
return;
}
@ -856,11 +878,8 @@ static void spice_gl_refresh(DisplayChangeListener *dcl)
if (ssd->gl_updates && ssd->have_surface) {
qemu_spice_gl_block(ssd, true);
glFlush();
cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
spice_qxl_gl_draw_async(&ssd->qxl, 0, 0,
surface_width(ssd->ds),
surface_height(ssd->ds),
cookie);
spice_gl_draw(ssd, 0, 0,
surface_width(ssd->ds), surface_height(ssd->ds));
ssd->gl_updates = 0;
}
}
@ -874,6 +893,81 @@ static void spice_gl_update(DisplayChangeListener *dcl,
ssd->gl_updates++;
}
static bool spice_gl_replace_fd_texture(SimpleSpiceDisplay *ssd,
int *fds, uint64_t *modifier,
int *num_planes)
{
uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES];
GLuint texture;
GLuint mem_obj;
int fourcc;
bool ret;
if (!spice_remote_client) {
return true;
}
if (*modifier == DRM_FORMAT_MOD_LINEAR) {
return true;
}
if (*num_planes > 1) {
error_report("spice: cannot replace texture with multiple planes");
return false;
}
/*
* We really want to ensure that the memory layout of the texture
* is linear; otherwise, the encoder's output may show corruption.
*/
if (!surface_gl_create_texture_from_fd(ssd->ds, fds[0], &texture,
&mem_obj)) {
error_report("spice: cannot create new texture from fd");
return false;
}
/*
* A successful return after glImportMemoryFdEXT() means that
* the ownership of fd has been passed to GL. In other words,
* the fd we got above should not be used anymore.
*/
ret = egl_dmabuf_export_texture(texture,
fds,
(EGLint *)offsets,
(EGLint *)strides,
&fourcc,
num_planes,
modifier);
if (!ret) {
glDeleteTextures(1, &texture);
#ifdef GL_EXT_memory_object_fd
glDeleteMemoryObjectsEXT(1, &mem_obj);
#endif
/*
* Since we couldn't export our newly create texture (or create,
* an fd associated with it) we need to backtrack and try to
* recreate the fd associated with the original texture.
*/
ret = egl_dmabuf_export_texture(ssd->ds->texture,
fds,
(EGLint *)offsets,
(EGLint *)strides,
&fourcc,
num_planes,
modifier);
if (!ret) {
surface_gl_destroy_texture(ssd->gls, ssd->ds);
warn_report("spice: no texture available to display");
}
} else {
surface_gl_destroy_texture(ssd->gls, ssd->ds);
ssd->ds->texture = texture;
ssd->ds->mem_obj = mem_obj;
}
return ret;
}
static void spice_server_gl_scanout(QXLInstance *qxl,
const int *fd,
uint32_t width, uint32_t height,
@ -898,6 +992,7 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
struct DisplaySurface *new_surface)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
bool ret;
if (ssd->ds) {
surface_gl_destroy_texture(ssd->gls, ssd->ds);
@ -920,6 +1015,12 @@ static void spice_gl_switch(DisplayChangeListener *dcl,
return;
}
ret = spice_gl_replace_fd_texture(ssd, fd, &modifier, &num_planes);
if (!ret) {
surface_gl_destroy_texture(ssd->gls, ssd->ds);
return;
}
trace_qemu_spice_gl_surface(ssd->qxl.id,
surface_width(ssd->ds),
surface_height(ssd->ds),
@ -953,6 +1054,20 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
trace_qemu_spice_gl_scanout_disable(ssd->qxl.id);
/*
* We need to check for the case of "lost" updates, where a gl_draw
* was not submitted because the timer did not get a chance to run.
* One case where this happens is when the Guest VM is getting
* rebooted. If the console is blocked in this situation, we need
* to unblock it. Otherwise, newer updates would not take effect.
*/
if (qemu_console_is_gl_blocked(ssd->dcl.con)) {
if (spice_remote_client && ssd->gl_updates && ssd->have_scanout) {
ssd->gl_updates = 0;
qemu_spice_gl_block(ssd, false);
}
}
spice_server_gl_scanout(&ssd->qxl, NULL, 0, 0, NULL, NULL, 0, DRM_FORMAT_INVALID,
DRM_FORMAT_MOD_INVALID, false);
qemu_spice_gl_monitor_config(ssd, 0, 0, 0, 0);
@ -971,7 +1086,7 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
EGLint offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES], fourcc = 0;
int fd[DMABUF_MAX_PLANES], num_planes;
int fd[DMABUF_MAX_PLANES], num_planes, i;
uint64_t modifier;
assert(tex_id);
@ -983,11 +1098,26 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
trace_qemu_spice_gl_scanout_texture(ssd->qxl.id, w, h, fourcc);
/* note: spice server will close the fd */
spice_server_gl_scanout(&ssd->qxl, fd, backing_width, backing_height,
(uint32_t *)offset, (uint32_t *)stride, num_planes,
fourcc, modifier, y_0_top);
qemu_spice_gl_monitor_config(ssd, x, y, w, h);
if (spice_remote_client && modifier != DRM_FORMAT_MOD_LINEAR) {
egl_fb_destroy(&ssd->guest_fb);
egl_fb_setup_for_tex(&ssd->guest_fb,
backing_width, backing_height,
tex_id, false);
ssd->backing_y_0_top = y_0_top;
ssd->blit_scanout_texture = true;
ssd->new_scanout_texture = true;
for (i = 0; i < num_planes; i++) {
close(fd[i]);
}
} else {
/* note: spice server will close the fd */
spice_server_gl_scanout(&ssd->qxl, fd, backing_width, backing_height,
(uint32_t *)offset, (uint32_t *)stride,
num_planes, fourcc, modifier, y_0_top);
qemu_spice_gl_monitor_config(ssd, x, y, w, h);
}
ssd->have_surface = false;
ssd->have_scanout = true;
}
@ -1053,6 +1183,50 @@ static void qemu_spice_gl_release_dmabuf(DisplayChangeListener *dcl,
egl_dmabuf_release_texture(dmabuf);
}
static bool spice_gl_blit_scanout_texture(SimpleSpiceDisplay *ssd,
egl_fb *scanout_tex_fb)
{
uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES];
int fds[DMABUF_MAX_PLANES], num_planes, fourcc;
uint64_t modifier;
bool ret;
egl_fb_destroy(scanout_tex_fb);
egl_fb_setup_for_tex(scanout_tex_fb,
surface_width(ssd->ds), surface_height(ssd->ds),
ssd->ds->texture, false);
egl_fb_blit(scanout_tex_fb, &ssd->guest_fb, false);
glFlush();
if (!ssd->new_scanout_texture) {
return true;
}
ret = egl_dmabuf_export_texture(ssd->ds->texture,
fds,
(EGLint *)offsets,
(EGLint *)strides,
&fourcc,
&num_planes,
&modifier);
if (!ret) {
error_report("spice: failed to get fd for texture");
return false;
}
spice_server_gl_scanout(&ssd->qxl, fds,
surface_width(ssd->ds),
surface_height(ssd->ds),
(uint32_t *)offsets, (uint32_t *)strides,
num_planes, fourcc, modifier,
ssd->backing_y_0_top);
qemu_spice_gl_monitor_config(ssd, 0, 0,
surface_width(ssd->ds),
surface_height(ssd->ds));
ssd->new_scanout_texture = false;
return true;
}
static void qemu_spice_gl_update(DisplayChangeListener *dcl,
uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
@ -1060,7 +1234,7 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
EGLint fourcc = 0;
bool render_cursor = false;
bool y_0_top = false; /* FIXME */
uint64_t cookie;
bool ret;
uint32_t width, height, texture;
if (!ssd->have_scanout) {
@ -1155,11 +1329,31 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
glFlush();
}
if (spice_remote_client && ssd->blit_scanout_texture) {
egl_fb scanout_tex_fb;
ret = spice_gl_blit_scanout_texture(ssd, &scanout_tex_fb);
if (!ret) {
return;
}
}
trace_qemu_spice_gl_update(ssd->qxl.id, w, h, x, y);
qemu_spice_gl_block(ssd, true);
glFlush();
cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie);
/*
* In the case of remote clients, the submission of gl_draw request is
* deferred here, so that it can be submitted later (to spice server)
* from spice_gl_refresh() timer callback. This is done to ensure that
* Guest updates are submitted at a steady rate (e.g. 60 FPS) instead
* of submitting them arbitrarily.
*/
if (spice_remote_client) {
ssd->gl_updates++;
} else {
spice_gl_draw(ssd, x, y, w, h);
}
}
static const DisplayChangeListenerOps display_listener_gl_ops = {

File diff suppressed because it is too large Load Diff

View File

@ -46,23 +46,23 @@ void vnc_zlib_zfree(void *x, void *addr)
g_free(addr);
}
static void vnc_zlib_start(VncState *vs)
static void vnc_zlib_start(VncState *vs, VncWorker *worker)
{
buffer_reset(&vs->zlib.zlib);
buffer_reset(&worker->zlib.zlib);
// make the output buffer be the zlib buffer, so we can compress it later
vs->zlib.tmp = vs->output;
vs->output = vs->zlib.zlib;
worker->zlib.tmp = vs->output;
vs->output = worker->zlib.zlib;
}
static int vnc_zlib_stop(VncState *vs)
static int vnc_zlib_stop(VncState *vs, VncWorker *worker)
{
z_streamp zstream = &vs->zlib.stream;
z_streamp zstream = &worker->zlib.stream;
int previous_out;
// switch back to normal output/zlib buffers
vs->zlib.zlib = vs->output;
vs->output = vs->zlib.tmp;
worker->zlib.zlib = vs->output;
vs->output = worker->zlib.tmp;
// compress the zlib buffer
@ -76,7 +76,7 @@ static int vnc_zlib_stop(VncState *vs)
zstream->zalloc = vnc_zlib_zalloc;
zstream->zfree = vnc_zlib_zfree;
err = deflateInit2(zstream, vs->tight->compression, Z_DEFLATED,
err = deflateInit2(zstream, worker->tight.compression, Z_DEFLATED,
MAX_WBITS,
MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
@ -85,24 +85,24 @@ static int vnc_zlib_stop(VncState *vs)
return -1;
}
vs->zlib.level = vs->tight->compression;
worker->zlib.level = worker->tight.compression;
zstream->opaque = vs;
}
if (vs->tight->compression != vs->zlib.level) {
if (deflateParams(zstream, vs->tight->compression,
if (worker->tight.compression != worker->zlib.level) {
if (deflateParams(zstream, worker->tight.compression,
Z_DEFAULT_STRATEGY) != Z_OK) {
return -1;
}
vs->zlib.level = vs->tight->compression;
worker->zlib.level = worker->tight.compression;
}
// reserve memory in output buffer
buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64);
buffer_reserve(&vs->output, worker->zlib.zlib.offset + 64);
// set pointers
zstream->next_in = vs->zlib.zlib.buffer;
zstream->avail_in = vs->zlib.zlib.offset;
zstream->next_in = worker->zlib.zlib.buffer;
zstream->avail_in = worker->zlib.zlib.offset;
zstream->next_out = vs->output.buffer + vs->output.offset;
zstream->avail_out = vs->output.capacity - vs->output.offset;
previous_out = zstream->avail_out;
@ -118,7 +118,8 @@ static int vnc_zlib_stop(VncState *vs)
return previous_out - zstream->avail_out;
}
int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
int vnc_zlib_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h)
{
int old_offset, new_offset, bytes_written;
@ -129,9 +130,9 @@ int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
vnc_write_s32(vs, 0);
// compress the stream
vnc_zlib_start(vs);
vnc_zlib_start(vs, worker);
vnc_raw_send_framebuffer_update(vs, x, y, w, h);
bytes_written = vnc_zlib_stop(vs);
bytes_written = vnc_zlib_stop(vs, worker);
if (bytes_written == -1)
return 0;
@ -145,10 +146,10 @@ int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
return 1;
}
void vnc_zlib_clear(VncState *vs)
void vnc_zlib_clear(VncWorker *worker)
{
if (vs->zlib.stream.opaque) {
deflateEnd(&vs->zlib.stream);
if (worker->zlib.stream.opaque) {
deflateEnd(&worker->zlib.stream);
}
buffer_free(&vs->zlib.zlib);
buffer_free(&worker->zlib.zlib);
}

View File

@ -35,45 +35,45 @@ static const int bits_per_packed_pixel[] = {
};
static void vnc_zrle_start(VncState *vs)
static void vnc_zrle_start(VncState *vs, VncZrle *zrle)
{
buffer_reset(&vs->zrle->zrle);
buffer_reset(&zrle->zrle);
/* make the output buffer be the zlib buffer, so we can compress it later */
vs->zrle->tmp = vs->output;
vs->output = vs->zrle->zrle;
zrle->tmp = vs->output;
vs->output = zrle->zrle;
}
static void vnc_zrle_stop(VncState *vs)
static void vnc_zrle_stop(VncState *vs, VncZrle *zrle)
{
/* switch back to normal output/zlib buffers */
vs->zrle->zrle = vs->output;
vs->output = vs->zrle->tmp;
zrle->zrle = vs->output;
vs->output = zrle->tmp;
}
static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h,
int bpp)
static void *zrle_convert_fb(VncState *vs, VncZrle *zrle,
int x, int y, int w, int h, int bpp)
{
Buffer tmp;
buffer_reset(&vs->zrle->fb);
buffer_reserve(&vs->zrle->fb, w * h * bpp + bpp);
buffer_reset(&zrle->fb);
buffer_reserve(&zrle->fb, w * h * bpp + bpp);
tmp = vs->output;
vs->output = vs->zrle->fb;
vs->output = zrle->fb;
vnc_raw_send_framebuffer_update(vs, x, y, w, h);
vs->zrle->fb = vs->output;
zrle->fb = vs->output;
vs->output = tmp;
return vs->zrle->fb.buffer;
return zrle->fb.buffer;
}
static int zrle_compress_data(VncState *vs, int level)
static int zrle_compress_data(VncState *vs, VncZrle *zrle, int level)
{
z_streamp zstream = &vs->zrle->stream;
z_streamp zstream = &zrle->stream;
buffer_reset(&vs->zrle->zlib);
buffer_reset(&zrle->zlib);
if (zstream->opaque != vs) {
int err;
@ -93,13 +93,13 @@ static int zrle_compress_data(VncState *vs, int level)
}
/* reserve memory in output buffer */
buffer_reserve(&vs->zrle->zlib, vs->zrle->zrle.offset + 64);
buffer_reserve(&zrle->zlib, zrle->zrle.offset + 64);
/* set pointers */
zstream->next_in = vs->zrle->zrle.buffer;
zstream->avail_in = vs->zrle->zrle.offset;
zstream->next_out = vs->zrle->zlib.buffer;
zstream->avail_out = vs->zrle->zlib.capacity;
zstream->next_in = zrle->zrle.buffer;
zstream->avail_in = zrle->zrle.offset;
zstream->next_out = zrle->zlib.buffer;
zstream->avail_out = zrle->zlib.capacity;
zstream->data_type = Z_BINARY;
/* start encoding */
@ -108,8 +108,8 @@ static int zrle_compress_data(VncState *vs, int level)
return -1;
}
vs->zrle->zlib.offset = vs->zrle->zlib.capacity - zstream->avail_out;
return vs->zrle->zlib.offset;
zrle->zlib.offset = zrle->zlib.capacity - zstream->avail_out;
return zrle->zlib.offset;
}
/* Try to work out whether to use RLE and/or a palette. We do this by
@ -252,21 +252,21 @@ static void zrle_write_u8(VncState *vs, uint8_t value)
#undef ZRLE_COMPACT_PIXEL
#undef ZRLE_BPP
static int zrle_send_framebuffer_update(VncState *vs, int x, int y,
int w, int h)
static int zrle_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h)
{
bool be = vs->client_endian == G_BIG_ENDIAN;
size_t bytes;
int zywrle_level;
if (vs->zrle->type == VNC_ENCODING_ZYWRLE) {
if (!vs->vd->lossy || vs->tight->quality == (uint8_t)-1
|| vs->tight->quality == 9) {
if (worker->zrle.type == VNC_ENCODING_ZYWRLE) {
if (!vs->vd->lossy || worker->tight.quality == (uint8_t)-1
|| worker->tight.quality == 9) {
zywrle_level = 0;
vs->zrle->type = VNC_ENCODING_ZRLE;
} else if (vs->tight->quality < 3) {
worker->zrle.type = VNC_ENCODING_ZRLE;
} else if (worker->tight.quality < 3) {
zywrle_level = 3;
} else if (vs->tight->quality < 6) {
} else if (worker->tight.quality < 6) {
zywrle_level = 2;
} else {
zywrle_level = 1;
@ -275,25 +275,25 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y,
zywrle_level = 0;
}
vnc_zrle_start(vs);
vnc_zrle_start(vs, &worker->zrle);
switch (vs->client_pf.bytes_per_pixel) {
case 1:
zrle_encode_8ne(vs, x, y, w, h, zywrle_level);
zrle_encode_8ne(vs, &worker->zrle, x, y, w, h, zywrle_level);
break;
case 2:
if (vs->client_pf.gmax > 0x1F) {
if (be) {
zrle_encode_16be(vs, x, y, w, h, zywrle_level);
zrle_encode_16be(vs, &worker->zrle, x, y, w, h, zywrle_level);
} else {
zrle_encode_16le(vs, x, y, w, h, zywrle_level);
zrle_encode_16le(vs, &worker->zrle, x, y, w, h, zywrle_level);
}
} else {
if (be) {
zrle_encode_15be(vs, x, y, w, h, zywrle_level);
zrle_encode_15be(vs, &worker->zrle, x, y, w, h, zywrle_level);
} else {
zrle_encode_15le(vs, x, y, w, h, zywrle_level);
zrle_encode_15le(vs, &worker->zrle, x, y, w, h, zywrle_level);
}
}
break;
@ -314,53 +314,55 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y,
if ((fits_in_ls3bytes && !be) || (fits_in_ms3bytes && be)) {
if (be) {
zrle_encode_24abe(vs, x, y, w, h, zywrle_level);
zrle_encode_24abe(vs, &worker->zrle, x, y, w, h, zywrle_level);
} else {
zrle_encode_24ale(vs, x, y, w, h, zywrle_level);
zrle_encode_24ale(vs, &worker->zrle, x, y, w, h, zywrle_level);
}
} else if ((fits_in_ls3bytes && be) || (fits_in_ms3bytes && !be)) {
if (be) {
zrle_encode_24bbe(vs, x, y, w, h, zywrle_level);
zrle_encode_24bbe(vs, &worker->zrle, x, y, w, h, zywrle_level);
} else {
zrle_encode_24ble(vs, x, y, w, h, zywrle_level);
zrle_encode_24ble(vs, &worker->zrle, x, y, w, h, zywrle_level);
}
} else {
if (be) {
zrle_encode_32be(vs, x, y, w, h, zywrle_level);
zrle_encode_32be(vs, &worker->zrle, x, y, w, h, zywrle_level);
} else {
zrle_encode_32le(vs, x, y, w, h, zywrle_level);
zrle_encode_32le(vs, &worker->zrle, x, y, w, h, zywrle_level);
}
}
}
break;
}
vnc_zrle_stop(vs);
bytes = zrle_compress_data(vs, Z_DEFAULT_COMPRESSION);
vnc_framebuffer_update(vs, x, y, w, h, vs->zrle->type);
vnc_zrle_stop(vs, &worker->zrle);
bytes = zrle_compress_data(vs, &worker->zrle, Z_DEFAULT_COMPRESSION);
vnc_framebuffer_update(vs, x, y, w, h, worker->zrle.type);
vnc_write_u32(vs, bytes);
vnc_write(vs, vs->zrle->zlib.buffer, vs->zrle->zlib.offset);
vnc_write(vs, worker->zrle.zlib.buffer, worker->zrle.zlib.offset);
return 1;
}
int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
int vnc_zrle_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h)
{
vs->zrle->type = VNC_ENCODING_ZRLE;
return zrle_send_framebuffer_update(vs, x, y, w, h);
worker->zrle.type = VNC_ENCODING_ZRLE;
return zrle_send_framebuffer_update(vs, worker, x, y, w, h);
}
int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
int vnc_zywrle_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h)
{
vs->zrle->type = VNC_ENCODING_ZYWRLE;
return zrle_send_framebuffer_update(vs, x, y, w, h);
worker->zrle.type = VNC_ENCODING_ZYWRLE;
return zrle_send_framebuffer_update(vs, worker, x, y, w, h);
}
void vnc_zrle_clear(VncState *vs)
void vnc_zrle_clear(VncWorker *worker)
{
if (vs->zrle->stream.opaque) {
deflateEnd(&vs->zrle->stream);
if (worker->zrle.stream.opaque) {
deflateEnd(&worker->zrle.stream);
}
buffer_free(&vs->zrle->zrle);
buffer_free(&vs->zrle->fb);
buffer_free(&vs->zrle->zlib);
buffer_free(&worker->zrle.zrle);
buffer_free(&worker->zrle.fb);
buffer_free(&worker->zrle.zlib);
}

View File

@ -62,16 +62,16 @@
#define ZRLE_ENCODE_TILE ZRLE_CONCAT2(zrle_encode_tile, ZRLE_ENCODE_SUFFIX)
#define ZRLE_WRITE_PALETTE ZRLE_CONCAT2(zrle_write_palette,ZRLE_ENCODE_SUFFIX)
static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h,
int zywrle_level);
static void ZRLE_ENCODE_TILE(VncState *vs, VncZrle *zrle, ZRLE_PIXEL *data,
int w, int h, int zywrle_level);
#if ZRLE_BPP != 8
#include "vnc-enc-zywrle-template.c"
#endif
static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h,
int zywrle_level)
static void ZRLE_ENCODE(VncState *vs, VncZrle *zrle,
int x, int y, int w, int h, int zywrle_level)
{
int ty;
@ -87,16 +87,16 @@ static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h,
tw = MIN(VNC_ZRLE_TILE_WIDTH, x + w - tx);
buf = zrle_convert_fb(vs, tx, ty, tw, th, ZRLE_BPP);
ZRLE_ENCODE_TILE(vs, buf, tw, th, zywrle_level);
buf = zrle_convert_fb(vs, zrle, tx, ty, tw, th, ZRLE_BPP);
ZRLE_ENCODE_TILE(vs, zrle, buf, tw, th, zywrle_level);
}
}
}
static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h,
int zywrle_level)
static void ZRLE_ENCODE_TILE(VncState *vs, VncZrle *zrle, ZRLE_PIXEL *data,
int w, int h, int zywrle_level)
{
VncPalette *palette = &vs->zrle->palette;
VncPalette *palette = &zrle->palette;
int runs = 0;
int single_pixels = 0;
@ -236,7 +236,7 @@ static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h,
#if ZRLE_BPP != 8
if (zywrle_level > 0 && !(zywrle_level & 0x80)) {
ZYWRLE_ANALYZE(data, data, w, h, w, zywrle_level, vs->zywrle.buf);
ZRLE_ENCODE_TILE(vs, data, w, h, zywrle_level | 0x80);
ZRLE_ENCODE_TILE(vs, zrle, data, w, h, zywrle_level | 0x80);
}
else
#endif

View File

@ -185,14 +185,10 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
local->vnc_encoding = orig->vnc_encoding;
local->features = orig->features;
local->vd = orig->vd;
local->lossy_rect = orig->lossy_rect;
local->write_pixels = orig->write_pixels;
local->client_pf = orig->client_pf;
local->client_endian = orig->client_endian;
local->tight = orig->tight;
local->zlib = orig->zlib;
local->hextile = orig->hextile;
local->zrle = orig->zrle;
local->client_width = orig->client_width;
local->client_height = orig->client_height;
}
@ -200,11 +196,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
static void vnc_async_encoding_end(VncState *orig, VncState *local)
{
buffer_free(&local->output);
orig->tight = local->tight;
orig->zlib = local->zlib;
orig->hextile = local->hextile;
orig->zrle = local->zrle;
orig->lossy_rect = local->lossy_rect;
}
static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect)
@ -237,6 +229,7 @@ static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect)
static int vnc_worker_thread_loop(VncJobQueue *queue)
{
VncConnection *vc;
VncJob *job;
VncRectEntry *entry, *tmp;
VncState vs = {};
@ -256,6 +249,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
}
assert(job->vs->magic == VNC_MAGIC);
vc = container_of(job->vs, VncConnection, vs);
vnc_lock_output(job->vs);
if (job->vs->ioc == NULL || job->vs->abort == true) {
@ -295,7 +289,8 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
}
if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y,
n = vnc_send_framebuffer_update(&vs, &vc->worker,
entry->rect.x, entry->rect.y,
entry->rect.w, entry->rect.h);
if (n >= 0) {

View File

@ -946,29 +946,30 @@ int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
return 1;
}
int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
int vnc_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h)
{
int n = 0;
switch(vs->vnc_encoding) {
case VNC_ENCODING_ZLIB:
n = vnc_zlib_send_framebuffer_update(vs, x, y, w, h);
n = vnc_zlib_send_framebuffer_update(vs, worker, x, y, w, h);
break;
case VNC_ENCODING_HEXTILE:
vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE);
n = vnc_hextile_send_framebuffer_update(vs, x, y, w, h);
break;
case VNC_ENCODING_TIGHT:
n = vnc_tight_send_framebuffer_update(vs, x, y, w, h);
n = vnc_tight_send_framebuffer_update(vs, worker, x, y, w, h);
break;
case VNC_ENCODING_TIGHT_PNG:
n = vnc_tight_png_send_framebuffer_update(vs, x, y, w, h);
n = vnc_tight_png_send_framebuffer_update(vs, worker, x, y, w, h);
break;
case VNC_ENCODING_ZRLE:
n = vnc_zrle_send_framebuffer_update(vs, x, y, w, h);
n = vnc_zrle_send_framebuffer_update(vs, worker, x, y, w, h);
break;
case VNC_ENCODING_ZYWRLE:
n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
n = vnc_zywrle_send_framebuffer_update(vs, worker, x, y, w, h);
break;
default:
vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
@ -1306,7 +1307,7 @@ static void vnc_disconnect_start(VncState *vs)
void vnc_disconnect_finish(VncState *vs)
{
int i;
VncConnection *vc = container_of(vs, VncConnection, vs);
trace_vnc_client_disconnect_finish(vs, vs->ioc);
@ -1320,9 +1321,9 @@ void vnc_disconnect_finish(VncState *vs)
qapi_free_VncClientInfo(vs->info);
vnc_zlib_clear(vs);
vnc_tight_clear(vs);
vnc_zrle_clear(vs);
vnc_zlib_clear(&vc->worker);
vnc_tight_clear(&vc->worker);
vnc_zrle_clear(&vc->worker);
#ifdef CONFIG_VNC_SASL
vnc_sasl_client_cleanup(vs);
@ -1350,19 +1351,12 @@ void vnc_disconnect_finish(VncState *vs)
}
buffer_free(&vs->jobs_buffer);
for (i = 0; i < VNC_STAT_ROWS; ++i) {
g_free(vs->lossy_rect[i]);
}
g_free(vs->lossy_rect);
object_unref(OBJECT(vs->ioc));
vs->ioc = NULL;
object_unref(OBJECT(vs->sioc));
vs->sioc = NULL;
vs->magic = 0;
g_free(vs->zrle);
g_free(vs->tight);
g_free(vs);
g_free(vc);
}
size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err)
@ -2126,13 +2120,14 @@ static void send_xvp_message(VncState *vs, int code)
static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
{
VncConnection *vc = container_of(vs, VncConnection, vs);
int i;
unsigned int enc = 0;
vs->features = 0;
vs->vnc_encoding = 0;
vs->tight->compression = 9;
vs->tight->quality = -1; /* Lossless by default */
vc->worker.tight.compression = 9;
vc->worker.tight.quality = -1; /* Lossless by default */
vs->absolute = -1;
/*
@ -2220,11 +2215,11 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
vnc_server_cut_text_caps(vs);
break;
case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9:
vs->tight->compression = (enc & 0x0F);
vc->worker.tight.compression = (enc & 0x0F);
break;
case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9:
if (vs->vd->lossy) {
vs->tight->quality = (enc & 0x0F);
vc->worker.tight.quality = (enc & 0x0F);
}
break;
default:
@ -2953,7 +2948,7 @@ static VncRectStat *vnc_stat_rect(VncDisplay *vd, int x, int y)
return &vs->stats[y / VNC_STAT_RECT][x / VNC_STAT_RECT];
}
void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h)
void vnc_sent_lossy_rect(VncWorker *worker, int x, int y, int w, int h)
{
int i, j;
@ -2964,7 +2959,7 @@ void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h)
for (j = y; j <= h; j++) {
for (i = x; i <= w; i++) {
vs->lossy_rect[j][i] = 1;
worker->lossy_rect[j][i] = 1;
}
}
}
@ -2980,6 +2975,7 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y)
x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT);
QTAILQ_FOREACH(vs, &vd->clients, next) {
VncConnection *vc = container_of(vs, VncConnection, vs);
int j;
/* kernel send buffers are full -> refresh later */
@ -2987,11 +2983,11 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y)
continue;
}
if (!vs->lossy_rect[sty][stx]) {
if (!vc->worker.lossy_rect[sty][stx]) {
continue;
}
vs->lossy_rect[sty][stx] = 0;
vc->worker.lossy_rect[sty][stx] = 0;
for (j = 0; j < VNC_STAT_RECT; ++j) {
bitmap_set(vs->dirty[y + j],
x / VNC_DIRTY_PIXELS_PER_BIT,
@ -3241,13 +3237,11 @@ static void vnc_refresh(DisplayChangeListener *dcl)
static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
bool skipauth, bool websocket)
{
VncState *vs = g_new0(VncState, 1);
VncConnection *vc = g_new0(VncConnection, 1);
VncState *vs = &vc->vs;
bool first_client = QTAILQ_EMPTY(&vd->clients);
int i;
trace_vnc_client_connect(vs, sioc);
vs->zrle = g_new0(VncZrle, 1);
vs->tight = g_new0(VncTight, 1);
vs->magic = VNC_MAGIC;
vs->sioc = sioc;
object_ref(OBJECT(vs->sioc));
@ -3255,23 +3249,23 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
object_ref(OBJECT(vs->ioc));
vs->vd = vd;
buffer_init(&vs->input, "vnc-input/%p", sioc);
buffer_init(&vs->output, "vnc-output/%p", sioc);
buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc);
buffer_init(&vs->input, "vnc-input/%p", sioc);
buffer_init(&vs->output, "vnc-output/%p", sioc);
buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc);
buffer_init(&vs->tight->tight, "vnc-tight/%p", sioc);
buffer_init(&vs->tight->zlib, "vnc-tight-zlib/%p", sioc);
buffer_init(&vs->tight->gradient, "vnc-tight-gradient/%p", sioc);
buffer_init(&vc->worker.tight.tight, "vnc-tight/%p", sioc);
buffer_init(&vc->worker.tight.zlib, "vnc-tight-zlib/%p", sioc);
buffer_init(&vc->worker.tight.gradient, "vnc-tight-gradient/%p", sioc);
#ifdef CONFIG_VNC_JPEG
buffer_init(&vs->tight->jpeg, "vnc-tight-jpeg/%p", sioc);
buffer_init(&vc->worker.tight.jpeg, "vnc-tight-jpeg/%p", sioc);
#endif
#ifdef CONFIG_PNG
buffer_init(&vs->tight->png, "vnc-tight-png/%p", sioc);
buffer_init(&vc->worker.tight.png, "vnc-tight-png/%p", sioc);
#endif
buffer_init(&vs->zlib.zlib, "vnc-zlib/%p", sioc);
buffer_init(&vs->zrle->zrle, "vnc-zrle/%p", sioc);
buffer_init(&vs->zrle->fb, "vnc-zrle-fb/%p", sioc);
buffer_init(&vs->zrle->zlib, "vnc-zrle-zlib/%p", sioc);
buffer_init(&vc->worker.zlib.zlib, "vnc-zlib/%p", sioc);
buffer_init(&vc->worker.zrle.zrle, "vnc-zrle/%p", sioc);
buffer_init(&vc->worker.zrle.fb, "vnc-zrle-fb/%p", sioc);
buffer_init(&vc->worker.zrle.zlib, "vnc-zrle-zlib/%p", sioc);
if (skipauth) {
vs->auth = VNC_AUTH_NONE;
@ -3288,11 +3282,6 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
VNC_DEBUG("Client sioc=%p ws=%d auth=%d subauth=%d\n",
sioc, websocket, vs->auth, vs->subauth);
vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect));
for (i = 0; i < VNC_STAT_ROWS; ++i) {
vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS);
}
VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
qio_channel_set_blocking(vs->ioc, false, NULL);

View File

@ -272,8 +272,6 @@ struct VncState
gboolean disconnecting;
DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS);
uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in
* vnc-jobs-async.c */
VncDisplay *vd;
VncStateUpdate update; /* Most recent pending request from client */
@ -341,10 +339,7 @@ struct VncState
/* Encoding specific, if you add something here, don't forget to
* update vnc_async_encoding_start()
*/
VncTight *tight;
VncZlib zlib;
VncHextile hextile;
VncZrle *zrle;
VncZywrle zywrle;
Notifier mouse_mode_notifier;
@ -356,6 +351,19 @@ struct VncState
QTAILQ_ENTRY(VncState) next;
};
typedef struct VncWorker {
uint8_t lossy_rect[VNC_STAT_ROWS][VNC_STAT_COLS];
VncTight tight;
VncZlib zlib;
VncZrle zrle;
} VncWorker;
typedef struct VncConnection {
VncState vs;
VncWorker worker;
} VncConnection;
/*****************************************************************************
*
@ -602,10 +610,11 @@ int vnc_server_fb_stride(VncDisplay *vd);
void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v);
double vnc_update_freq(VncState *vs, int x, int y, int w, int h);
void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h);
void vnc_sent_lossy_rect(VncWorker *worker, int x, int y, int w, int h);
/* Encodings */
int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
int vnc_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h);
int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
@ -615,17 +624,21 @@ void vnc_hextile_set_pixel_conversion(VncState *vs, int generic);
void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size);
void vnc_zlib_zfree(void *x, void *addr);
int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
void vnc_zlib_clear(VncState *vs);
int vnc_zlib_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h);
void vnc_zlib_clear(VncWorker *worker);
int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y,
int w, int h);
void vnc_tight_clear(VncState *vs);
int vnc_tight_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h);
int vnc_tight_png_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h);
void vnc_tight_clear(VncWorker *worker);
int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
void vnc_zrle_clear(VncState *vs);
int vnc_zrle_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h);
int vnc_zywrle_send_framebuffer_update(VncState *vs, VncWorker *worker,
int x, int y, int w, int h);
void vnc_zrle_clear(VncWorker *worker);
/* vnc-clipboard.c */
void vnc_server_cut_text_caps(VncState *vs);