ui/spice: Add an option to submit gl_draw requests at fixed rate

In the specific case where the display layer (virtio-gpu) is using
dmabuf, and if remote clients are enabled (-spice gl=on,port=xxxx),
it makes sense to limit the maximum (streaming) rate (refresh rate)
to a fixed value using the GUI refresh timer. Otherwise, the updates
or gl_draw requests would be sent as soon as the Guest submits a new
frame which is not optimal as it would lead to increased network
traffic and wastage of GPU cycles if the frames get dropped.

Cc: Gerd Hoffmann <kraxel@redhat.com>
Cc: Marc-André Lureau <marcandre.lureau@redhat.com>
Cc: Dmitry Osipenko <dmitry.osipenko@collabora.com>
Cc: Frediano Ziglio <freddy77@gmail.com>
Cc: Dongwon Kim <dongwon.kim@intel.com>
Cc: Michael Scherle <michael.scherle@rz.uni-freiburg.de>
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Signed-off-by: Vivek Kasireddy <vivek.kasireddy@intel.com>
Message-Id: <20250617043546.1022779-5-vivek.kasireddy@intel.com>
This commit is contained in:
Vivek Kasireddy 2025-06-16 21:32:28 -07:00 committed by Marc-André Lureau
parent bd46161dd1
commit 50d135e377
4 changed files with 70 additions and 10 deletions

View file

@ -152,6 +152,7 @@ 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

@ -2282,6 +2282,7 @@ DEF("spice", HAS_ARG, QEMU_OPTION_spice,
" [,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",
@ -2377,6 +2378,10 @@ SRST
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

@ -56,6 +56,8 @@ struct SpiceTimer {
QEMUTimer *timer;
};
#define DEFAULT_MAX_REFRESH_RATE 30
static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
{
SpiceTimer *timer;
@ -491,6 +493,9 @@ static QemuOptsList qemu_spice_opts = {
},{
.name = "video-codec",
.type = QEMU_OPT_STRING,
},{
.name = "max-refresh-rate",
.type = QEMU_OPT_NUMBER,
},{
.name = "agent-mouse",
.type = QEMU_OPT_BOOL,
@ -804,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

View file

@ -32,6 +32,7 @@
bool spice_opengl;
bool spice_remote_client;
int spice_max_refresh_rate;
int qemu_spice_rect_is_empty(const QXLRect* r)
{
@ -844,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;
}
@ -857,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;
}
}
@ -954,6 +972,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);
@ -1061,7 +1093,6 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
EGLint fourcc = 0;
bool render_cursor = false;
bool y_0_top = false; /* FIXME */
uint64_t cookie;
uint32_t width, height, texture;
if (!ssd->have_scanout) {
@ -1159,8 +1190,19 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl,
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 = {