/* 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include "display-channel-private.h" G_DEFINE_TYPE(DisplayChannel, display_channel, TYPE_COMMON_GRAPHICS_CHANNEL) #define DISPLAY_CHANNEL_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), TYPE_DISPLAY_CHANNEL, DisplayChannelPrivate)) enum { PROP0, PROP_N_SURFACES }; static void display_channel_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { DisplayChannel *self = DISPLAY_CHANNEL(object); switch (property_id) { case PROP_N_SURFACES: g_value_set_uint(value, self->priv->n_surfaces); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } } static void display_channel_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { DisplayChannel *self = DISPLAY_CHANNEL(object); switch (property_id) { case PROP_N_SURFACES: self->priv->n_surfaces = g_value_get_uint(value); if (self->priv->surfaces == NULL) self->priv->surfaces = g_new0(RedSurface, self->priv->n_surfaces); else self->priv->surfaces = g_renew(RedSurface, self->priv->surfaces, self->priv->n_surfaces); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } } static void display_channel_finalize(GObject *object) { DisplayChannel *self = DISPLAY_CHANNEL(object); G_OBJECT_CLASS(display_channel_parent_class)->finalize(object); g_free(self->priv->drawables); g_free(self->priv->surfaces); g_free(self->priv->streams_buf); g_free(self->priv->items_trace); g_free(self->priv->image_cache); } static void display_channel_constructed(GObject *object) { DisplayChannel *self = DISPLAY_CHANNEL(object); G_OBJECT_CLASS(display_channel_parent_class)->constructed(object); self->priv->renderer = RED_RENDERER_INVALID; stat_init(&self->priv->add_stat, "add", CLOCK_THREAD_CPUTIME_ID); stat_init(&self->priv->exclude_stat, "exclude", CLOCK_THREAD_CPUTIME_ID); stat_init(&self->priv->__exclude_stat, "__exclude", CLOCK_THREAD_CPUTIME_ID); #ifdef RED_STATISTICS QXLInstance *qxl = common_graphics_channel_get_qxl(&self->parent); RedsState *reds = red_qxl_get_server(qxl->st); RedChannel *channel = RED_CHANNEL(self); self->priv->cache_hits_counter = stat_add_counter(reds, red_channel_get_stat_node(channel), "cache_hits", TRUE); self->priv->add_to_cache_counter = stat_add_counter(reds, red_channel_get_stat_node(channel), "add_to_cache", TRUE); self->priv->non_cache_counter = stat_add_counter(reds, red_channel_get_stat_node(channel), "non_cache", TRUE); #endif image_cache_init(self->priv->image_cache); self->priv->stream_video = SPICE_STREAM_VIDEO_OFF; display_channel_init_streams(self); } static SpiceCanvas *image_surfaces_get(SpiceImageSurfaces *surfaces, uint32_t surface_id) { DisplayChannelPrivate *p = SPICE_CONTAINEROF(surfaces, DisplayChannelPrivate, image_surfaces); DisplayChannel *display = p->pub; spice_return_val_if_fail(display_channel_validate_surface(display, surface_id), NULL); return p->surfaces[surface_id].context.canvas; } static void drawables_init(DisplayChannel *display); static void display_channel_init(DisplayChannel *self) { static SpiceImageSurfacesOps image_surfaces_ops = { image_surfaces_get, }; self->priv = DISPLAY_CHANNEL_PRIVATE(self); self->priv->pub = self; stat_compress_init(&self->priv->lz_stat, "lz", CLOCK_THREAD_CPUTIME_ID); stat_compress_init(&self->priv->glz_stat, "glz", CLOCK_THREAD_CPUTIME_ID); stat_compress_init(&self->priv->quic_stat, "quic", CLOCK_THREAD_CPUTIME_ID); stat_compress_init(&self->priv->jpeg_stat, "jpeg", CLOCK_THREAD_CPUTIME_ID); stat_compress_init(&self->priv->zlib_glz_stat, "zlib", CLOCK_THREAD_CPUTIME_ID); stat_compress_init(&self->priv->jpeg_alpha_stat, "jpeg_alpha", CLOCK_THREAD_CPUTIME_ID); stat_compress_init(&self->priv->lz4_stat, "lz4", CLOCK_THREAD_CPUTIME_ID); ring_init(&self->priv->current_list); drawables_init(self); self->priv->image_surfaces.ops = &image_surfaces_ops; self->priv->streams_buf = g_new0(Stream, NUM_STREAMS); self->priv->items_trace = g_new0(ItemTrace, NUM_TRACE_ITEMS); self->priv->image_cache = g_new0(ImageCache, 1); } static void drawable_draw(DisplayChannel *display, Drawable *drawable); uint32_t display_channel_generate_uid(DisplayChannel *display) { spice_return_val_if_fail(display != NULL, 0); return ++display->priv->bits_unique; } #define stat_start(stat, var) \ stat_start_time_t var; stat_start_time_init(&var, stat); void display_channel_compress_stats_reset(DisplayChannel *display) { spice_return_if_fail(display); stat_reset(&display->priv->off_stat); stat_reset(&display->priv->quic_stat); stat_reset(&display->priv->lz_stat); stat_reset(&display->priv->glz_stat); stat_reset(&display->priv->jpeg_stat); stat_reset(&display->priv->zlib_glz_stat); stat_reset(&display->priv->jpeg_alpha_stat); stat_reset(&display->priv->lz4_stat); } void display_channel_compress_stats_print(DisplayChannel *display_channel) { spice_return_if_fail(display_channel); #ifdef COMPRESS_STAT DisplayChannelPrivate *priv = display_channel->priv; uint64_t glz_enc_size; uint32_t id; glz_enc_size = priv->enable_zlib_glz_wrap ? priv->zlib_glz_stat.comp_size : priv->glz_stat.comp_size; g_object_get(display_channel, "id", &id, NULL); spice_info("==> Compression stats for display %u", id); spice_info("Method \t count \torig_size(MB)\tenc_size(MB)\tenc_time(s)"); spice_info("OFF \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->off_stat.count, stat_byte_to_mega(priv->off_stat.orig_size), stat_byte_to_mega(priv->off_stat.comp_size), stat_cpu_time_to_sec(priv->off_stat.total) ); spice_info("QUIC \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->quic_stat.count, stat_byte_to_mega(priv->quic_stat.orig_size), stat_byte_to_mega(priv->quic_stat.comp_size), stat_cpu_time_to_sec(priv->quic_stat.total) ); spice_info("GLZ \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->glz_stat.count, stat_byte_to_mega(priv->glz_stat.orig_size), stat_byte_to_mega(priv->glz_stat.comp_size), stat_cpu_time_to_sec(priv->glz_stat.total) ); spice_info("ZLIB GLZ \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->zlib_glz_stat.count, stat_byte_to_mega(priv->zlib_glz_stat.orig_size), stat_byte_to_mega(priv->zlib_glz_stat.comp_size), stat_cpu_time_to_sec(priv->zlib_glz_stat.total) ); spice_info("LZ \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->lz_stat.count, stat_byte_to_mega(priv->lz_stat.orig_size), stat_byte_to_mega(priv->lz_stat.comp_size), stat_cpu_time_to_sec(priv->lz_stat.total) ); spice_info("JPEG \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->jpeg_stat.count, stat_byte_to_mega(priv->jpeg_stat.orig_size), stat_byte_to_mega(priv->jpeg_stat.comp_size), stat_cpu_time_to_sec(priv->jpeg_stat.total) ); spice_info("JPEG-RGBA\t%8d\t%13.2f\t%12.2f\t%12.2f", priv->jpeg_alpha_stat.count, stat_byte_to_mega(priv->jpeg_alpha_stat.orig_size), stat_byte_to_mega(priv->jpeg_alpha_stat.comp_size), stat_cpu_time_to_sec(priv->jpeg_alpha_stat.total) ); spice_info("LZ4 \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->lz4_stat.count, stat_byte_to_mega(priv->lz4_stat.orig_size), stat_byte_to_mega(priv->lz4_stat.comp_size), stat_cpu_time_to_sec(priv->lz4_stat.total) ); spice_info("-------------------------------------------------------------------"); spice_info("Total \t%8d\t%13.2f\t%12.2f\t%12.2f", priv->lz_stat.count + priv->glz_stat.count + priv->off_stat.count + priv->quic_stat.count + priv->jpeg_stat.count + priv->lz4_stat.count + priv->jpeg_alpha_stat.count, stat_byte_to_mega(priv->lz_stat.orig_size + priv->glz_stat.orig_size + priv->off_stat.orig_size + priv->quic_stat.orig_size + priv->jpeg_stat.orig_size + priv->lz4_stat.orig_size + priv->jpeg_alpha_stat.orig_size), stat_byte_to_mega(priv->lz_stat.comp_size + glz_enc_size + priv->off_stat.comp_size + priv->quic_stat.comp_size + priv->jpeg_stat.comp_size + priv->lz4_stat.comp_size + priv->jpeg_alpha_stat.comp_size), stat_cpu_time_to_sec(priv->lz_stat.total + priv->glz_stat.total + priv->zlib_glz_stat.total + priv->off_stat.total + priv->quic_stat.total + priv->jpeg_stat.total + priv->lz4_stat.total + priv->jpeg_alpha_stat.total) ); #endif } MonitorsConfig* monitors_config_ref(MonitorsConfig *monitors_config) { monitors_config->refs++; return monitors_config; } void monitors_config_unref(MonitorsConfig *monitors_config) { if (!monitors_config) { return; } if (--monitors_config->refs != 0) { return; } spice_debug("freeing monitors config"); free(monitors_config); } static void monitors_config_debug(MonitorsConfig *mc) { int i; spice_debug("monitors config count:%d max:%d", mc->count, mc->max_allowed); for (i = 0; i < mc->count; i++) spice_debug("+%d+%d %dx%d", mc->heads[i].x, mc->heads[i].y, mc->heads[i].width, mc->heads[i].height); } MonitorsConfig* monitors_config_new(QXLHead *heads, ssize_t nheads, ssize_t max) { MonitorsConfig *mc; mc = spice_malloc(sizeof(MonitorsConfig) + nheads * sizeof(QXLHead)); mc->refs = 1; mc->count = nheads; mc->max_allowed = max; memcpy(mc->heads, heads, nheads * sizeof(QXLHead)); monitors_config_debug(mc); return mc; } int display_channel_get_streams_timeout(DisplayChannel *display) { int timeout = INT_MAX; Ring *ring = &display->priv->streams; RingItem *item = ring; red_time_t now = spice_get_monotonic_time_ns(); while ((item = ring_next(ring, item))) { Stream *stream; stream = SPICE_CONTAINEROF(item, Stream, link); red_time_t delta = (stream->last_time + RED_STREAM_TIMEOUT) - now; if (delta < 1000 * 1000) { return 0; } timeout = MIN(timeout, (unsigned int)(delta / (1000 * 1000))); } return timeout; } void display_channel_set_stream_video(DisplayChannel *display, int stream_video) { spice_return_if_fail(display); spice_return_if_fail(stream_video != SPICE_STREAM_VIDEO_INVALID); switch (stream_video) { case SPICE_STREAM_VIDEO_ALL: spice_info("sv all"); break; case SPICE_STREAM_VIDEO_FILTER: spice_info("sv filter"); break; case SPICE_STREAM_VIDEO_OFF: spice_info("sv off"); break; default: spice_warn_if_reached(); return; } display->priv->stream_video = stream_video; } int display_channel_get_stream_video(DisplayChannel *display) { return display->priv->stream_video; } static void stop_streams(DisplayChannel *display) { Ring *ring = &display->priv->streams; RingItem *item = ring_get_head(ring); while (item) { Stream *stream = SPICE_CONTAINEROF(item, Stream, link); item = ring_next(ring, item); if (!stream->current) { stream_stop(display, stream); } else { spice_info("attached stream"); } } display->priv->next_item_trace = 0; memset(display->priv->items_trace, 0, NUM_TRACE_ITEMS * sizeof(*display->priv->items_trace)); } void display_channel_surface_unref(DisplayChannel *display, uint32_t surface_id) { RedSurface *surface = &display->priv->surfaces[surface_id]; QXLInstance *qxl = common_graphics_channel_get_qxl(COMMON_GRAPHICS_CHANNEL(display)); DisplayChannelClient *dcc; GList *link, *next; if (--surface->refs != 0) { return; } // only primary surface streams are supported if (is_primary_surface(display, surface_id)) { stop_streams(display); } spice_assert(surface->context.canvas); surface->context.canvas->ops->destroy(surface->context.canvas); if (surface->create.info) { red_qxl_release_resource(qxl, surface->create); } if (surface->destroy.info) { red_qxl_release_resource(qxl, surface->destroy); } region_destroy(&surface->draw_dirty_region); surface->context.canvas = NULL; FOREACH_DCC(display, link, next, dcc) { dcc_destroy_surface(dcc, surface_id); } spice_warn_if_fail(ring_is_empty(&surface->depend_on_me)); } /* TODO: perhaps rename to "ready" or "realized" ? */ bool display_channel_surface_has_canvas(DisplayChannel *display, uint32_t surface_id) { return display->priv->surfaces[surface_id].context.canvas != NULL; } static void streams_update_visible_region(DisplayChannel *display, Drawable *drawable) { Ring *ring; RingItem *item; GList *link, *next; DisplayChannelClient *dcc; if (!red_channel_is_connected(RED_CHANNEL(display))) { return; } if (!is_primary_surface(display, drawable->surface_id)) { return; } ring = &display->priv->streams; item = ring_get_head(ring); while (item) { Stream *stream = SPICE_CONTAINEROF(item, Stream, link); StreamAgent *agent; item = ring_next(ring, item); if (stream->current == drawable) { continue; } FOREACH_DCC(display, link, next, dcc) { agent = dcc_get_stream_agent(dcc, display_channel_get_stream_id(display, stream)); if (region_intersects(&agent->vis_region, &drawable->tree_item.base.rgn)) { region_exclude(&agent->vis_region, &drawable->tree_item.base.rgn); region_exclude(&agent->clip, &drawable->tree_item.base.rgn); dcc_stream_agent_clip(dcc, agent); } } } } static void pipes_add_drawable(DisplayChannel *display, Drawable *drawable) { DisplayChannelClient *dcc; GList *link, *next; spice_warn_if_fail(drawable->pipes == NULL); FOREACH_DCC(display, link, next, dcc) { dcc_prepend_drawable(dcc, drawable); } } static void pipes_add_drawable_after(DisplayChannel *display, Drawable *drawable, Drawable *pos_after) { RedDrawablePipeItem *dpi_pos_after; DisplayChannelClient *dcc; int num_other_linked = 0; GList *l, *next; l = pos_after->pipes; while (l) { next = l->next; dpi_pos_after = l->data; num_other_linked++; dcc_add_drawable_after(dpi_pos_after->dcc, drawable, &dpi_pos_after->dpi_pipe_item); l = next; } if (num_other_linked == 0) { pipes_add_drawable(display, drawable); return; } if (num_other_linked != red_channel_get_n_clients(RED_CHANNEL(display))) { GList *link, *next; spice_debug("TODO: not O(n^2)"); FOREACH_DCC(display, link, next, dcc) { int sent = 0; GList *l, *next; l = pos_after->pipes; while (l) { next = l->next; dpi_pos_after = l->data; if (dpi_pos_after->dcc == dcc) { sent = 1; break; } l = next; } if (!sent) { dcc_prepend_drawable(dcc, drawable); } } } } static void current_add_drawable(DisplayChannel *display, Drawable *drawable, RingItem *pos) { RedSurface *surface; uint32_t surface_id = drawable->surface_id; surface = &display->priv->surfaces[surface_id]; ring_add_after(&drawable->tree_item.base.siblings_link, pos); ring_add(&display->priv->current_list, &drawable->list_link); ring_add(&surface->current_list, &drawable->surface_list_link); display->priv->current_size++; drawable->refs++; } static void current_remove_drawable(DisplayChannel *display, Drawable *item) { /* todo: move all to unref? */ stream_trace_add_drawable(display, item); draw_item_remove_shadow(&item->tree_item); ring_remove(&item->tree_item.base.siblings_link); ring_remove(&item->list_link); ring_remove(&item->surface_list_link); drawable_unref(item); display->priv->current_size--; } static void drawable_remove_from_pipes(Drawable *drawable) { RedDrawablePipeItem *dpi; GList *l, *next; l = drawable->pipes; while (l) { next = l->next; RedChannelClient *rcc; dpi = l->data; rcc = RED_CHANNEL_CLIENT(dpi->dcc); if (red_channel_client_pipe_item_is_linked(rcc, &dpi->dpi_pipe_item)) { red_channel_client_pipe_remove_and_release(rcc, &dpi->dpi_pipe_item); } l = next; } } static void current_remove(DisplayChannel *display, TreeItem *item) { TreeItem *now = item; /* depth-first tree traversal, TODO: do a to tree_foreach()? */ for (;;) { Container *container = now->container; RingItem *ring_item; if (now->type == TREE_ITEM_TYPE_DRAWABLE) { Drawable *drawable = SPICE_CONTAINEROF(now, Drawable, tree_item.base); ring_item = now->siblings_link.prev; drawable_remove_from_pipes(drawable); current_remove_drawable(display, drawable); } else { Container *container = (Container *)now; spice_assert(now->type == TREE_ITEM_TYPE_CONTAINER); if ((ring_item = ring_get_head(&container->items))) { now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link); continue; } ring_item = now->siblings_link.prev; container_free(container); } if (now == item) { return; } if ((ring_item = ring_next(&container->items, ring_item))) { now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link); } else { now = (TreeItem *)container; } } } static void current_remove_all(DisplayChannel *display, int surface_id) { Ring *ring = &display->priv->surfaces[surface_id].current; RingItem *ring_item; while ((ring_item = ring_get_head(ring))) { TreeItem *now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link); current_remove(display, now); } } static int current_add_equal(DisplayChannel *display, DrawItem *item, TreeItem *other) { DrawItem *other_draw_item; Drawable *drawable; Drawable *other_drawable; if (other->type != TREE_ITEM_TYPE_DRAWABLE) { return FALSE; } other_draw_item = (DrawItem *)other; if (item->shadow || other_draw_item->shadow || item->effect != other_draw_item->effect) { return FALSE; } drawable = SPICE_CONTAINEROF(item, Drawable, tree_item); other_drawable = SPICE_CONTAINEROF(other_draw_item, Drawable, tree_item); if (item->effect == QXL_EFFECT_OPAQUE) { int add_after = !!other_drawable->stream && is_drawable_independent_from_surfaces(drawable); stream_maintenance(display, drawable, other_drawable); current_add_drawable(display, drawable, &other->siblings_link); other_drawable->refs++; current_remove_drawable(display, other_drawable); if (add_after) { pipes_add_drawable_after(display, drawable, other_drawable); } else { pipes_add_drawable(display, drawable); } drawable_remove_from_pipes(other_drawable); drawable_unref(other_drawable); return TRUE; } switch (item->effect) { case QXL_EFFECT_REVERT_ON_DUP: if (is_same_drawable(drawable, other_drawable)) { DisplayChannelClient *dcc; RedDrawablePipeItem *dpi; GList *dpi_item; GList *link; other_drawable->refs++; current_remove_drawable(display, other_drawable); /* sending the drawable to clients that already received * (or will receive) other_drawable */ link = red_channel_get_clients(RED_CHANNEL(display)); dpi_item = g_list_first(other_drawable->pipes); /* dpi contains a sublist of dcc's, ordered the same */ while (link) { dcc = link->data; dpi = dpi_item->data; while (link && (!dpi || dcc != dpi->dcc)) { dcc_prepend_drawable(dcc, drawable); link = link->next; if (link) dcc = link->data; } if (dpi_item) { dpi_item = dpi_item->next; } if (link) { link = link->next; } } /* not sending other_drawable where possible */ drawable_remove_from_pipes(other_drawable); drawable_unref(other_drawable); return TRUE; } break; case QXL_EFFECT_OPAQUE_BRUSH: if (is_same_geometry(drawable, other_drawable)) { current_add_drawable(display, drawable, &other->siblings_link); drawable_remove_from_pipes(other_drawable); current_remove_drawable(display, other_drawable); pipes_add_drawable(display, drawable); return TRUE; } break; case QXL_EFFECT_NOP_ON_DUP: if (is_same_drawable(drawable, other_drawable)) { return TRUE; } break; } return FALSE; } static void __exclude_region(DisplayChannel *display, Ring *ring, TreeItem *item, QRegion *rgn, Ring **top_ring, Drawable *frame_candidate) { QRegion and_rgn; stat_start(&display->priv->__exclude_stat, start_time); region_clone(&and_rgn, rgn); region_and(&and_rgn, &item->rgn); if (!region_is_empty(&and_rgn)) { if (IS_DRAW_ITEM(item)) { DrawItem *draw = (DrawItem *)item; if (draw->effect == QXL_EFFECT_OPAQUE) { region_exclude(rgn, &and_rgn); } if (draw->shadow) { Shadow *shadow; int32_t x = item->rgn.extents.x1; int32_t y = item->rgn.extents.y1; region_exclude(&draw->base.rgn, &and_rgn); shadow = draw->shadow; region_offset(&and_rgn, shadow->base.rgn.extents.x1 - x, shadow->base.rgn.extents.y1 - y); region_exclude(&shadow->base.rgn, &and_rgn); region_and(&and_rgn, &shadow->on_hold); if (!region_is_empty(&and_rgn)) { region_exclude(&shadow->on_hold, &and_rgn); region_or(rgn, &and_rgn); // in flat representation of current, shadow is always his owner next if (!tree_item_contained_by((TreeItem*)shadow, *top_ring)) { *top_ring = tree_item_container_items((TreeItem*)shadow, ring); } } } else { if (frame_candidate) { Drawable *drawable = SPICE_CONTAINEROF(draw, Drawable, tree_item); stream_maintenance(display, frame_candidate, drawable); } region_exclude(&draw->base.rgn, &and_rgn); } } else if (item->type == TREE_ITEM_TYPE_CONTAINER) { region_exclude(&item->rgn, &and_rgn); if (region_is_empty(&item->rgn)) { //assume container removal will follow Shadow *shadow; region_exclude(rgn, &and_rgn); if ((shadow = tree_item_find_shadow(item))) { region_or(rgn, &shadow->on_hold); if (!tree_item_contained_by((TreeItem*)shadow, *top_ring)) { *top_ring = tree_item_container_items((TreeItem*)shadow, ring); } } } } else { Shadow *shadow; spice_assert(item->type == TREE_ITEM_TYPE_SHADOW); shadow = (Shadow *)item; region_exclude(rgn, &and_rgn); region_or(&shadow->on_hold, &and_rgn); } } region_destroy(&and_rgn); stat_add(&display->priv->__exclude_stat, start_time); } static void exclude_region(DisplayChannel *display, Ring *ring, RingItem *ring_item, QRegion *rgn, TreeItem **last, Drawable *frame_candidate) { Ring *top_ring; stat_start(&display->priv->exclude_stat, start_time); if (!ring_item) { return; } top_ring = ring; for (;;) { TreeItem *now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link); Container *container = now->container; spice_assert(!region_is_empty(&now->rgn)); if (region_intersects(rgn, &now->rgn)) { __exclude_region(display, ring, now, rgn, &top_ring, frame_candidate); if (region_is_empty(&now->rgn)) { spice_assert(now->type != TREE_ITEM_TYPE_SHADOW); ring_item = now->siblings_link.prev; current_remove(display, now); if (last && *last == now) { *last = (TreeItem *)ring_next(ring, ring_item); } } else if (now->type == TREE_ITEM_TYPE_CONTAINER) { Container *container = (Container *)now; if ((ring_item = ring_get_head(&container->items))) { ring = &container->items; spice_assert(((TreeItem *)ring_item)->container); continue; } ring_item = &now->siblings_link; } if (region_is_empty(rgn)) { stat_add(&display->priv->exclude_stat, start_time); return; } } while ((last && *last == (TreeItem *)ring_item) || !(ring_item = ring_next(ring, ring_item))) { if (ring == top_ring) { stat_add(&display->priv->exclude_stat, start_time); return; } ring_item = &container->base.siblings_link; container = container->base.container; ring = (container) ? &container->items : top_ring; } } } static int current_add_with_shadow(DisplayChannel *display, Ring *ring, Drawable *item) { stat_start(&display->priv->add_stat, start_time); #ifdef RED_WORKER_STAT ++display->priv->add_with_shadow_count; #endif RedDrawable *red_drawable = item->red_drawable; SpicePoint delta = { .x = red_drawable->u.copy_bits.src_pos.x - red_drawable->bbox.left, .y = red_drawable->u.copy_bits.src_pos.y - red_drawable->bbox.top }; Shadow *shadow = shadow_new(&item->tree_item, &delta); if (!shadow) { stat_add(&display->priv->add_stat, start_time); return FALSE; } // item and his shadow must initially be placed in the same container. // for now putting them on root. // only primary surface streams are supported if (is_primary_surface(display, item->surface_id)) { stream_detach_behind(display, &shadow->base.rgn, NULL); } ring_add(ring, &shadow->base.siblings_link); current_add_drawable(display, item, ring); if (item->tree_item.effect == QXL_EFFECT_OPAQUE) { QRegion exclude_rgn; region_clone(&exclude_rgn, &item->tree_item.base.rgn); exclude_region(display, ring, &shadow->base.siblings_link, &exclude_rgn, NULL, NULL); region_destroy(&exclude_rgn); streams_update_visible_region(display, item); } else { if (is_primary_surface(display, item->surface_id)) { stream_detach_behind(display, &item->tree_item.base.rgn, item); } } stat_add(&display->priv->add_stat, start_time); return TRUE; } static int current_add(DisplayChannel *display, Ring *ring, Drawable *drawable) { DrawItem *item = &drawable->tree_item; RingItem *now; QRegion exclude_rgn; RingItem *exclude_base = NULL; stat_start(&display->priv->add_stat, start_time); spice_assert(!region_is_empty(&item->base.rgn)); region_init(&exclude_rgn); now = ring_next(ring, ring); while (now) { TreeItem *sibling = SPICE_CONTAINEROF(now, TreeItem, siblings_link); int test_res; if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) { now = ring_next(ring, now); continue; } test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL); if (!(test_res & REGION_TEST_SHARED)) { now = ring_next(ring, now); continue; } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) { if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && !(test_res & REGION_TEST_LEFT_EXCLUSIVE) && current_add_equal(display, item, sibling)) { stat_add(&display->priv->add_stat, start_time); return FALSE; } if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) { Shadow *shadow; int skip = now == exclude_base; if ((shadow = tree_item_find_shadow(sibling))) { if (exclude_base) { TreeItem *next = sibling; exclude_region(display, ring, exclude_base, &exclude_rgn, &next, NULL); if (next != sibling) { now = next ? &next->siblings_link : NULL; exclude_base = NULL; continue; } } region_or(&exclude_rgn, &shadow->on_hold); } now = now->prev; current_remove(display, sibling); now = ring_next(ring, now); if (shadow || skip) { exclude_base = now; } continue; } if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) { Container *container; if (exclude_base) { exclude_region(display, ring, exclude_base, &exclude_rgn, NULL, NULL); region_clear(&exclude_rgn); exclude_base = NULL; } if (sibling->type == TREE_ITEM_TYPE_CONTAINER) { container = (Container *)sibling; ring = &container->items; item->base.container = container; now = ring_next(ring, ring); continue; } spice_assert(IS_DRAW_ITEM(sibling)); if (!DRAW_ITEM(sibling)->container_root) { container = container_new(DRAW_ITEM(sibling)); if (!container) { spice_warning("create new container failed"); region_destroy(&exclude_rgn); return FALSE; } item->base.container = container; ring = &container->items; } } } if (!exclude_base) { exclude_base = now; } break; } if (item->effect == QXL_EFFECT_OPAQUE) { region_or(&exclude_rgn, &item->base.rgn); exclude_region(display, ring, exclude_base, &exclude_rgn, NULL, drawable); stream_trace_update(display, drawable); streams_update_visible_region(display, drawable); /* * Performing the insertion after exclude_region for * safety (todo: Not sure if exclude_region can affect the drawable * if it is added to the tree before calling exclude_region). */ current_add_drawable(display, drawable, ring); } else { /* * stream_detach_behind can affect the current tree since * it may trigger calls to display_channel_draw. Thus, the * drawable should be added to the tree before calling * stream_detach_behind */ current_add_drawable(display, drawable, ring); if (is_primary_surface(display, drawable->surface_id)) { stream_detach_behind(display, &drawable->tree_item.base.rgn, drawable); } } region_destroy(&exclude_rgn); stat_add(&display->priv->add_stat, start_time); return TRUE; } static bool drawable_can_stream(DisplayChannel *display, Drawable *drawable) { RedDrawable *red_drawable = drawable->red_drawable; SpiceImage *image; if (display->priv->stream_video == SPICE_STREAM_VIDEO_OFF) { return FALSE; } if (!is_primary_surface(display, drawable->surface_id)) { return FALSE; } if (drawable->tree_item.effect != QXL_EFFECT_OPAQUE || red_drawable->type != QXL_DRAW_COPY || red_drawable->u.copy.rop_descriptor != SPICE_ROPD_OP_PUT) { return FALSE; } image = red_drawable->u.copy.src_bitmap; if (image == NULL || image->descriptor.type != SPICE_IMAGE_TYPE_BITMAP) { return FALSE; } if (display->priv->stream_video == SPICE_STREAM_VIDEO_FILTER) { SpiceRect* rect; int size; rect = &drawable->red_drawable->u.copy.src_area; size = (rect->right - rect->left) * (rect->bottom - rect->top); if (size < RED_STREAM_MIN_SIZE) { return FALSE; } } return TRUE; } void display_channel_print_stats(DisplayChannel *display) { #ifdef RED_WORKER_STAT stat_time_t total = display->priv->add_stat.total; spice_info("add with shadow count %u", display->priv->add_with_shadow_count); display->priv->add_with_shadow_count = 0; spice_info("add[%u] %f exclude[%u] %f __exclude[%u] %f", display->priv->add_stat.count, stat_cpu_time_to_sec(total), display->priv->exclude_stat.count, stat_cpu_time_to_sec(display->priv->exclude_stat.total), display->priv->__exclude_stat.count, stat_cpu_time_to_sec(display->priv->__exclude_stat.total)); spice_info("add %f%% exclude %f%% exclude2 %f%% __exclude %f%%", (double)(total - display->priv->exclude_stat.total) / total * 100, (double)(display->priv->exclude_stat.total) / total * 100, (double)(display->priv->exclude_stat.total - display->priv->__exclude_stat.total) / display->priv->exclude_stat.total * 100, (double)(display->priv->__exclude_stat.total) / display->priv->exclude_stat.total * 100); stat_reset(&display->priv->add_stat); stat_reset(&display->priv->exclude_stat); stat_reset(&display->priv->__exclude_stat); #endif } static void drawable_ref_surface_deps(DisplayChannel *display, Drawable *drawable) { int x; int surface_id; RedSurface *surface; for (x = 0; x < 3; ++x) { surface_id = drawable->surface_deps[x]; if (surface_id == -1) { continue; } surface = &display->priv->surfaces[surface_id]; surface->refs++; } } static void surface_read_bits(DisplayChannel *display, int surface_id, const SpiceRect *area, uint8_t *dest, int dest_stride) { SpiceCanvas *canvas; RedSurface *surface = &display->priv->surfaces[surface_id]; canvas = surface->context.canvas; canvas->ops->read_bits(canvas, dest, dest_stride, area); } static void handle_self_bitmap(DisplayChannel *display, Drawable *drawable) { RedDrawable *red_drawable = drawable->red_drawable; SpiceImage *image; int32_t width; int32_t height; uint8_t *dest; int dest_stride; RedSurface *surface; int bpp; int all_set; surface = &display->priv->surfaces[drawable->surface_id]; bpp = SPICE_SURFACE_FMT_DEPTH(surface->context.format) / 8; width = red_drawable->self_bitmap_area.right - red_drawable->self_bitmap_area.left; height = red_drawable->self_bitmap_area.bottom - red_drawable->self_bitmap_area.top; dest_stride = SPICE_ALIGN(width * bpp, 4); image = spice_new0(SpiceImage, 1); image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP; image->descriptor.flags = 0; QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_RED, display_channel_generate_uid(display)); image->u.bitmap.flags = surface->context.top_down ? SPICE_BITMAP_FLAGS_TOP_DOWN : 0; image->u.bitmap.format = spice_bitmap_from_surface_type(surface->context.format); image->u.bitmap.stride = dest_stride; image->descriptor.width = image->u.bitmap.x = width; image->descriptor.height = image->u.bitmap.y = height; image->u.bitmap.palette = NULL; dest = (uint8_t *)spice_malloc_n(height, dest_stride); image->u.bitmap.data = spice_chunks_new_linear(dest, height * dest_stride); image->u.bitmap.data->flags |= SPICE_CHUNKS_FLAGS_FREE; display_channel_draw(display, &red_drawable->self_bitmap_area, drawable->surface_id); surface_read_bits(display, drawable->surface_id, &red_drawable->self_bitmap_area, dest, dest_stride); /* 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, drawable->surface_id) && image->u.bitmap.format == SPICE_BITMAP_FMT_32BIT && rgb32_data_has_alpha(width, height, dest_stride, dest, &all_set)) { if (all_set) { image->descriptor.flags |= SPICE_IMAGE_FLAGS_HIGH_BITS_SET; } else { image->u.bitmap.format = SPICE_BITMAP_FMT_RGBA; } } red_drawable->self_bitmap_image = image; } static void surface_add_reverse_dependency(DisplayChannel *display, int surface_id, DependItem *depend_item, Drawable *drawable) { RedSurface *surface; if (surface_id == -1) { depend_item->drawable = NULL; return; } surface = &display->priv->surfaces[surface_id]; depend_item->drawable = drawable; ring_add(&surface->depend_on_me, &depend_item->ring_item); } static int handle_surface_deps(DisplayChannel *display, Drawable *drawable) { int x; for (x = 0; x < 3; ++x) { // surface self dependency is handled by shadows in "current", or by // handle_self_bitmap if (drawable->surface_deps[x] != drawable->surface_id) { surface_add_reverse_dependency(display, drawable->surface_deps[x], &drawable->depend_items[x], drawable); if (drawable->surface_deps[x] == 0) { QRegion depend_region; region_init(&depend_region); region_add(&depend_region, &drawable->red_drawable->surfaces_rects[x]); stream_detach_behind(display, &depend_region, NULL); } } } return TRUE; } static void draw_depend_on_me(DisplayChannel *display, uint32_t surface_id) { RedSurface *surface; RingItem *ring_item; surface = &display->priv->surfaces[surface_id]; while ((ring_item = ring_get_tail(&surface->depend_on_me))) { Drawable *drawable; DependItem *depended_item = SPICE_CONTAINEROF(ring_item, DependItem, ring_item); drawable = depended_item->drawable; display_channel_draw(display, &drawable->red_drawable->bbox, drawable->surface_id); } } static int validate_drawable_bbox(DisplayChannel *display, RedDrawable *drawable) { DrawContext *context; uint32_t surface_id = drawable->surface_id; /* surface_id must be validated before calling into * validate_drawable_bbox */ if (!display_channel_validate_surface(display, drawable->surface_id)) { return FALSE; } context = &display->priv->surfaces[surface_id].context; if (drawable->bbox.top < 0) return FALSE; if (drawable->bbox.left < 0) return FALSE; if (drawable->bbox.bottom < 0) return FALSE; if (drawable->bbox.right < 0) return FALSE; if (drawable->bbox.bottom > context->height) return FALSE; if (drawable->bbox.right > context->width) return FALSE; return TRUE; } /** * @brief Get a new Drawable * * The Drawable returned is fully initialized. * * @return initialized Drawable or NULL on failure */ static Drawable *display_channel_get_drawable(DisplayChannel *display, uint8_t effect, RedDrawable *red_drawable, uint32_t process_commands_generation) { Drawable *drawable; int x; /* Validate all surface ids before updating counters * to avoid invalid updates if we find an invalid id. */ if (!validate_drawable_bbox(display, red_drawable)) { return NULL; } for (x = 0; x < 3; ++x) { if (red_drawable->surface_deps[x] != -1 && !display_channel_validate_surface(display, red_drawable->surface_deps[x])) { return NULL; } } drawable = display_channel_drawable_try_new(display, process_commands_generation); if (!drawable) { return NULL; } drawable->tree_item.effect = effect; drawable->red_drawable = red_drawable_ref(red_drawable); drawable->surface_id = red_drawable->surface_id; display->priv->surfaces[drawable->surface_id].refs++; memcpy(drawable->surface_deps, red_drawable->surface_deps, sizeof(drawable->surface_deps)); /* surface->refs is affected by a drawable (that is dependent on the surface) as long as the drawable is alive. However, surface->depend_on_me is affected by a drawable only as long as it is in the current tree (hasn't been rendered yet). */ drawable_ref_surface_deps(display, drawable); return drawable; } /** * Add a Drawable to the items to draw. * On failure the Drawable is not added. */ static void display_channel_add_drawable(DisplayChannel *display, Drawable *drawable) { int surface_id = drawable->surface_id; RedDrawable *red_drawable = drawable->red_drawable; red_drawable->mm_time = reds_get_mm_time(); region_add(&drawable->tree_item.base.rgn, &red_drawable->bbox); if (red_drawable->clip.type == SPICE_CLIP_TYPE_RECTS) { QRegion rgn; region_init(&rgn); region_add_clip_rects(&rgn, red_drawable->clip.rects); region_and(&drawable->tree_item.base.rgn, &rgn); region_destroy(&rgn); } if (region_is_empty(&drawable->tree_item.base.rgn)) { return; } if (red_drawable->self_bitmap) { handle_self_bitmap(display, drawable); } draw_depend_on_me(display, surface_id); if (!handle_surface_deps(display, drawable)) { return; } Ring *ring = &display->priv->surfaces[surface_id].current; int add_to_pipe; if (has_shadow(red_drawable)) { add_to_pipe = current_add_with_shadow(display, ring, drawable); } else { drawable->streamable = drawable_can_stream(display, drawable); add_to_pipe = current_add(display, ring, drawable); } if (add_to_pipe) pipes_add_drawable(display, drawable); #ifdef RED_WORKER_STAT if ((++display->priv->add_count % 100) == 0) display_channel_print_stats(display); #endif } void display_channel_process_draw(DisplayChannel *display, RedDrawable *red_drawable, int process_commands_generation) { Drawable *drawable = display_channel_get_drawable(display, red_drawable->effect, red_drawable, process_commands_generation); if (!drawable) { return; } display_channel_add_drawable(display, drawable); drawable_unref(drawable); } int display_channel_wait_for_migrate_data(DisplayChannel *display) { uint64_t end_time = spice_get_monotonic_time_ns() + DISPLAY_CLIENT_MIGRATE_DATA_TIMEOUT; RedChannelClient *rcc; int ret = FALSE; GList *clients = red_channel_get_clients(RED_CHANNEL(display));; if (!red_channel_is_waiting_for_migrate_data(RED_CHANNEL(display))) { return FALSE; } spice_debug(NULL); spice_warn_if_fail(g_list_length(clients) == 1); rcc = clients->data; g_object_ref(rcc); for (;;) { red_channel_client_receive(rcc); if (!red_channel_client_is_connected(rcc)) { break; } if (!red_channel_client_is_waiting_for_migrate_data(rcc)) { ret = TRUE; break; } if (spice_get_monotonic_time_ns() > end_time) { spice_warning("timeout"); red_channel_client_disconnect(rcc); break; } usleep(DISPLAY_CLIENT_RETRY_INTERVAL); } g_object_unref(rcc); return ret; } void display_channel_flush_all_surfaces(DisplayChannel *display) { int x; for (x = 0; x < display->priv->n_surfaces; ++x) { if (display->priv->surfaces[x].context.canvas) { display_channel_current_flush(display, x); } } } void display_channel_free_glz_drawables_to_free(DisplayChannel *display) { GList *link, *next; DisplayChannelClient *dcc; spice_return_if_fail(display); FOREACH_DCC(display, link, next, dcc) { dcc_free_glz_drawables_to_free(dcc); } } void display_channel_free_glz_drawables(DisplayChannel *display) { GList *link, *next; DisplayChannelClient *dcc; spice_return_if_fail(display); FOREACH_DCC(display, link, next, dcc) { dcc_free_glz_drawables(dcc); } } static bool free_one_drawable(DisplayChannel *display, int force_glz_free) { RingItem *ring_item = ring_get_tail(&display->priv->current_list); Drawable *drawable; Container *container; if (!ring_item) { return FALSE; } drawable = SPICE_CONTAINEROF(ring_item, Drawable, list_link); if (force_glz_free) { RingItem *glz_item, *next_item; RedGlzDrawable *glz; DRAWABLE_FOREACH_GLZ_SAFE(drawable, glz_item, next_item, glz) { dcc_free_glz_drawable(glz->dcc, glz); } } drawable_draw(display, drawable); container = drawable->tree_item.base.container; current_remove_drawable(display, drawable); container_cleanup(container); return TRUE; } void display_channel_current_flush(DisplayChannel *display, int surface_id) { while (!ring_is_empty(&display->priv->surfaces[surface_id].current_list)) { free_one_drawable(display, FALSE); } current_remove_all(display, surface_id); } void display_channel_free_some(DisplayChannel *display) { int n = 0; DisplayChannelClient *dcc; GList *link, *next; spice_debug("#draw=%d, #glz_draw=%d", display->priv->drawable_count, display->priv->glz_drawable_count); FOREACH_DCC(display, link, next, dcc) { GlzSharedDictionary *glz_dict = dcc_get_glz_dictionary(dcc); if (glz_dict) { // encoding using the dictionary is prevented since the following operations might // change the dictionary pthread_rwlock_wrlock(&glz_dict->encode_lock); n = dcc_free_some_independent_glz_drawables(dcc); } } while (!ring_is_empty(&display->priv->current_list) && n++ < RED_RELEASE_BUNCH_SIZE) { free_one_drawable(display, TRUE); } FOREACH_DCC(display, link, next, dcc) { GlzSharedDictionary *glz_dict = dcc_get_glz_dictionary(dcc); if (glz_dict) { pthread_rwlock_unlock(&glz_dict->encode_lock); } } } static Drawable* drawable_try_new(DisplayChannel *display) { Drawable *drawable; if (!display->priv->free_drawables) return NULL; drawable = &display->priv->free_drawables->u.drawable; display->priv->free_drawables = display->priv->free_drawables->u.next; display->priv->drawable_count++; return drawable; } static void drawable_free(DisplayChannel *display, Drawable *drawable) { ((_Drawable *)drawable)->u.next = display->priv->free_drawables; display->priv->free_drawables = (_Drawable *)drawable; } static void drawables_init(DisplayChannel *display) { int i; display->priv->drawables = g_new0(_Drawable, NUM_DRAWABLES); display->priv->free_drawables = NULL; for (i = 0; i < NUM_DRAWABLES; i++) { drawable_free(display, &display->priv->drawables[i].u.drawable); } } /** * Allocate a Drawable * * @return pointer to uninitialized Drawable or NULL on failure */ Drawable *display_channel_drawable_try_new(DisplayChannel *display, int process_commands_generation) { Drawable *drawable; while (!(drawable = drawable_try_new(display))) { if (!free_one_drawable(display, FALSE)) return NULL; } bzero(drawable, sizeof(Drawable)); /* Pointer to the display from which the drawable is allocated. This * pointer is safe to be retained as DisplayChannel lifespan is bigger than * all drawables. */ drawable->display = display; drawable->refs = 1; drawable->creation_time = drawable->first_frame_time = spice_get_monotonic_time_ns(); ring_item_init(&drawable->list_link); ring_item_init(&drawable->surface_list_link); ring_item_init(&drawable->tree_item.base.siblings_link); drawable->tree_item.base.type = TREE_ITEM_TYPE_DRAWABLE; region_init(&drawable->tree_item.base.rgn); ring_init(&drawable->glz_ring); drawable->process_commands_generation = process_commands_generation; return drawable; } static void depended_item_remove(DependItem *item) { spice_return_if_fail(item->drawable); spice_return_if_fail(ring_item_is_linked(&item->ring_item)); item->drawable = NULL; ring_remove(&item->ring_item); } static void drawable_remove_dependencies(DisplayChannel *display, Drawable *drawable) { int x; int surface_id; for (x = 0; x < 3; ++x) { surface_id = drawable->surface_deps[x]; if (surface_id != -1 && drawable->depend_items[x].drawable) { depended_item_remove(&drawable->depend_items[x]); } } } static void drawable_unref_surface_deps(DisplayChannel *display, Drawable *drawable) { int x; int surface_id; for (x = 0; x < 3; ++x) { surface_id = drawable->surface_deps[x]; if (surface_id == -1) { continue; } display_channel_surface_unref(display, surface_id); } } void drawable_unref(Drawable *drawable) { DisplayChannel *display = drawable->display; RingItem *item, *next; if (--drawable->refs != 0) return; spice_warn_if_fail(!drawable->tree_item.shadow); spice_warn_if_fail(drawable->pipes == NULL); if (drawable->stream) { detach_stream(display, drawable->stream, TRUE); } region_destroy(&drawable->tree_item.base.rgn); drawable_remove_dependencies(display, drawable); drawable_unref_surface_deps(display, drawable); display_channel_surface_unref(display, drawable->surface_id); RING_FOREACH_SAFE(item, next, &drawable->glz_ring) { SPICE_CONTAINEROF(item, RedGlzDrawable, drawable_link)->drawable = NULL; ring_remove(item); } if (drawable->red_drawable) { red_drawable_unref(drawable->red_drawable); } drawable_free(display, drawable); display->priv->drawable_count--; } static void drawable_deps_draw(DisplayChannel *display, Drawable *drawable) { int x; int surface_id; for (x = 0; x < 3; ++x) { surface_id = drawable->surface_deps[x]; if (surface_id != -1 && drawable->depend_items[x].drawable) { depended_item_remove(&drawable->depend_items[x]); display_channel_draw(display, &drawable->red_drawable->surfaces_rects[x], surface_id); } } } static void drawable_draw(DisplayChannel *display, Drawable *drawable) { RedSurface *surface; SpiceCanvas *canvas; SpiceClip clip = drawable->red_drawable->clip; drawable_deps_draw(display, drawable); surface = &display->priv->surfaces[drawable->surface_id]; canvas = surface->context.canvas; spice_return_if_fail(canvas); image_cache_aging(display->priv->image_cache); region_add(&surface->draw_dirty_region, &drawable->red_drawable->bbox); switch (drawable->red_drawable->type) { case QXL_DRAW_FILL: { SpiceFill fill = drawable->red_drawable->u.fill; SpiceImage img1, img2; image_cache_localize_brush(display->priv->image_cache, &fill.brush, &img1); image_cache_localize_mask(display->priv->image_cache, &fill.mask, &img2); canvas->ops->draw_fill(canvas, &drawable->red_drawable->bbox, &clip, &fill); break; } case QXL_DRAW_OPAQUE: { SpiceOpaque opaque = drawable->red_drawable->u.opaque; SpiceImage img1, img2, img3; image_cache_localize_brush(display->priv->image_cache, &opaque.brush, &img1); image_cache_localize(display->priv->image_cache, &opaque.src_bitmap, &img2, drawable); image_cache_localize_mask(display->priv->image_cache, &opaque.mask, &img3); canvas->ops->draw_opaque(canvas, &drawable->red_drawable->bbox, &clip, &opaque); break; } case QXL_DRAW_COPY: { SpiceCopy copy = drawable->red_drawable->u.copy; SpiceImage img1, img2; image_cache_localize(display->priv->image_cache, ©.src_bitmap, &img1, drawable); image_cache_localize_mask(display->priv->image_cache, ©.mask, &img2); canvas->ops->draw_copy(canvas, &drawable->red_drawable->bbox, &clip, ©); break; } case QXL_DRAW_TRANSPARENT: { SpiceTransparent transparent = drawable->red_drawable->u.transparent; SpiceImage img1; image_cache_localize(display->priv->image_cache, &transparent.src_bitmap, &img1, drawable); canvas->ops->draw_transparent(canvas, &drawable->red_drawable->bbox, &clip, &transparent); break; } case QXL_DRAW_ALPHA_BLEND: { SpiceAlphaBlend alpha_blend = drawable->red_drawable->u.alpha_blend; SpiceImage img1; image_cache_localize(display->priv->image_cache, &alpha_blend.src_bitmap, &img1, drawable); canvas->ops->draw_alpha_blend(canvas, &drawable->red_drawable->bbox, &clip, &alpha_blend); break; } case QXL_COPY_BITS: { canvas->ops->copy_bits(canvas, &drawable->red_drawable->bbox, &clip, &drawable->red_drawable->u.copy_bits.src_pos); break; } case QXL_DRAW_BLEND: { SpiceBlend blend = drawable->red_drawable->u.blend; SpiceImage img1, img2; image_cache_localize(display->priv->image_cache, &blend.src_bitmap, &img1, drawable); image_cache_localize_mask(display->priv->image_cache, &blend.mask, &img2); canvas->ops->draw_blend(canvas, &drawable->red_drawable->bbox, &clip, &blend); break; } case QXL_DRAW_BLACKNESS: { SpiceBlackness blackness = drawable->red_drawable->u.blackness; SpiceImage img1; image_cache_localize_mask(display->priv->image_cache, &blackness.mask, &img1); canvas->ops->draw_blackness(canvas, &drawable->red_drawable->bbox, &clip, &blackness); break; } case QXL_DRAW_WHITENESS: { SpiceWhiteness whiteness = drawable->red_drawable->u.whiteness; SpiceImage img1; image_cache_localize_mask(display->priv->image_cache, &whiteness.mask, &img1); canvas->ops->draw_whiteness(canvas, &drawable->red_drawable->bbox, &clip, &whiteness); break; } case QXL_DRAW_INVERS: { SpiceInvers invers = drawable->red_drawable->u.invers; SpiceImage img1; image_cache_localize_mask(display->priv->image_cache, &invers.mask, &img1); canvas->ops->draw_invers(canvas, &drawable->red_drawable->bbox, &clip, &invers); break; } case QXL_DRAW_ROP3: { SpiceRop3 rop3 = drawable->red_drawable->u.rop3; SpiceImage img1, img2, img3; image_cache_localize_brush(display->priv->image_cache, &rop3.brush, &img1); image_cache_localize(display->priv->image_cache, &rop3.src_bitmap, &img2, drawable); image_cache_localize_mask(display->priv->image_cache, &rop3.mask, &img3); canvas->ops->draw_rop3(canvas, &drawable->red_drawable->bbox, &clip, &rop3); break; } case QXL_DRAW_COMPOSITE: { SpiceComposite composite = drawable->red_drawable->u.composite; SpiceImage src, mask; image_cache_localize(display->priv->image_cache, &composite.src_bitmap, &src, drawable); if (composite.mask_bitmap) image_cache_localize(display->priv->image_cache, &composite.mask_bitmap, &mask, drawable); canvas->ops->draw_composite(canvas, &drawable->red_drawable->bbox, &clip, &composite); break; } case QXL_DRAW_STROKE: { SpiceStroke stroke = drawable->red_drawable->u.stroke; SpiceImage img1; image_cache_localize_brush(display->priv->image_cache, &stroke.brush, &img1); canvas->ops->draw_stroke(canvas, &drawable->red_drawable->bbox, &clip, &stroke); break; } case QXL_DRAW_TEXT: { SpiceText text = drawable->red_drawable->u.text; SpiceImage img1, img2; image_cache_localize_brush(display->priv->image_cache, &text.fore_brush, &img1); image_cache_localize_brush(display->priv->image_cache, &text.back_brush, &img2); canvas->ops->draw_text(canvas, &drawable->red_drawable->bbox, &clip, &text); break; } default: spice_warning("invalid type"); } } static void surface_update_dest(RedSurface *surface, const SpiceRect *area) { SpiceCanvas *canvas = surface->context.canvas; int stride = surface->context.stride; uint8_t *line_0 = surface->context.line_0; if (surface->context.canvas_draws_on_surface) return; int h = area->bottom - area->top; if (h == 0) return; spice_return_if_fail(stride < 0); uint8_t *dest = line_0 + (area->top * stride) + area->left * sizeof(uint32_t); dest += (h - 1) * stride; canvas->ops->read_bits(canvas, dest, -stride, area); } static void draw_until(DisplayChannel *display, RedSurface *surface, Drawable *last) { RingItem *ring_item; Container *container; Drawable *now; do { ring_item = ring_get_tail(&surface->current_list); now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link); now->refs++; container = now->tree_item.base.container; current_remove_drawable(display, now); container_cleanup(container); /* drawable_draw may call display_channel_draw for the surfaces 'now' depends on. Notice, that it is valid to call display_channel_draw in this case and not display_channel_draw_till: It is impossible that there was newer item then 'last' in one of the surfaces that display_channel_draw is called for, Otherwise, 'now' would have already been rendered. See the call for red_handle_depends_on_target_surface in red_process_draw */ drawable_draw(display, now); drawable_unref(now); } while (now != last); } static Drawable* current_find_intersects_rect(Ring *current, RingItem *from, const SpiceRect *area) { RingItem *it; QRegion rgn; Drawable *last = NULL; region_init(&rgn); region_add(&rgn, area); for (it = from ? from : ring_next(current, current); it != NULL; it = ring_next(current, it)) { Drawable *now = SPICE_CONTAINEROF(it, Drawable, surface_list_link); if (region_intersects(&rgn, &now->tree_item.base.rgn)) { last = now; break; } } region_destroy(&rgn); return last; } /* * Renders drawables for updating the requested area, but only drawables that are older * than 'last' (exclusive). * FIXME: merge with display_channel_draw()? */ void display_channel_draw_until(DisplayChannel *display, const SpiceRect *area, int surface_id, Drawable *last) { RedSurface *surface; Drawable *surface_last = NULL; Ring *ring; RingItem *ring_item; Drawable *now; spice_return_if_fail(last); spice_return_if_fail(ring_item_is_linked(&last->list_link)); surface = &display->priv->surfaces[surface_id]; if (surface_id != last->surface_id) { // find the nearest older drawable from the appropriate surface ring = &display->priv->current_list; ring_item = &last->list_link; while ((ring_item = ring_next(ring, ring_item))) { now = SPICE_CONTAINEROF(ring_item, Drawable, list_link); if (now->surface_id == surface_id) { surface_last = now; break; } } } else { ring_item = ring_next(&surface->current_list, &last->surface_list_link); if (ring_item) { surface_last = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link); } } if (!surface_last) return; last = current_find_intersects_rect(&surface->current_list, &surface_last->surface_list_link, area); if (!last) return; draw_until(display, surface, last); surface_update_dest(surface, area); } void display_channel_draw(DisplayChannel *display, const SpiceRect *area, int surface_id) { RedSurface *surface; Drawable *last; spice_debug("surface %d: area ==>", surface_id); rect_debug(area); spice_return_if_fail(surface_id >= 0 && surface_id < display->priv->n_surfaces); spice_return_if_fail(area); spice_return_if_fail(area->left >= 0 && area->top >= 0 && area->left < area->right && area->top < area->bottom); surface = &display->priv->surfaces[surface_id]; last = current_find_intersects_rect(&surface->current_list, NULL, area); if (last) draw_until(display, surface, last); surface_update_dest(surface, area); } static void region_to_qxlrects(QRegion *region, QXLRect *qxl_rects, uint32_t num_rects) { SpiceRect *rects; int i; rects = spice_new0(SpiceRect, num_rects); region_ret_rects(region, rects, num_rects); for (i = 0; i < num_rects; i++) { qxl_rects[i].top = rects[i].top; qxl_rects[i].left = rects[i].left; qxl_rects[i].bottom = rects[i].bottom; qxl_rects[i].right = rects[i].right; } free(rects); } void display_channel_update(DisplayChannel *display, uint32_t surface_id, const QXLRect *area, uint32_t clear_dirty, QXLRect **qxl_dirty_rects, uint32_t *num_dirty_rects) { SpiceRect rect; RedSurface *surface; spice_return_if_fail(display_channel_validate_surface(display, surface_id)); red_get_rect_ptr(&rect, area); display_channel_draw(display, &rect, surface_id); surface = &display->priv->surfaces[surface_id]; if (*qxl_dirty_rects == NULL) { *num_dirty_rects = pixman_region32_n_rects(&surface->draw_dirty_region); *qxl_dirty_rects = spice_new0(QXLRect, *num_dirty_rects); } region_to_qxlrects(&surface->draw_dirty_region, *qxl_dirty_rects, *num_dirty_rects); if (clear_dirty) region_clear(&surface->draw_dirty_region); } static void clear_surface_drawables_from_pipes(DisplayChannel *display, int surface_id, int wait_if_used) { GList *link, *next; DisplayChannelClient *dcc; FOREACH_DCC(display, link, next, dcc) { if (!dcc_clear_surface_drawables_from_pipe(dcc, surface_id, wait_if_used)) { red_channel_client_disconnect(RED_CHANNEL_CLIENT(dcc)); } } } /* TODO: cleanup/refactor destroy functions */ void display_channel_destroy_surface(DisplayChannel *display, uint32_t surface_id) { draw_depend_on_me(display, surface_id); /* note that draw_depend_on_me must be called before current_remove_all. otherwise "current" will hold items that other drawables may depend on, and then current_remove_all will remove them from the pipe. */ current_remove_all(display, surface_id); clear_surface_drawables_from_pipes(display, surface_id, FALSE); display_channel_surface_unref(display, surface_id); } void display_channel_destroy_surface_wait(DisplayChannel *display, uint32_t surface_id) { if (!display_channel_validate_surface(display, surface_id)) return; if (!display->priv->surfaces[surface_id].context.canvas) return; draw_depend_on_me(display, surface_id); /* note that draw_depend_on_me must be called before current_remove_all. otherwise "current" will hold items that other drawables may depend on, and then current_remove_all will remove them from the pipe. */ current_remove_all(display, surface_id); clear_surface_drawables_from_pipes(display, surface_id, TRUE); } /* called upon device reset */ /* TODO: split me*/ void display_channel_destroy_surfaces(DisplayChannel *display) { int i; spice_debug(NULL); //to handle better for (i = 0; i < display->priv->n_surfaces; ++i) { if (display->priv->surfaces[i].context.canvas) { display_channel_destroy_surface_wait(display, i); if (display->priv->surfaces[i].context.canvas) { display_channel_surface_unref(display, i); } spice_assert(!display->priv->surfaces[i].context.canvas); } } spice_warn_if_fail(ring_is_empty(&display->priv->streams)); if (red_channel_is_connected(RED_CHANNEL(display))) { red_channel_pipes_add_type(RED_CHANNEL(display), RED_PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE); red_pipes_add_verb(RED_CHANNEL(display), SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL); } display_channel_free_glz_drawables(display); } static void send_create_surface(DisplayChannel *display, int surface_id, int image_ready) { DisplayChannelClient *dcc; GList *link, *next; FOREACH_DCC(display, link, next, dcc) { dcc_create_surface(dcc, surface_id); if (image_ready) dcc_push_surface_image(dcc, surface_id); } } static SpiceCanvas* create_canvas_for_surface(DisplayChannel *display, RedSurface *surface, uint32_t renderer) { SpiceCanvas *canvas; switch (renderer) { case RED_RENDERER_SW: canvas = canvas_create_for_data(surface->context.width, surface->context.height, surface->context.format, surface->context.line_0, surface->context.stride, &display->priv->image_cache->base, &display->priv->image_surfaces, NULL, NULL, NULL); surface->context.top_down = TRUE; surface->context.canvas_draws_on_surface = TRUE; return canvas; default: spice_warn_if_reached(); }; return NULL; } void display_channel_create_surface(DisplayChannel *display, uint32_t surface_id, uint32_t width, uint32_t height, int32_t stride, uint32_t format, void *line_0, int data_is_valid, int send_client) { RedSurface *surface = &display->priv->surfaces[surface_id]; spice_warn_if_fail(!surface->context.canvas); surface->context.canvas_draws_on_surface = FALSE; surface->context.width = width; surface->context.height = height; surface->context.format = format; surface->context.stride = stride; surface->context.line_0 = line_0; if (!data_is_valid) { char *data = line_0; if (stride < 0) { data -= abs(stride) * (height - 1); } memset(data, 0, height*abs(stride)); } surface->create.info = NULL; surface->destroy.info = NULL; ring_init(&surface->current); ring_init(&surface->current_list); ring_init(&surface->depend_on_me); region_init(&surface->draw_dirty_region); surface->refs = 1; if (display->priv->renderer == RED_RENDERER_INVALID) { int i; QXLInstance *qxl = common_graphics_channel_get_qxl(COMMON_GRAPHICS_CHANNEL(display)); RedsState *reds = red_qxl_get_server(qxl->st); GArray *renderers = reds_get_renderers(reds); for (i = 0; i < renderers->len; i++) { uint32_t renderer = g_array_index(renderers, uint32_t, i); surface->context.canvas = create_canvas_for_surface(display, surface, renderer); if (surface->context.canvas) { display->priv->renderer = renderer; break; } } } else { surface->context.canvas = create_canvas_for_surface(display, surface, display->priv->renderer); } spice_return_if_fail(surface->context.canvas); if (send_client) send_create_surface(display, surface_id, data_is_valid); } static void on_disconnect(RedChannelClient *rcc) { DisplayChannel *display; DisplayChannelClient *dcc; spice_info(NULL); spice_return_if_fail(rcc != NULL); dcc = DISPLAY_CHANNEL_CLIENT(rcc); display = DCC_TO_DC(dcc); dcc_stop(dcc); // 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->glz_drawable_count); } static void send_item(RedChannelClient *rcc, RedPipeItem *item) { dcc_send_item(DISPLAY_CHANNEL_CLIENT(rcc), item); } static void hold_item(RedChannelClient *rcc, RedPipeItem *item) { spice_return_if_fail(item); switch (item->type) { case RED_PIPE_ITEM_TYPE_DRAW: case RED_PIPE_ITEM_TYPE_IMAGE: case RED_PIPE_ITEM_TYPE_STREAM_CLIP: case RED_PIPE_ITEM_TYPE_UPGRADE: red_pipe_item_ref(item); break; default: spice_warn_if_reached(); } } static void release_item(RedChannelClient *rcc, RedPipeItem *item, int item_pushed) { DisplayChannelClient *dcc = DISPLAY_CHANNEL_CLIENT(rcc); spice_return_if_fail(item != NULL); dcc_release_item(dcc, item, item_pushed); } static int handle_migrate_flush_mark(RedChannelClient *rcc) { RedChannel *channel = red_channel_client_get_channel(rcc); red_channel_pipes_add_type(channel, RED_PIPE_ITEM_TYPE_MIGRATE_DATA); return TRUE; } static uint64_t handle_migrate_data_get_serial(RedChannelClient *rcc, uint32_t size, void *message) { SpiceMigrateDataDisplay *migrate_data; migrate_data = (SpiceMigrateDataDisplay *)((uint8_t *)message + sizeof(SpiceMigrateDataHeader)); return migrate_data->message_serial; } static int handle_migrate_data(RedChannelClient *rcc, uint32_t size, void *message) { return dcc_handle_migrate_data(DISPLAY_CHANNEL_CLIENT(rcc), size, message); } DisplayChannel* display_channel_new(RedsState *reds, QXLInstance *qxl, const SpiceCoreInterfaceInternal *core, int migrate, int stream_video, uint32_t n_surfaces) { DisplayChannel *display; /* FIXME: migrate is not used...? */ spice_info("create display channel"); display = g_object_new(TYPE_DISPLAY_CHANNEL, "spice-server", reds, "core-interface", core, "channel-type", SPICE_CHANNEL_DISPLAY, "migration-flags", (SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER), "qxl", qxl, "n-surfaces", n_surfaces, NULL); if (display) { display_channel_set_stream_video(display, stream_video); } return display; } void display_channel_process_surface_cmd(DisplayChannel *display, RedSurfaceCmd *surface, int loadvm) { uint32_t surface_id; RedSurface *red_surface; uint8_t *data; surface_id = surface->surface_id; if SPICE_UNLIKELY(surface_id >= display->priv->n_surfaces) { return; } red_surface = &display->priv->surfaces[surface_id]; switch (surface->type) { case QXL_SURFACE_CMD_CREATE: { uint32_t height = surface->u.surface_create.height; int32_t stride = surface->u.surface_create.stride; int reloaded_surface = loadvm || (surface->flags & QXL_SURF_FLAG_KEEP_DATA); if (red_surface->refs) { spice_warning("avoiding creating a surface twice"); break; } data = surface->u.surface_create.data; if (stride < 0) { data -= (int32_t)(stride * (height - 1)); } display_channel_create_surface(display, surface_id, surface->u.surface_create.width, height, stride, surface->u.surface_create.format, data, reloaded_surface, // reloaded surfaces will be sent on demand !reloaded_surface); red_surface->create = surface->release_info_ext; break; } case QXL_SURFACE_CMD_DESTROY: if (!red_surface->refs) { spice_warning("avoiding destroying a surface twice"); break; } red_surface->destroy = surface->release_info_ext; display_channel_destroy_surface(display, surface_id); break; default: spice_warn_if_reached(); }; } void display_channel_update_compression(DisplayChannel *display, DisplayChannelClient *dcc) { gboolean is_low_bw = common_graphics_channel_client_is_low_bandwidth(COMMON_GRAPHICS_CHANNEL_CLIENT(dcc)); if (dcc_get_jpeg_state(dcc) == SPICE_WAN_COMPRESSION_AUTO) { display->priv->enable_jpeg = is_low_bw; } else { display->priv->enable_jpeg = (dcc_get_jpeg_state(dcc) == SPICE_WAN_COMPRESSION_ALWAYS); } if (dcc_get_zlib_glz_state(dcc) == SPICE_WAN_COMPRESSION_AUTO) { display->priv->enable_zlib_glz_wrap = is_low_bw; } else { display->priv->enable_zlib_glz_wrap = (dcc_get_zlib_glz_state(dcc) == SPICE_WAN_COMPRESSION_ALWAYS); } spice_info("jpeg %s", display->priv->enable_jpeg ? "enabled" : "disabled"); spice_info("zlib-over-glz %s", display->priv->enable_zlib_glz_wrap ? "enabled" : "disabled"); } void display_channel_gl_scanout(DisplayChannel *display) { red_channel_pipes_new_add_push(RED_CHANNEL(display), dcc_gl_scanout_item_new, NULL); } static void set_gl_draw_async_count(DisplayChannel *display, int num) { QXLInstance *qxl = common_graphics_channel_get_qxl(COMMON_GRAPHICS_CHANNEL(display)); display->priv->gl_draw_async_count = num; if (num == 0) { red_qxl_gl_draw_async_complete(qxl); } } void display_channel_gl_draw(DisplayChannel *display, SpiceMsgDisplayGlDraw *draw) { int num; spice_return_if_fail(display->priv->gl_draw_async_count == 0); num = red_channel_pipes_new_add_push(RED_CHANNEL(display), dcc_gl_draw_item_new, draw); set_gl_draw_async_count(display, num); } void display_channel_gl_draw_done(DisplayChannel *display) { set_gl_draw_async_count(display, display->priv->gl_draw_async_count - 1); } int display_channel_get_stream_id(DisplayChannel *display, Stream *stream) { return (int)(stream - display->priv->streams_buf); } gboolean display_channel_validate_surface(DisplayChannel *display, uint32_t surface_id) { if SPICE_UNLIKELY(surface_id >= display->priv->n_surfaces) { spice_warning("invalid surface_id %u", surface_id); return 0; } if (!display->priv->surfaces[surface_id].context.canvas) { spice_warning("canvas address is %p for %d (and is NULL)\n", &(display->priv->surfaces[surface_id].context.canvas), surface_id); spice_warning("failed on %d", surface_id); return 0; } return 1; } void display_channel_update_monitors_config(DisplayChannel *display, QXLMonitorsConfig *config, uint16_t count, uint16_t max_allowed) { if (display->priv->monitors_config) monitors_config_unref(display->priv->monitors_config); display->priv->monitors_config = monitors_config_new(config->heads, count, max_allowed); } void display_channel_set_monitors_config_to_primary(DisplayChannel *display) { DrawContext *context = &display->priv->surfaces[0].context; QXLHead head = { 0, }; spice_return_if_fail(display->priv->surfaces[0].context.canvas); if (display->priv->monitors_config) monitors_config_unref(display->priv->monitors_config); head.width = context->width; head.height = context->height; display->priv->monitors_config = monitors_config_new(&head, 1, 1); } void display_channel_reset_image_cache(DisplayChannel *self) { image_cache_reset(self->priv->image_cache); } static void display_channel_class_init(DisplayChannelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); RedChannelClass *channel_class = RED_CHANNEL_CLASS(klass); g_type_class_add_private(klass, sizeof(DisplayChannelPrivate)); object_class->get_property = display_channel_get_property; object_class->set_property = display_channel_set_property; object_class->constructed = display_channel_constructed; object_class->finalize = display_channel_finalize; channel_class->parser = spice_get_client_channel_parser(SPICE_CHANNEL_DISPLAY, NULL); channel_class->handle_parsed = dcc_handle_message; channel_class->on_disconnect = on_disconnect; channel_class->send_item = send_item; channel_class->hold_item = hold_item; channel_class->release_item = release_item; channel_class->handle_migrate_flush_mark = handle_migrate_flush_mark; channel_class->handle_migrate_data = handle_migrate_data; channel_class->handle_migrate_data_get_serial = handle_migrate_data_get_serial; g_object_class_install_property(object_class, PROP_N_SURFACES, g_param_spec_uint("n-surfaces", "number of surfaces", "Number of surfaces for this channel", 0, G_MAXUINT, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); }