mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice
synced 2025-12-26 14:41:25 +00:00
1290 lines
43 KiB
C++
1290 lines
43 KiB
C++
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
/*
|
|
Copyright (C) 2009-2015 Red Hat, Inc.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <config.h>
|
|
|
|
#include <common/utils.h>
|
|
#include "dcc-private.h"
|
|
#include "display-channel.h"
|
|
#include "display-channel-private.h"
|
|
#include "red-client.h"
|
|
#include "main-channel-client.h"
|
|
#include <spice-server-enums.h>
|
|
|
|
#define DISPLAY_CLIENT_SHORT_TIMEOUT 15000000000ULL //nano
|
|
#define DISPLAY_FREE_LIST_DEFAULT_SIZE 128
|
|
|
|
static void dcc_init_stream_agents(DisplayChannelClient *dcc);
|
|
|
|
DisplayChannelClient::DisplayChannelClient(DisplayChannel *display,
|
|
RedClient *client, RedStream *stream,
|
|
RedChannelCapabilities *caps,
|
|
uint32_t id,
|
|
SpiceImageCompression image_compression,
|
|
spice_wan_compression_t jpeg_state,
|
|
spice_wan_compression_t zlib_glz_state):
|
|
CommonGraphicsChannelClient(display, client, stream, caps, true),
|
|
priv(new DisplayChannelClientPrivate)
|
|
{
|
|
|
|
// XXX from display_channel_client_init, put somewhere else
|
|
ring_init(&priv->palette_cache_lru);
|
|
// todo: tune quality according to bandwidth
|
|
priv->encoders.jpeg_quality = 85;
|
|
|
|
priv->send_data.free_list.res = (SpiceResourceList*)
|
|
g_malloc(sizeof(SpiceResourceList) +
|
|
DISPLAY_FREE_LIST_DEFAULT_SIZE * sizeof(SpiceResourceID));
|
|
priv->send_data.free_list.res_size = DISPLAY_FREE_LIST_DEFAULT_SIZE;
|
|
|
|
|
|
priv->image_compression = image_compression;
|
|
priv->jpeg_state = jpeg_state;
|
|
priv->zlib_glz_state = zlib_glz_state;
|
|
|
|
|
|
priv->id = id;
|
|
|
|
image_encoders_init(&priv->encoders, &DCC_TO_DC(this)->priv->encoder_shared_data);
|
|
|
|
dcc_init_stream_agents(this);
|
|
}
|
|
|
|
DisplayChannelClient::~DisplayChannelClient()
|
|
{
|
|
g_clear_pointer(&priv->preferred_video_codecs, g_array_unref);
|
|
g_clear_pointer(&priv->client_preferred_video_codecs, g_array_unref);
|
|
}
|
|
|
|
static RedSurfaceCreateItem *red_surface_create_item_new(RedChannel* channel,
|
|
uint32_t surface_id,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
uint32_t format,
|
|
uint32_t flags)
|
|
{
|
|
RedSurfaceCreateItem *create;
|
|
|
|
create = g_new(RedSurfaceCreateItem, 1);
|
|
|
|
create->surface_create.surface_id = surface_id;
|
|
create->surface_create.width = width;
|
|
create->surface_create.height = height;
|
|
create->surface_create.flags = flags;
|
|
create->surface_create.format = format;
|
|
|
|
red_pipe_item_init(&create->base, RED_PIPE_ITEM_TYPE_CREATE_SURFACE);
|
|
return create;
|
|
}
|
|
|
|
bool dcc_drawable_is_in_pipe(DisplayChannelClient *dcc, Drawable *drawable)
|
|
{
|
|
RedDrawablePipeItem *dpi;
|
|
GList *l;
|
|
|
|
for (l = drawable->pipes; l != NULL; l = l->next) {
|
|
dpi = (RedDrawablePipeItem *) l->data;
|
|
if (dpi->dcc == dcc) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Return: TRUE if wait_if_used == FALSE, or otherwise, if all of the pipe items that
|
|
* are related to the surface have been cleared (or sent) from the pipe.
|
|
*/
|
|
bool dcc_clear_surface_drawables_from_pipe(DisplayChannelClient *dcc, int surface_id,
|
|
int wait_if_used)
|
|
{
|
|
GList *l;
|
|
int x;
|
|
|
|
spice_return_val_if_fail(dcc != NULL, TRUE);
|
|
/* removing the newest drawables that their destination is surface_id and
|
|
no other drawable depends on them */
|
|
|
|
for (l = dcc->get_pipe()->head; l != NULL; ) {
|
|
Drawable *drawable;
|
|
RedDrawablePipeItem *dpi = NULL;
|
|
int depend_found = FALSE;
|
|
RedPipeItem *item = (RedPipeItem *) l->data;
|
|
GList *item_pos = l;
|
|
|
|
l = l->next;
|
|
if (item->type == RED_PIPE_ITEM_TYPE_DRAW) {
|
|
dpi = SPICE_UPCAST(RedDrawablePipeItem, item);
|
|
drawable = dpi->drawable;
|
|
} else if (item->type == RED_PIPE_ITEM_TYPE_UPGRADE) {
|
|
drawable = SPICE_UPCAST(RedUpgradeItem, item)->drawable;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (drawable->surface_id == surface_id) {
|
|
dcc->pipe_remove_and_release_pos(item_pos);
|
|
continue;
|
|
}
|
|
|
|
for (x = 0; x < 3; ++x) {
|
|
if (drawable->surface_deps[x] == surface_id) {
|
|
depend_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (depend_found) {
|
|
spice_debug("surface %d dependent item found %p, %p", surface_id, drawable, item);
|
|
if (!wait_if_used) {
|
|
return TRUE;
|
|
}
|
|
return dcc->wait_pipe_item_sent(item_pos, COMMON_CLIENT_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
if (!wait_if_used) {
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* in case that the pipe didn't contain any item that is dependent on the surface, but
|
|
* there is one during sending. Use a shorter timeout, since it is just one item
|
|
*/
|
|
return dcc->wait_outgoing_item(DISPLAY_CLIENT_SHORT_TIMEOUT);
|
|
}
|
|
|
|
void dcc_create_surface(DisplayChannelClient *dcc, int surface_id)
|
|
{
|
|
DisplayChannel *display;
|
|
RedSurface *surface;
|
|
RedSurfaceCreateItem *create;
|
|
uint32_t flags;
|
|
|
|
if (!dcc) {
|
|
return;
|
|
}
|
|
|
|
display = DCC_TO_DC(dcc);
|
|
flags = is_primary_surface(display, surface_id) ? SPICE_SURFACE_FLAGS_PRIMARY : 0;
|
|
|
|
/* don't send redundant create surface commands to client */
|
|
if (display->get_during_target_migrate() ||
|
|
dcc->priv->surface_client_created[surface_id]) {
|
|
return;
|
|
}
|
|
surface = &display->priv->surfaces[surface_id];
|
|
create = red_surface_create_item_new(display,
|
|
surface_id, surface->context.width,
|
|
surface->context.height,
|
|
surface->context.format, flags);
|
|
dcc->priv->surface_client_created[surface_id] = TRUE;
|
|
dcc->pipe_add(&create->base);
|
|
}
|
|
|
|
// adding the pipe item after pos. If pos == NULL, adding to head.
|
|
RedImageItem *dcc_add_surface_area_image(DisplayChannelClient *dcc,
|
|
int surface_id,
|
|
SpiceRect *area,
|
|
GList *pipe_item_pos,
|
|
int can_lossy)
|
|
{
|
|
DisplayChannel *display = DCC_TO_DC(dcc);
|
|
RedSurface *surface = &display->priv->surfaces[surface_id];
|
|
SpiceCanvas *canvas = surface->context.canvas;
|
|
RedImageItem *item;
|
|
int stride;
|
|
int width;
|
|
int height;
|
|
int bpp;
|
|
int all_set;
|
|
|
|
spice_assert(area);
|
|
|
|
width = area->right - area->left;
|
|
height = area->bottom - area->top;
|
|
bpp = SPICE_SURFACE_FMT_DEPTH(surface->context.format) / 8;
|
|
stride = width * bpp;
|
|
|
|
item = (RedImageItem *)g_malloc(height * stride + sizeof(RedImageItem));
|
|
|
|
red_pipe_item_init(&item->base, RED_PIPE_ITEM_TYPE_IMAGE);
|
|
|
|
item->surface_id = surface_id;
|
|
item->image_format =
|
|
spice_bitmap_from_surface_type(surface->context.format);
|
|
item->image_flags = 0;
|
|
item->pos.x = area->left;
|
|
item->pos.y = area->top;
|
|
item->width = width;
|
|
item->height = height;
|
|
item->stride = stride;
|
|
item->top_down = surface->context.top_down;
|
|
item->can_lossy = can_lossy;
|
|
|
|
canvas->ops->read_bits(canvas, item->data, stride, area);
|
|
|
|
/* For 32bit non-primary surfaces we need to keep any non-zero
|
|
high bytes as the surface may be used as source to an alpha_blend */
|
|
if (!is_primary_surface(display, surface_id) &&
|
|
item->image_format == SPICE_BITMAP_FMT_32BIT &&
|
|
rgb32_data_has_alpha(item->width, item->height, item->stride, item->data, &all_set)) {
|
|
if (all_set) {
|
|
item->image_flags |= SPICE_IMAGE_FLAGS_HIGH_BITS_SET;
|
|
} else {
|
|
item->image_format = SPICE_BITMAP_FMT_RGBA;
|
|
}
|
|
}
|
|
|
|
if (pipe_item_pos) {
|
|
dcc->pipe_add_after_pos(&item->base, pipe_item_pos);
|
|
} else {
|
|
dcc->pipe_add(&item->base);
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
void dcc_push_surface_image(DisplayChannelClient *dcc, int surface_id)
|
|
{
|
|
DisplayChannel *display;
|
|
SpiceRect area;
|
|
RedSurface *surface;
|
|
|
|
if (!dcc) {
|
|
return;
|
|
}
|
|
|
|
display = DCC_TO_DC(dcc);
|
|
surface = &display->priv->surfaces[surface_id];
|
|
if (!surface->context.canvas) {
|
|
return;
|
|
}
|
|
area.top = area.left = 0;
|
|
area.right = surface->context.width;
|
|
area.bottom = surface->context.height;
|
|
|
|
/* not allowing lossy compression because probably, especially if it is a primary surface,
|
|
it combines both "picture-like" areas with areas that are more "artificial"*/
|
|
dcc_add_surface_area_image(dcc, surface_id, &area, NULL, FALSE);
|
|
}
|
|
|
|
static void add_drawable_surface_images(DisplayChannelClient *dcc, Drawable *drawable)
|
|
{
|
|
DisplayChannel *display = DCC_TO_DC(dcc);
|
|
int x;
|
|
|
|
for (x = 0; x < 3; ++x) {
|
|
int surface_id;
|
|
|
|
surface_id = drawable->surface_deps[x];
|
|
if (surface_id != -1) {
|
|
if (dcc->priv->surface_client_created[surface_id]) {
|
|
continue;
|
|
}
|
|
dcc_create_surface(dcc, surface_id);
|
|
display_channel_current_flush(display, surface_id);
|
|
dcc_push_surface_image(dcc, surface_id);
|
|
}
|
|
}
|
|
|
|
if (dcc->priv->surface_client_created[drawable->surface_id]) {
|
|
return;
|
|
}
|
|
|
|
dcc_create_surface(dcc, drawable->surface_id);
|
|
display_channel_current_flush(display, drawable->surface_id);
|
|
dcc_push_surface_image(dcc, drawable->surface_id);
|
|
}
|
|
|
|
static void red_drawable_pipe_item_free(RedPipeItem *item)
|
|
{
|
|
RedDrawablePipeItem *dpi = SPICE_UPCAST(RedDrawablePipeItem, item);
|
|
spice_assert(item->refcount == 0);
|
|
|
|
dpi->drawable->pipes = g_list_remove(dpi->drawable->pipes, dpi);
|
|
drawable_unref(dpi->drawable);
|
|
g_free(dpi);
|
|
}
|
|
|
|
static RedDrawablePipeItem *red_drawable_pipe_item_new(DisplayChannelClient *dcc,
|
|
Drawable *drawable)
|
|
{
|
|
RedDrawablePipeItem *dpi;
|
|
|
|
dpi = g_new0(RedDrawablePipeItem, 1);
|
|
dpi->drawable = drawable;
|
|
dpi->dcc = dcc;
|
|
drawable->pipes = g_list_prepend(drawable->pipes, dpi);
|
|
red_pipe_item_init_full(&dpi->base, RED_PIPE_ITEM_TYPE_DRAW,
|
|
red_drawable_pipe_item_free);
|
|
drawable->refs++;
|
|
return dpi;
|
|
}
|
|
|
|
void dcc_prepend_drawable(DisplayChannelClient *dcc, Drawable *drawable)
|
|
{
|
|
RedDrawablePipeItem *dpi = red_drawable_pipe_item_new(dcc, drawable);
|
|
|
|
add_drawable_surface_images(dcc, drawable);
|
|
dcc->pipe_add(&dpi->base);
|
|
}
|
|
|
|
void dcc_append_drawable(DisplayChannelClient *dcc, Drawable *drawable)
|
|
{
|
|
RedDrawablePipeItem *dpi = red_drawable_pipe_item_new(dcc, drawable);
|
|
|
|
add_drawable_surface_images(dcc, drawable);
|
|
dcc->pipe_add_tail(&dpi->base);
|
|
}
|
|
|
|
void dcc_add_drawable_after(DisplayChannelClient *dcc, Drawable *drawable, RedPipeItem *pos)
|
|
{
|
|
RedDrawablePipeItem *dpi = red_drawable_pipe_item_new(dcc, drawable);
|
|
|
|
add_drawable_surface_images(dcc, drawable);
|
|
dcc->pipe_add_after(&dpi->base, pos);
|
|
}
|
|
|
|
static void dcc_init_stream_agents(DisplayChannelClient *dcc)
|
|
{
|
|
int i;
|
|
DisplayChannel *display = DCC_TO_DC(dcc);
|
|
|
|
for (i = 0; i < NUM_STREAMS; i++) {
|
|
VideoStreamAgent *agent = &dcc->priv->stream_agents[i];
|
|
agent->stream = display_channel_get_nth_video_stream(display, i);
|
|
region_init(&agent->vis_region);
|
|
region_init(&agent->clip);
|
|
}
|
|
}
|
|
|
|
DisplayChannelClient *dcc_new(DisplayChannel *display,
|
|
RedClient *client, RedStream *stream,
|
|
int mig_target,
|
|
RedChannelCapabilities *caps,
|
|
SpiceImageCompression image_compression,
|
|
spice_wan_compression_t jpeg_state,
|
|
spice_wan_compression_t zlib_glz_state)
|
|
|
|
{
|
|
auto dcc =
|
|
red::make_shared<DisplayChannelClient>(display, client, stream, caps,
|
|
display->priv->qxl->id, image_compression,
|
|
jpeg_state, zlib_glz_state);
|
|
if (!dcc->init()) {
|
|
return nullptr;
|
|
}
|
|
spice_debug("New display (client %p) dcc %p stream %p", client, dcc.get(), stream);
|
|
display->set_during_target_migrate(mig_target);
|
|
|
|
return dcc.get();
|
|
}
|
|
|
|
static void dcc_create_all_streams(DisplayChannelClient *dcc)
|
|
{
|
|
Ring *ring = &DCC_TO_DC(dcc)->priv->streams;
|
|
RingItem *item = ring;
|
|
|
|
while ((item = ring_next(ring, item))) {
|
|
VideoStream *stream = SPICE_CONTAINEROF(item, VideoStream, link);
|
|
dcc_create_stream(dcc, stream);
|
|
}
|
|
}
|
|
|
|
/* TODO: this function is evil^Wsynchronous, fix */
|
|
static bool display_channel_client_wait_for_init(DisplayChannelClient *dcc)
|
|
{
|
|
dcc->priv->expect_init = TRUE;
|
|
uint64_t end_time = spice_get_monotonic_time_ns() + COMMON_CLIENT_TIMEOUT;
|
|
for (;;) {
|
|
dcc->receive();
|
|
if (!dcc->is_connected()) {
|
|
break;
|
|
}
|
|
if (dcc->priv->pixmap_cache && dcc->priv->encoders.glz_dict) {
|
|
dcc->priv->pixmap_cache_generation = dcc->priv->pixmap_cache->generation;
|
|
/* TODO: move common.id? if it's used for a per client structure.. */
|
|
spice_debug("creating encoder with id == %d", dcc->priv->id);
|
|
if (!image_encoders_glz_create(&dcc->priv->encoders, dcc->priv->id)) {
|
|
spice_critical("create global lz failed");
|
|
}
|
|
return TRUE;
|
|
}
|
|
if (spice_get_monotonic_time_ns() > end_time) {
|
|
spice_warning("timeout");
|
|
dcc->disconnect();
|
|
break;
|
|
}
|
|
usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
void dcc_start(DisplayChannelClient *dcc)
|
|
{
|
|
DisplayChannel *display = DCC_TO_DC(dcc);
|
|
|
|
dcc->push_set_ack();
|
|
|
|
if (dcc->is_waiting_for_migrate_data())
|
|
return;
|
|
|
|
if (!display_channel_client_wait_for_init(dcc))
|
|
return;
|
|
|
|
red::shared_ptr<DisplayChannelClient> self(dcc);
|
|
dcc->ack_zero_messages_window();
|
|
if (display->priv->surfaces[0].context.canvas) {
|
|
display_channel_current_flush(display, 0);
|
|
dcc->pipe_add_type(RED_PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE);
|
|
dcc_create_surface(dcc, 0);
|
|
dcc_push_surface_image(dcc, 0);
|
|
dcc_push_monitors_config(dcc);
|
|
dcc->pipe_add_empty_msg(SPICE_MSG_DISPLAY_MARK);
|
|
dcc_create_all_streams(dcc);
|
|
}
|
|
|
|
if (red_stream_is_plain_unix(dcc->get_stream()) &&
|
|
dcc->test_remote_cap(SPICE_DISPLAY_CAP_GL_SCANOUT)) {
|
|
dcc->pipe_add(dcc_gl_scanout_item_new(dcc, NULL, 0));
|
|
dcc_push_monitors_config(dcc);
|
|
}
|
|
}
|
|
|
|
static void dcc_destroy_stream_agents(DisplayChannelClient *dcc)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NUM_STREAMS; i++) {
|
|
VideoStreamAgent *agent = &dcc->priv->stream_agents[i];
|
|
region_destroy(&agent->vis_region);
|
|
region_destroy(&agent->clip);
|
|
if (agent->video_encoder) {
|
|
agent->video_encoder->destroy(agent->video_encoder);
|
|
agent->video_encoder = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dcc_stop(DisplayChannelClient *dcc)
|
|
{
|
|
DisplayChannel *dc = DCC_TO_DC(dcc);
|
|
|
|
pixmap_cache_unref(dcc->priv->pixmap_cache);
|
|
dcc->priv->pixmap_cache = NULL;
|
|
dcc_palette_cache_reset(dcc);
|
|
g_free(dcc->priv->send_data.free_list.res);
|
|
dcc_destroy_stream_agents(dcc);
|
|
image_encoders_free(&dcc->priv->encoders);
|
|
|
|
if (dcc->priv->gl_draw_ongoing) {
|
|
display_channel_gl_draw_done(dc);
|
|
}
|
|
}
|
|
|
|
void dcc_video_stream_agent_clip(DisplayChannelClient* dcc, VideoStreamAgent *agent)
|
|
{
|
|
VideoStreamClipItem *item = video_stream_clip_item_new(agent);
|
|
|
|
dcc->pipe_add(&item->base);
|
|
}
|
|
|
|
static void red_monitors_config_item_free(RedPipeItem *pipe_item)
|
|
{
|
|
RedMonitorsConfigItem *item = SPICE_UPCAST(RedMonitorsConfigItem, pipe_item);
|
|
|
|
monitors_config_unref(item->monitors_config);
|
|
g_free(item);
|
|
}
|
|
|
|
static RedMonitorsConfigItem *red_monitors_config_item_new(RedChannel* channel,
|
|
MonitorsConfig *monitors_config)
|
|
{
|
|
RedMonitorsConfigItem *mci;
|
|
|
|
mci = g_new(RedMonitorsConfigItem, 1);
|
|
mci->monitors_config = monitors_config_ref(monitors_config);
|
|
|
|
red_pipe_item_init_full(&mci->base, RED_PIPE_ITEM_TYPE_MONITORS_CONFIG,
|
|
red_monitors_config_item_free);
|
|
return mci;
|
|
}
|
|
|
|
void dcc_push_monitors_config(DisplayChannelClient *dcc)
|
|
{
|
|
DisplayChannel *dc = DCC_TO_DC(dcc);
|
|
MonitorsConfig *monitors_config = dc->priv->monitors_config;
|
|
RedMonitorsConfigItem *mci;
|
|
|
|
if (monitors_config == NULL) {
|
|
spice_warning("monitors_config is NULL");
|
|
return;
|
|
}
|
|
|
|
if (!dcc->test_remote_cap(SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
|
|
return;
|
|
}
|
|
|
|
mci = red_monitors_config_item_new(dcc->get_channel(),
|
|
monitors_config);
|
|
dcc->pipe_add(&mci->base);
|
|
}
|
|
|
|
static RedSurfaceDestroyItem *red_surface_destroy_item_new(uint32_t surface_id)
|
|
{
|
|
RedSurfaceDestroyItem *destroy;
|
|
|
|
destroy = g_new(RedSurfaceDestroyItem, 1);
|
|
destroy->surface_destroy.surface_id = surface_id;
|
|
red_pipe_item_init(&destroy->base, RED_PIPE_ITEM_TYPE_DESTROY_SURFACE);
|
|
|
|
return destroy;
|
|
}
|
|
|
|
RedPipeItem *dcc_gl_scanout_item_new(RedChannelClient *rcc, void *data, int num)
|
|
{
|
|
RedGlScanoutUnixItem *item;
|
|
|
|
/* FIXME: on !unix peer, start streaming with a video codec */
|
|
if (!red_stream_is_plain_unix(rcc->get_stream()) ||
|
|
!rcc->test_remote_cap(SPICE_DISPLAY_CAP_GL_SCANOUT)) {
|
|
red_channel_warning(rcc->get_channel(),
|
|
"FIXME: client does not support GL scanout");
|
|
rcc->disconnect();
|
|
return NULL;
|
|
}
|
|
|
|
item = g_new(RedGlScanoutUnixItem, 1);
|
|
red_pipe_item_init(&item->base, RED_PIPE_ITEM_TYPE_GL_SCANOUT);
|
|
|
|
return &item->base;
|
|
}
|
|
|
|
XXX_CAST(RedChannelClient, DisplayChannelClient, DISPLAY_CHANNEL_CLIENT);
|
|
|
|
RedPipeItem *dcc_gl_draw_item_new(RedChannelClient *rcc, void *data, int num)
|
|
{
|
|
DisplayChannelClient *dcc = DISPLAY_CHANNEL_CLIENT(rcc);
|
|
const SpiceMsgDisplayGlDraw *draw = (const SpiceMsgDisplayGlDraw *) data;
|
|
RedGlDrawItem *item;
|
|
|
|
if (!red_stream_is_plain_unix(rcc->get_stream()) ||
|
|
!rcc->test_remote_cap(SPICE_DISPLAY_CAP_GL_SCANOUT)) {
|
|
red_channel_warning(rcc->get_channel(),
|
|
"FIXME: client does not support GL scanout");
|
|
rcc->disconnect();
|
|
return NULL;
|
|
}
|
|
|
|
dcc->priv->gl_draw_ongoing = TRUE;
|
|
item = g_new(RedGlDrawItem, 1);
|
|
item->draw = *draw;
|
|
red_pipe_item_init(&item->base, RED_PIPE_ITEM_TYPE_GL_DRAW);
|
|
|
|
return &item->base;
|
|
}
|
|
|
|
void dcc_destroy_surface(DisplayChannelClient *dcc, uint32_t surface_id)
|
|
{
|
|
DisplayChannel *display;
|
|
RedSurfaceDestroyItem *destroy;
|
|
|
|
if (!dcc) {
|
|
return;
|
|
}
|
|
|
|
display = DCC_TO_DC(dcc);
|
|
|
|
if (display->get_during_target_migrate() ||
|
|
!dcc->priv->surface_client_created[surface_id]) {
|
|
return;
|
|
}
|
|
|
|
dcc->priv->surface_client_created[surface_id] = FALSE;
|
|
destroy = red_surface_destroy_item_new(surface_id);
|
|
dcc->pipe_add(&destroy->base);
|
|
}
|
|
|
|
#define MIN_DIMENSION_TO_QUIC 3
|
|
/**
|
|
* quic doesn't handle:
|
|
* (1) palette
|
|
*/
|
|
static bool can_quic_compress(SpiceBitmap *bitmap)
|
|
{
|
|
return !bitmap_fmt_is_plt(bitmap->format) &&
|
|
bitmap->x >= MIN_DIMENSION_TO_QUIC && bitmap->y >= MIN_DIMENSION_TO_QUIC;
|
|
}
|
|
/**
|
|
* lz/glz doesn't handle:
|
|
* (1) bitmaps with strides that are larger than the width of the image in bytes
|
|
* (2) unstable bitmaps
|
|
*/
|
|
static bool can_lz_compress(SpiceBitmap *bitmap)
|
|
{
|
|
return !bitmap_has_extra_stride(bitmap) &&
|
|
!(bitmap->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE);
|
|
}
|
|
|
|
#define MIN_SIZE_TO_COMPRESS 54
|
|
static SpiceImageCompression get_compression_for_bitmap(SpiceBitmap *bitmap,
|
|
SpiceImageCompression preferred_compression,
|
|
Drawable *drawable)
|
|
{
|
|
if (bitmap->y * bitmap->stride < MIN_SIZE_TO_COMPRESS) { // TODO: change the size cond
|
|
return SPICE_IMAGE_COMPRESSION_OFF;
|
|
}
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_OFF) {
|
|
return SPICE_IMAGE_COMPRESSION_OFF;
|
|
}
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_QUIC) {
|
|
if (can_quic_compress(bitmap)) {
|
|
return SPICE_IMAGE_COMPRESSION_QUIC;
|
|
}
|
|
return SPICE_IMAGE_COMPRESSION_OFF;
|
|
}
|
|
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_AUTO_GLZ ||
|
|
preferred_compression == SPICE_IMAGE_COMPRESSION_AUTO_LZ) {
|
|
if (can_quic_compress(bitmap)) {
|
|
if (drawable == NULL ||
|
|
drawable->copy_bitmap_graduality == BITMAP_GRADUAL_INVALID) {
|
|
if (bitmap_fmt_has_graduality(bitmap->format) &&
|
|
bitmap_get_graduality_level(bitmap) == BITMAP_GRADUAL_HIGH) {
|
|
return SPICE_IMAGE_COMPRESSION_QUIC;
|
|
}
|
|
} else if (drawable->copy_bitmap_graduality == BITMAP_GRADUAL_HIGH) {
|
|
return SPICE_IMAGE_COMPRESSION_QUIC;
|
|
}
|
|
if (!can_lz_compress(bitmap)) {
|
|
return SPICE_IMAGE_COMPRESSION_QUIC;
|
|
}
|
|
}
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_AUTO_LZ) {
|
|
preferred_compression = SPICE_IMAGE_COMPRESSION_LZ;
|
|
} else {
|
|
preferred_compression = SPICE_IMAGE_COMPRESSION_GLZ;
|
|
}
|
|
}
|
|
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_GLZ) {
|
|
if (drawable == NULL || !bitmap_fmt_has_graduality(bitmap->format)) {
|
|
preferred_compression = SPICE_IMAGE_COMPRESSION_LZ;
|
|
}
|
|
}
|
|
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_LZ4) {
|
|
if (!bitmap_fmt_is_rgb(bitmap->format)) {
|
|
preferred_compression = SPICE_IMAGE_COMPRESSION_LZ;
|
|
}
|
|
}
|
|
|
|
if (preferred_compression == SPICE_IMAGE_COMPRESSION_LZ ||
|
|
preferred_compression == SPICE_IMAGE_COMPRESSION_LZ4 ||
|
|
preferred_compression == SPICE_IMAGE_COMPRESSION_GLZ) {
|
|
if (can_lz_compress(bitmap)) {
|
|
return preferred_compression;
|
|
}
|
|
return SPICE_IMAGE_COMPRESSION_OFF;
|
|
}
|
|
|
|
return SPICE_IMAGE_COMPRESSION_INVALID;
|
|
}
|
|
|
|
int dcc_compress_image(DisplayChannelClient *dcc,
|
|
SpiceImage *dest, SpiceBitmap *src, Drawable *drawable,
|
|
int can_lossy,
|
|
compress_send_data_t* o_comp_data)
|
|
{
|
|
DisplayChannel *display_channel = DCC_TO_DC(dcc);
|
|
SpiceImageCompression image_compression;
|
|
stat_start_time_t start_time;
|
|
int success = FALSE;
|
|
|
|
stat_start_time_init(&start_time, &display_channel->priv->encoder_shared_data.off_stat);
|
|
|
|
image_compression = get_compression_for_bitmap(src, dcc->priv->image_compression, drawable);
|
|
switch (image_compression) {
|
|
case SPICE_IMAGE_COMPRESSION_OFF:
|
|
break;
|
|
case SPICE_IMAGE_COMPRESSION_QUIC:
|
|
if (can_lossy && display_channel->priv->enable_jpeg &&
|
|
(src->format != SPICE_BITMAP_FMT_RGBA || !bitmap_has_extra_stride(src))) {
|
|
success = image_encoders_compress_jpeg(&dcc->priv->encoders, dest, src, o_comp_data);
|
|
break;
|
|
}
|
|
success = image_encoders_compress_quic(&dcc->priv->encoders, dest, src, o_comp_data);
|
|
break;
|
|
case SPICE_IMAGE_COMPRESSION_GLZ:
|
|
success = image_encoders_compress_glz(&dcc->priv->encoders, dest, src,
|
|
drawable->red_drawable, &drawable->glz_retention,
|
|
o_comp_data,
|
|
display_channel->priv->enable_zlib_glz_wrap);
|
|
if (success) {
|
|
break;
|
|
}
|
|
goto lz_compress;
|
|
#ifdef USE_LZ4
|
|
case SPICE_IMAGE_COMPRESSION_LZ4:
|
|
if (dcc->test_remote_cap(SPICE_DISPLAY_CAP_LZ4_COMPRESSION)) {
|
|
success = image_encoders_compress_lz4(&dcc->priv->encoders, dest, src, o_comp_data);
|
|
break;
|
|
}
|
|
#endif
|
|
/* fall through */
|
|
case SPICE_IMAGE_COMPRESSION_LZ:
|
|
lz_compress:
|
|
success = image_encoders_compress_lz(&dcc->priv->encoders, dest, src, o_comp_data);
|
|
if (success && !bitmap_fmt_is_rgb(src->format)) {
|
|
dcc_palette_cache_palette(dcc, dest->u.lz_plt.palette, &(dest->u.lz_plt.flags));
|
|
}
|
|
break;
|
|
default:
|
|
spice_error("invalid image compression type %u", image_compression);
|
|
}
|
|
|
|
if (!success) {
|
|
uint64_t image_size = src->stride * (uint64_t)src->y;
|
|
stat_compress_add(&display_channel->priv->encoder_shared_data.off_stat, start_time, image_size, image_size);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
#define CLIENT_PALETTE_CACHE
|
|
#include "cache-item.tmpl.cpp"
|
|
#undef CLIENT_PALETTE_CACHE
|
|
|
|
void dcc_palette_cache_palette(DisplayChannelClient *dcc, SpicePalette *palette,
|
|
uint8_t *flags)
|
|
{
|
|
if (palette == NULL) {
|
|
return;
|
|
}
|
|
if (palette->unique) {
|
|
if (red_palette_cache_find(dcc, palette->unique)) {
|
|
*flags |= SPICE_BITMAP_FLAGS_PAL_FROM_CACHE;
|
|
return;
|
|
}
|
|
if (red_palette_cache_add(dcc, palette->unique, 1)) {
|
|
*flags |= SPICE_BITMAP_FLAGS_PAL_CACHE_ME;
|
|
}
|
|
}
|
|
}
|
|
|
|
void dcc_palette_cache_reset(DisplayChannelClient *dcc)
|
|
{
|
|
red_palette_cache_reset(dcc, CLIENT_PALETTE_CACHE_SIZE);
|
|
}
|
|
|
|
static void dcc_push_release(DisplayChannelClient *dcc, uint8_t type, uint64_t id,
|
|
uint64_t* sync_data)
|
|
{
|
|
FreeList *free_list = &dcc->priv->send_data.free_list;
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
|
|
free_list->sync[i] = MAX(free_list->sync[i], sync_data[i]);
|
|
}
|
|
|
|
if (free_list->res->count == free_list->res_size) {
|
|
free_list->res = (SpiceResourceList*) g_realloc(free_list->res,
|
|
sizeof(*free_list->res) +
|
|
free_list->res_size * sizeof(SpiceResourceID) * 2);
|
|
free_list->res_size *= 2;
|
|
}
|
|
free_list->res->resources[free_list->res->count].type = type;
|
|
free_list->res->resources[free_list->res->count++].id = id;
|
|
}
|
|
|
|
bool dcc_pixmap_cache_unlocked_add(DisplayChannelClient *dcc, uint64_t id,
|
|
uint32_t size, int lossy)
|
|
{
|
|
PixmapCache *cache = dcc->priv->pixmap_cache;
|
|
NewCacheItem *item;
|
|
uint64_t serial;
|
|
int key;
|
|
|
|
spice_assert(size > 0);
|
|
|
|
item = g_new(NewCacheItem, 1);
|
|
serial = dcc->get_message_serial();
|
|
|
|
if (cache->generation != dcc->priv->pixmap_cache_generation) {
|
|
if (!dcc->priv->pending_pixmaps_sync) {
|
|
dcc->pipe_add_type(RED_PIPE_ITEM_TYPE_PIXMAP_SYNC);
|
|
dcc->priv->pending_pixmaps_sync = TRUE;
|
|
}
|
|
g_free(item);
|
|
return FALSE;
|
|
}
|
|
|
|
cache->available -= size;
|
|
while (cache->available < 0) {
|
|
NewCacheItem *tail;
|
|
NewCacheItem **now;
|
|
|
|
SPICE_VERIFY(SPICE_OFFSETOF(NewCacheItem, lru_link) == 0);
|
|
if (!(tail = SPICE_CONTAINEROF(ring_get_tail(&cache->lru), NewCacheItem, lru_link)) ||
|
|
tail->sync[dcc->priv->id] == serial) {
|
|
cache->available += size;
|
|
g_free(item);
|
|
return FALSE;
|
|
}
|
|
|
|
now = &cache->hash_table[BITS_CACHE_HASH_KEY(tail->id)];
|
|
for (;;) {
|
|
spice_assert(*now);
|
|
if (*now == tail) {
|
|
*now = tail->next;
|
|
break;
|
|
}
|
|
now = &(*now)->next;
|
|
}
|
|
ring_remove(&tail->lru_link);
|
|
cache->items--;
|
|
cache->available += tail->size;
|
|
cache->sync[dcc->priv->id] = serial;
|
|
dcc_push_release(dcc, SPICE_RES_TYPE_PIXMAP, tail->id, tail->sync);
|
|
g_free(tail);
|
|
}
|
|
++cache->items;
|
|
item->next = cache->hash_table[(key = BITS_CACHE_HASH_KEY(id))];
|
|
cache->hash_table[key] = item;
|
|
ring_item_init(&item->lru_link);
|
|
ring_add(&cache->lru, &item->lru_link);
|
|
item->id = id;
|
|
item->size = size;
|
|
item->lossy = lossy;
|
|
memset(item->sync, 0, sizeof(item->sync));
|
|
item->sync[dcc->priv->id] = serial;
|
|
cache->sync[dcc->priv->id] = serial;
|
|
return TRUE;
|
|
}
|
|
|
|
static bool dcc_handle_init(DisplayChannelClient *dcc, SpiceMsgcDisplayInit *init)
|
|
{
|
|
gboolean success;
|
|
RedClient *client = dcc->get_client();
|
|
|
|
spice_return_val_if_fail(dcc->priv->expect_init, FALSE);
|
|
dcc->priv->expect_init = FALSE;
|
|
|
|
spice_return_val_if_fail(!dcc->priv->pixmap_cache, FALSE);
|
|
dcc->priv->pixmap_cache = pixmap_cache_get(client,
|
|
init->pixmap_cache_id,
|
|
init->pixmap_cache_size);
|
|
spice_return_val_if_fail(dcc->priv->pixmap_cache, FALSE);
|
|
|
|
success = image_encoders_get_glz_dictionary(&dcc->priv->encoders,
|
|
client,
|
|
init->glz_dictionary_id,
|
|
init->glz_dictionary_window_size);
|
|
spice_return_val_if_fail(success, FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static bool dcc_handle_stream_report(DisplayChannelClient *dcc,
|
|
SpiceMsgcDisplayStreamReport *report)
|
|
{
|
|
VideoStreamAgent *agent;
|
|
|
|
if (report->stream_id >= NUM_STREAMS) {
|
|
spice_warning("stream_report: invalid stream id %u",
|
|
report->stream_id);
|
|
return FALSE;
|
|
}
|
|
|
|
agent = &dcc->priv->stream_agents[report->stream_id];
|
|
if (!agent->video_encoder) {
|
|
spice_debug("stream_report: no encoder for stream id %u. "
|
|
"The stream has probably been destroyed",
|
|
report->stream_id);
|
|
return TRUE;
|
|
}
|
|
|
|
if (report->num_frames == 0 && report->num_drops == UINT32_MAX) {
|
|
spice_warning("stream_report: the client does not support stream %u",
|
|
report->stream_id);
|
|
/* Stop streaming the video so the client can see it */
|
|
agent->video_encoder->destroy(agent->video_encoder);
|
|
agent->video_encoder = NULL;
|
|
return TRUE;
|
|
}
|
|
|
|
if (report->unique_id != agent->report_id) {
|
|
spice_warning("stream_report: unique id mismatch: local (%u) != msg (%u) "
|
|
"The old stream was probably replaced by a new one",
|
|
agent->report_id, report->unique_id);
|
|
return TRUE;
|
|
}
|
|
|
|
agent->video_encoder->client_stream_report(agent->video_encoder,
|
|
report->num_frames,
|
|
report->num_drops,
|
|
report->start_frame_mm_time,
|
|
report->end_frame_mm_time,
|
|
report->last_frame_delay,
|
|
report->audio_delay);
|
|
return TRUE;
|
|
}
|
|
|
|
static bool dcc_handle_preferred_compression(DisplayChannelClient *dcc,
|
|
SpiceMsgcDisplayPreferredCompression *pc)
|
|
{
|
|
switch (pc->image_compression) {
|
|
case SPICE_IMAGE_COMPRESSION_AUTO_LZ:
|
|
case SPICE_IMAGE_COMPRESSION_AUTO_GLZ:
|
|
case SPICE_IMAGE_COMPRESSION_QUIC:
|
|
#ifdef USE_LZ4
|
|
case SPICE_IMAGE_COMPRESSION_LZ4:
|
|
#endif
|
|
case SPICE_IMAGE_COMPRESSION_LZ:
|
|
case SPICE_IMAGE_COMPRESSION_GLZ:
|
|
case SPICE_IMAGE_COMPRESSION_OFF:
|
|
dcc->priv->image_compression = (SpiceImageCompression) pc->image_compression;
|
|
break;
|
|
default:
|
|
spice_warning("preferred-compression: unsupported image compression setting");
|
|
}
|
|
g_debug("Setting preferred compression to %s",
|
|
spice_genum_get_nick(SPICE_TYPE_SPICE_IMAGE_COMPRESSION_T,
|
|
dcc->priv->image_compression));
|
|
return TRUE;
|
|
}
|
|
|
|
/* TODO: Client preference should only be considered when host has video-codecs
|
|
* with the same priority value. At the moment, the video-codec GArray will be
|
|
* sorted following only the client's preference (@user_data)
|
|
*
|
|
* example:
|
|
* host encoding preference: gstreamer:mjpeg;gstreamer:vp8;gstreamer:h264
|
|
* client decoding preference: h264, vp9, mjpeg
|
|
* result: gstreamer:h264;gstreamer:mjpeg;gstreamer:vp8
|
|
*/
|
|
static gint sort_video_codecs_by_client_preference(gconstpointer a_pointer,
|
|
gconstpointer b_pointer,
|
|
gpointer user_data)
|
|
{
|
|
const RedVideoCodec *a = (const RedVideoCodec *) a_pointer;
|
|
const RedVideoCodec *b = (const RedVideoCodec *) b_pointer;
|
|
GArray *client_pref = (GArray *) user_data;
|
|
|
|
return (g_array_index(client_pref, gint, a->type) -
|
|
g_array_index(client_pref, gint, b->type));
|
|
}
|
|
|
|
static void dcc_update_preferred_video_codecs(DisplayChannelClient *dcc)
|
|
{
|
|
GArray *video_codecs, *server_codecs;
|
|
char *codecs_str;
|
|
|
|
server_codecs = display_channel_get_video_codecs(DCC_TO_DC(dcc));
|
|
spice_return_if_fail(server_codecs != NULL);
|
|
|
|
/* Copy current host preference */
|
|
video_codecs = g_array_sized_new(FALSE, FALSE, sizeof(RedVideoCodec), server_codecs->len);
|
|
g_array_append_vals(video_codecs, server_codecs->data, server_codecs->len);
|
|
|
|
/* Sort the copy of current host preference based on client's preference */
|
|
g_array_sort_with_data(video_codecs, sort_video_codecs_by_client_preference,
|
|
dcc->priv->client_preferred_video_codecs);
|
|
g_clear_pointer(&dcc->priv->preferred_video_codecs, g_array_unref);
|
|
dcc->priv->preferred_video_codecs = video_codecs;
|
|
|
|
codecs_str = video_codecs_to_string(video_codecs, " ");
|
|
spice_debug("Preferred video-codecs: %s", codecs_str);
|
|
g_free(codecs_str);
|
|
}
|
|
|
|
void dcc_video_codecs_update(DisplayChannelClient *dcc)
|
|
{
|
|
/* Only worry about video-codecs update if client has sent
|
|
* SPICE_MSGC_DISPLAY_PREFERRED_VIDEO_CODEC_TYPE */
|
|
if (dcc->priv->client_preferred_video_codecs == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* New host preference */
|
|
dcc_update_preferred_video_codecs(dcc);
|
|
}
|
|
|
|
static int dcc_handle_preferred_video_codec_type(DisplayChannelClient *dcc,
|
|
SpiceMsgcDisplayPreferredVideoCodecType *msg)
|
|
{
|
|
g_return_val_if_fail(msg->num_of_codecs > 0, TRUE);
|
|
|
|
g_clear_pointer(&dcc->priv->client_preferred_video_codecs, g_array_unref);
|
|
dcc->priv->client_preferred_video_codecs = video_stream_parse_preferred_codecs(msg);
|
|
|
|
/* New client preference */
|
|
dcc_update_preferred_video_codecs(dcc);
|
|
video_stream_detach_and_stop(DCC_TO_DC(dcc));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GArray *dcc_get_preferred_video_codecs_for_encoding(DisplayChannelClient *dcc)
|
|
{
|
|
if (dcc->priv->preferred_video_codecs != NULL) {
|
|
return dcc->priv->preferred_video_codecs;
|
|
}
|
|
return display_channel_get_video_codecs(DCC_TO_DC(dcc));
|
|
}
|
|
|
|
static bool dcc_handle_gl_draw_done(DisplayChannelClient *dcc)
|
|
{
|
|
DisplayChannel *display = DCC_TO_DC(dcc);
|
|
|
|
if (G_UNLIKELY(!dcc->priv->gl_draw_ongoing)) {
|
|
g_warning("unexpected DRAW_DONE received\n");
|
|
/* close client connection */
|
|
return FALSE;
|
|
}
|
|
|
|
dcc->priv->gl_draw_ongoing = FALSE;
|
|
display_channel_gl_draw_done(display);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
bool DisplayChannelClient::handle_message(uint16_t type, uint32_t size, void *msg)
|
|
{
|
|
switch (type) {
|
|
case SPICE_MSGC_DISPLAY_INIT:
|
|
return dcc_handle_init(this, (SpiceMsgcDisplayInit *)msg);
|
|
case SPICE_MSGC_DISPLAY_STREAM_REPORT:
|
|
return dcc_handle_stream_report(this, (SpiceMsgcDisplayStreamReport *)msg);
|
|
case SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION:
|
|
return dcc_handle_preferred_compression(this,
|
|
(SpiceMsgcDisplayPreferredCompression *)msg);
|
|
case SPICE_MSGC_DISPLAY_GL_DRAW_DONE:
|
|
return dcc_handle_gl_draw_done(this);
|
|
case SPICE_MSGC_DISPLAY_PREFERRED_VIDEO_CODEC_TYPE:
|
|
return dcc_handle_preferred_video_codec_type(this,
|
|
(SpiceMsgcDisplayPreferredVideoCodecType *)msg);
|
|
default:
|
|
return RedChannelClient::handle_message(type, size, msg);
|
|
}
|
|
}
|
|
|
|
static int dcc_handle_migrate_glz_dictionary(DisplayChannelClient *dcc,
|
|
SpiceMigrateDataDisplay *migrate)
|
|
{
|
|
GlzEncDictRestoreData glz_dict_data = migrate->glz_dict_data;
|
|
return image_encoders_restore_glz_dictionary(&dcc->priv->encoders,
|
|
dcc->get_client(),
|
|
migrate->glz_dict_id,
|
|
&glz_dict_data);
|
|
}
|
|
|
|
static bool restore_surface(DisplayChannelClient *dcc, uint32_t surface_id)
|
|
{
|
|
/* we don't process commands till we receive the migration data, thus,
|
|
* we should have not sent any surface to the client. */
|
|
if (dcc->priv->surface_client_created[surface_id]) {
|
|
spice_warning("surface %u is already marked as client_created", surface_id);
|
|
return FALSE;
|
|
}
|
|
dcc->priv->surface_client_created[surface_id] = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
static bool restore_surfaces_lossless(DisplayChannelClient *dcc,
|
|
MigrateDisplaySurfacesAtClientLossless *mig_surfaces)
|
|
{
|
|
uint32_t i;
|
|
|
|
spice_debug("trace");
|
|
for (i = 0; i < mig_surfaces->num_surfaces; i++) {
|
|
uint32_t surface_id = mig_surfaces->surfaces[i].id;
|
|
|
|
if (!restore_surface(dcc, surface_id))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static bool restore_surfaces_lossy(DisplayChannelClient *dcc,
|
|
MigrateDisplaySurfacesAtClientLossy *mig_surfaces)
|
|
{
|
|
uint32_t i;
|
|
|
|
spice_debug("trace");
|
|
for (i = 0; i < mig_surfaces->num_surfaces; i++) {
|
|
uint32_t surface_id = mig_surfaces->surfaces[i].id;
|
|
SpiceMigrateDataRect *mig_lossy_rect;
|
|
SpiceRect lossy_rect;
|
|
|
|
if (!restore_surface(dcc, surface_id))
|
|
return FALSE;
|
|
|
|
mig_lossy_rect = &mig_surfaces->surfaces[i].lossy_rect;
|
|
lossy_rect.left = mig_lossy_rect->left;
|
|
lossy_rect.top = mig_lossy_rect->top;
|
|
lossy_rect.right = mig_lossy_rect->right;
|
|
lossy_rect.bottom = mig_lossy_rect->bottom;
|
|
region_init(&dcc->priv->surface_client_lossy_region[surface_id]);
|
|
region_add(&dcc->priv->surface_client_lossy_region[surface_id], &lossy_rect);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
bool DisplayChannelClient::handle_migrate_data(uint32_t size, void *message)
|
|
{
|
|
DisplayChannelClient *dcc = this;
|
|
DisplayChannel *display = DCC_TO_DC(dcc);
|
|
int surfaces_restored = FALSE;
|
|
SpiceMigrateDataHeader *header = (SpiceMigrateDataHeader *)message;
|
|
SpiceMigrateDataDisplay *migrate_data = (SpiceMigrateDataDisplay *)(header + 1);
|
|
uint8_t *surfaces;
|
|
int i;
|
|
|
|
spice_return_val_if_fail(
|
|
size >= (sizeof(*migrate_data) + sizeof(SpiceMigrateDataHeader)), FALSE);
|
|
spice_return_val_if_fail(
|
|
migration_protocol_validate_header(header,
|
|
SPICE_MIGRATE_DATA_DISPLAY_MAGIC, SPICE_MIGRATE_DATA_DISPLAY_VERSION), FALSE);
|
|
|
|
/* size is set to -1 in order to keep the cache frozen until the original
|
|
* channel client that froze the cache on the src size receives the migrate
|
|
* data and unfreezes the cache by setting its size > 0 and by triggering
|
|
* pixmap_cache_reset */
|
|
dcc->priv->pixmap_cache = pixmap_cache_get(dcc->get_client(),
|
|
migrate_data->pixmap_cache_id, -1);
|
|
spice_return_val_if_fail(dcc->priv->pixmap_cache, FALSE);
|
|
|
|
pthread_mutex_lock(&dcc->priv->pixmap_cache->lock);
|
|
for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
|
|
dcc->priv->pixmap_cache->sync[i] = MAX(dcc->priv->pixmap_cache->sync[i],
|
|
migrate_data->pixmap_cache_clients[i]);
|
|
}
|
|
pthread_mutex_unlock(&dcc->priv->pixmap_cache->lock);
|
|
|
|
if (migrate_data->pixmap_cache_freezer) {
|
|
/* activating the cache. The cache will start to be active after
|
|
* pixmap_cache_reset is called, when handling RED_PIPE_ITEM_TYPE_PIXMAP_RESET */
|
|
dcc->priv->pixmap_cache->size = migrate_data->pixmap_cache_size;
|
|
dcc->pipe_add_type(RED_PIPE_ITEM_TYPE_PIXMAP_RESET);
|
|
}
|
|
|
|
if (dcc_handle_migrate_glz_dictionary(dcc, migrate_data)) {
|
|
image_encoders_glz_create(&dcc->priv->encoders, dcc->priv->id);
|
|
} else {
|
|
spice_critical("restoring global lz dictionary failed");
|
|
}
|
|
|
|
dcc->is_low_bandwidth = migrate_data->low_bandwidth_setting;
|
|
|
|
if (migrate_data->low_bandwidth_setting) {
|
|
dcc->ack_set_client_window(WIDE_CLIENT_ACK_WINDOW);
|
|
if (dcc->priv->jpeg_state == SPICE_WAN_COMPRESSION_AUTO) {
|
|
display->priv->enable_jpeg = TRUE;
|
|
}
|
|
if (dcc->priv->zlib_glz_state == SPICE_WAN_COMPRESSION_AUTO) {
|
|
display->priv->enable_zlib_glz_wrap = TRUE;
|
|
}
|
|
}
|
|
|
|
surfaces = (uint8_t *)message + migrate_data->surfaces_at_client_ptr;
|
|
surfaces_restored = display->priv->enable_jpeg ?
|
|
restore_surfaces_lossy(dcc, (MigrateDisplaySurfacesAtClientLossy *)surfaces) :
|
|
restore_surfaces_lossless(dcc, (MigrateDisplaySurfacesAtClientLossless*)surfaces);
|
|
|
|
spice_return_val_if_fail(surfaces_restored, FALSE);
|
|
|
|
dcc->pipe_add_type(RED_PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE);
|
|
/* enable sending messages */
|
|
dcc->ack_zero_messages_window();
|
|
return TRUE;
|
|
}
|
|
|
|
VideoStreamAgent* dcc_get_video_stream_agent(DisplayChannelClient *dcc, int stream_id)
|
|
{
|
|
return &dcc->priv->stream_agents[stream_id];
|
|
}
|
|
|
|
ImageEncoders* dcc_get_encoders(DisplayChannelClient *dcc)
|
|
{
|
|
return &dcc->priv->encoders;
|
|
}
|
|
|
|
spice_wan_compression_t dcc_get_jpeg_state(DisplayChannelClient *dcc)
|
|
{
|
|
return dcc->priv->jpeg_state;
|
|
}
|
|
|
|
spice_wan_compression_t dcc_get_zlib_glz_state(DisplayChannelClient *dcc)
|
|
{
|
|
return dcc->priv->zlib_glz_state;
|
|
}
|
|
|
|
uint32_t dcc_get_max_stream_latency(DisplayChannelClient *dcc)
|
|
{
|
|
return dcc->priv->streams_max_latency;
|
|
}
|
|
|
|
void dcc_set_max_stream_latency(DisplayChannelClient *dcc, uint32_t latency)
|
|
{
|
|
dcc->priv->streams_max_latency = latency;
|
|
}
|
|
|
|
uint64_t dcc_get_max_stream_bit_rate(DisplayChannelClient *dcc)
|
|
{
|
|
return dcc->priv->streams_max_bit_rate;
|
|
}
|
|
|
|
void dcc_set_max_stream_bit_rate(DisplayChannelClient *dcc, uint64_t rate)
|
|
{
|
|
dcc->priv->streams_max_bit_rate = rate;
|
|
}
|
|
|
|
bool DisplayChannelClient::config_socket()
|
|
{
|
|
RedClient *client = get_client();
|
|
MainChannelClient *mcc = client->get_main();
|
|
|
|
is_low_bandwidth = mcc->is_low_bandwidth();
|
|
|
|
return CommonGraphicsChannelClient::config_socket();
|
|
}
|
|
|
|
void DisplayChannelClient::on_disconnect()
|
|
{
|
|
DisplayChannel *display;
|
|
|
|
spice_debug("trace");
|
|
|
|
display = DCC_TO_DC(this);
|
|
|
|
dcc_stop(this); // TODO: start/stop -> connect/disconnect?
|
|
display_channel_compress_stats_print(display);
|
|
|
|
// this was the last channel client
|
|
spice_debug("#draw=%d, #glz_draw=%d",
|
|
display->priv->drawable_count,
|
|
display->priv->encoder_shared_data.glz_drawable_count);
|
|
}
|
|
|
|
gboolean dcc_is_low_bandwidth(DisplayChannelClient *dcc)
|
|
{
|
|
return dcc->is_low_bandwidth;
|
|
}
|