/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* Copyright (C) 2009 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 #define SPICE_LOG_DOMAIN "SpiceWorker" /* Common variable abbreviations: * * rcc - RedChannelClient * ccc - CursorChannelClient (not to be confused with common_cc) * common_cc - CommonChannelClient * dcc - DisplayChannelClient * cursor_red_channel - downcast of CursorChannel to RedChannel * display_red_channel - downcast of DisplayChannel to RedChannel */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/lz.h" #include "common/marshaller.h" #include "common/rect.h" #include "common/region.h" #include "common/ring.h" #include "common/generated_server_marshallers.h" #include "display-channel.h" #include "stream.h" #include "spice.h" #include "red_worker.h" #include "spice_timer_queue.h" #include "cursor-channel.h" #include "tree.h" //#define COMPRESS_STAT //#define DUMP_BITMAP //#define COMPRESS_DEBUG #define CMD_RING_POLL_TIMEOUT 10 //milli #define CMD_RING_POLL_RETRIES 200 #define DISPLAY_CLIENT_SHORT_TIMEOUT 15000000000ULL //nano #define MAX_EVENT_SOURCES 20 #define INF_EVENT_WAIT ~0 struct SpiceWatch { struct RedWorker *worker; SpiceWatchFunc watch_func; void *watch_func_opaque; }; #define MAX_LZ_ENCODERS MAX_CACHE_CLIENTS #define MAX_PIPE_SIZE 50 #define WIDE_CLIENT_ACK_WINDOW 40 #define NARROW_CLIENT_ACK_WINDOW 20 pthread_mutex_t glz_dictionary_list_lock = PTHREAD_MUTEX_INITIALIZER; Ring glz_dictionary_list = {&glz_dictionary_list, &glz_dictionary_list}; typedef struct UpgradeItem { PipeItem base; int refs; Drawable *drawable; SpiceClipRects *rects; } UpgradeItem; struct RedWorker { pthread_t thread; clockid_t clockid; QXLInstance *qxl; RedDispatcher *red_dispatcher; int running; struct pollfd poll_fds[MAX_EVENT_SOURCES]; struct SpiceWatch watches[MAX_EVENT_SOURCES]; unsigned int event_timeout; DisplayChannel *display_channel; uint32_t display_poll_tries; CursorChannel *cursor_channel; uint32_t cursor_poll_tries; uint32_t bits_unique; RedMemSlotInfo mem_slots; SpiceImageCompression image_compression; spice_wan_compression_t jpeg_state; spice_wan_compression_t zlib_glz_state; uint32_t process_commands_generation; #ifdef RED_STATISTICS StatNodeRef stat; uint64_t *wakeup_counter; uint64_t *command_counter; #endif int driver_cap_monitors_config; FILE *record_fd; }; typedef enum { BITMAP_DATA_TYPE_INVALID, BITMAP_DATA_TYPE_CACHE, BITMAP_DATA_TYPE_SURFACE, BITMAP_DATA_TYPE_BITMAP, BITMAP_DATA_TYPE_BITMAP_TO_CACHE, } BitmapDataType; typedef struct BitmapData { BitmapDataType type; uint64_t id; // surface id or cache item id SpiceRect lossy_rect; } BitmapData; static inline int validate_surface(DisplayChannel *display, uint32_t surface_id); static void red_update_area(DisplayChannel *display, const SpiceRect *area, int surface_id); static void red_update_area_till(DisplayChannel *display, const SpiceRect *area, int surface_id, Drawable *last); static inline void display_begin_send_message(RedChannelClient *rcc); static void dcc_release_glz(DisplayChannelClient *dcc); static void display_channel_client_release_item_before_push(DisplayChannelClient *dcc, PipeItem *item); static void display_channel_client_release_item_after_push(DisplayChannelClient *dcc, PipeItem *item); static void red_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); void attach_stream(DisplayChannel *display, Drawable *drawable, Stream *stream) { DisplayChannelClient *dcc; RingItem *item, *next; spice_assert(!drawable->stream && !stream->current); spice_assert(drawable && stream); stream->current = drawable; drawable->stream = stream; stream->last_time = drawable->creation_time; uint64_t duration = drawable->creation_time - stream->input_fps_start_time; if (duration >= RED_STREAM_INPUT_FPS_TIMEOUT) { /* Round to the nearest integer, for instance 24 for 23.976 */ stream->input_fps = ((uint64_t)stream->num_input_frames * 1000 * 1000 * 1000 + duration / 2) / duration; spice_debug("input-fps=%u", stream->input_fps); stream->num_input_frames = 0; stream->input_fps_start_time = drawable->creation_time; } else { stream->num_input_frames++; } FOREACH_DCC(display, item, next, dcc) { StreamAgent *agent; QRegion clip_in_draw_dest; agent = &dcc->stream_agents[get_stream_id(display, stream)]; region_or(&agent->vis_region, &drawable->tree_item.base.rgn); region_init(&clip_in_draw_dest); region_add(&clip_in_draw_dest, &drawable->red_drawable->bbox); region_and(&clip_in_draw_dest, &agent->clip); if (!region_is_equal(&clip_in_draw_dest, &drawable->tree_item.base.rgn)) { region_remove(&agent->clip, &drawable->red_drawable->bbox); region_or(&agent->clip, &drawable->tree_item.base.rgn); dcc_stream_agent_clip(dcc, agent); } #ifdef STREAM_STATS agent->stats.num_input_frames++; #endif } } /* fixme: move to display channel */ DrawablePipeItem *drawable_pipe_item_new(DisplayChannelClient *dcc, Drawable *drawable) { DrawablePipeItem *dpi; dpi = spice_malloc0(sizeof(*dpi)); dpi->drawable = drawable; dpi->dcc = dcc; ring_item_init(&dpi->base); ring_add(&drawable->pipes, &dpi->base); red_channel_pipe_item_init(RED_CHANNEL_CLIENT(dcc)->channel, &dpi->dpi_pipe_item, PIPE_ITEM_TYPE_DRAW); dpi->refs++; drawable->refs++; return dpi; } DrawablePipeItem *drawable_pipe_item_ref(DrawablePipeItem *dpi) { dpi->refs++; return dpi; } void drawable_pipe_item_unref(DrawablePipeItem *dpi) { DisplayChannel *display = DCC_TO_DC(dpi->dcc); if (--dpi->refs != 0) { return; } spice_warn_if_fail(!ring_item_is_linked(&dpi->dpi_pipe_item.link)); spice_warn_if_fail(!ring_item_is_linked(&dpi->base)); display_channel_drawable_unref(display, dpi->drawable); free(dpi); } QXLInstance* red_worker_get_qxl(RedWorker *worker) { spice_return_val_if_fail(worker != NULL, NULL); return worker->qxl; } 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 (!validate_surface(display, drawable->surface_id)) { return FALSE; } context = &display->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; } static inline int validate_surface(DisplayChannel *display, uint32_t surface_id) { if SPICE_UNLIKELY(surface_id >= display->n_surfaces) { spice_warning("invalid surface_id %u", surface_id); return 0; } if (!display->surfaces[surface_id].context.canvas) { spice_warning("canvas address is %p for %d (and is NULL)\n", &(display->surfaces[surface_id].context.canvas), surface_id); spice_warning("failed on %d", surface_id); return 0; } return 1; } static inline void red_handle_drawable_surfaces_client_synced( 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->surface_client_created[surface_id] == TRUE) { continue; } dcc_create_surface(dcc, surface_id); display_channel_current_flush(display, surface_id); dcc_push_surface_image(dcc, surface_id); } } if (dcc->surface_client_created[drawable->surface_id] == TRUE) { 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 int display_is_connected(RedWorker *worker) { return (worker->display_channel && red_channel_is_connected( &worker->display_channel->common.base)); } static int cursor_is_connected(RedWorker *worker) { return worker->cursor_channel && red_channel_is_connected(RED_CHANNEL(worker->cursor_channel)); } void dcc_add_drawable(DisplayChannelClient *dcc, Drawable *drawable) { DrawablePipeItem *dpi; red_handle_drawable_surfaces_client_synced(dcc, drawable); dpi = drawable_pipe_item_new(dcc, drawable); red_channel_client_pipe_add(RED_CHANNEL_CLIENT(dcc), &dpi->dpi_pipe_item); } void red_pipes_add_drawable(DisplayChannel *display, Drawable *drawable) { DisplayChannelClient *dcc; RingItem *dcc_ring_item, *next; spice_warn_if(!ring_is_empty(&drawable->pipes)); FOREACH_DCC(display, dcc_ring_item, next, dcc) { dcc_add_drawable(dcc, drawable); } } static void dcc_add_drawable_to_tail(DisplayChannelClient *dcc, Drawable *drawable) { DrawablePipeItem *dpi; if (!dcc) { return; } red_handle_drawable_surfaces_client_synced(dcc, drawable); dpi = drawable_pipe_item_new(dcc, drawable); red_channel_client_pipe_add_tail(RED_CHANNEL_CLIENT(dcc), &dpi->dpi_pipe_item); } void red_pipes_add_drawable_after(DisplayChannel *display, Drawable *drawable, Drawable *pos_after) { DrawablePipeItem *dpi, *dpi_pos_after; RingItem *dpi_link, *dpi_next; DisplayChannelClient *dcc; int num_other_linked = 0; DRAWABLE_FOREACH_DPI_SAFE(pos_after, dpi_link, dpi_next, dpi_pos_after) { num_other_linked++; dcc = dpi_pos_after->dcc; red_handle_drawable_surfaces_client_synced(dcc, drawable); dpi = drawable_pipe_item_new(dcc, drawable); red_channel_client_pipe_add_after(RED_CHANNEL_CLIENT(dcc), &dpi->dpi_pipe_item, &dpi_pos_after->dpi_pipe_item); } if (num_other_linked == 0) { red_pipes_add_drawable(display, drawable); return; } if (num_other_linked != display->common.base.clients_num) { RingItem *item, *next; spice_debug("TODO: not O(n^2)"); FOREACH_DCC(display, item, next, dcc) { int sent = 0; DRAWABLE_FOREACH_DPI_SAFE(pos_after, dpi_link, dpi_next, dpi_pos_after) { if (dpi_pos_after->dcc == dcc) { sent = 1; break; } } if (!sent) { dcc_add_drawable(dcc, drawable); } } } } static PipeItem *dcc_get_tail(DisplayChannelClient *dcc) { return (PipeItem*)ring_get_tail(&RED_CHANNEL_CLIENT(dcc)->pipe); } void red_pipes_remove_drawable(Drawable *drawable) { DrawablePipeItem *dpi; RingItem *item, *next; RING_FOREACH_SAFE(item, next, &drawable->pipes) { dpi = SPICE_CONTAINEROF(item, DrawablePipeItem, base); if (pipe_item_is_linked(&dpi->dpi_pipe_item)) { red_channel_client_pipe_remove_and_release(RED_CHANNEL_CLIENT(dpi->dcc), &dpi->dpi_pipe_item); } } } static void release_image_item(ImageItem *item) { if (!--item->refs) { free(item); } } static void upgrade_item_unref(DisplayChannel *display, UpgradeItem *item) { if (--item->refs != 0) return; display_channel_drawable_unref(display, item->drawable); free(item->rects); free(item); } static uint8_t *common_alloc_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size) { CommonChannel *common = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base); /* SPICE_MSGC_MIGRATE_DATA is the only client message whose size is dynamic */ if (type == SPICE_MSGC_MIGRATE_DATA) { return spice_malloc(size); } if (size > CHANNEL_RECEIVE_BUF_SIZE) { spice_critical("unexpected message size %u (max is %d)", size, CHANNEL_RECEIVE_BUF_SIZE); return NULL; } return common->recv_buf; } static void common_release_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size, uint8_t* msg) { if (type == SPICE_MSGC_MIGRATE_DATA) { free(msg); } } static void drawable_free(DisplayChannel *display, Drawable *drawable) { ((_Drawable *)drawable)->u.next = display->free_drawables; display->free_drawables = (_Drawable *)drawable; } static void drawables_init(DisplayChannel *display) { int i; display->free_drawables = NULL; for (i = 0; i < NUM_DRAWABLES; i++) { drawable_free(display, &display->drawables[i].u.drawable); } } static inline void set_surface_release_info(QXLReleaseInfoExt *release_info_ext, QXLReleaseInfo *release_info, uint32_t group_id) { release_info_ext->info = release_info; release_info_ext->group_id = group_id; } void red_drawable_unref(RedWorker *worker, RedDrawable *red_drawable, uint32_t group_id) { QXLReleaseInfoExt release_info_ext; if (--red_drawable->refs) { return; } worker->display_channel->red_drawable_count--; release_info_ext.group_id = group_id; release_info_ext.info = red_drawable->release_info; worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext); red_put_drawable(red_drawable); free(red_drawable); } static void remove_depended_item(DependItem *item) { spice_assert(item->drawable); spice_assert(ring_item_is_linked(&item->ring_item)); item->drawable = NULL; ring_remove(&item->ring_item); } 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); } } 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) { remove_depended_item(&drawable->depend_items[x]); } } } void display_channel_drawable_unref(DisplayChannel *display, Drawable *drawable) { RingItem *item, *next; if (--drawable->refs != 0) return; spice_warn_if_fail(!drawable->tree_item.shadow); spice_warn_if_fail(ring_is_empty(&drawable->pipes)); 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); } red_drawable_unref(COMMON_CHANNEL(display)->worker, drawable->red_drawable, drawable->group_id); drawable_free(display, drawable); display->drawable_count--; } static void display_stream_trace_add_drawable(DisplayChannel *display, Drawable *item) { ItemTrace *trace; if (item->stream || !item->streamable) { return; } trace = &display->items_trace[display->next_item_trace++ & ITEMS_TRACE_MASK]; trace->time = item->creation_time; trace->frames_count = item->frames_count; trace->gradual_frames_count = item->gradual_frames_count; trace->last_gradual_frame = item->last_gradual_frame; SpiceRect* src_area = &item->red_drawable->u.copy.src_area; trace->width = src_area->right - src_area->left; trace->height = src_area->bottom - src_area->top; trace->dest_area = item->red_drawable->bbox; } static void surface_flush(DisplayChannel *display, int surface_id, SpiceRect *rect) { red_update_area(display, rect, surface_id); } static void red_flush_source_surfaces(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) { remove_depended_item(&drawable->depend_items[x]); surface_flush(display, surface_id, &drawable->red_drawable->surfaces_rects[x]); } } } void current_remove_drawable(DisplayChannel *display, Drawable *item) { /* todo: move all to unref? */ display_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); display_channel_drawable_unref(display, item); display->current_size--; } 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); ring_item = now->siblings_link.prev; red_pipes_remove_drawable(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; } } } void current_remove_all(DisplayChannel *display, int surface_id) { Ring *ring = &display->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); } } /* * 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. */ static int red_clear_surface_drawables_from_pipe(DisplayChannelClient *dcc, int surface_id, int wait_if_used) { Ring *ring; PipeItem *item; int x; RedChannelClient *rcc; if (!dcc) { return TRUE; } /* removing the newest drawables that their destination is surface_id and no other drawable depends on them */ rcc = RED_CHANNEL_CLIENT(dcc); ring = &rcc->pipe; item = (PipeItem *) ring; while ((item = (PipeItem *)ring_next(ring, (RingItem *)item))) { Drawable *drawable; DrawablePipeItem *dpi = NULL; int depend_found = FALSE; if (item->type == PIPE_ITEM_TYPE_DRAW) { dpi = SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item); drawable = dpi->drawable; } else if (item->type == PIPE_ITEM_TYPE_UPGRADE) { drawable = ((UpgradeItem *)item)->drawable; } else { continue; } if (drawable->surface_id == surface_id) { PipeItem *tmp_item = item; item = (PipeItem *)ring_prev(ring, (RingItem *)item); red_channel_client_pipe_remove_and_release(rcc, tmp_item); if (!item) { item = (PipeItem *)ring; } 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) { break; } else { return TRUE; } } } if (!wait_if_used) { return TRUE; } if (item) { return red_channel_client_wait_pipe_item_sent(RED_CHANNEL_CLIENT(dcc), item, DISPLAY_CLIENT_TIMEOUT); } else { /* * 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 red_channel_client_wait_outgoing_item(RED_CHANNEL_CLIENT(dcc), DISPLAY_CLIENT_SHORT_TIMEOUT); } return TRUE; } static void red_clear_surface_drawables_from_pipes(DisplayChannel *display, int surface_id, int wait_if_used) { RingItem *item, *next; DisplayChannelClient *dcc; FOREACH_DCC(display, item, next, dcc) { if (!red_clear_surface_drawables_from_pipe(dcc, surface_id, wait_if_used)) { red_channel_client_disconnect(RED_CHANNEL_CLIENT(dcc)); } } } void detach_stream(DisplayChannel *display, Stream *stream, int detach_sized) { spice_assert(stream->current && stream->current->stream); spice_assert(stream->current->stream == stream); stream->current->stream = NULL; if (detach_sized) { stream->current->sized_stream = NULL; } stream->current = NULL; } static int red_display_drawable_is_in_pipe(DisplayChannelClient *dcc, Drawable *drawable) { DrawablePipeItem *dpi; RingItem *dpi_link, *dpi_next; DRAWABLE_FOREACH_DPI_SAFE(drawable, dpi_link, dpi_next, dpi) { if (dpi->dcc == dcc) { return TRUE; } } return FALSE; } /* * after dcc_detach_stream_gracefully is called for all the display channel clients, * detach_stream should be called. See comment (1). */ static void dcc_detach_stream_gracefully(DisplayChannelClient *dcc, Stream *stream, Drawable *update_area_limit) { DisplayChannel *display = DCC_TO_DC(dcc); int stream_id = get_stream_id(display, stream); StreamAgent *agent = &dcc->stream_agents[stream_id]; /* stopping the client from playing older frames at once*/ region_clear(&agent->clip); dcc_stream_agent_clip(dcc, agent); if (region_is_empty(&agent->vis_region)) { spice_debug("stream %d: vis region empty", stream_id); return; } if (stream->current && region_contains(&stream->current->tree_item.base.rgn, &agent->vis_region)) { RedChannel *channel; RedChannelClient *rcc; UpgradeItem *upgrade_item; int n_rects; /* (1) The caller should detach the drawable from the stream. This will * lead to sending the drawable losslessly, as an ordinary drawable. */ if (red_display_drawable_is_in_pipe(dcc, stream->current)) { spice_debug("stream %d: upgrade by linked drawable. sized %d, box ==>", stream_id, stream->current->sized_stream != NULL); rect_debug(&stream->current->red_drawable->bbox); goto clear_vis_region; } spice_debug("stream %d: upgrade by drawable. sized %d, box ==>", stream_id, stream->current->sized_stream != NULL); rect_debug(&stream->current->red_drawable->bbox); rcc = RED_CHANNEL_CLIENT(dcc); channel = rcc->channel; upgrade_item = spice_new(UpgradeItem, 1); upgrade_item->refs = 1; red_channel_pipe_item_init(channel, &upgrade_item->base, PIPE_ITEM_TYPE_UPGRADE); upgrade_item->drawable = stream->current; upgrade_item->drawable->refs++; n_rects = pixman_region32_n_rects(&upgrade_item->drawable->tree_item.base.rgn); upgrade_item->rects = spice_malloc_n_m(n_rects, sizeof(SpiceRect), sizeof(SpiceClipRects)); upgrade_item->rects->num_rects = n_rects; region_ret_rects(&upgrade_item->drawable->tree_item.base.rgn, upgrade_item->rects->rects, n_rects); red_channel_client_pipe_add(rcc, &upgrade_item->base); } else { SpiceRect upgrade_area; region_extents(&agent->vis_region, &upgrade_area); spice_debug("stream %d: upgrade by screenshot. has current %d. box ==>", stream_id, stream->current != NULL); rect_debug(&upgrade_area); if (update_area_limit) { red_update_area_till(DCC_TO_DC(dcc), &upgrade_area, 0, update_area_limit); } else { red_update_area(DCC_TO_DC(dcc), &upgrade_area, 0); } dcc_add_surface_area_image(dcc, 0, &upgrade_area, NULL, FALSE); } clear_vis_region: region_clear(&agent->vis_region); } static void detach_stream_gracefully(DisplayChannel *display, Stream *stream, Drawable *update_area_limit) { RingItem *item, *next; DisplayChannelClient *dcc; FOREACH_DCC(display, item, next, dcc) { dcc_detach_stream_gracefully(dcc, stream, update_area_limit); } if (stream->current) { detach_stream(display, stream, TRUE); } } /* * region : a primary surface region. Streams that intersects with the given * region will be detached. * drawable: If detaching the stream is triggered by the addition of a new drawable * that is dependent on the given region, and the drawable is already a part * of the "current tree", the drawable parameter should be set with * this drawable, otherwise, it should be NULL. Then, if detaching the stream * involves sending an upgrade image to the client, this drawable won't be rendered * (see dcc_detach_stream_gracefully). */ void detach_streams_behind(DisplayChannel *display, QRegion *region, Drawable *drawable) { Ring *ring = &display->streams; RingItem *item = ring_get_head(ring); RingItem *dcc_ring_item, *next; DisplayChannelClient *dcc; bool is_connected = red_channel_is_connected(RED_CHANNEL(display)); while (item) { Stream *stream = SPICE_CONTAINEROF(item, Stream, link); int detach = 0; item = ring_next(ring, item); FOREACH_DCC(display, dcc_ring_item, next, dcc) { StreamAgent *agent = &dcc->stream_agents[get_stream_id(display, stream)]; if (region_intersects(&agent->vis_region, region)) { dcc_detach_stream_gracefully(dcc, stream, drawable); detach = 1; spice_debug("stream %d", get_stream_id(display, stream)); } } if (detach && stream->current) { detach_stream(display, stream, TRUE); } else if (!is_connected) { if (stream->current && region_intersects(&stream->current->tree_item.base.rgn, region)) { detach_stream(display, stream, TRUE); } } } } static void display_channel_streams_timeout(DisplayChannel *display) { Ring *ring = &display->streams; RingItem *item; red_time_t now = red_get_monotonic_time(); item = ring_get_head(ring); while (item) { Stream *stream = SPICE_CONTAINEROF(item, Stream, link); item = ring_next(ring, item); if (now >= (stream->last_time + RED_STREAM_TIMEOUT)) { detach_stream_gracefully(display, stream, NULL); stream_stop(display, stream); } } } static void dcc_destroy_stream_agents(DisplayChannelClient *dcc) { int i; for (i = 0; i < NUM_STREAMS; i++) { StreamAgent *agent = &dcc->stream_agents[i]; region_destroy(&agent->vis_region); region_destroy(&agent->clip); if (agent->mjpeg_encoder) { mjpeg_encoder_destroy(agent->mjpeg_encoder); agent->mjpeg_encoder = NULL; } } } static void red_get_area(DisplayChannel *display, int surface_id, const SpiceRect *area, uint8_t *dest, int dest_stride, int update) { SpiceCanvas *canvas; RedSurface *surface; surface = &display->surfaces[surface_id]; if (update) { red_update_area(display, area, surface_id); } canvas = surface->context.canvas; canvas->ops->read_bits(canvas, dest, dest_stride, area); } static inline int red_handle_self_bitmap(RedWorker *worker, Drawable *drawable) { DisplayChannel *display = worker->display_channel; SpiceImage *image; int32_t width; int32_t height; uint8_t *dest; int dest_stride; RedSurface *surface; int bpp; int all_set; RedDrawable *red_drawable = drawable->red_drawable; if (!red_drawable->self_bitmap) { return TRUE; } surface = &display->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, ++worker->bits_unique); 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; red_get_area(display, drawable->surface_id, &red_drawable->self_bitmap_area, dest, dest_stride, TRUE); /* 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; return TRUE; } static Drawable *get_drawable(RedWorker *worker, uint8_t effect, RedDrawable *red_drawable, uint32_t group_id) { DisplayChannel *display = worker->display_channel; Drawable *drawable; int x; if (!validate_drawable_bbox(display, red_drawable)) { return NULL; } for (x = 0; x < 3; ++x) { if (red_drawable->surface_deps[x] != -1 && !validate_surface(display, red_drawable->surface_deps[x])) { return NULL; } } drawable = display_channel_drawable_try_new(display, group_id, worker->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; memcpy(drawable->surface_deps, red_drawable->surface_deps, sizeof(drawable->surface_deps)); return drawable; } static inline int red_handle_depends_on_target_surface(DisplayChannel *display, uint32_t surface_id) { RedSurface *surface; RingItem *ring_item; surface = &display->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; surface_flush(display, drawable->surface_id, &drawable->red_drawable->bbox); } return TRUE; } static inline void add_to_surface_dependency(DisplayChannel *display, int depend_on_surface_id, DependItem *depend_item, Drawable *drawable) { RedSurface *surface; if (depend_on_surface_id == -1) { depend_item->drawable = NULL; return; } surface = &display->surfaces[depend_on_surface_id]; depend_item->drawable = drawable; ring_add(&surface->depend_on_me, &depend_item->ring_item); } static inline int red_handle_surfaces_dependencies(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) { add_to_surface_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]); detach_streams_behind(display, &depend_region, NULL); } } } return TRUE; } static inline void red_inc_surfaces_drawable_dependencies(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->surfaces[surface_id]; surface->refs++; } } static inline void red_process_draw(RedWorker *worker, RedDrawable *red_drawable, uint32_t group_id) { DisplayChannel *display = worker->display_channel; int surface_id; Drawable *drawable = get_drawable(worker, red_drawable->effect, red_drawable, group_id); if (!drawable) { return; } red_drawable->mm_time = reds_get_mm_time(); surface_id = drawable->surface_id; display->surfaces[surface_id].refs++; 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); } /* 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). */ red_inc_surfaces_drawable_dependencies(worker->display_channel, drawable); if (region_is_empty(&drawable->tree_item.base.rgn)) { goto cleanup; } if (!red_handle_self_bitmap(worker, drawable)) { goto cleanup; } if (!red_handle_depends_on_target_surface(worker->display_channel, surface_id)) { goto cleanup; } if (!red_handle_surfaces_dependencies(worker->display_channel, drawable)) { goto cleanup; } if (display_channel_add_drawable(worker->display_channel, drawable)) { red_pipes_add_drawable(worker->display_channel, drawable); } cleanup: display_channel_drawable_unref(display, drawable); } static inline void red_process_surface(RedWorker *worker, RedSurfaceCmd *surface, uint32_t group_id, int loadvm) { DisplayChannel *display = worker->display_channel; uint32_t surface_id; RedSurface *red_surface; uint8_t *data; surface_id = surface->surface_id; if SPICE_UNLIKELY(surface_id >= display->n_surfaces) { goto exit; } red_surface = &display->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)); } red_create_surface(worker->display_channel, 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); set_surface_release_info(&red_surface->create, surface->release_info, group_id); break; } case QXL_SURFACE_CMD_DESTROY: if (!red_surface->refs) { spice_warning("avoiding destroying a surface twice"); break; } set_surface_release_info(&red_surface->destroy, surface->release_info, group_id); red_handle_depends_on_target_surface(display, surface_id); /* note that red_handle_depends_on_target_surface 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); red_clear_surface_drawables_from_pipes(display, surface_id, FALSE); display_channel_surface_unref(display, surface_id); break; default: spice_error("unknown surface command"); }; exit: red_put_surface_cmd(surface); free(surface); } static SpiceCanvas *image_surfaces_get(SpiceImageSurfaces *surfaces, uint32_t surface_id) { DisplayChannel *display = SPICE_CONTAINEROF(surfaces, DisplayChannel, image_surfaces); spice_return_val_if_fail(validate_surface(display, surface_id), NULL); return display->surfaces[surface_id].context.canvas; } static void image_surface_init(DisplayChannel *display) { static SpiceImageSurfacesOps image_surfaces_ops = { image_surfaces_get, }; display->image_surfaces.ops = &image_surfaces_ops; } void drawable_draw(DisplayChannel *display, Drawable *drawable) { RedSurface *surface; SpiceCanvas *canvas; SpiceClip clip = drawable->red_drawable->clip; red_flush_source_surfaces(display, drawable); surface = &display->surfaces[drawable->surface_id]; canvas = surface->context.canvas; spice_return_if_fail(canvas); image_cache_aging(&display->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->image_cache, &fill.brush, &img1); image_cache_localize_mask(&display->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->image_cache, &opaque.brush, &img1); image_cache_localize(&display->image_cache, &opaque.src_bitmap, &img2, drawable); image_cache_localize_mask(&display->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->image_cache, ©.src_bitmap, &img1, drawable); image_cache_localize_mask(&display->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->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->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->image_cache, &blend.src_bitmap, &img1, drawable); image_cache_localize_mask(&display->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->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->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->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->image_cache, &rop3.brush, &img1); image_cache_localize(&display->image_cache, &rop3.src_bitmap, &img2, drawable); image_cache_localize_mask(&display->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->image_cache, &composite.src_bitmap, &src, drawable); if (composite.mask_bitmap) image_cache_localize(&display->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->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->image_cache, &text.fore_brush, &img1); image_cache_localize_brush(&display->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 validate_area(DisplayChannel *display, const SpiceRect *area, uint32_t surface_id) { RedSurface *surface; surface = &display->surfaces[surface_id]; if (!surface->context.canvas_draws_on_surface) { SpiceCanvas *canvas = surface->context.canvas; int h; int stride = surface->context.stride; uint8_t *line_0 = surface->context.line_0; if (!(h = area->bottom - area->top)) { return; } spice_assert(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); } } /* Renders drawables for updating the requested area, but only drawables that are older than 'last' (exclusive). */ static void red_update_area_till(DisplayChannel *display, const SpiceRect *area, int surface_id, Drawable *last) { RedSurface *surface; Drawable *surface_last = NULL; Ring *ring; RingItem *ring_item; Drawable *now; QRegion rgn; spice_assert(last); spice_assert(ring_item_is_linked(&last->list_link)); surface = &display->surfaces[surface_id]; if (surface_id != last->surface_id) { // find the nearest older drawable from the appropriate surface ring = &display->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; } ring = &surface->current_list; ring_item = &surface_last->surface_list_link; region_init(&rgn); region_add(&rgn, area); // find the first older drawable that intersects with the area do { now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link); if (region_intersects(&rgn, &now->tree_item.base.rgn)) { surface_last = now; break; } } while ((ring_item = ring_next(ring, ring_item))); region_destroy(&rgn); if (!surface_last) { return; } do { Container *container; 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 red_update_area for the surfaces 'now' depends on. Notice, that it is valid to call red_update_area in this case and not red_update_area_till: It is impossible that there was newer item then 'last' in one of the surfaces that red_update_area 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); display_channel_drawable_unref(display, now); } while (now != surface_last); validate_area(display, area, surface_id); } static void red_update_area(DisplayChannel *display, const SpiceRect *area, int surface_id) { RedSurface *surface; Ring *ring; RingItem *ring_item; QRegion rgn; Drawable *last; Drawable *now; spice_debug("surface %d: area ==>", surface_id); rect_debug(area); spice_return_if_fail(surface_id >= 0 && surface_id < NUM_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->surfaces[surface_id]; last = NULL; ring = &surface->current_list; ring_item = ring; region_init(&rgn); region_add(&rgn, area); while ((ring_item = ring_next(ring, ring_item))) { now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link); if (region_intersects(&rgn, &now->tree_item.base.rgn)) { last = now; break; } } region_destroy(&rgn); if (!last) { validate_area(display, area, surface_id); return; } do { Container *container; 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(display, now); display_channel_drawable_unref(display, now); } while (now != last); validate_area(display, area, surface_id); } static int red_process_cursor(RedWorker *worker, uint32_t max_pipe_size, int *ring_is_empty) { QXLCommandExt ext_cmd; int n = 0; if (!worker->running) { *ring_is_empty = TRUE; return n; } *ring_is_empty = FALSE; while (!cursor_is_connected(worker) || red_channel_min_pipe_size(RED_CHANNEL(worker->cursor_channel)) <= max_pipe_size) { if (!worker->qxl->st->qif->get_cursor_command(worker->qxl, &ext_cmd)) { *ring_is_empty = TRUE; if (worker->cursor_poll_tries < CMD_RING_POLL_RETRIES) { worker->cursor_poll_tries++; worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT); return n; } if (worker->cursor_poll_tries > CMD_RING_POLL_RETRIES || worker->qxl->st->qif->req_cursor_notification(worker->qxl)) { worker->cursor_poll_tries++; return n; } continue; } worker->cursor_poll_tries = 0; switch (ext_cmd.cmd.type) { case QXL_CMD_CURSOR: { RedCursorCmd *cursor = spice_new0(RedCursorCmd, 1); if (red_get_cursor_cmd(&worker->mem_slots, ext_cmd.group_id, cursor, ext_cmd.cmd.data)) { free(cursor); break; } cursor_channel_process_cmd(worker->cursor_channel, cursor, ext_cmd.group_id); break; } default: spice_warning("bad command type"); } n++; } return n; } static RedDrawable *red_drawable_new(RedWorker *worker) { RedDrawable * red = spice_new0(RedDrawable, 1); red->refs = 1; worker->display_channel->red_drawable_count++; return red; } static int red_process_commands(RedWorker *worker, uint32_t max_pipe_size, int *ring_is_empty) { QXLCommandExt ext_cmd; int n = 0; uint64_t start = red_get_monotonic_time(); if (!worker->running) { *ring_is_empty = TRUE; return n; } worker->process_commands_generation++; *ring_is_empty = FALSE; while (!display_is_connected(worker) || // TODO: change to average pipe size? red_channel_min_pipe_size(RED_CHANNEL(worker->display_channel)) <= max_pipe_size) { if (!worker->qxl->st->qif->get_command(worker->qxl, &ext_cmd)) { *ring_is_empty = TRUE;; if (worker->display_poll_tries < CMD_RING_POLL_RETRIES) { worker->display_poll_tries++; worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT); return n; } if (worker->display_poll_tries > CMD_RING_POLL_RETRIES || worker->qxl->st->qif->req_cmd_notification(worker->qxl)) { worker->display_poll_tries++; return n; } continue; } if (worker->record_fd) red_record_qxl_command(worker->record_fd, &worker->mem_slots, ext_cmd, stat_now(worker->clockid)); stat_inc_counter(worker->command_counter, 1); worker->display_poll_tries = 0; switch (ext_cmd.cmd.type) { case QXL_CMD_DRAW: { RedDrawable *red_drawable = red_drawable_new(worker); // returns with 1 ref if (!red_get_drawable(&worker->mem_slots, ext_cmd.group_id, red_drawable, ext_cmd.cmd.data, ext_cmd.flags)) { red_process_draw(worker, red_drawable, ext_cmd.group_id); } // release the red_drawable red_drawable_unref(worker, red_drawable, ext_cmd.group_id); break; } case QXL_CMD_UPDATE: { RedUpdateCmd update; QXLReleaseInfoExt release_info_ext; if (red_get_update_cmd(&worker->mem_slots, ext_cmd.group_id, &update, ext_cmd.cmd.data)) { break; } if (!validate_surface(worker->display_channel, update.surface_id)) { spice_warning("Invalid surface in QXL_CMD_UPDATE"); break; } red_update_area(worker->display_channel, &update.area, update.surface_id); worker->qxl->st->qif->notify_update(worker->qxl, update.update_id); release_info_ext.group_id = ext_cmd.group_id; release_info_ext.info = update.release_info; worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext); red_put_update_cmd(&update); break; } case QXL_CMD_MESSAGE: { RedMessage message; QXLReleaseInfoExt release_info_ext; if (red_get_message(&worker->mem_slots, ext_cmd.group_id, &message, ext_cmd.cmd.data)) { break; } #ifdef DEBUG /* alert: accessing message.data is insecure */ spice_warning("MESSAGE: %s", message.data); #endif release_info_ext.group_id = ext_cmd.group_id; release_info_ext.info = message.release_info; worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext); red_put_message(&message); break; } case QXL_CMD_SURFACE: { RedSurfaceCmd *surface = spice_new0(RedSurfaceCmd, 1); if (red_get_surface_cmd(&worker->mem_slots, ext_cmd.group_id, surface, ext_cmd.cmd.data)) { free(surface); break; } red_process_surface(worker, surface, ext_cmd.group_id, FALSE); break; } default: spice_error("bad command type"); } n++; if ((worker->display_channel && red_channel_all_blocked(&worker->display_channel->common.base)) || red_get_monotonic_time() - start > 10 * 1000 * 1000) { worker->event_timeout = 0; return n; } } return n; } static void fill_base(SpiceMarshaller *base_marshaller, Drawable *drawable) { SpiceMsgDisplayBase base; base.surface_id = drawable->surface_id; base.box = drawable->red_drawable->bbox; base.clip = drawable->red_drawable->clip; spice_marshall_DisplayBase(base_marshaller, &base); } static inline void red_display_add_image_to_pixmap_cache(RedChannelClient *rcc, SpiceImage *image, SpiceImage *io_image, int is_lossy) { DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base); DisplayChannelClient *dcc = RCC_TO_DCC(rcc); if ((image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) { spice_assert(image->descriptor.width * image->descriptor.height > 0); if (!(io_image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME)) { if (dcc_pixmap_cache_unlocked_add(dcc, image->descriptor.id, image->descriptor.width * image->descriptor.height, is_lossy)) { io_image->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME; dcc->send_data.pixmap_cache_items[dcc->send_data.num_pixmap_cache_items++] = image->descriptor.id; stat_inc_counter(display_channel->add_to_cache_counter, 1); } } } if (!(io_image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) { stat_inc_counter(display_channel->non_cache_counter, 1); } } static int dcc_pixmap_cache_unlocked_hit(DisplayChannelClient *dcc, uint64_t id, int *lossy) { PixmapCache *cache = dcc->pixmap_cache; NewCacheItem *item; uint64_t serial; serial = red_channel_client_get_message_serial(RED_CHANNEL_CLIENT(dcc)); item = cache->hash_table[BITS_CACHE_HASH_KEY(id)]; while (item) { if (item->id == id) { ring_remove(&item->lru_link); ring_add(&cache->lru, &item->lru_link); spice_assert(dcc->common.id < MAX_CACHE_CLIENTS); item->sync[dcc->common.id] = serial; cache->sync[dcc->common.id] = serial; *lossy = item->lossy; break; } item = item->next; } return !!item; } static int dcc_pixmap_cache_hit(DisplayChannelClient *dcc, uint64_t id, int *lossy) { int hit; PixmapCache *cache = dcc->pixmap_cache; pthread_mutex_lock(&cache->lock); hit = dcc_pixmap_cache_unlocked_hit(dcc, id, lossy); pthread_mutex_unlock(&cache->lock); return hit; } typedef enum { FILL_BITS_TYPE_INVALID, FILL_BITS_TYPE_CACHE, FILL_BITS_TYPE_SURFACE, FILL_BITS_TYPE_COMPRESS_LOSSLESS, FILL_BITS_TYPE_COMPRESS_LOSSY, FILL_BITS_TYPE_BITMAP, } FillBitsType; /* if the number of times fill_bits can be called per one qxl_drawable increases - MAX_LZ_DRAWABLE_INSTANCES must be increased as well */ static FillBitsType fill_bits(DisplayChannelClient *dcc, SpiceMarshaller *m, SpiceImage *simage, Drawable *drawable, int can_lossy) { RedChannelClient *rcc = RED_CHANNEL_CLIENT(dcc); DisplayChannel *display = DCC_TO_DC(dcc); SpiceImage image; compress_send_data_t comp_send_data = {0}; SpiceMarshaller *bitmap_palette_out, *lzplt_palette_out; if (simage == NULL) { spice_assert(drawable->red_drawable->self_bitmap_image); simage = drawable->red_drawable->self_bitmap_image; } image.descriptor = simage->descriptor; image.descriptor.flags = 0; if (simage->descriptor.flags & SPICE_IMAGE_FLAGS_HIGH_BITS_SET) { image.descriptor.flags = SPICE_IMAGE_FLAGS_HIGH_BITS_SET; } pthread_mutex_lock(&dcc->pixmap_cache->lock); if ((simage->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) { int lossy_cache_item; if (dcc_pixmap_cache_unlocked_hit(dcc, image.descriptor.id, &lossy_cache_item)) { dcc->send_data.pixmap_cache_items[dcc->send_data.num_pixmap_cache_items++] = image.descriptor.id; if (can_lossy || !lossy_cache_item) { if (!display->enable_jpeg || lossy_cache_item) { image.descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE; } else { // making sure, in multiple monitor scenario, that lossy items that // should have been replaced with lossless data by one display channel, // will be retrieved as lossless by another display channel. image.descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS; } spice_marshall_Image(m, &image, &bitmap_palette_out, &lzplt_palette_out); spice_assert(bitmap_palette_out == NULL); spice_assert(lzplt_palette_out == NULL); stat_inc_counter(display->cache_hits_counter, 1); pthread_mutex_unlock(&dcc->pixmap_cache->lock); return FILL_BITS_TYPE_CACHE; } else { pixmap_cache_unlocked_set_lossy(dcc->pixmap_cache, simage->descriptor.id, FALSE); image.descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME; } } } switch (simage->descriptor.type) { case SPICE_IMAGE_TYPE_SURFACE: { int surface_id; RedSurface *surface; surface_id = simage->u.surface.surface_id; if (!validate_surface(display, surface_id)) { spice_warning("Invalid surface in SPICE_IMAGE_TYPE_SURFACE"); pthread_mutex_unlock(&dcc->pixmap_cache->lock); return FILL_BITS_TYPE_SURFACE; } surface = &display->surfaces[surface_id]; image.descriptor.type = SPICE_IMAGE_TYPE_SURFACE; image.descriptor.flags = 0; image.descriptor.width = surface->context.width; image.descriptor.height = surface->context.height; image.u.surface.surface_id = surface_id; spice_marshall_Image(m, &image, &bitmap_palette_out, &lzplt_palette_out); spice_assert(bitmap_palette_out == NULL); spice_assert(lzplt_palette_out == NULL); pthread_mutex_unlock(&dcc->pixmap_cache->lock); return FILL_BITS_TYPE_SURFACE; } case SPICE_IMAGE_TYPE_BITMAP: { SpiceBitmap *bitmap = &image.u.bitmap; #ifdef DUMP_BITMAP dump_bitmap(&simage->u.bitmap); #endif /* Images must be added to the cache only after they are compressed in order to prevent starvation in the client between pixmap_cache and global dictionary (in cases of multiple monitors) */ if (reds_stream_get_family(rcc->stream) == AF_UNIX || !dcc_compress_image(dcc, &image, &simage->u.bitmap, drawable, can_lossy, &comp_send_data)) { SpicePalette *palette; red_display_add_image_to_pixmap_cache(rcc, simage, &image, FALSE); *bitmap = simage->u.bitmap; bitmap->flags = bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN; palette = bitmap->palette; dcc_palette_cache_palette(dcc, palette, &bitmap->flags); spice_marshall_Image(m, &image, &bitmap_palette_out, &lzplt_palette_out); spice_assert(lzplt_palette_out == NULL); if (bitmap_palette_out && palette) { spice_marshall_Palette(bitmap_palette_out, palette); } spice_marshaller_add_ref_chunks(m, bitmap->data); pthread_mutex_unlock(&dcc->pixmap_cache->lock); return FILL_BITS_TYPE_BITMAP; } else { red_display_add_image_to_pixmap_cache(rcc, simage, &image, comp_send_data.is_lossy); spice_marshall_Image(m, &image, &bitmap_palette_out, &lzplt_palette_out); spice_assert(bitmap_palette_out == NULL); marshaller_add_compressed(m, comp_send_data.comp_buf, comp_send_data.comp_buf_size); if (lzplt_palette_out && comp_send_data.lzplt_palette) { spice_marshall_Palette(lzplt_palette_out, comp_send_data.lzplt_palette); } spice_assert(!comp_send_data.is_lossy || can_lossy); pthread_mutex_unlock(&dcc->pixmap_cache->lock); return (comp_send_data.is_lossy ? FILL_BITS_TYPE_COMPRESS_LOSSY : FILL_BITS_TYPE_COMPRESS_LOSSLESS); } break; } case SPICE_IMAGE_TYPE_QUIC: red_display_add_image_to_pixmap_cache(rcc, simage, &image, FALSE); image.u.quic = simage->u.quic; spice_marshall_Image(m, &image, &bitmap_palette_out, &lzplt_palette_out); spice_assert(bitmap_palette_out == NULL); spice_assert(lzplt_palette_out == NULL); spice_marshaller_add_ref_chunks(m, image.u.quic.data); pthread_mutex_unlock(&dcc->pixmap_cache->lock); return FILL_BITS_TYPE_COMPRESS_LOSSLESS; default: spice_error("invalid image type %u", image.descriptor.type); } pthread_mutex_unlock(&dcc->pixmap_cache->lock); return 0; } static void fill_mask(RedChannelClient *rcc, SpiceMarshaller *m, SpiceImage *mask_bitmap, Drawable *drawable) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); if (mask_bitmap && m) { if (dcc->image_compression != SPICE_IMAGE_COMPRESSION_OFF) { /* todo: pass compression argument */ SpiceImageCompression save_img_comp = dcc->image_compression; dcc->image_compression = SPICE_IMAGE_COMPRESSION_OFF; fill_bits(dcc, m, mask_bitmap, drawable, FALSE); dcc->image_compression = save_img_comp; } else { fill_bits(dcc, m, mask_bitmap, drawable, FALSE); } } } static void fill_attr(SpiceMarshaller *m, SpiceLineAttr *attr, uint32_t group_id) { int i; if (m && attr->style_nseg) { for (i = 0 ; i < attr->style_nseg; i++) { spice_marshaller_add_uint32(m, attr->style[i]); } } } static inline void red_display_reset_send_data(DisplayChannelClient *dcc) { dcc->send_data.free_list.res->count = 0; dcc->send_data.num_pixmap_cache_items = 0; memset(dcc->send_data.free_list.sync, 0, sizeof(dcc->send_data.free_list.sync)); } /* set area=NULL for testing the whole surface */ static int is_surface_area_lossy(DisplayChannelClient *dcc, uint32_t surface_id, const SpiceRect *area, SpiceRect *out_lossy_area) { RedSurface *surface; QRegion *surface_lossy_region; QRegion lossy_region; DisplayChannel *display = DCC_TO_DC(dcc); spice_return_val_if_fail(validate_surface(display, surface_id), FALSE); surface = &display->surfaces[surface_id]; surface_lossy_region = &dcc->surface_client_lossy_region[surface_id]; if (!area) { if (region_is_empty(surface_lossy_region)) { return FALSE; } else { out_lossy_area->top = 0; out_lossy_area->left = 0; out_lossy_area->bottom = surface->context.height; out_lossy_area->right = surface->context.width; return TRUE; } } region_init(&lossy_region); region_add(&lossy_region, area); region_and(&lossy_region, surface_lossy_region); if (!region_is_empty(&lossy_region)) { out_lossy_area->left = lossy_region.extents.x1; out_lossy_area->top = lossy_region.extents.y1; out_lossy_area->right = lossy_region.extents.x2; out_lossy_area->bottom = lossy_region.extents.y2; region_destroy(&lossy_region); return TRUE; } else { return FALSE; } } /* returns if the bitmap was already sent lossy to the client. If the bitmap hasn't been sent yet to the client, returns false. "area" is for surfaces. If area = NULL, all the surface is considered. out_lossy_data will hold info about the bitmap, and its lossy area in case it is lossy and part of a surface. */ static int is_bitmap_lossy(RedChannelClient *rcc, SpiceImage *image, SpiceRect *area, Drawable *drawable, BitmapData *out_data) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); if (image == NULL) { // self bitmap out_data->type = BITMAP_DATA_TYPE_BITMAP; return FALSE; } if ((image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) { int is_hit_lossy; out_data->id = image->descriptor.id; if (dcc_pixmap_cache_hit(dcc, image->descriptor.id, &is_hit_lossy)) { out_data->type = BITMAP_DATA_TYPE_CACHE; if (is_hit_lossy) { return TRUE; } else { return FALSE; } } else { out_data->type = BITMAP_DATA_TYPE_BITMAP_TO_CACHE; } } else { out_data->type = BITMAP_DATA_TYPE_BITMAP; } if (image->descriptor.type != SPICE_IMAGE_TYPE_SURFACE) { return FALSE; } out_data->type = BITMAP_DATA_TYPE_SURFACE; out_data->id = image->u.surface.surface_id; if (is_surface_area_lossy(dcc, out_data->id, area, &out_data->lossy_rect)) { return TRUE; } else { return FALSE; } } static int is_brush_lossy(RedChannelClient *rcc, SpiceBrush *brush, Drawable *drawable, BitmapData *out_data) { if (brush->type == SPICE_BRUSH_TYPE_PATTERN) { return is_bitmap_lossy(rcc, brush->u.pattern.pat, NULL, drawable, out_data); } else { out_data->type = BITMAP_DATA_TYPE_INVALID; return FALSE; } } static void surface_lossy_region_update(DisplayChannelClient *dcc, Drawable *item, int has_mask, int lossy) { QRegion *surface_lossy_region; RedDrawable *drawable; if (has_mask && !lossy) { return; } surface_lossy_region = &dcc->surface_client_lossy_region[item->surface_id]; drawable = item->red_drawable; if (drawable->clip.type == SPICE_CLIP_TYPE_RECTS ) { QRegion clip_rgn; QRegion draw_region; region_init(&clip_rgn); region_init(&draw_region); region_add(&draw_region, &drawable->bbox); region_add_clip_rects(&clip_rgn, drawable->clip.rects); region_and(&draw_region, &clip_rgn); if (lossy) { region_or(surface_lossy_region, &draw_region); } else { region_exclude(surface_lossy_region, &draw_region); } region_destroy(&clip_rgn); region_destroy(&draw_region); } else { /* no clip */ if (!lossy) { region_remove(surface_lossy_region, &drawable->bbox); } else { region_add(surface_lossy_region, &drawable->bbox); } } } static inline int drawable_intersects_with_areas(Drawable *drawable, int surface_ids[], SpiceRect *surface_areas[], int num_surfaces) { int i; for (i = 0; i < num_surfaces; i++) { if (surface_ids[i] == drawable->red_drawable->surface_id) { if (rect_intersects(surface_areas[i], &drawable->red_drawable->bbox)) { return TRUE; } } } return FALSE; } static inline int drawable_depends_on_areas(Drawable *drawable, int surface_ids[], SpiceRect surface_areas[], int num_surfaces) { int i; RedDrawable *red_drawable; int drawable_has_shadow; SpiceRect shadow_rect = {0, 0, 0, 0}; red_drawable = drawable->red_drawable; drawable_has_shadow = has_shadow(red_drawable); if (drawable_has_shadow) { int delta_x = red_drawable->u.copy_bits.src_pos.x - red_drawable->bbox.left; int delta_y = red_drawable->u.copy_bits.src_pos.y - red_drawable->bbox.top; shadow_rect.left = red_drawable->u.copy_bits.src_pos.x; shadow_rect.top = red_drawable->u.copy_bits.src_pos.y; shadow_rect.right = red_drawable->bbox.right + delta_x; shadow_rect.bottom = red_drawable->bbox.bottom + delta_y; } for (i = 0; i < num_surfaces; i++) { int x; int dep_surface_id; for (x = 0; x < 3; ++x) { dep_surface_id = drawable->surface_deps[x]; if (dep_surface_id == surface_ids[i]) { if (rect_intersects(&surface_areas[i], &red_drawable->surfaces_rects[x])) { return TRUE; } } } if (surface_ids[i] == red_drawable->surface_id) { if (drawable_has_shadow) { if (rect_intersects(&surface_areas[i], &shadow_rect)) { return TRUE; } } // not dependent on dest if (red_drawable->effect == QXL_EFFECT_OPAQUE) { continue; } if (rect_intersects(&surface_areas[i], &red_drawable->bbox)) { return TRUE; } } } return FALSE; } static int pipe_rendered_drawables_intersect_with_areas(DisplayChannelClient *dcc, int surface_ids[], SpiceRect *surface_areas[], int num_surfaces) { PipeItem *pipe_item; Ring *pipe; spice_assert(num_surfaces); pipe = &RED_CHANNEL_CLIENT(dcc)->pipe; for (pipe_item = (PipeItem *)ring_get_head(pipe); pipe_item; pipe_item = (PipeItem *)ring_next(pipe, &pipe_item->link)) { Drawable *drawable; if (pipe_item->type != PIPE_ITEM_TYPE_DRAW) continue; drawable = SPICE_CONTAINEROF(pipe_item, DrawablePipeItem, dpi_pipe_item)->drawable; if (ring_item_is_linked(&drawable->list_link)) continue; // item hasn't been rendered if (drawable_intersects_with_areas(drawable, surface_ids, surface_areas, num_surfaces)) { return TRUE; } } return FALSE; } static void red_pipe_replace_rendered_drawables_with_images(DisplayChannelClient *dcc, int first_surface_id, SpiceRect *first_area) { /* TODO: can't have those statics with multiple clients */ static int resent_surface_ids[MAX_PIPE_SIZE]; static SpiceRect resent_areas[MAX_PIPE_SIZE]; // not pointers since drawbales may be released int num_resent; PipeItem *pipe_item; Ring *pipe; resent_surface_ids[0] = first_surface_id; resent_areas[0] = *first_area; num_resent = 1; pipe = &RED_CHANNEL_CLIENT(dcc)->pipe; // going from the oldest to the newest for (pipe_item = (PipeItem *)ring_get_tail(pipe); pipe_item; pipe_item = (PipeItem *)ring_prev(pipe, &pipe_item->link)) { Drawable *drawable; DrawablePipeItem *dpi; ImageItem *image; if (pipe_item->type != PIPE_ITEM_TYPE_DRAW) continue; dpi = SPICE_CONTAINEROF(pipe_item, DrawablePipeItem, dpi_pipe_item); drawable = dpi->drawable; if (ring_item_is_linked(&drawable->list_link)) continue; // item hasn't been rendered // When a drawable command, X, depends on bitmaps that were resent, // these bitmaps state at the client might not be synchronized with X // (i.e., the bitmaps can be more futuristic w.r.t X). Thus, X shouldn't // be rendered at the client, and we replace it with an image as well. if (!drawable_depends_on_areas(drawable, resent_surface_ids, resent_areas, num_resent)) { continue; } image = dcc_add_surface_area_image(dcc, drawable->red_drawable->surface_id, &drawable->red_drawable->bbox, pipe_item, TRUE); resent_surface_ids[num_resent] = drawable->red_drawable->surface_id; resent_areas[num_resent] = drawable->red_drawable->bbox; num_resent++; spice_assert(image); red_channel_client_pipe_remove_and_release(RED_CHANNEL_CLIENT(dcc), &dpi->dpi_pipe_item); pipe_item = &image->link; } } static void red_add_lossless_drawable_dependencies(RedChannelClient *rcc, Drawable *item, int deps_surfaces_ids[], SpiceRect *deps_areas[], int num_deps) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); DisplayChannel *display = DCC_TO_DC(dcc); RedDrawable *drawable = item->red_drawable; int sync_rendered = FALSE; int i; if (!ring_item_is_linked(&item->list_link)) { /* drawable was already rendered, we may not be able to retrieve the lossless data for the lossy areas */ sync_rendered = TRUE; // checking if the drawable itself or one of the other commands // that were rendered, affected the areas that need to be resent if (!drawable_intersects_with_areas(item, deps_surfaces_ids, deps_areas, num_deps)) { if (pipe_rendered_drawables_intersect_with_areas(dcc, deps_surfaces_ids, deps_areas, num_deps)) { sync_rendered = TRUE; } } else { sync_rendered = TRUE; } } else { sync_rendered = FALSE; for (i = 0; i < num_deps; i++) { red_update_area_till(display, deps_areas[i], deps_surfaces_ids[i], item); } } if (!sync_rendered) { // pushing the pipe item back to the pipe dcc_add_drawable_to_tail(dcc, item); // the surfaces areas will be sent as DRAW_COPY commands, that // will be executed before the current drawable for (i = 0; i < num_deps; i++) { dcc_add_surface_area_image(dcc, deps_surfaces_ids[i], deps_areas[i], dcc_get_tail(dcc), FALSE); } } else { int drawable_surface_id[1]; SpiceRect *drawable_bbox[1]; drawable_surface_id[0] = drawable->surface_id; drawable_bbox[0] = &drawable->bbox; // check if the other rendered images in the pipe have updated the drawable bbox if (pipe_rendered_drawables_intersect_with_areas(dcc, drawable_surface_id, drawable_bbox, 1)) { red_pipe_replace_rendered_drawables_with_images(dcc, drawable->surface_id, &drawable->bbox); } dcc_add_surface_area_image(dcc, drawable->surface_id, &drawable->bbox, dcc_get_tail(dcc), TRUE); } } static void red_marshall_qxl_draw_fill(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); SpiceMarshaller *brush_pat_out; SpiceMarshaller *mask_bitmap_out; SpiceFill fill; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_FILL, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); fill = drawable->u.fill; spice_marshall_Fill(base_marshaller, &fill, &brush_pat_out, &mask_bitmap_out); if (brush_pat_out) { fill_bits(dcc, brush_pat_out, fill.brush.u.pattern.pat, item, FALSE); } fill_mask(rcc, mask_bitmap_out, fill.mask.bitmap, item); } static void red_lossy_marshall_qxl_draw_fill(RedChannelClient *rcc, SpiceMarshaller *m, DrawablePipeItem *dpi) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; int dest_allowed_lossy = FALSE; int dest_is_lossy = FALSE; SpiceRect dest_lossy_area; int brush_is_lossy; BitmapData brush_bitmap_data; uint16_t rop; rop = drawable->u.fill.rop_descriptor; dest_allowed_lossy = !((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) || (rop & SPICE_ROPD_OP_XOR)); brush_is_lossy = is_brush_lossy(rcc, &drawable->u.fill.brush, item, &brush_bitmap_data); if (!dest_allowed_lossy) { dest_is_lossy = is_surface_area_lossy(dcc, item->surface_id, &drawable->bbox, &dest_lossy_area); } if (!dest_is_lossy && !(brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE))) { int has_mask = !!drawable->u.fill.mask.bitmap; red_marshall_qxl_draw_fill(rcc, m, dpi); // either the brush operation is opaque, or the dest is not lossy surface_lossy_region_update(dcc, item, has_mask, FALSE); } else { int resend_surface_ids[2]; SpiceRect *resend_areas[2]; int num_resend = 0; if (dest_is_lossy) { resend_surface_ids[num_resend] = item->surface_id; resend_areas[num_resend] = &dest_lossy_area; num_resend++; } if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = brush_bitmap_data.id; resend_areas[num_resend] = &brush_bitmap_data.lossy_rect; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static FillBitsType red_marshall_qxl_draw_opaque(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi, int src_allowed_lossy) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; SpiceMarshaller *brush_pat_out; SpiceMarshaller *src_bitmap_out; SpiceMarshaller *mask_bitmap_out; SpiceOpaque opaque; FillBitsType src_send_type; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_OPAQUE, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); opaque = drawable->u.opaque; spice_marshall_Opaque(base_marshaller, &opaque, &src_bitmap_out, &brush_pat_out, &mask_bitmap_out); src_send_type = fill_bits(dcc, src_bitmap_out, opaque.src_bitmap, item, src_allowed_lossy); if (brush_pat_out) { fill_bits(dcc, brush_pat_out, opaque.brush.u.pattern.pat, item, FALSE); } fill_mask(rcc, mask_bitmap_out, opaque.mask.bitmap, item); return src_send_type; } static void red_lossy_marshall_qxl_draw_opaque(RedChannelClient *rcc, SpiceMarshaller *m, DrawablePipeItem *dpi) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; int src_allowed_lossy; int rop; int src_is_lossy = FALSE; int brush_is_lossy = FALSE; BitmapData src_bitmap_data; BitmapData brush_bitmap_data; rop = drawable->u.opaque.rop_descriptor; src_allowed_lossy = !((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) || (rop & SPICE_ROPD_OP_XOR)); brush_is_lossy = is_brush_lossy(rcc, &drawable->u.opaque.brush, item, &brush_bitmap_data); if (!src_allowed_lossy) { src_is_lossy = is_bitmap_lossy(rcc, drawable->u.opaque.src_bitmap, &drawable->u.opaque.src_area, item, &src_bitmap_data); } if (!(brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) && !(src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE))) { FillBitsType src_send_type; int has_mask = !!drawable->u.opaque.mask.bitmap; src_send_type = red_marshall_qxl_draw_opaque(rcc, m, dpi, src_allowed_lossy); if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) { src_is_lossy = TRUE; } else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) { src_is_lossy = FALSE; } surface_lossy_region_update(dcc, item, has_mask, src_is_lossy); } else { int resend_surface_ids[2]; SpiceRect *resend_areas[2]; int num_resend = 0; if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = src_bitmap_data.id; resend_areas[num_resend] = &src_bitmap_data.lossy_rect; num_resend++; } if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = brush_bitmap_data.id; resend_areas[num_resend] = &brush_bitmap_data.lossy_rect; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static FillBitsType red_marshall_qxl_draw_copy(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi, int src_allowed_lossy) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; SpiceMarshaller *src_bitmap_out; SpiceMarshaller *mask_bitmap_out; SpiceCopy copy; FillBitsType src_send_type; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COPY, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); copy = drawable->u.copy; spice_marshall_Copy(base_marshaller, ©, &src_bitmap_out, &mask_bitmap_out); src_send_type = fill_bits(dcc, src_bitmap_out, copy.src_bitmap, item, src_allowed_lossy); fill_mask(rcc, mask_bitmap_out, copy.mask.bitmap, item); return src_send_type; } static void red_lossy_marshall_qxl_draw_copy(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; int has_mask = !!drawable->u.copy.mask.bitmap; int src_is_lossy; BitmapData src_bitmap_data; FillBitsType src_send_type; src_is_lossy = is_bitmap_lossy(rcc, drawable->u.copy.src_bitmap, &drawable->u.copy.src_area, item, &src_bitmap_data); src_send_type = red_marshall_qxl_draw_copy(rcc, base_marshaller, dpi, TRUE); if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) { src_is_lossy = TRUE; } else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) { src_is_lossy = FALSE; } surface_lossy_region_update(dcc, item, has_mask, src_is_lossy); } static void red_marshall_qxl_draw_transparent(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; SpiceMarshaller *src_bitmap_out; SpiceTransparent transparent; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_TRANSPARENT, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); transparent = drawable->u.transparent; spice_marshall_Transparent(base_marshaller, &transparent, &src_bitmap_out); fill_bits(dcc, src_bitmap_out, transparent.src_bitmap, item, FALSE); } static void red_lossy_marshall_qxl_draw_transparent(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; int src_is_lossy; BitmapData src_bitmap_data; src_is_lossy = is_bitmap_lossy(rcc, drawable->u.transparent.src_bitmap, &drawable->u.transparent.src_area, item, &src_bitmap_data); if (!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) { red_marshall_qxl_draw_transparent(rcc, base_marshaller, dpi); // don't update surface lossy region since transperent areas might be lossy } else { int resend_surface_ids[1]; SpiceRect *resend_areas[1]; resend_surface_ids[0] = src_bitmap_data.id; resend_areas[0] = &src_bitmap_data.lossy_rect; red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, 1); } } static FillBitsType red_marshall_qxl_draw_alpha_blend(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi, int src_allowed_lossy) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceMarshaller *src_bitmap_out; SpiceAlphaBlend alpha_blend; FillBitsType src_send_type; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); alpha_blend = drawable->u.alpha_blend; spice_marshall_AlphaBlend(base_marshaller, &alpha_blend, &src_bitmap_out); src_send_type = fill_bits(dcc, src_bitmap_out, alpha_blend.src_bitmap, item, src_allowed_lossy); return src_send_type; } static void red_lossy_marshall_qxl_draw_alpha_blend(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int src_is_lossy; BitmapData src_bitmap_data; FillBitsType src_send_type; src_is_lossy = is_bitmap_lossy(rcc, drawable->u.alpha_blend.src_bitmap, &drawable->u.alpha_blend.src_area, item, &src_bitmap_data); src_send_type = red_marshall_qxl_draw_alpha_blend(rcc, base_marshaller, dpi, TRUE); if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) { src_is_lossy = TRUE; } else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) { src_is_lossy = FALSE; } if (src_is_lossy) { surface_lossy_region_update(dcc, item, FALSE, src_is_lossy); } // else, the area stays lossy/lossless as the destination } static void red_marshall_qxl_copy_bits(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; SpicePoint copy_bits; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_COPY_BITS, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); copy_bits = drawable->u.copy_bits.src_pos; spice_marshall_Point(base_marshaller, ©_bits); } static void red_lossy_marshall_qxl_copy_bits(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceRect src_rect; int horz_offset; int vert_offset; int src_is_lossy; SpiceRect src_lossy_area; red_marshall_qxl_copy_bits(rcc, base_marshaller, dpi); horz_offset = drawable->u.copy_bits.src_pos.x - drawable->bbox.left; vert_offset = drawable->u.copy_bits.src_pos.y - drawable->bbox.top; src_rect.left = drawable->u.copy_bits.src_pos.x; src_rect.top = drawable->u.copy_bits.src_pos.y; src_rect.right = drawable->bbox.right + horz_offset; src_rect.bottom = drawable->bbox.bottom + vert_offset; src_is_lossy = is_surface_area_lossy(dcc, item->surface_id, &src_rect, &src_lossy_area); surface_lossy_region_update(dcc, item, FALSE, src_is_lossy); } static void red_marshall_qxl_draw_blend(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceMarshaller *src_bitmap_out; SpiceMarshaller *mask_bitmap_out; SpiceBlend blend; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_BLEND, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); blend = drawable->u.blend; spice_marshall_Blend(base_marshaller, &blend, &src_bitmap_out, &mask_bitmap_out); fill_bits(dcc, src_bitmap_out, blend.src_bitmap, item, FALSE); fill_mask(rcc, mask_bitmap_out, blend.mask.bitmap, item); } static void red_lossy_marshall_qxl_draw_blend(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int src_is_lossy; BitmapData src_bitmap_data; int dest_is_lossy; SpiceRect dest_lossy_area; src_is_lossy = is_bitmap_lossy(rcc, drawable->u.blend.src_bitmap, &drawable->u.blend.src_area, item, &src_bitmap_data); dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id, &drawable->bbox, &dest_lossy_area); if (!dest_is_lossy && (!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) { red_marshall_qxl_draw_blend(rcc, base_marshaller, dpi); } else { int resend_surface_ids[2]; SpiceRect *resend_areas[2]; int num_resend = 0; if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = src_bitmap_data.id; resend_areas[num_resend] = &src_bitmap_data.lossy_rect; num_resend++; } if (dest_is_lossy) { resend_surface_ids[num_resend] = item->surface_id; resend_areas[num_resend] = &dest_lossy_area; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static void red_marshall_qxl_draw_blackness(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; SpiceMarshaller *mask_bitmap_out; SpiceBlackness blackness; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_BLACKNESS, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); blackness = drawable->u.blackness; spice_marshall_Blackness(base_marshaller, &blackness, &mask_bitmap_out); fill_mask(rcc, mask_bitmap_out, blackness.mask.bitmap, item); } static void red_lossy_marshall_qxl_draw_blackness(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int has_mask = !!drawable->u.blackness.mask.bitmap; red_marshall_qxl_draw_blackness(rcc, base_marshaller, dpi); surface_lossy_region_update(dcc, item, has_mask, FALSE); } static void red_marshall_qxl_draw_whiteness(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; SpiceMarshaller *mask_bitmap_out; SpiceWhiteness whiteness; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_WHITENESS, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); whiteness = drawable->u.whiteness; spice_marshall_Whiteness(base_marshaller, &whiteness, &mask_bitmap_out); fill_mask(rcc, mask_bitmap_out, whiteness.mask.bitmap, item); } static void red_lossy_marshall_qxl_draw_whiteness(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int has_mask = !!drawable->u.whiteness.mask.bitmap; red_marshall_qxl_draw_whiteness(rcc, base_marshaller, dpi); surface_lossy_region_update(dcc, item, has_mask, FALSE); } static void red_marshall_qxl_draw_inverse(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, Drawable *item) { RedDrawable *drawable = item->red_drawable; SpiceMarshaller *mask_bitmap_out; SpiceInvers inverse; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_INVERS, NULL); fill_base(base_marshaller, item); inverse = drawable->u.invers; spice_marshall_Invers(base_marshaller, &inverse, &mask_bitmap_out); fill_mask(rcc, mask_bitmap_out, inverse.mask.bitmap, item); } static void red_lossy_marshall_qxl_draw_inverse(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, Drawable *item) { red_marshall_qxl_draw_inverse(rcc, base_marshaller, item); } static void red_marshall_qxl_draw_rop3(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceRop3 rop3; SpiceMarshaller *src_bitmap_out; SpiceMarshaller *brush_pat_out; SpiceMarshaller *mask_bitmap_out; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_ROP3, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); rop3 = drawable->u.rop3; spice_marshall_Rop3(base_marshaller, &rop3, &src_bitmap_out, &brush_pat_out, &mask_bitmap_out); fill_bits(dcc, src_bitmap_out, rop3.src_bitmap, item, FALSE); if (brush_pat_out) { fill_bits(dcc, brush_pat_out, rop3.brush.u.pattern.pat, item, FALSE); } fill_mask(rcc, mask_bitmap_out, rop3.mask.bitmap, item); } static void red_lossy_marshall_qxl_draw_rop3(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int src_is_lossy; BitmapData src_bitmap_data; int brush_is_lossy; BitmapData brush_bitmap_data; int dest_is_lossy; SpiceRect dest_lossy_area; src_is_lossy = is_bitmap_lossy(rcc, drawable->u.rop3.src_bitmap, &drawable->u.rop3.src_area, item, &src_bitmap_data); brush_is_lossy = is_brush_lossy(rcc, &drawable->u.rop3.brush, item, &brush_bitmap_data); dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id, &drawable->bbox, &dest_lossy_area); if ((!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) && (!brush_is_lossy || (brush_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) && !dest_is_lossy) { int has_mask = !!drawable->u.rop3.mask.bitmap; red_marshall_qxl_draw_rop3(rcc, base_marshaller, dpi); surface_lossy_region_update(dcc, item, has_mask, FALSE); } else { int resend_surface_ids[3]; SpiceRect *resend_areas[3]; int num_resend = 0; if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = src_bitmap_data.id; resend_areas[num_resend] = &src_bitmap_data.lossy_rect; num_resend++; } if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = brush_bitmap_data.id; resend_areas[num_resend] = &brush_bitmap_data.lossy_rect; num_resend++; } if (dest_is_lossy) { resend_surface_ids[num_resend] = item->surface_id; resend_areas[num_resend] = &dest_lossy_area; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static void red_marshall_qxl_draw_composite(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceMarshaller *src_bitmap_out; SpiceMarshaller *mask_bitmap_out; SpiceComposite composite; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COMPOSITE, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); composite = drawable->u.composite; spice_marshall_Composite(base_marshaller, &composite, &src_bitmap_out, &mask_bitmap_out); fill_bits(dcc, src_bitmap_out, composite.src_bitmap, item, FALSE); if (mask_bitmap_out) { fill_bits(dcc, mask_bitmap_out, composite.mask_bitmap, item, FALSE); } } static void red_lossy_marshall_qxl_draw_composite(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int src_is_lossy; BitmapData src_bitmap_data; int mask_is_lossy; BitmapData mask_bitmap_data; int dest_is_lossy; SpiceRect dest_lossy_area; src_is_lossy = is_bitmap_lossy(rcc, drawable->u.composite.src_bitmap, NULL, item, &src_bitmap_data); mask_is_lossy = drawable->u.composite.mask_bitmap && is_bitmap_lossy(rcc, drawable->u.composite.mask_bitmap, NULL, item, &mask_bitmap_data); dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id, &drawable->bbox, &dest_lossy_area); if ((!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) && (!mask_is_lossy || (mask_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) && !dest_is_lossy) { red_marshall_qxl_draw_composite(rcc, base_marshaller, dpi); surface_lossy_region_update(dcc, item, FALSE, FALSE); } else { int resend_surface_ids[3]; SpiceRect *resend_areas[3]; int num_resend = 0; if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = src_bitmap_data.id; resend_areas[num_resend] = &src_bitmap_data.lossy_rect; num_resend++; } if (mask_is_lossy && (mask_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = mask_bitmap_data.id; resend_areas[num_resend] = &mask_bitmap_data.lossy_rect; num_resend++; } if (dest_is_lossy) { resend_surface_ids[num_resend] = item->surface_id; resend_areas[num_resend] = &dest_lossy_area; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static void red_marshall_qxl_draw_stroke(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceStroke stroke; SpiceMarshaller *brush_pat_out; SpiceMarshaller *style_out; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_STROKE, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); stroke = drawable->u.stroke; spice_marshall_Stroke(base_marshaller, &stroke, &style_out, &brush_pat_out); fill_attr(style_out, &stroke.attr, item->group_id); if (brush_pat_out) { fill_bits(dcc, brush_pat_out, stroke.brush.u.pattern.pat, item, FALSE); } } static void red_lossy_marshall_qxl_draw_stroke(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int brush_is_lossy; BitmapData brush_bitmap_data; int dest_is_lossy = FALSE; SpiceRect dest_lossy_area; int rop; brush_is_lossy = is_brush_lossy(rcc, &drawable->u.stroke.brush, item, &brush_bitmap_data); // back_mode is not used at the client. Ignoring. rop = drawable->u.stroke.fore_mode; // assuming that if the brush type is solid, the destination can // be lossy, no matter what the rop is. if (drawable->u.stroke.brush.type != SPICE_BRUSH_TYPE_SOLID && ((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) || (rop & SPICE_ROPD_OP_XOR))) { dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id, &drawable->bbox, &dest_lossy_area); } if (!dest_is_lossy && (!brush_is_lossy || (brush_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) { red_marshall_qxl_draw_stroke(rcc, base_marshaller, dpi); } else { int resend_surface_ids[2]; SpiceRect *resend_areas[2]; int num_resend = 0; if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = brush_bitmap_data.id; resend_areas[num_resend] = &brush_bitmap_data.lossy_rect; num_resend++; } // TODO: use the path in order to resend smaller areas if (dest_is_lossy) { resend_surface_ids[num_resend] = drawable->surface_id; resend_areas[num_resend] = &dest_lossy_area; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static void red_marshall_qxl_draw_text(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; SpiceText text; SpiceMarshaller *brush_pat_out; SpiceMarshaller *back_brush_pat_out; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_TEXT, &dpi->dpi_pipe_item); fill_base(base_marshaller, item); text = drawable->u.text; spice_marshall_Text(base_marshaller, &text, &brush_pat_out, &back_brush_pat_out); if (brush_pat_out) { fill_bits(dcc, brush_pat_out, text.fore_brush.u.pattern.pat, item, FALSE); } if (back_brush_pat_out) { fill_bits(dcc, back_brush_pat_out, text.back_brush.u.pattern.pat, item, FALSE); } } static void red_lossy_marshall_qxl_draw_text(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *drawable = item->red_drawable; int fg_is_lossy; BitmapData fg_bitmap_data; int bg_is_lossy; BitmapData bg_bitmap_data; int dest_is_lossy = FALSE; SpiceRect dest_lossy_area; int rop = 0; fg_is_lossy = is_brush_lossy(rcc, &drawable->u.text.fore_brush, item, &fg_bitmap_data); bg_is_lossy = is_brush_lossy(rcc, &drawable->u.text.back_brush, item, &bg_bitmap_data); // assuming that if the brush type is solid, the destination can // be lossy, no matter what the rop is. if (drawable->u.text.fore_brush.type != SPICE_BRUSH_TYPE_SOLID) { rop = drawable->u.text.fore_mode; } if (drawable->u.text.back_brush.type != SPICE_BRUSH_TYPE_SOLID) { rop |= drawable->u.text.back_mode; } if ((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) || (rop & SPICE_ROPD_OP_XOR)) { dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id, &drawable->bbox, &dest_lossy_area); } if (!dest_is_lossy && (!fg_is_lossy || (fg_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) && (!bg_is_lossy || (bg_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) { red_marshall_qxl_draw_text(rcc, base_marshaller, dpi); } else { int resend_surface_ids[3]; SpiceRect *resend_areas[3]; int num_resend = 0; if (fg_is_lossy && (fg_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = fg_bitmap_data.id; resend_areas[num_resend] = &fg_bitmap_data.lossy_rect; num_resend++; } if (bg_is_lossy && (bg_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) { resend_surface_ids[num_resend] = bg_bitmap_data.id; resend_areas[num_resend] = &bg_bitmap_data.lossy_rect; num_resend++; } if (dest_is_lossy) { resend_surface_ids[num_resend] = drawable->surface_id; resend_areas[num_resend] = &dest_lossy_area; num_resend++; } red_add_lossless_drawable_dependencies(rcc, item, resend_surface_ids, resend_areas, num_resend); } } static void red_lossy_marshall_qxl_drawable(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; switch (item->red_drawable->type) { case QXL_DRAW_FILL: red_lossy_marshall_qxl_draw_fill(rcc, base_marshaller, dpi); break; case QXL_DRAW_OPAQUE: red_lossy_marshall_qxl_draw_opaque(rcc, base_marshaller, dpi); break; case QXL_DRAW_COPY: red_lossy_marshall_qxl_draw_copy(rcc, base_marshaller, dpi); break; case QXL_DRAW_TRANSPARENT: red_lossy_marshall_qxl_draw_transparent(rcc, base_marshaller, dpi); break; case QXL_DRAW_ALPHA_BLEND: red_lossy_marshall_qxl_draw_alpha_blend(rcc, base_marshaller, dpi); break; case QXL_COPY_BITS: red_lossy_marshall_qxl_copy_bits(rcc, base_marshaller, dpi); break; case QXL_DRAW_BLEND: red_lossy_marshall_qxl_draw_blend(rcc, base_marshaller, dpi); break; case QXL_DRAW_BLACKNESS: red_lossy_marshall_qxl_draw_blackness(rcc, base_marshaller, dpi); break; case QXL_DRAW_WHITENESS: red_lossy_marshall_qxl_draw_whiteness(rcc, base_marshaller, dpi); break; case QXL_DRAW_INVERS: red_lossy_marshall_qxl_draw_inverse(rcc, base_marshaller, item); break; case QXL_DRAW_ROP3: red_lossy_marshall_qxl_draw_rop3(rcc, base_marshaller, dpi); break; case QXL_DRAW_COMPOSITE: red_lossy_marshall_qxl_draw_composite(rcc, base_marshaller, dpi); break; case QXL_DRAW_STROKE: red_lossy_marshall_qxl_draw_stroke(rcc, base_marshaller, dpi); break; case QXL_DRAW_TEXT: red_lossy_marshall_qxl_draw_text(rcc, base_marshaller, dpi); break; default: spice_error("invalid type"); } } static inline void red_marshall_qxl_drawable(RedChannelClient *rcc, SpiceMarshaller *m, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; RedDrawable *drawable = item->red_drawable; switch (drawable->type) { case QXL_DRAW_FILL: red_marshall_qxl_draw_fill(rcc, m, dpi); break; case QXL_DRAW_OPAQUE: red_marshall_qxl_draw_opaque(rcc, m, dpi, FALSE); break; case QXL_DRAW_COPY: red_marshall_qxl_draw_copy(rcc, m, dpi, FALSE); break; case QXL_DRAW_TRANSPARENT: red_marshall_qxl_draw_transparent(rcc, m, dpi); break; case QXL_DRAW_ALPHA_BLEND: red_marshall_qxl_draw_alpha_blend(rcc, m, dpi, FALSE); break; case QXL_COPY_BITS: red_marshall_qxl_copy_bits(rcc, m, dpi); break; case QXL_DRAW_BLEND: red_marshall_qxl_draw_blend(rcc, m, dpi); break; case QXL_DRAW_BLACKNESS: red_marshall_qxl_draw_blackness(rcc, m, dpi); break; case QXL_DRAW_WHITENESS: red_marshall_qxl_draw_whiteness(rcc, m, dpi); break; case QXL_DRAW_INVERS: red_marshall_qxl_draw_inverse(rcc, m, item); break; case QXL_DRAW_ROP3: red_marshall_qxl_draw_rop3(rcc, m, dpi); break; case QXL_DRAW_STROKE: red_marshall_qxl_draw_stroke(rcc, m, dpi); break; case QXL_DRAW_COMPOSITE: red_marshall_qxl_draw_composite(rcc, m, dpi); break; case QXL_DRAW_TEXT: red_marshall_qxl_draw_text(rcc, m, dpi); break; default: spice_error("invalid type"); } } static inline void display_marshal_sub_msg_inval_list(SpiceMarshaller *m, FreeList *free_list) { /* type + size + submessage */ spice_marshaller_add_uint16(m, SPICE_MSG_DISPLAY_INVAL_LIST); spice_marshaller_add_uint32(m, sizeof(*free_list->res) + free_list->res->count * sizeof(free_list->res->resources[0])); spice_marshall_msg_display_inval_list(m, free_list->res); } static inline void display_marshal_sub_msg_inval_list_wait(SpiceMarshaller *m, FreeList *free_list) { /* type + size + submessage */ spice_marshaller_add_uint16(m, SPICE_MSG_WAIT_FOR_CHANNELS); spice_marshaller_add_uint32(m, sizeof(free_list->wait.header) + free_list->wait.header.wait_count * sizeof(free_list->wait.buf[0])); spice_marshall_msg_wait_for_channels(m, &free_list->wait.header); } /* use legacy SpiceDataHeader (with sub_list) */ static inline void display_channel_send_free_list_legacy(RedChannelClient *rcc) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); FreeList *free_list = &dcc->send_data.free_list; SpiceMarshaller *marshaller; int sub_list_len = 1; SpiceMarshaller *wait_m = NULL; SpiceMarshaller *inval_m; SpiceMarshaller *sub_list_m; marshaller = red_channel_client_get_marshaller(rcc); inval_m = spice_marshaller_get_submarshaller(marshaller); display_marshal_sub_msg_inval_list(inval_m, free_list); if (free_list->wait.header.wait_count) { wait_m = spice_marshaller_get_submarshaller(marshaller); display_marshal_sub_msg_inval_list_wait(wait_m, free_list); sub_list_len++; } sub_list_m = spice_marshaller_get_submarshaller(marshaller); spice_marshaller_add_uint16(sub_list_m, sub_list_len); if (wait_m) { spice_marshaller_add_uint32(sub_list_m, spice_marshaller_get_offset(wait_m)); } spice_marshaller_add_uint32(sub_list_m, spice_marshaller_get_offset(inval_m)); red_channel_client_set_header_sub_list(rcc, spice_marshaller_get_offset(sub_list_m)); } /* use mini header and SPICE_MSG_LIST */ static inline void display_channel_send_free_list(RedChannelClient *rcc) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); FreeList *free_list = &dcc->send_data.free_list; int sub_list_len = 1; SpiceMarshaller *urgent_marshaller; SpiceMarshaller *wait_m = NULL; SpiceMarshaller *inval_m; uint32_t sub_arr_offset; uint32_t wait_offset = 0; uint32_t inval_offset = 0; int i; urgent_marshaller = red_channel_client_switch_to_urgent_sender(rcc); for (i = 0; i < dcc->send_data.num_pixmap_cache_items; i++) { int dummy; /* When using the urgent marshaller, the serial number of the message that is * going to be sent right after the SPICE_MSG_LIST, is increased by one. * But all this message pixmaps cache references used its old serial. * we use pixmap_cache_items to collect these pixmaps, and we update their serial * by calling pixmap_cache_hit. */ dcc_pixmap_cache_hit(dcc, dcc->send_data.pixmap_cache_items[i], &dummy); } if (free_list->wait.header.wait_count) { red_channel_client_init_send_data(rcc, SPICE_MSG_LIST, NULL); } else { /* only one message, no need for a list */ red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_INVAL_LIST, NULL); spice_marshall_msg_display_inval_list(urgent_marshaller, free_list->res); return; } inval_m = spice_marshaller_get_submarshaller(urgent_marshaller); display_marshal_sub_msg_inval_list(inval_m, free_list); if (free_list->wait.header.wait_count) { wait_m = spice_marshaller_get_submarshaller(urgent_marshaller); display_marshal_sub_msg_inval_list_wait(wait_m, free_list); sub_list_len++; } sub_arr_offset = sub_list_len * sizeof(uint32_t); spice_marshaller_add_uint16(urgent_marshaller, sub_list_len); inval_offset = spice_marshaller_get_offset(inval_m); // calc the offset before // adding the sub list // offsets array to the marshaller /* adding the array of offsets */ if (wait_m) { wait_offset = spice_marshaller_get_offset(wait_m); spice_marshaller_add_uint32(urgent_marshaller, wait_offset + sub_arr_offset); } spice_marshaller_add_uint32(urgent_marshaller, inval_offset + sub_arr_offset); } static inline void display_begin_send_message(RedChannelClient *rcc) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); FreeList *free_list = &dcc->send_data.free_list; if (free_list->res->count) { int sync_count = 0; int i; for (i = 0; i < MAX_CACHE_CLIENTS; i++) { if (i != dcc->common.id && free_list->sync[i] != 0) { free_list->wait.header.wait_list[sync_count].channel_type = SPICE_CHANNEL_DISPLAY; free_list->wait.header.wait_list[sync_count].channel_id = i; free_list->wait.header.wait_list[sync_count++].message_serial = free_list->sync[i]; } } free_list->wait.header.wait_count = sync_count; if (rcc->is_mini_header) { display_channel_send_free_list(rcc); } else { display_channel_send_free_list_legacy(rcc); } } red_channel_client_begin_send_message(rcc); } static inline int red_marshall_stream_data(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, Drawable *drawable) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); DisplayChannel *display = DCC_TO_DC(dcc); Stream *stream = drawable->stream; SpiceImage *image; uint32_t frame_mm_time; int n; int width, height; int ret; if (!stream) { spice_assert(drawable->sized_stream); stream = drawable->sized_stream; } spice_assert(drawable->red_drawable->type == QXL_DRAW_COPY); image = drawable->red_drawable->u.copy.src_bitmap; if (image->descriptor.type != SPICE_IMAGE_TYPE_BITMAP) { return FALSE; } if (drawable->sized_stream) { if (red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_SIZED_STREAM)) { SpiceRect *src_rect = &drawable->red_drawable->u.copy.src_area; width = src_rect->right - src_rect->left; height = src_rect->bottom - src_rect->top; } else { return FALSE; } } else { width = stream->width; height = stream->height; } StreamAgent *agent = &dcc->stream_agents[get_stream_id(display, stream)]; uint64_t time_now = red_get_monotonic_time(); size_t outbuf_size; if (!dcc->use_mjpeg_encoder_rate_control) { if (time_now - agent->last_send_time < (1000 * 1000 * 1000) / agent->fps) { agent->frames--; #ifdef STREAM_STATS agent->stats.num_drops_fps++; #endif return TRUE; } } /* workaround for vga streams */ frame_mm_time = drawable->red_drawable->mm_time ? drawable->red_drawable->mm_time : reds_get_mm_time(); outbuf_size = dcc->send_data.stream_outbuf_size; ret = mjpeg_encoder_encode_frame(agent->mjpeg_encoder, &image->u.bitmap, width, height, &drawable->red_drawable->u.copy.src_area, stream->top_down, frame_mm_time, &dcc->send_data.stream_outbuf, &outbuf_size, &n); switch (ret) { case MJPEG_ENCODER_FRAME_DROP: spice_assert(dcc->use_mjpeg_encoder_rate_control); #ifdef STREAM_STATS agent->stats.num_drops_fps++; #endif return TRUE; case MJPEG_ENCODER_FRAME_UNSUPPORTED: return FALSE; case MJPEG_ENCODER_FRAME_ENCODE_DONE: break; default: spice_error("bad return value (%d) from mjpeg_encoder_encode_frame", ret); return FALSE; } dcc->send_data.stream_outbuf_size = outbuf_size; if (!drawable->sized_stream) { SpiceMsgDisplayStreamData stream_data; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_DATA, NULL); stream_data.base.id = get_stream_id(display, stream); stream_data.base.multi_media_time = frame_mm_time; stream_data.data_size = n; spice_marshall_msg_display_stream_data(base_marshaller, &stream_data); } else { SpiceMsgDisplayStreamDataSized stream_data; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_DATA_SIZED, NULL); stream_data.base.id = get_stream_id(display, stream); stream_data.base.multi_media_time = frame_mm_time; stream_data.data_size = n; stream_data.width = width; stream_data.height = height; stream_data.dest = drawable->red_drawable->bbox; spice_debug("stream %d: sized frame: dest ==> ", stream_data.base.id); rect_debug(&stream_data.dest); spice_marshall_msg_display_stream_data_sized(base_marshaller, &stream_data); } spice_marshaller_add_ref(base_marshaller, dcc->send_data.stream_outbuf, n); agent->last_send_time = time_now; #ifdef STREAM_STATS agent->stats.num_frames_sent++; agent->stats.size_sent += n; agent->stats.end = frame_mm_time; #endif return TRUE; } static inline void marshall_qxl_drawable(RedChannelClient *rcc, SpiceMarshaller *m, DrawablePipeItem *dpi) { Drawable *item = dpi->drawable; DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base); spice_assert(display_channel && rcc); /* allow sized frames to be streamed, even if they where replaced by another frame, since * newer frames might not cover sized frames completely if they are bigger */ if ((item->stream || item->sized_stream) && red_marshall_stream_data(rcc, m, item)) { return; } if (!display_channel->enable_jpeg) red_marshall_qxl_drawable(rcc, m, dpi); else red_lossy_marshall_qxl_drawable(rcc, m, dpi); } static inline void red_marshall_inval_palette(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, CacheItem *cache_item) { SpiceMsgDisplayInvalOne inval_one; red_channel_client_init_send_data(rcc, cache_item->inval_type, NULL); inval_one.id = *(uint64_t *)&cache_item->id; spice_marshall_msg_display_inval_palette(base_marshaller, &inval_one); } static void display_channel_marshall_migrate_data_surfaces(DisplayChannelClient *dcc, SpiceMarshaller *m, int lossy) { SpiceMarshaller *m2 = spice_marshaller_get_ptr_submarshaller(m, 0); uint32_t *num_surfaces_created; uint32_t i; num_surfaces_created = (uint32_t *)spice_marshaller_reserve_space(m2, sizeof(uint32_t)); *num_surfaces_created = 0; for (i = 0; i < NUM_SURFACES; i++) { SpiceRect lossy_rect; if (!dcc->surface_client_created[i]) { continue; } spice_marshaller_add_uint32(m2, i); (*num_surfaces_created)++; if (!lossy) { continue; } region_extents(&dcc->surface_client_lossy_region[i], &lossy_rect); spice_marshaller_add_int32(m2, lossy_rect.left); spice_marshaller_add_int32(m2, lossy_rect.top); spice_marshaller_add_int32(m2, lossy_rect.right); spice_marshaller_add_int32(m2, lossy_rect.bottom); } } static void display_channel_marshall_migrate_data(RedChannelClient *rcc, SpiceMarshaller *base_marshaller) { DisplayChannel *display_channel; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); SpiceMigrateDataDisplay display_data = {0,}; display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base); red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, NULL); spice_marshaller_add_uint32(base_marshaller, SPICE_MIGRATE_DATA_DISPLAY_MAGIC); spice_marshaller_add_uint32(base_marshaller, SPICE_MIGRATE_DATA_DISPLAY_VERSION); spice_assert(dcc->pixmap_cache); spice_assert(MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS == 4 && MIGRATE_DATA_DISPLAY_MAX_CACHE_CLIENTS == MAX_CACHE_CLIENTS); display_data.message_serial = red_channel_client_get_message_serial(rcc); display_data.low_bandwidth_setting = dcc->common.is_low_bandwidth; display_data.pixmap_cache_freezer = pixmap_cache_freeze(dcc->pixmap_cache); display_data.pixmap_cache_id = dcc->pixmap_cache->id; display_data.pixmap_cache_size = dcc->pixmap_cache->size; memcpy(display_data.pixmap_cache_clients, dcc->pixmap_cache->sync, sizeof(display_data.pixmap_cache_clients)); spice_assert(dcc->glz_dict); dcc_freeze_glz(dcc); display_data.glz_dict_id = dcc->glz_dict->id; glz_enc_dictionary_get_restore_data(dcc->glz_dict->dict, &display_data.glz_dict_data, &dcc->glz_data.usr); /* all data besided the surfaces ref */ spice_marshaller_add(base_marshaller, (uint8_t *)&display_data, sizeof(display_data) - sizeof(uint32_t)); display_channel_marshall_migrate_data_surfaces(dcc, base_marshaller, display_channel->enable_jpeg); } static void display_channel_marshall_pixmap_sync(RedChannelClient *rcc, SpiceMarshaller *base_marshaller) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); SpiceMsgWaitForChannels wait; PixmapCache *pixmap_cache; red_channel_client_init_send_data(rcc, SPICE_MSG_WAIT_FOR_CHANNELS, NULL); pixmap_cache = dcc->pixmap_cache; pthread_mutex_lock(&pixmap_cache->lock); wait.wait_count = 1; wait.wait_list[0].channel_type = SPICE_CHANNEL_DISPLAY; wait.wait_list[0].channel_id = pixmap_cache->generation_initiator.client; wait.wait_list[0].message_serial = pixmap_cache->generation_initiator.message; dcc->pixmap_cache_generation = pixmap_cache->generation; dcc->pending_pixmaps_sync = FALSE; pthread_mutex_unlock(&pixmap_cache->lock); spice_marshall_msg_wait_for_channels(base_marshaller, &wait); } static void dcc_pixmap_cache_reset(DisplayChannelClient *dcc, SpiceMsgWaitForChannels* sync_data) { PixmapCache *cache = dcc->pixmap_cache; uint8_t wait_count; uint64_t serial; uint32_t i; serial = red_channel_client_get_message_serial(RED_CHANNEL_CLIENT(dcc)); pthread_mutex_lock(&cache->lock); pixmap_cache_clear(cache); dcc->pixmap_cache_generation = ++cache->generation; cache->generation_initiator.client = dcc->common.id; cache->generation_initiator.message = serial; cache->sync[dcc->common.id] = serial; wait_count = 0; for (i = 0; i < MAX_CACHE_CLIENTS; i++) { if (cache->sync[i] && i != dcc->common.id) { sync_data->wait_list[wait_count].channel_type = SPICE_CHANNEL_DISPLAY; sync_data->wait_list[wait_count].channel_id = i; sync_data->wait_list[wait_count++].message_serial = cache->sync[i]; } } sync_data->wait_count = wait_count; pthread_mutex_unlock(&cache->lock); } static void display_channel_marshall_reset_cache(RedChannelClient *rcc, SpiceMarshaller *base_marshaller) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); SpiceMsgWaitForChannels wait; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS, NULL); dcc_pixmap_cache_reset(dcc, &wait); spice_marshall_msg_display_inval_all_pixmaps(base_marshaller, &wait); } static void red_marshall_image(RedChannelClient *rcc, SpiceMarshaller *m, ImageItem *item) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); DisplayChannel *display_channel = DCC_TO_DC(dcc); SpiceImage red_image; RedWorker *worker; SpiceBitmap bitmap; SpiceChunks *chunks; QRegion *surface_lossy_region; int comp_succeeded = FALSE; int lossy_comp = FALSE; int quic_comp = FALSE; SpiceImageCompression comp_mode; SpiceMsgDisplayDrawCopy copy; SpiceMarshaller *src_bitmap_out, *mask_bitmap_out; SpiceMarshaller *bitmap_palette_out, *lzplt_palette_out; spice_assert(rcc && display_channel && item); worker = display_channel->common.worker; QXL_SET_IMAGE_ID(&red_image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique); red_image.descriptor.type = SPICE_IMAGE_TYPE_BITMAP; red_image.descriptor.flags = item->image_flags; red_image.descriptor.width = item->width; red_image.descriptor.height = item->height; bitmap.format = item->image_format; bitmap.flags = 0; if (item->top_down) { bitmap.flags |= SPICE_BITMAP_FLAGS_TOP_DOWN; } bitmap.x = item->width; bitmap.y = item->height; bitmap.stride = item->stride; bitmap.palette = 0; bitmap.palette_id = 0; chunks = spice_chunks_new_linear(item->data, bitmap.stride * bitmap.y); bitmap.data = chunks; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COPY, &item->link); copy.base.surface_id = item->surface_id; copy.base.box.left = item->pos.x; copy.base.box.top = item->pos.y; copy.base.box.right = item->pos.x + bitmap.x; copy.base.box.bottom = item->pos.y + bitmap.y; copy.base.clip.type = SPICE_CLIP_TYPE_NONE; copy.data.rop_descriptor = SPICE_ROPD_OP_PUT; copy.data.src_area.left = 0; copy.data.src_area.top = 0; copy.data.src_area.right = bitmap.x; copy.data.src_area.bottom = bitmap.y; copy.data.scale_mode = 0; copy.data.src_bitmap = 0; copy.data.mask.flags = 0; copy.data.mask.flags = 0; copy.data.mask.pos.x = 0; copy.data.mask.pos.y = 0; copy.data.mask.bitmap = 0; spice_marshall_msg_display_draw_copy(m, ©, &src_bitmap_out, &mask_bitmap_out); compress_send_data_t comp_send_data = {0}; comp_mode = dcc->image_compression; if (((comp_mode == SPICE_IMAGE_COMPRESSION_AUTO_LZ) || (comp_mode == SPICE_IMAGE_COMPRESSION_AUTO_GLZ)) && !bitmap_has_extra_stride(&bitmap)) { if (bitmap_fmt_has_graduality(item->image_format)) { BitmapGradualType grad_level; grad_level = bitmap_get_graduality_level(&bitmap); if (grad_level == BITMAP_GRADUAL_HIGH) { // if we use lz for alpha, the stride can't be extra lossy_comp = display_channel->enable_jpeg && item->can_lossy; quic_comp = TRUE; } } } else if (comp_mode == SPICE_IMAGE_COMPRESSION_QUIC) { quic_comp = TRUE; } if (lossy_comp) { comp_succeeded = dcc_compress_image_jpeg(dcc, &red_image, &bitmap, &comp_send_data, worker->mem_slots.internal_groupslot_id); } else if (quic_comp) { comp_succeeded = dcc_compress_image_quic(dcc, &red_image, &bitmap, &comp_send_data, worker->mem_slots.internal_groupslot_id); #ifdef USE_LZ4 } else if (comp_mode == SPICE_IMAGE_COMPRESSION_LZ4 && bitmap_fmt_is_rgb(bitmap.format) && red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_LZ4_COMPRESSION)) { comp_succeeded = dcc_compress_image_lz4(dcc, &red_image, &bitmap, &comp_send_data, worker->mem_slots.internal_groupslot_id); #endif } else if (comp_mode != SPICE_IMAGE_COMPRESSION_OFF) { comp_succeeded = dcc_compress_image_lz(dcc, &red_image, &bitmap, &comp_send_data, worker->mem_slots.internal_groupslot_id); } surface_lossy_region = &dcc->surface_client_lossy_region[item->surface_id]; if (comp_succeeded) { spice_marshall_Image(src_bitmap_out, &red_image, &bitmap_palette_out, &lzplt_palette_out); marshaller_add_compressed(src_bitmap_out, comp_send_data.comp_buf, comp_send_data.comp_buf_size); if (lzplt_palette_out && comp_send_data.lzplt_palette) { spice_marshall_Palette(lzplt_palette_out, comp_send_data.lzplt_palette); } if (lossy_comp) { region_add(surface_lossy_region, ©.base.box); } else { region_remove(surface_lossy_region, ©.base.box); } } else { red_image.descriptor.type = SPICE_IMAGE_TYPE_BITMAP; red_image.u.bitmap = bitmap; spice_marshall_Image(src_bitmap_out, &red_image, &bitmap_palette_out, &lzplt_palette_out); spice_marshaller_add_ref(src_bitmap_out, item->data, bitmap.y * bitmap.stride); region_remove(surface_lossy_region, ©.base.box); } spice_chunks_destroy(chunks); } static void red_display_marshall_upgrade(RedChannelClient *rcc, SpiceMarshaller *m, UpgradeItem *item) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); RedDrawable *red_drawable; SpiceMsgDisplayDrawCopy copy; SpiceMarshaller *src_bitmap_out, *mask_bitmap_out; spice_assert(rcc && rcc->channel && item && item->drawable); red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COPY, &item->base); red_drawable = item->drawable->red_drawable; spice_assert(red_drawable->type == QXL_DRAW_COPY); spice_assert(red_drawable->u.copy.rop_descriptor == SPICE_ROPD_OP_PUT); spice_assert(red_drawable->u.copy.mask.bitmap == 0); copy.base.surface_id = 0; copy.base.box = red_drawable->bbox; copy.base.clip.type = SPICE_CLIP_TYPE_RECTS; copy.base.clip.rects = item->rects; copy.data = red_drawable->u.copy; spice_marshall_msg_display_draw_copy(m, ©, &src_bitmap_out, &mask_bitmap_out); fill_bits(dcc, src_bitmap_out, copy.data.src_bitmap, item->drawable, FALSE); } static void red_display_marshall_stream_start(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, StreamAgent *agent) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); Stream *stream = agent->stream; agent->last_send_time = 0; spice_assert(stream); red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_CREATE, NULL); SpiceMsgDisplayStreamCreate stream_create; SpiceClipRects clip_rects; stream_create.surface_id = 0; stream_create.id = get_stream_id(DCC_TO_DC(dcc), stream); stream_create.flags = stream->top_down ? SPICE_STREAM_FLAGS_TOP_DOWN : 0; stream_create.codec_type = SPICE_VIDEO_CODEC_TYPE_MJPEG; stream_create.src_width = stream->width; stream_create.src_height = stream->height; stream_create.stream_width = stream_create.src_width; stream_create.stream_height = stream_create.src_height; stream_create.dest = stream->dest_area; if (stream->current) { RedDrawable *red_drawable = stream->current->red_drawable; stream_create.clip = red_drawable->clip; } else { stream_create.clip.type = SPICE_CLIP_TYPE_RECTS; clip_rects.num_rects = 0; stream_create.clip.rects = &clip_rects; } stream_create.stamp = 0; spice_marshall_msg_display_stream_create(base_marshaller, &stream_create); } static void red_display_marshall_stream_clip(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, StreamClipItem *item) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); StreamAgent *agent = item->stream_agent; spice_assert(agent->stream); red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_CLIP, &item->base); SpiceMsgDisplayStreamClip stream_clip; stream_clip.id = get_stream_id(DCC_TO_DC(dcc), agent->stream); stream_clip.clip.type = item->clip_type; stream_clip.clip.rects = item->rects; spice_marshall_msg_display_stream_clip(base_marshaller, &stream_clip); } static void red_display_marshall_stream_end(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, StreamAgent* agent) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); SpiceMsgDisplayStreamDestroy destroy; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_DESTROY, NULL); destroy.id = get_stream_id(DCC_TO_DC(dcc), agent->stream); stream_agent_stop(agent); spice_marshall_msg_display_stream_destroy(base_marshaller, &destroy); } static void red_marshall_surface_create(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, SpiceMsgSurfaceCreate *surface_create) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); region_init(&dcc->surface_client_lossy_region[surface_create->surface_id]); red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SURFACE_CREATE, NULL); spice_marshall_msg_display_surface_create(base_marshaller, surface_create); } static void red_marshall_surface_destroy(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, uint32_t surface_id) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); SpiceMsgSurfaceDestroy surface_destroy; region_destroy(&dcc->surface_client_lossy_region[surface_id]); red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SURFACE_DESTROY, NULL); surface_destroy.surface_id = surface_id; spice_marshall_msg_display_surface_destroy(base_marshaller, &surface_destroy); } static void red_marshall_monitors_config(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, MonitorsConfig *monitors_config) { int heads_size = sizeof(SpiceHead) * monitors_config->count; int i; SpiceMsgDisplayMonitorsConfig *msg = spice_malloc0(sizeof(*msg) + heads_size); int count = 0; // ignore monitors_config->count, it may contain zero width monitors, remove them now red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_MONITORS_CONFIG, NULL); for (i = 0 ; i < monitors_config->count; ++i) { if (monitors_config->heads[i].width == 0 || monitors_config->heads[i].height == 0) { continue; } msg->heads[count].id = monitors_config->heads[i].id; msg->heads[count].surface_id = monitors_config->heads[i].surface_id; msg->heads[count].width = monitors_config->heads[i].width; msg->heads[count].height = monitors_config->heads[i].height; msg->heads[count].x = monitors_config->heads[i].x; msg->heads[count].y = monitors_config->heads[i].y; count++; } msg->count = count; msg->max_allowed = monitors_config->max_allowed; spice_marshall_msg_display_monitors_config(base_marshaller, msg); free(msg); } static void red_marshall_stream_activate_report(RedChannelClient *rcc, SpiceMarshaller *base_marshaller, uint32_t stream_id) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); StreamAgent *agent = &dcc->stream_agents[stream_id]; SpiceMsgDisplayStreamActivateReport msg; red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT, NULL); msg.stream_id = stream_id; msg.unique_id = agent->report_id; msg.max_window_size = RED_STREAM_CLIENT_REPORT_WINDOW; msg.timeout_ms = RED_STREAM_CLIENT_REPORT_TIMEOUT; spice_marshall_msg_display_stream_activate_report(base_marshaller, &msg); } static void display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item) { SpiceMarshaller *m = red_channel_client_get_marshaller(rcc); DisplayChannelClient *dcc = RCC_TO_DCC(rcc); red_display_reset_send_data(dcc); switch (pipe_item->type) { case PIPE_ITEM_TYPE_DRAW: { DrawablePipeItem *dpi = SPICE_CONTAINEROF(pipe_item, DrawablePipeItem, dpi_pipe_item); marshall_qxl_drawable(rcc, m, dpi); break; } case PIPE_ITEM_TYPE_INVAL_ONE: red_marshall_inval_palette(rcc, m, (CacheItem *)pipe_item); break; case PIPE_ITEM_TYPE_STREAM_CREATE: { StreamAgent *agent = SPICE_CONTAINEROF(pipe_item, StreamAgent, create_item); red_display_marshall_stream_start(rcc, m, agent); break; } case PIPE_ITEM_TYPE_STREAM_CLIP: { StreamClipItem* clip_item = (StreamClipItem *)pipe_item; red_display_marshall_stream_clip(rcc, m, clip_item); break; } case PIPE_ITEM_TYPE_STREAM_DESTROY: { StreamAgent *agent = SPICE_CONTAINEROF(pipe_item, StreamAgent, destroy_item); red_display_marshall_stream_end(rcc, m, agent); break; } case PIPE_ITEM_TYPE_UPGRADE: red_display_marshall_upgrade(rcc, m, (UpgradeItem *)pipe_item); break; case PIPE_ITEM_TYPE_VERB: red_marshall_verb(rcc, (VerbItem*)pipe_item); break; case PIPE_ITEM_TYPE_MIGRATE_DATA: display_channel_marshall_migrate_data(rcc, m); break; case PIPE_ITEM_TYPE_IMAGE: red_marshall_image(rcc, m, (ImageItem *)pipe_item); break; case PIPE_ITEM_TYPE_PIXMAP_SYNC: display_channel_marshall_pixmap_sync(rcc, m); break; case PIPE_ITEM_TYPE_PIXMAP_RESET: display_channel_marshall_reset_cache(rcc, m); break; case PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE: dcc_palette_cache_reset(dcc); red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES, NULL); break; case PIPE_ITEM_TYPE_CREATE_SURFACE: { SurfaceCreateItem *surface_create = SPICE_CONTAINEROF(pipe_item, SurfaceCreateItem, pipe_item); red_marshall_surface_create(rcc, m, &surface_create->surface_create); break; } case PIPE_ITEM_TYPE_DESTROY_SURFACE: { SurfaceDestroyItem *surface_destroy = SPICE_CONTAINEROF(pipe_item, SurfaceDestroyItem, pipe_item); red_marshall_surface_destroy(rcc, m, surface_destroy->surface_destroy.surface_id); break; } case PIPE_ITEM_TYPE_MONITORS_CONFIG: { MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(pipe_item, MonitorsConfigItem, pipe_item); red_marshall_monitors_config(rcc, m, monconf_item->monitors_config); break; } case PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT: { StreamActivateReportItem *report_item = SPICE_CONTAINEROF(pipe_item, StreamActivateReportItem, pipe_item); red_marshall_stream_activate_report(rcc, m, report_item->stream_id); break; } default: spice_error("invalid pipe item type"); } display_channel_client_release_item_before_push(dcc, pipe_item); // a message is pending if (red_channel_client_send_message_pending(rcc)) { display_begin_send_message(rcc); } } static inline void red_push(RedWorker *worker) { if (worker->cursor_channel) { red_channel_push(RED_CHANNEL(worker->cursor_channel)); } if (worker->display_channel) { red_channel_push(RED_CHANNEL(worker->display_channel)); } } static void display_channel_client_on_disconnect(RedChannelClient *rcc) { DisplayChannel *display; DisplayChannelClient *dcc = RCC_TO_DCC(rcc); CommonChannel *common; RedWorker *worker; if (!rcc) { return; } spice_info(NULL); common = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base); worker = common->worker; display = (DisplayChannel *)rcc->channel; spice_assert(display == worker->display_channel); display_channel_compress_stats_print(display); pixmap_cache_unref(dcc->pixmap_cache); dcc->pixmap_cache = NULL; dcc_release_glz(dcc); dcc_palette_cache_reset(dcc); free(dcc->send_data.stream_outbuf); free(dcc->send_data.free_list.res); dcc_destroy_stream_agents(dcc); dcc_encoders_free(dcc); // this was the last channel client spice_debug("#draw=%d, #red_draw=%d, #glz_draw=%d", display->drawable_count, display->red_drawable_count, display->glz_drawable_count); } void red_disconnect_all_display_TODO_remove_me(RedChannel *channel) { // TODO: we need to record the client that actually causes the timeout. So // we need to check the locations of the various pipe heads when counting, // and disconnect only those/that. if (!channel) { return; } red_channel_apply_clients(channel, red_channel_client_disconnect); } static void detach_and_stop_streams(DisplayChannel *display) { RingItem *stream_item; spice_debug(NULL); while ((stream_item = ring_get_head(&display->streams))) { Stream *stream = SPICE_CONTAINEROF(stream_item, Stream, link); detach_stream_gracefully(display, stream, NULL); stream_stop(display, stream); } } static void red_migrate_display(DisplayChannel *display, RedChannelClient *rcc) { /* We need to stop the streams, and to send upgrade_items to the client. * Otherwise, (1) the client might display lossy regions that we don't track * (streams are not part of the migration data) (2) streams_timeout may occur * after the MIGRATE message has been sent. This can result in messages * being sent to the client after MSG_MIGRATE and before MSG_MIGRATE_DATA (e.g., * STREAM_CLIP, STREAM_DESTROY, DRAW_COPY) * No message besides MSG_MIGRATE_DATA should be sent after MSG_MIGRATE. * Notice that detach_and_stop_streams won't lead to any dev ram changes, since * handle_dev_stop already took care of releasing all the dev ram resources. */ detach_and_stop_streams(display); if (red_channel_client_is_connected(rcc)) { red_channel_client_default_migrate(rcc); } } static inline void *create_canvas_for_surface(DisplayChannel *display, RedSurface *surface, uint32_t renderer, uint32_t width, uint32_t height, int32_t stride, uint32_t format, void *line_0) { SpiceCanvas *canvas; switch (renderer) { case RED_RENDERER_SW: canvas = canvas_create_for_data(width, height, format, line_0, stride, &display->image_cache.base, &display->image_surfaces, NULL, NULL, NULL); surface->context.top_down = TRUE; surface->context.canvas_draws_on_surface = TRUE; return canvas; default: spice_error("invalid renderer type"); }; return NULL; } static void red_worker_create_surface_item(DisplayChannel *display, int surface_id) { DisplayChannelClient *dcc; RingItem *item, *next; FOREACH_DCC(display, item, next, dcc) { dcc_create_surface(dcc, surface_id); } } static void red_worker_push_surface_image(DisplayChannel *display, int surface_id) { DisplayChannelClient *dcc; RingItem *item, *next; FOREACH_DCC(display, item, next, dcc) { dcc_push_surface_image(dcc, surface_id); } } static void red_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->surfaces[surface_id]; uint32_t i; spice_warn_if(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->renderer != RED_RENDERER_INVALID) { surface->context.canvas = create_canvas_for_surface(display, surface, display->renderer, width, height, stride, surface->context.format, line_0); if (!surface->context.canvas) { spice_critical("drawing canvas creating failed - can`t create same type canvas"); } if (send_client) { red_worker_create_surface_item(display, surface_id); if (data_is_valid) { red_worker_push_surface_image(display, surface_id); } } return; } for (i = 0; i < display->num_renderers; i++) { surface->context.canvas = create_canvas_for_surface(display, surface, display->renderers[i], width, height, stride, surface->context.format, line_0); if (surface->context.canvas) { //no need canvas check display->renderer = display->renderers[i]; if (send_client) { red_worker_create_surface_item(display, surface_id); if (data_is_valid) { red_worker_push_surface_image(display, surface_id); } } return; } } spice_critical("unable to create drawing canvas"); } static inline void flush_display_commands(RedWorker *worker) { RedChannel *display_red_channel = RED_CHANNEL(worker->display_channel); for (;;) { uint64_t end_time; int ring_is_empty; red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty); if (ring_is_empty) { break; } while (red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty)) { red_channel_push(RED_CHANNEL(worker->display_channel)); } if (ring_is_empty) { break; } end_time = red_get_monotonic_time() + DISPLAY_CLIENT_TIMEOUT; int sleep_count = 0; for (;;) { red_channel_push(RED_CHANNEL(worker->display_channel)); if (!display_is_connected(worker) || red_channel_max_pipe_size(display_red_channel) <= MAX_PIPE_SIZE) { break; } RedChannel *channel = (RedChannel *)worker->display_channel; red_channel_receive(channel); red_channel_send(channel); // TODO: MC: the whole timeout will break since it takes lowest timeout, should // do it client by client. if (red_get_monotonic_time() >= end_time) { spice_warning("update timeout"); red_disconnect_all_display_TODO_remove_me(channel); } else { sleep_count++; usleep(DISPLAY_CLIENT_RETRY_INTERVAL); } } } } static inline void flush_cursor_commands(RedWorker *worker) { RedChannel *cursor_red_channel = RED_CHANNEL(worker->cursor_channel); for (;;) { uint64_t end_time; int ring_is_empty = FALSE; red_process_cursor(worker, MAX_PIPE_SIZE, &ring_is_empty); if (ring_is_empty) { break; } while (red_process_cursor(worker, MAX_PIPE_SIZE, &ring_is_empty)) { red_channel_push(RED_CHANNEL(worker->cursor_channel)); } if (ring_is_empty) { break; } end_time = red_get_monotonic_time() + DISPLAY_CLIENT_TIMEOUT; int sleep_count = 0; for (;;) { red_channel_push(RED_CHANNEL(worker->cursor_channel)); if (!cursor_is_connected(worker) || red_channel_min_pipe_size(cursor_red_channel) <= MAX_PIPE_SIZE) { break; } RedChannel *channel = (RedChannel *)worker->cursor_channel; red_channel_receive(channel); red_channel_send(channel); if (red_get_monotonic_time() >= end_time) { spice_warning("flush cursor timeout"); cursor_channel_disconnect(worker->cursor_channel); worker->cursor_channel = NULL; } else { sleep_count++; usleep(DISPLAY_CLIENT_RETRY_INTERVAL); } } } } // TODO: on timeout, don't disconnect all channels immediatly - try to disconnect the slowest ones // first and maybe turn timeouts to several timeouts in order to disconnect channels gradually. // Should use disconnect or shutdown? static inline void flush_all_qxl_commands(RedWorker *worker) { flush_display_commands(worker); flush_cursor_commands(worker); } static GlzSharedDictionary *_red_find_glz_dictionary(RedClient *client, uint8_t dict_id) { RingItem *now; GlzSharedDictionary *ret = NULL; now = &glz_dictionary_list; while ((now = ring_next(&glz_dictionary_list, now))) { GlzSharedDictionary *dict = (GlzSharedDictionary *)now; if ((dict->client == client) && (dict->id == dict_id)) { ret = dict; break; } } return ret; } static GlzSharedDictionary *_red_create_glz_dictionary(RedClient *client, uint8_t id, GlzEncDictContext *opaque_dict) { GlzSharedDictionary *shared_dict = spice_new0(GlzSharedDictionary, 1); shared_dict->dict = opaque_dict; shared_dict->id = id; shared_dict->refs = 1; shared_dict->migrate_freeze = FALSE; shared_dict->client = client; ring_item_init(&shared_dict->base); pthread_rwlock_init(&shared_dict->encode_lock, NULL); return shared_dict; } static GlzSharedDictionary *red_create_glz_dictionary(DisplayChannelClient *dcc, uint8_t id, int window_size) { GlzEncDictContext *glz_dict = glz_enc_dictionary_create(window_size, MAX_LZ_ENCODERS, &dcc->glz_data.usr); #ifdef COMPRESS_DEBUG spice_info("Lz Window %d Size=%d", id, window_size); #endif if (!glz_dict) { spice_critical("failed creating lz dictionary"); return NULL; } return _red_create_glz_dictionary(RED_CHANNEL_CLIENT(dcc)->client, id, glz_dict); } static GlzSharedDictionary *red_create_restored_glz_dictionary(DisplayChannelClient *dcc, uint8_t id, GlzEncDictRestoreData *restore_data) { GlzEncDictContext *glz_dict = glz_enc_dictionary_restore(restore_data, &dcc->glz_data.usr); if (!glz_dict) { spice_critical("failed creating lz dictionary"); return NULL; } return _red_create_glz_dictionary(RED_CHANNEL_CLIENT(dcc)->client, id, glz_dict); } static GlzSharedDictionary *red_get_glz_dictionary(DisplayChannelClient *dcc, uint8_t id, int window_size) { GlzSharedDictionary *shared_dict = NULL; pthread_mutex_lock(&glz_dictionary_list_lock); shared_dict = _red_find_glz_dictionary(RED_CHANNEL_CLIENT(dcc)->client, id); if (!shared_dict) { shared_dict = red_create_glz_dictionary(dcc, id, window_size); ring_add(&glz_dictionary_list, &shared_dict->base); } else { shared_dict->refs++; } pthread_mutex_unlock(&glz_dictionary_list_lock); return shared_dict; } static GlzSharedDictionary *red_restore_glz_dictionary(DisplayChannelClient *dcc, uint8_t id, GlzEncDictRestoreData *restore_data) { GlzSharedDictionary *shared_dict = NULL; pthread_mutex_lock(&glz_dictionary_list_lock); shared_dict = _red_find_glz_dictionary(RED_CHANNEL_CLIENT(dcc)->client, id); if (!shared_dict) { shared_dict = red_create_restored_glz_dictionary(dcc, id, restore_data); ring_add(&glz_dictionary_list, &shared_dict->base); } else { shared_dict->refs++; } pthread_mutex_unlock(&glz_dictionary_list_lock); return shared_dict; } /* destroy encoder, and dictionary if no one uses it*/ static void dcc_release_glz(DisplayChannelClient *dcc) { GlzSharedDictionary *shared_dict; dcc_free_glz_drawables(dcc); glz_encoder_destroy(dcc->glz); dcc->glz = NULL; if (!(shared_dict = dcc->glz_dict)) { return; } dcc->glz_dict = NULL; pthread_mutex_lock(&glz_dictionary_list_lock); if (--shared_dict->refs) { pthread_mutex_unlock(&glz_dictionary_list_lock); return; } ring_remove(&shared_dict->base); pthread_mutex_unlock(&glz_dictionary_list_lock); glz_enc_dictionary_destroy(shared_dict->dict, &dcc->glz_data.usr); free(shared_dict); } static int display_channel_init_cache(DisplayChannelClient *dcc, SpiceMsgcDisplayInit *init_info) { spice_assert(!dcc->pixmap_cache); return !!(dcc->pixmap_cache = pixmap_cache_get(RED_CHANNEL_CLIENT(dcc)->client, init_info->pixmap_cache_id, init_info->pixmap_cache_size)); } static int display_channel_init_glz_dictionary(DisplayChannelClient *dcc, SpiceMsgcDisplayInit *init_info) { spice_assert(!dcc->glz_dict); ring_init(&dcc->glz_drawables); ring_init(&dcc->glz_drawables_inst_to_free); pthread_mutex_init(&dcc->glz_drawables_inst_to_free_lock, NULL); return !!(dcc->glz_dict = red_get_glz_dictionary(dcc, init_info->glz_dictionary_id, init_info->glz_dictionary_window_size)); } static int display_channel_init(DisplayChannelClient *dcc, SpiceMsgcDisplayInit *init_info) { return (display_channel_init_cache(dcc, init_info) && display_channel_init_glz_dictionary(dcc, init_info)); } static int display_channel_handle_migrate_glz_dictionary(DisplayChannelClient *dcc, SpiceMigrateDataDisplay *migrate_info) { spice_assert(!dcc->glz_dict); ring_init(&dcc->glz_drawables); ring_init(&dcc->glz_drawables_inst_to_free); pthread_mutex_init(&dcc->glz_drawables_inst_to_free_lock, NULL); return !!(dcc->glz_dict = red_restore_glz_dictionary(dcc, migrate_info->glz_dict_id, &migrate_info->glz_dict_data)); } static int display_channel_handle_migrate_mark(RedChannelClient *rcc) { DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base); RedChannel *channel = RED_CHANNEL(display_channel); red_channel_pipes_add_type(channel, PIPE_ITEM_TYPE_MIGRATE_DATA); return TRUE; } static uint64_t display_channel_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 display_channel_client_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->surface_client_created[surface_id]) { spice_warning("surface %u is already marked as client_created", surface_id); return FALSE; } dcc->surface_client_created[surface_id] = TRUE; return TRUE; } static int display_channel_client_restore_surfaces_lossless(DisplayChannelClient *dcc, MigrateDisplaySurfacesAtClientLossless *mig_surfaces) { uint32_t i; spice_debug(NULL); for (i = 0; i < mig_surfaces->num_surfaces; i++) { uint32_t surface_id = mig_surfaces->surfaces[i].id; if (!display_channel_client_restore_surface(dcc, surface_id)) { return FALSE; } } return TRUE; } static int display_channel_client_restore_surfaces_lossy(DisplayChannelClient *dcc, MigrateDisplaySurfacesAtClientLossy *mig_surfaces) { uint32_t i; spice_debug(NULL); 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 (!display_channel_client_restore_surface(dcc, surface_id)) { return FALSE; } spice_assert(dcc->surface_client_created[surface_id]); 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->surface_client_lossy_region[surface_id]); region_add(&dcc->surface_client_lossy_region[surface_id], &lossy_rect); } return TRUE; } static int display_channel_handle_migrate_data(RedChannelClient *rcc, uint32_t size, void *message) { SpiceMigrateDataHeader *header; SpiceMigrateDataDisplay *migrate_data; DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base); DisplayChannelClient *dcc = RCC_TO_DCC(rcc); uint8_t *surfaces; int surfaces_restored = FALSE; int i; spice_debug(NULL); if (size < sizeof(*migrate_data) + sizeof(SpiceMigrateDataHeader)) { spice_error("bad message size"); return FALSE; } header = (SpiceMigrateDataHeader *)message; migrate_data = (SpiceMigrateDataDisplay *)(header + 1); if (!migration_protocol_validate_header(header, SPICE_MIGRATE_DATA_DISPLAY_MAGIC, SPICE_MIGRATE_DATA_DISPLAY_VERSION)) { spice_error("bad header"); return 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->pixmap_cache = pixmap_cache_get(RED_CHANNEL_CLIENT(dcc)->client, migrate_data->pixmap_cache_id, -1); if (!dcc->pixmap_cache) { return FALSE; } pthread_mutex_lock(&dcc->pixmap_cache->lock); for (i = 0; i < MAX_CACHE_CLIENTS; i++) { dcc->pixmap_cache->sync[i] = MAX(dcc->pixmap_cache->sync[i], migrate_data->pixmap_cache_clients[i]); } pthread_mutex_unlock(&dcc->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 PIPE_ITEM_TYPE_PIXMAP_RESET */ dcc->pixmap_cache->size = migrate_data->pixmap_cache_size; red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_PIXMAP_RESET); } if (display_channel_handle_migrate_glz_dictionary(dcc, migrate_data)) { dcc->glz = glz_encoder_create(dcc->common.id, dcc->glz_dict->dict, &dcc->glz_data.usr); if (!dcc->glz) { spice_critical("create global lz failed"); } } else { spice_critical("restoring global lz dictionary failed"); } dcc->common.is_low_bandwidth = migrate_data->low_bandwidth_setting; if (migrate_data->low_bandwidth_setting) { red_channel_client_ack_set_client_window(rcc, WIDE_CLIENT_ACK_WINDOW); if (dcc->jpeg_state == SPICE_WAN_COMPRESSION_AUTO) { display_channel->enable_jpeg = TRUE; } if (dcc->zlib_glz_state == SPICE_WAN_COMPRESSION_AUTO) { display_channel->enable_zlib_glz_wrap = TRUE; } } surfaces = (uint8_t *)message + migrate_data->surfaces_at_client_ptr; if (display_channel->enable_jpeg) { surfaces_restored = display_channel_client_restore_surfaces_lossy(dcc, (MigrateDisplaySurfacesAtClientLossy *)surfaces); } else { surfaces_restored = display_channel_client_restore_surfaces_lossless(dcc, (MigrateDisplaySurfacesAtClientLossless*)surfaces); } if (!surfaces_restored) { return FALSE; } red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE); /* enable sending messages */ red_channel_client_ack_zero_messages_window(rcc); return TRUE; } static int display_channel_handle_stream_report(DisplayChannelClient *dcc, SpiceMsgcDisplayStreamReport *stream_report) { StreamAgent *stream_agent; if (stream_report->stream_id >= NUM_STREAMS) { spice_warning("stream_report: invalid stream id %u", stream_report->stream_id); return FALSE; } stream_agent = &dcc->stream_agents[stream_report->stream_id]; if (!stream_agent->mjpeg_encoder) { spice_info("stream_report: no encoder for stream id %u." "Probably the stream has been destroyed", stream_report->stream_id); return TRUE; } if (stream_report->unique_id != stream_agent->report_id) { spice_warning("local reoprt-id (%u) != msg report-id (%u)", stream_agent->report_id, stream_report->unique_id); return TRUE; } mjpeg_encoder_client_stream_report(stream_agent->mjpeg_encoder, stream_report->num_frames, stream_report->num_drops, stream_report->start_frame_mm_time, stream_report->end_frame_mm_time, stream_report->last_frame_delay, stream_report->audio_delay); return TRUE; } static int display_channel_handle_preferred_compression(DisplayChannelClient *dcc, SpiceMsgcDisplayPreferredCompression *pc) { DisplayChannel *display_channel = DCC_TO_DC(dcc); 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: display_channel->common.worker->image_compression = pc->image_compression; dcc->image_compression = pc->image_compression; return TRUE; default: spice_warning("preferred-compression: unsupported image compression setting"); return FALSE; } } static int display_channel_handle_message(RedChannelClient *rcc, uint32_t size, uint16_t type, void *message) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); switch (type) { case SPICE_MSGC_DISPLAY_INIT: if (!dcc->expect_init) { spice_warning("unexpected SPICE_MSGC_DISPLAY_INIT"); return FALSE; } dcc->expect_init = FALSE; return display_channel_init(dcc, (SpiceMsgcDisplayInit *)message); case SPICE_MSGC_DISPLAY_STREAM_REPORT: return display_channel_handle_stream_report(dcc, (SpiceMsgcDisplayStreamReport *)message); case SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION: return display_channel_handle_preferred_compression(dcc, (SpiceMsgcDisplayPreferredCompression *)message); default: return red_channel_client_handle_message(rcc, size, type, message); } } static int common_channel_config_socket(RedChannelClient *rcc) { RedClient *client = red_channel_client_get_client(rcc); MainChannelClient *mcc = red_client_get_main(client); RedsStream *stream = red_channel_client_get_stream(rcc); CommonChannelClient *ccc = COMMON_CHANNEL_CLIENT(rcc); int flags; int delay_val; if ((flags = fcntl(stream->socket, F_GETFL)) == -1) { spice_warning("accept failed, %s", strerror(errno)); return FALSE; } if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) { spice_warning("accept failed, %s", strerror(errno)); return FALSE; } // TODO - this should be dynamic, not one time at channel creation ccc->is_low_bandwidth = main_channel_client_is_low_bandwidth(mcc); delay_val = ccc->is_low_bandwidth ? 0 : 1; /* FIXME: Using Nagle's Algorithm can lead to apparent delays, depending * on the delayed ack timeout on the other side. * Instead of using Nagle's, we need to implement message buffering on * the application level. * see: http://www.stuartcheshire.org/papers/NagleDelayedAck/ */ if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { if (errno != ENOTSUP) { spice_warning("setsockopt failed, %s", strerror(errno)); } } return TRUE; } static void worker_watch_update_mask(SpiceWatch *watch, int event_mask) { struct RedWorker *worker; int i; if (!watch) { return; } worker = watch->worker; i = watch - worker->watches; worker->poll_fds[i].events = 0; if (event_mask & SPICE_WATCH_EVENT_READ) { worker->poll_fds[i].events |= POLLIN; } if (event_mask & SPICE_WATCH_EVENT_WRITE) { worker->poll_fds[i].events |= POLLOUT; } } static SpiceWatch *worker_watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque) { /* Since we are a channel core implementation, we always get called from red_channel_client_create(), so opaque always is our rcc */ RedChannelClient *rcc = opaque; struct RedWorker *worker; int i; /* Since we are called from red_channel_client_create() CommonChannelClient->worker has not been set yet! */ worker = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base)->worker; /* Search for a free slot in our poll_fds & watches arrays */ for (i = 0; i < MAX_EVENT_SOURCES; i++) { if (worker->poll_fds[i].fd == -1) { break; } } if (i == MAX_EVENT_SOURCES) { spice_warning("could not add a watch for channel type %u id %u", rcc->channel->type, rcc->channel->id); return NULL; } worker->poll_fds[i].fd = fd; worker->watches[i].worker = worker; worker->watches[i].watch_func = func; worker->watches[i].watch_func_opaque = opaque; worker_watch_update_mask(&worker->watches[i], event_mask); return &worker->watches[i]; } static void worker_watch_remove(SpiceWatch *watch) { if (!watch) { return; } /* Note we don't touch the poll_fd here, to avoid the poll_fds/watches table entry getting re-used in the same red_worker_main loop over the fds as it is removed. This is done because re-using it while events were pending on the fd previously occupying the slot would lead to incorrectly calling the watch_func for the new fd. */ memset(watch, 0, sizeof(SpiceWatch)); } SpiceCoreInterface worker_core = { .timer_add = spice_timer_queue_add, .timer_start = spice_timer_set, .timer_cancel = spice_timer_cancel, .timer_remove = spice_timer_remove, .watch_update_mask = worker_watch_update_mask, .watch_add = worker_watch_add, .watch_remove = worker_watch_remove, }; CommonChannelClient *common_channel_new_client(CommonChannel *common, int size, RedClient *client, RedsStream *stream, int mig_target, int monitor_latency, uint32_t *common_caps, int num_common_caps, uint32_t *caps, int num_caps) { RedChannelClient *rcc = red_channel_client_create(size, &common->base, client, stream, monitor_latency, num_common_caps, common_caps, num_caps, caps); if (!rcc) { return NULL; } CommonChannelClient *common_cc = (CommonChannelClient*)rcc; common_cc->worker = common->worker; common_cc->id = common->worker->qxl->id; common->during_target_migrate = mig_target; // TODO: move wide/narrow ack setting to red_channel. red_channel_client_ack_set_client_window(rcc, common_cc->is_low_bandwidth ? WIDE_CLIENT_ACK_WINDOW : NARROW_CLIENT_ACK_WINDOW); return common_cc; } RedChannel *red_worker_new_channel(RedWorker *worker, int size, const char *name, uint32_t channel_type, int migration_flags, ChannelCbs *channel_cbs, channel_handle_parsed_proc handle_parsed) { RedChannel *channel = NULL; CommonChannel *common; spice_return_val_if_fail(worker, NULL); spice_return_val_if_fail(channel_cbs, NULL); spice_return_val_if_fail(!channel_cbs->config_socket, NULL); spice_return_val_if_fail(!channel_cbs->alloc_recv_buf, NULL); spice_return_val_if_fail(!channel_cbs->release_recv_buf, NULL); channel_cbs->config_socket = common_channel_config_socket; channel_cbs->alloc_recv_buf = common_alloc_recv_buf; channel_cbs->release_recv_buf = common_release_recv_buf; channel = red_channel_create_parser(size, &worker_core, channel_type, worker->qxl->id, TRUE /* handle_acks */, spice_get_client_channel_parser(channel_type, NULL), handle_parsed, channel_cbs, migration_flags); spice_return_val_if_fail(channel, NULL); red_channel_set_stat_node(channel, stat_add_node(worker->stat, name, TRUE)); common = (CommonChannel *)channel; common->worker = worker; return channel; } static void display_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item) { spice_assert(item); switch (item->type) { case PIPE_ITEM_TYPE_DRAW: drawable_pipe_item_ref(SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item)); break; case PIPE_ITEM_TYPE_STREAM_CLIP: ((StreamClipItem *)item)->refs++; break; case PIPE_ITEM_TYPE_UPGRADE: ((UpgradeItem *)item)->refs++; break; case PIPE_ITEM_TYPE_IMAGE: ((ImageItem *)item)->refs++; break; default: spice_critical("invalid item type"); } } static void display_channel_client_release_item_after_push(DisplayChannelClient *dcc, PipeItem *item) { DisplayChannel *display = DCC_TO_DC(dcc); switch (item->type) { case PIPE_ITEM_TYPE_DRAW: drawable_pipe_item_unref(SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item)); break; case PIPE_ITEM_TYPE_STREAM_CLIP: stream_clip_item_unref(dcc, (StreamClipItem *)item); break; case PIPE_ITEM_TYPE_UPGRADE: upgrade_item_unref(display, (UpgradeItem *)item); break; case PIPE_ITEM_TYPE_IMAGE: release_image_item((ImageItem *)item); break; case PIPE_ITEM_TYPE_VERB: free(item); break; case PIPE_ITEM_TYPE_MONITORS_CONFIG: { MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(item, MonitorsConfigItem, pipe_item); monitors_config_unref(monconf_item->monitors_config); free(item); break; } default: spice_critical("invalid item type"); } } // TODO: share code between before/after_push since most of the items need the same // release static void display_channel_client_release_item_before_push(DisplayChannelClient *dcc, PipeItem *item) { DisplayChannel *display = DCC_TO_DC(dcc); switch (item->type) { case PIPE_ITEM_TYPE_DRAW: { DrawablePipeItem *dpi = SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item); ring_remove(&dpi->base); drawable_pipe_item_unref(dpi); break; } case PIPE_ITEM_TYPE_STREAM_CREATE: { StreamAgent *agent = SPICE_CONTAINEROF(item, StreamAgent, create_item); stream_agent_unref(display, agent); break; } case PIPE_ITEM_TYPE_STREAM_CLIP: stream_clip_item_unref(dcc, (StreamClipItem *)item); break; case PIPE_ITEM_TYPE_STREAM_DESTROY: { StreamAgent *agent = SPICE_CONTAINEROF(item, StreamAgent, destroy_item); stream_agent_unref(display, agent); break; } case PIPE_ITEM_TYPE_UPGRADE: upgrade_item_unref(display, (UpgradeItem *)item); break; case PIPE_ITEM_TYPE_IMAGE: release_image_item((ImageItem *)item); break; case PIPE_ITEM_TYPE_CREATE_SURFACE: { SurfaceCreateItem *surface_create = SPICE_CONTAINEROF(item, SurfaceCreateItem, pipe_item); free(surface_create); break; } case PIPE_ITEM_TYPE_DESTROY_SURFACE: { SurfaceDestroyItem *surface_destroy = SPICE_CONTAINEROF(item, SurfaceDestroyItem, pipe_item); free(surface_destroy); break; } case PIPE_ITEM_TYPE_MONITORS_CONFIG: { MonitorsConfigItem *monconf_item = SPICE_CONTAINEROF(item, MonitorsConfigItem, pipe_item); monitors_config_unref(monconf_item->monitors_config); free(item); break; } case PIPE_ITEM_TYPE_INVAL_ONE: case PIPE_ITEM_TYPE_VERB: case PIPE_ITEM_TYPE_MIGRATE_DATA: case PIPE_ITEM_TYPE_PIXMAP_SYNC: case PIPE_ITEM_TYPE_PIXMAP_RESET: case PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE: case PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT: free(item); break; default: spice_critical("invalid item type"); } } static void display_channel_release_item(RedChannelClient *rcc, PipeItem *item, int item_pushed) { DisplayChannelClient *dcc = RCC_TO_DCC(rcc); spice_assert(item); if (item_pushed) { display_channel_client_release_item_after_push(dcc, item); } else { spice_debug("not pushed (%d)", item->type); display_channel_client_release_item_before_push(dcc, item); } } static void display_channel_create(RedWorker *worker, int migrate, int stream_video, uint32_t n_surfaces) { DisplayChannel *display_channel; ChannelCbs cbs = { .on_disconnect = display_channel_client_on_disconnect, .send_item = display_channel_send_item, .hold_item = display_channel_hold_pipe_item, .release_item = display_channel_release_item, .handle_migrate_flush_mark = display_channel_handle_migrate_mark, .handle_migrate_data = display_channel_handle_migrate_data, .handle_migrate_data_get_serial = display_channel_handle_migrate_data_get_serial }; spice_return_if_fail(num_renderers > 0); spice_info("create display channel"); if (!(display_channel = (DisplayChannel *)red_worker_new_channel( worker, sizeof(*display_channel), "display_channel", SPICE_CHANNEL_DISPLAY, SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER, &cbs, display_channel_handle_message))) { spice_warning("failed to create display channel"); return; } worker->display_channel = display_channel; stat_init(&display_channel->add_stat, "add", worker->clockid); stat_init(&display_channel->exclude_stat, "exclude", worker->clockid); stat_init(&display_channel->__exclude_stat, "__exclude", worker->clockid); #ifdef RED_STATISTICS RedChannel *channel = RED_CHANNEL(display_channel); display_channel->cache_hits_counter = stat_add_counter(channel->stat, "cache_hits", TRUE); display_channel->add_to_cache_counter = stat_add_counter(channel->stat, "add_to_cache", TRUE); display_channel->non_cache_counter = stat_add_counter(channel->stat, "non_cache", TRUE); #endif stat_compress_init(&display_channel->lz_stat, "lz"); stat_compress_init(&display_channel->glz_stat, "glz"); stat_compress_init(&display_channel->quic_stat, "quic"); stat_compress_init(&display_channel->jpeg_stat, "jpeg"); stat_compress_init(&display_channel->zlib_glz_stat, "zlib"); stat_compress_init(&display_channel->jpeg_alpha_stat, "jpeg_alpha"); stat_compress_init(&display_channel->lz4_stat, "lz4"); display_channel->n_surfaces = n_surfaces; display_channel->num_renderers = num_renderers; memcpy(display_channel->renderers, renderers, sizeof(display_channel->renderers)); display_channel->renderer = RED_RENDERER_INVALID; ring_init(&display_channel->current_list); image_surface_init(display_channel); drawables_init(display_channel); image_cache_init(&display_channel->image_cache); display_channel->stream_video = stream_video; display_channel_init_streams(display_channel); } static void guest_set_client_capabilities(RedWorker *worker) { int i; DisplayChannelClient *dcc; RedChannelClient *rcc; RingItem *link, *next; uint8_t caps[SPICE_CAPABILITIES_SIZE] = { 0 }; int caps_available[] = { SPICE_DISPLAY_CAP_SIZED_STREAM, SPICE_DISPLAY_CAP_MONITORS_CONFIG, SPICE_DISPLAY_CAP_COMPOSITE, SPICE_DISPLAY_CAP_A8_SURFACE, }; if (worker->qxl->st->qif->base.major_version < 3 || (worker->qxl->st->qif->base.major_version == 3 && worker->qxl->st->qif->base.minor_version < 2) || !worker->qxl->st->qif->set_client_capabilities) { return; } #define SET_CAP(a,c) \ ((a)[(c) / 8] |= (1 << ((c) % 8))) #define CLEAR_CAP(a,c) \ ((a)[(c) / 8] &= ~(1 << ((c) % 8))) if (!worker->running) { return; } if ((worker->display_channel == NULL) || (RED_CHANNEL(worker->display_channel)->clients_num == 0)) { worker->qxl->st->qif->set_client_capabilities(worker->qxl, FALSE, caps); } else { // Take least common denominator for (i = 0 ; i < sizeof(caps_available) / sizeof(caps_available[0]); ++i) { SET_CAP(caps, caps_available[i]); } DCC_FOREACH_SAFE(link, next, dcc, RED_CHANNEL(worker->display_channel)) { rcc = (RedChannelClient *)dcc; for (i = 0 ; i < sizeof(caps_available) / sizeof(caps_available[0]); ++i) { if (!red_channel_client_test_remote_cap(rcc, caps_available[i])) CLEAR_CAP(caps, caps_available[i]); } } worker->qxl->st->qif->set_client_capabilities(worker->qxl, TRUE, caps); } } static void handle_new_display_channel(RedWorker *worker, RedClient *client, RedsStream *stream, int migrate, uint32_t *common_caps, int num_common_caps, uint32_t *caps, int num_caps) { DisplayChannel *display_channel; DisplayChannelClient *dcc; spice_return_if_fail(worker->display_channel); display_channel = worker->display_channel; spice_info("add display channel client"); dcc = dcc_new(display_channel, client, stream, migrate, common_caps, num_common_caps, caps, num_caps, worker->image_compression, worker->jpeg_state, worker->zlib_glz_state); if (!dcc) { return; } if (dcc->jpeg_state == SPICE_WAN_COMPRESSION_AUTO) { display_channel->enable_jpeg = dcc->common.is_low_bandwidth; } else { display_channel->enable_jpeg = (dcc->jpeg_state == SPICE_WAN_COMPRESSION_ALWAYS); } if (dcc->zlib_glz_state == SPICE_WAN_COMPRESSION_AUTO) { display_channel->enable_zlib_glz_wrap = dcc->common.is_low_bandwidth; } else { display_channel->enable_zlib_glz_wrap = (dcc->zlib_glz_state == SPICE_WAN_COMPRESSION_ALWAYS); } spice_info("jpeg %s", display_channel->enable_jpeg ? "enabled" : "disabled"); spice_info("zlib-over-glz %s", display_channel->enable_zlib_glz_wrap ? "enabled" : "disabled"); guest_set_client_capabilities(worker); dcc_start(dcc); } static void red_connect_cursor(RedWorker *worker, RedClient *client, RedsStream *stream, int migrate, uint32_t *common_caps, int num_common_caps, uint32_t *caps, int num_caps) { CursorChannel *channel; CursorChannelClient *ccc; spice_return_if_fail(worker->cursor_channel != NULL); channel = worker->cursor_channel; spice_info("add cursor channel client"); ccc = cursor_channel_client_new(channel, client, stream, migrate, common_caps, num_common_caps, caps, num_caps); spice_return_if_fail(ccc != NULL); RedChannelClient *rcc = RED_CHANNEL_CLIENT(ccc); red_channel_client_ack_zero_messages_window(rcc); red_channel_client_push_set_ack(rcc); // TODO: why do we check for context.canvas? defer this to after display cc is connected // and test it's canvas? this is just a test to see if there is an active renderer? if (display_channel_surface_has_canvas(worker->display_channel, 0)) cursor_channel_init(channel, ccc); } 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(validate_surface(display, surface_id)); red_get_rect_ptr(&rect, area); red_update_area(display, &rect, surface_id); surface = &display->surfaces[surface_id]; if (!*qxl_dirty_rects) { *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 handle_dev_update_async(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageUpdateAsync *msg = payload; QXLRect *qxl_dirty_rects = NULL; uint32_t num_dirty_rects = 0; spice_return_if_fail(worker->running); spice_return_if_fail(worker->qxl->st->qif->update_area_complete); flush_display_commands(worker); display_channel_update(worker->display_channel, msg->surface_id, &msg->qxl_area, msg->clear_dirty_region, &qxl_dirty_rects, &num_dirty_rects); worker->qxl->st->qif->update_area_complete(worker->qxl, msg->surface_id, qxl_dirty_rects, num_dirty_rects); free(qxl_dirty_rects); } static void handle_dev_update(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageUpdate *msg = payload; spice_return_if_fail(worker->running); flush_display_commands(worker); display_channel_update(worker->display_channel, msg->surface_id, msg->qxl_area, msg->clear_dirty_region, &msg->qxl_dirty_rects, &msg->num_dirty_rects); } static void handle_dev_del_memslot(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageDelMemslot *msg = payload; uint32_t slot_id = msg->slot_id; uint32_t slot_group_id = msg->slot_group_id; red_memslot_info_del_slot(&worker->mem_slots, slot_group_id, slot_id); } void display_channel_destroy_surface_wait(DisplayChannel *display, int surface_id) { if (!validate_surface(display, surface_id)) return; if (!display->surfaces[surface_id].context.canvas) return; red_handle_depends_on_target_surface(display, surface_id); /* note that red_handle_depends_on_target_surface 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); red_clear_surface_drawables_from_pipes(display, surface_id, TRUE); } static void handle_dev_destroy_surface_wait(void *opaque, void *payload) { RedWorkerMessageDestroySurfaceWait *msg = payload; RedWorker *worker = opaque; spice_return_if_fail(msg->surface_id == 0); flush_all_qxl_commands(worker); display_channel_destroy_surface_wait(worker->display_channel, msg->surface_id); } /* 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 < NUM_SURFACES; ++i) { if (display->surfaces[i].context.canvas) { display_channel_destroy_surface_wait(display, i); if (display->surfaces[i].context.canvas) { display_channel_surface_unref(display, i); } spice_assert(!display->surfaces[i].context.canvas); } } spice_warn_if_fail(ring_is_empty(&display->streams)); if (red_channel_is_connected(RED_CHANNEL(display))) { red_channel_pipes_add_type(RED_CHANNEL(display), 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 handle_dev_destroy_surfaces(void *opaque, void *payload) { RedWorker *worker = opaque; flush_all_qxl_commands(worker); display_channel_destroy_surfaces(worker->display_channel); cursor_channel_reset(worker->cursor_channel); } static void display_update_monitors_config(DisplayChannel *display, QXLMonitorsConfig *config, uint16_t count, uint16_t max_allowed) { if (display->monitors_config) monitors_config_unref(display->monitors_config); display->monitors_config = monitors_config_new(config->heads, count, max_allowed); } static void red_worker_push_monitors_config(RedWorker *worker) { DisplayChannelClient *dcc; RingItem *item, *next; FOREACH_DCC(worker->display_channel, item, next, dcc) { dcc_push_monitors_config(dcc); } } static void set_monitors_config_to_primary(DisplayChannel *display) { DrawContext *context = &display->surfaces[0].context; QXLHead head = { 0, }; spice_return_if_fail(display->surfaces[0].context.canvas); if (display->monitors_config) monitors_config_unref(display->monitors_config); head.width = context->width; head.height = context->height; display->monitors_config = monitors_config_new(&head, 1, 1); } static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id, QXLDevSurfaceCreate surface) { DisplayChannel *display = worker->display_channel; uint8_t *line_0; int error; spice_debug(NULL); spice_warn_if(surface_id != 0); spice_warn_if(surface.height == 0); spice_warn_if(((uint64_t)abs(surface.stride) * (uint64_t)surface.height) != abs(surface.stride) * surface.height); line_0 = (uint8_t*)get_virt(&worker->mem_slots, surface.mem, surface.height * abs(surface.stride), surface.group_id, &error); if (error) { return; } if (worker->record_fd) { red_record_dev_input_primary_surface_create(worker->record_fd, &surface, line_0); } if (surface.stride < 0) { line_0 -= (int32_t)(surface.stride * (surface.height -1)); } red_create_surface(display, 0, surface.width, surface.height, surface.stride, surface.format, line_0, surface.flags & QXL_SURF_FLAG_KEEP_DATA, TRUE); set_monitors_config_to_primary(display); if (display_is_connected(worker) && !worker->display_channel->common.during_target_migrate) { /* guest created primary, so it will (hopefully) send a monitors_config * now, don't send our own temporary one */ if (!worker->driver_cap_monitors_config) { red_worker_push_monitors_config(worker); } red_pipes_add_verb(&worker->display_channel->common.base, SPICE_MSG_DISPLAY_MARK); red_channel_push(&worker->display_channel->common.base); } cursor_channel_init(worker->cursor_channel, NULL); } static void handle_dev_create_primary_surface(void *opaque, void *payload) { RedWorkerMessageCreatePrimarySurface *msg = payload; RedWorker *worker = opaque; dev_create_primary_surface(worker, msg->surface_id, msg->surface); } static void destroy_primary_surface(RedWorker *worker, uint32_t surface_id) { DisplayChannel *display = worker->display_channel; if (!validate_surface(display, surface_id)) return; spice_warn_if(surface_id != 0); spice_debug(NULL); if (!display->surfaces[surface_id].context.canvas) { spice_warning("double destroy of primary surface"); return; } flush_all_qxl_commands(worker); display_channel_destroy_surface_wait(display, 0); display_channel_surface_unref(display, 0); spice_warn_if_fail(ring_is_empty(&display->streams)); spice_warn_if_fail(!display->surfaces[surface_id].context.canvas); cursor_channel_reset(worker->cursor_channel); } static void handle_dev_destroy_primary_surface(void *opaque, void *payload) { RedWorkerMessageDestroyPrimarySurface *msg = payload; RedWorker *worker = opaque; uint32_t surface_id = msg->surface_id; destroy_primary_surface(worker, surface_id); } static void handle_dev_destroy_primary_surface_async(void *opaque, void *payload) { RedWorkerMessageDestroyPrimarySurfaceAsync *msg = payload; RedWorker *worker = opaque; uint32_t surface_id = msg->surface_id; destroy_primary_surface(worker, surface_id); } static void handle_dev_flush_surfaces_async(void *opaque, void *payload) { RedWorker *worker = opaque; flush_all_qxl_commands(worker); display_channel_flush_all_surfaces(worker->display_channel); } static void handle_dev_stop(void *opaque, void *payload) { RedWorker *worker = opaque; spice_info("stop"); spice_assert(worker->running); worker->running = FALSE; display_channel_free_glz_drawables(worker->display_channel); display_channel_flush_all_surfaces(worker->display_channel); /* todo: when the waiting is expected to take long (slow connection and * overloaded pipe), don't wait, and in case of migration, * purge the pipe, send destroy_all_surfaces * to the client (there is no such message right now), and start * from scratch on the destination side */ if (!red_channel_wait_all_sent(RED_CHANNEL(worker->display_channel), DISPLAY_CLIENT_TIMEOUT)) { red_channel_apply_clients(RED_CHANNEL(worker->display_channel), red_channel_client_disconnect_if_pending_send); } if (!red_channel_wait_all_sent(RED_CHANNEL(worker->cursor_channel), DISPLAY_CLIENT_TIMEOUT)) { red_channel_apply_clients(RED_CHANNEL(worker->cursor_channel), red_channel_client_disconnect_if_pending_send); } } static void handle_dev_start(void *opaque, void *payload) { RedWorker *worker = opaque; spice_assert(!worker->running); if (worker->cursor_channel) { COMMON_CHANNEL(worker->cursor_channel)->during_target_migrate = FALSE; } if (worker->display_channel) { worker->display_channel->common.during_target_migrate = FALSE; if (red_channel_waits_for_migrate_data(&worker->display_channel->common.base)) { display_channel_wait_for_migrate_data(worker->display_channel); } } worker->running = TRUE; guest_set_client_capabilities(worker); } static void handle_dev_wakeup(void *opaque, void *payload) { RedWorker *worker = opaque; stat_inc_counter(worker->wakeup_counter, 1); red_dispatcher_clear_pending(worker->red_dispatcher, RED_DISPATCHER_PENDING_WAKEUP); } static void handle_dev_oom(void *opaque, void *payload) { RedWorker *worker = opaque; DisplayChannel *display = worker->display_channel; RedChannel *display_red_channel = &worker->display_channel->common.base; int ring_is_empty; spice_return_if_fail(worker->running); // streams? but without streams also leak spice_debug("OOM1 #draw=%u, #red_draw=%u, #glz_draw=%u current %u pipes %u", display->drawable_count, display->red_drawable_count, display->glz_drawable_count, display->current_size, worker->display_channel ? red_channel_sum_pipes_size(display_red_channel) : 0); while (red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty)) { red_channel_push(&worker->display_channel->common.base); } if (worker->qxl->st->qif->flush_resources(worker->qxl) == 0) { display_channel_free_some(worker->display_channel); worker->qxl->st->qif->flush_resources(worker->qxl); } spice_debug("OOM2 #draw=%u, #red_draw=%u, #glz_draw=%u current %u pipes %u", display->drawable_count, display->red_drawable_count, display->glz_drawable_count, display->current_size, worker->display_channel ? red_channel_sum_pipes_size(display_red_channel) : 0); red_dispatcher_clear_pending(worker->red_dispatcher, RED_DISPATCHER_PENDING_OOM); } static void handle_dev_reset_cursor(void *opaque, void *payload) { RedWorker *worker = opaque; cursor_channel_reset(worker->cursor_channel); } static void handle_dev_reset_image_cache(void *opaque, void *payload) { RedWorker *worker = opaque; DisplayChannel *display = worker->display_channel; image_cache_reset(&display->image_cache); } static void handle_dev_destroy_surface_wait_async(void *opaque, void *payload) { RedWorkerMessageDestroySurfaceWaitAsync *msg = payload; RedWorker *worker = opaque; display_channel_destroy_surface_wait(worker->display_channel, msg->surface_id); } static void handle_dev_destroy_surfaces_async(void *opaque, void *payload) { RedWorker *worker = opaque; flush_all_qxl_commands(worker); display_channel_destroy_surfaces(worker->display_channel); cursor_channel_reset(worker->cursor_channel); } static void handle_dev_create_primary_surface_async(void *opaque, void *payload) { RedWorkerMessageCreatePrimarySurfaceAsync *msg = payload; RedWorker *worker = opaque; dev_create_primary_surface(worker, msg->surface_id, msg->surface); } static void handle_dev_display_connect(void *opaque, void *payload) { RedWorkerMessageDisplayConnect *msg = payload; RedWorker *worker = opaque; RedsStream *stream = msg->stream; RedClient *client = msg->client; int migration = msg->migration; spice_info("connect"); handle_new_display_channel(worker, client, stream, migration, msg->common_caps, msg->num_common_caps, msg->caps, msg->num_caps); free(msg->caps); free(msg->common_caps); } static void handle_dev_display_disconnect(void *opaque, void *payload) { RedWorkerMessageDisplayDisconnect *msg = payload; RedChannelClient *rcc = msg->rcc; RedWorker *worker = opaque; spice_info("disconnect display client"); spice_assert(rcc); guest_set_client_capabilities(worker); red_channel_client_disconnect(rcc); } static void handle_dev_display_migrate(void *opaque, void *payload) { RedWorkerMessageDisplayMigrate *msg = payload; RedWorker *worker = opaque; RedChannelClient *rcc = msg->rcc; spice_info("migrate display client"); spice_assert(rcc); red_migrate_display(worker->display_channel, rcc); } static inline uint32_t qxl_monitors_config_size(uint32_t heads) { return sizeof(QXLMonitorsConfig) + sizeof(QXLHead) * heads; } static void handle_dev_monitors_config_async(void *opaque, void *payload) { RedWorkerMessageMonitorsConfigAsync *msg = payload; RedWorker *worker = opaque; int error; uint16_t count, max_allowed; QXLMonitorsConfig *dev_monitors_config = (QXLMonitorsConfig*)get_virt(&worker->mem_slots, msg->monitors_config, qxl_monitors_config_size(1), msg->group_id, &error); if (error) { /* TODO: raise guest bug (requires added QXL interface) */ return; } worker->driver_cap_monitors_config = 1; count = dev_monitors_config->count; max_allowed = dev_monitors_config->max_allowed; if (count == 0) { spice_warning("ignoring an empty monitors config message from driver"); return; } if (count > max_allowed) { spice_warning("ignoring malformed monitors_config from driver, " "count > max_allowed %d > %d", count, max_allowed); return; } /* get pointer again to check virtual size */ dev_monitors_config = (QXLMonitorsConfig*)get_virt(&worker->mem_slots, msg->monitors_config, qxl_monitors_config_size(count), msg->group_id, &error); if (error) { /* TODO: raise guest bug (requires added QXL interface) */ return; } display_update_monitors_config(worker->display_channel, dev_monitors_config, MIN(count, msg->max_monitors), MIN(max_allowed, msg->max_monitors)); red_worker_push_monitors_config(worker); } /* TODO: special, perhaps use another dispatcher? */ static void handle_dev_cursor_connect(void *opaque, void *payload) { RedWorkerMessageCursorConnect *msg = payload; RedWorker *worker = opaque; RedsStream *stream = msg->stream; RedClient *client = msg->client; int migration = msg->migration; spice_info("cursor connect"); red_connect_cursor(worker, client, stream, migration, msg->common_caps, msg->num_common_caps, msg->caps, msg->num_caps); free(msg->caps); free(msg->common_caps); } static void handle_dev_cursor_disconnect(void *opaque, void *payload) { RedWorkerMessageCursorDisconnect *msg = payload; RedChannelClient *rcc = msg->rcc; spice_info("disconnect cursor client"); spice_return_if_fail(rcc); red_channel_client_disconnect(rcc); } static void handle_dev_cursor_migrate(void *opaque, void *payload) { RedWorkerMessageCursorMigrate *msg = payload; RedChannelClient *rcc = msg->rcc; spice_info("migrate cursor client"); cursor_channel_client_migrate(CURSOR_CHANNEL_CLIENT(rcc)); } static void handle_dev_set_compression(void *opaque, void *payload) { RedWorkerMessageSetCompression *msg = payload; RedWorker *worker = opaque; worker->image_compression = msg->image_compression; switch (worker->image_compression) { case SPICE_IMAGE_COMPRESSION_AUTO_LZ: spice_info("ic auto_lz"); break; case SPICE_IMAGE_COMPRESSION_AUTO_GLZ: spice_info("ic auto_glz"); break; case SPICE_IMAGE_COMPRESSION_QUIC: spice_info("ic quic"); break; #ifdef USE_LZ4 case SPICE_IMAGE_COMPRESSION_LZ4: spice_info("ic lz4"); break; #endif case SPICE_IMAGE_COMPRESSION_LZ: spice_info("ic lz"); break; case SPICE_IMAGE_COMPRESSION_GLZ: spice_info("ic glz"); break; case SPICE_IMAGE_COMPRESSION_OFF: spice_info("ic off"); break; default: spice_warning("ic invalid"); } display_channel_compress_stats_print(worker->display_channel); display_channel_compress_stats_reset(worker->display_channel); } static void handle_dev_set_streaming_video(void *opaque, void *payload) { RedWorkerMessageSetStreamingVideo *msg = payload; RedWorker *worker = opaque; display_channel_set_stream_video(worker->display_channel, msg->streaming_video); } static void handle_dev_set_mouse_mode(void *opaque, void *payload) { RedWorkerMessageSetMouseMode *msg = payload; RedWorker *worker = opaque; spice_info("mouse mode %u", msg->mode); cursor_channel_set_mouse_mode(worker->cursor_channel, msg->mode); } static void dev_add_memslot(RedWorker *worker, QXLDevMemSlot mem_slot) { red_memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id, mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end, mem_slot.generation); } static void handle_dev_add_memslot(void *opaque, void *payload) { RedWorker *worker = opaque; RedWorkerMessageAddMemslot *msg = payload; QXLDevMemSlot mem_slot = msg->mem_slot; red_memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id, mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end, mem_slot.generation); } static void handle_dev_add_memslot_async(void *opaque, void *payload) { RedWorkerMessageAddMemslotAsync *msg = payload; RedWorker *worker = opaque; dev_add_memslot(worker, msg->mem_slot); } static void handle_dev_reset_memslots(void *opaque, void *payload) { RedWorker *worker = opaque; red_memslot_info_reset(&worker->mem_slots); } static void handle_dev_driver_unload(void *opaque, void *payload) { RedWorker *worker = opaque; worker->driver_cap_monitors_config = 0; } static int loadvm_command(RedWorker *worker, QXLCommandExt *ext) { RedCursorCmd *cursor_cmd; RedSurfaceCmd *surface_cmd; switch (ext->cmd.type) { case QXL_CMD_CURSOR: cursor_cmd = spice_new0(RedCursorCmd, 1); if (red_get_cursor_cmd(&worker->mem_slots, ext->group_id, cursor_cmd, ext->cmd.data)) { free(cursor_cmd); return FALSE; } cursor_channel_process_cmd(worker->cursor_channel, cursor_cmd, ext->group_id); break; case QXL_CMD_SURFACE: surface_cmd = spice_new0(RedSurfaceCmd, 1); if (red_get_surface_cmd(&worker->mem_slots, ext->group_id, surface_cmd, ext->cmd.data)) { free(surface_cmd); return FALSE; } red_process_surface(worker, surface_cmd, ext->group_id, TRUE); break; default: spice_warning("unhandled loadvm command type (%d)", ext->cmd.type); } return TRUE; } static void handle_dev_loadvm_commands(void *opaque, void *payload) { RedWorkerMessageLoadvmCommands *msg = payload; RedWorker *worker = opaque; uint32_t i; uint32_t count = msg->count; QXLCommandExt *ext = msg->ext; spice_info("loadvm_commands"); for (i = 0 ; i < count ; ++i) { if (!loadvm_command(worker, &ext[i])) { /* XXX allow failure in loadvm? */ spice_warning("failed loadvm command type (%d)", ext[i].cmd.type); } } } static void worker_handle_dispatcher_async_done(void *opaque, uint32_t message_type, void *payload) { RedWorker *worker = opaque; RedWorkerMessageAsync *msg_async = payload; spice_debug(NULL); red_dispatcher_async_complete(worker->red_dispatcher, msg_async->cmd); } static void worker_dispatcher_record(void *opaque, uint32_t message_type, void *payload) { RedWorker *worker = opaque; red_record_event(worker->record_fd, 1, message_type, stat_now(worker->clockid)); } static void register_callbacks(Dispatcher *dispatcher) { dispatcher_register_async_done_callback( dispatcher, worker_handle_dispatcher_async_done); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DISPLAY_CONNECT, handle_dev_display_connect, sizeof(RedWorkerMessageDisplayConnect), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DISPLAY_DISCONNECT, handle_dev_display_disconnect, sizeof(RedWorkerMessageDisplayDisconnect), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DISPLAY_MIGRATE, handle_dev_display_migrate, sizeof(RedWorkerMessageDisplayMigrate), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CURSOR_CONNECT, handle_dev_cursor_connect, sizeof(RedWorkerMessageCursorConnect), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CURSOR_DISCONNECT, handle_dev_cursor_disconnect, sizeof(RedWorkerMessageCursorDisconnect), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CURSOR_MIGRATE, handle_dev_cursor_migrate, sizeof(RedWorkerMessageCursorMigrate), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_UPDATE, handle_dev_update, sizeof(RedWorkerMessageUpdate), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_UPDATE_ASYNC, handle_dev_update_async, sizeof(RedWorkerMessageUpdateAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_ADD_MEMSLOT, handle_dev_add_memslot, sizeof(RedWorkerMessageAddMemslot), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_ADD_MEMSLOT_ASYNC, handle_dev_add_memslot_async, sizeof(RedWorkerMessageAddMemslotAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DEL_MEMSLOT, handle_dev_del_memslot, sizeof(RedWorkerMessageDelMemslot), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACES, handle_dev_destroy_surfaces, sizeof(RedWorkerMessageDestroySurfaces), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACES_ASYNC, handle_dev_destroy_surfaces_async, sizeof(RedWorkerMessageDestroySurfacesAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE, handle_dev_destroy_primary_surface, sizeof(RedWorkerMessageDestroyPrimarySurface), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE_ASYNC, handle_dev_destroy_primary_surface_async, sizeof(RedWorkerMessageDestroyPrimarySurfaceAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC, handle_dev_create_primary_surface_async, sizeof(RedWorkerMessageCreatePrimarySurfaceAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE, handle_dev_create_primary_surface, sizeof(RedWorkerMessageCreatePrimarySurface), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_RESET_IMAGE_CACHE, handle_dev_reset_image_cache, sizeof(RedWorkerMessageResetImageCache), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_RESET_CURSOR, handle_dev_reset_cursor, sizeof(RedWorkerMessageResetCursor), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_WAKEUP, handle_dev_wakeup, sizeof(RedWorkerMessageWakeup), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_OOM, handle_dev_oom, sizeof(RedWorkerMessageOom), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_START, handle_dev_start, sizeof(RedWorkerMessageStart), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_FLUSH_SURFACES_ASYNC, handle_dev_flush_surfaces_async, sizeof(RedWorkerMessageFlushSurfacesAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_STOP, handle_dev_stop, sizeof(RedWorkerMessageStop), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_LOADVM_COMMANDS, handle_dev_loadvm_commands, sizeof(RedWorkerMessageLoadvmCommands), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_COMPRESSION, handle_dev_set_compression, sizeof(RedWorkerMessageSetCompression), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_STREAMING_VIDEO, handle_dev_set_streaming_video, sizeof(RedWorkerMessageSetStreamingVideo), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_SET_MOUSE_MODE, handle_dev_set_mouse_mode, sizeof(RedWorkerMessageSetMouseMode), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT, handle_dev_destroy_surface_wait, sizeof(RedWorkerMessageDestroySurfaceWait), DISPATCHER_ACK); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT_ASYNC, handle_dev_destroy_surface_wait_async, sizeof(RedWorkerMessageDestroySurfaceWaitAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_RESET_MEMSLOTS, handle_dev_reset_memslots, sizeof(RedWorkerMessageResetMemslots), DISPATCHER_NONE); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_MONITORS_CONFIG_ASYNC, handle_dev_monitors_config_async, sizeof(RedWorkerMessageMonitorsConfigAsync), DISPATCHER_ASYNC); dispatcher_register_handler(dispatcher, RED_WORKER_MESSAGE_DRIVER_UNLOAD, handle_dev_driver_unload, sizeof(RedWorkerMessageDriverUnload), DISPATCHER_NONE); } static void handle_dev_input(int fd, int event, void *opaque) { RedWorker *worker = opaque; dispatcher_handle_recv_read(red_dispatcher_get_dispatcher(worker->red_dispatcher)); } RedWorker* red_worker_new(QXLInstance *qxl, RedDispatcher *red_dispatcher) { QXLDevInitInfo init_info; RedWorker *worker; Dispatcher *dispatcher; int i; const char *record_filename; qxl->st->qif->get_init_info(qxl, &init_info); worker = spice_new0(RedWorker, 1); record_filename = getenv("SPICE_WORKER_RECORD_FILENAME"); if (record_filename) { static const char header[] = "SPICE_REPLAY 1\n"; worker->record_fd = fopen(record_filename, "w+"); if (worker->record_fd == NULL) { spice_error("failed to open recording file %s\n", record_filename); } if (fwrite(header, sizeof(header)-1, 1, worker->record_fd) != 1) { spice_error("failed to write replay header"); } } dispatcher = red_dispatcher_get_dispatcher(red_dispatcher); dispatcher_set_opaque(dispatcher, worker); worker->red_dispatcher = red_dispatcher; worker->qxl = qxl; register_callbacks(dispatcher); if (worker->record_fd) { dispatcher_register_universal_handler(dispatcher, worker_dispatcher_record); } worker->image_compression = image_compression; worker->jpeg_state = jpeg_state; worker->zlib_glz_state = zlib_glz_state; worker->driver_cap_monitors_config = 0; #ifdef RED_STATISTICS char worker_str[20]; sprintf(worker_str, "display[%d]", worker->qxl->id); worker->stat = stat_add_node(INVALID_STAT_REF, worker_str, TRUE); worker->wakeup_counter = stat_add_counter(worker->stat, "wakeups", TRUE); worker->command_counter = stat_add_counter(worker->stat, "commands", TRUE); #endif for (i = 0; i < MAX_EVENT_SOURCES; i++) { worker->poll_fds[i].fd = -1; } worker->poll_fds[0].fd = dispatcher_get_recv_fd(dispatcher); worker->poll_fds[0].events = POLLIN; worker->watches[0].worker = worker; worker->watches[0].watch_func = handle_dev_input; worker->watches[0].watch_func_opaque = worker; red_memslot_info_init(&worker->mem_slots, init_info.num_memslots_groups, init_info.num_memslots, init_info.memslot_gen_bits, init_info.memslot_id_bits, init_info.internal_groupslot_id); spice_warn_if(init_info.n_surfaces > NUM_SURFACES); worker->event_timeout = INF_EVENT_WAIT; worker->cursor_channel = cursor_channel_new(worker); // TODO: handle seemless migration. Temp, setting migrate to FALSE display_channel_create(worker, FALSE, streaming_video, init_info.n_surfaces); return worker; } SPICE_GNUC_NORETURN static void *red_worker_main(void *arg) { RedWorker *worker = arg; spice_info("begin"); spice_assert(MAX_PIPE_SIZE > WIDE_CLIENT_ACK_WINDOW && MAX_PIPE_SIZE > NARROW_CLIENT_ACK_WINDOW); //ensure wakeup by ack message if (!spice_timer_queue_create()) { spice_error("failed to create timer queue"); } if (pthread_getcpuclockid(pthread_self(), &worker->clockid)) { spice_warning("getcpuclockid failed"); } RED_CHANNEL(worker->cursor_channel)->thread_id = pthread_self(); RED_CHANNEL(worker->display_channel)->thread_id = pthread_self(); for (;;) { int i, num_events; unsigned int timeout; timeout = spice_timer_queue_get_timeout_ms(); worker->event_timeout = MIN(timeout, worker->event_timeout); timeout = display_channel_get_streams_timeout(worker->display_channel); worker->event_timeout = MIN(timeout, worker->event_timeout); num_events = poll(worker->poll_fds, MAX_EVENT_SOURCES, worker->event_timeout); display_channel_streams_timeout(worker->display_channel); spice_timer_queue_cb(); if (worker->display_channel) { /* during migration, in the dest, the display channel can be initialized while the global lz data not since migrate data msg hasn't been received yet */ display_channel_free_glz_drawables_to_free(worker->display_channel); } worker->event_timeout = INF_EVENT_WAIT; if (num_events == -1) { if (errno != EINTR) { spice_error("poll failed, %s", strerror(errno)); } } for (i = 0; i < MAX_EVENT_SOURCES; i++) { /* The watch may have been removed by the watch-func from another fd (ie a disconnect through the dispatcher), in this case watch_func is NULL. */ if (worker->poll_fds[i].revents && worker->watches[i].watch_func) { int events = 0; if (worker->poll_fds[i].revents & POLLIN) { events |= SPICE_WATCH_EVENT_READ; } if (worker->poll_fds[i].revents & POLLOUT) { events |= SPICE_WATCH_EVENT_WRITE; } worker->watches[i].watch_func(worker->poll_fds[i].fd, events, worker->watches[i].watch_func_opaque); } } /* Clear the poll_fd for any removed watches, see the comment in watch_remove for why we don't do this there. */ for (i = 0; i < MAX_EVENT_SOURCES; i++) { if (!worker->watches[i].watch_func) { worker->poll_fds[i].fd = -1; } } if (worker->running) { int ring_is_empty; red_process_cursor(worker, MAX_PIPE_SIZE, &ring_is_empty); red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty); } red_push(worker); } spice_warn_if_reached(); } bool red_worker_run(RedWorker *worker) { sigset_t thread_sig_mask; sigset_t curr_sig_mask; int r; spice_return_val_if_fail(worker, FALSE); spice_return_val_if_fail(!worker->thread, FALSE); sigfillset(&thread_sig_mask); sigdelset(&thread_sig_mask, SIGILL); sigdelset(&thread_sig_mask, SIGFPE); sigdelset(&thread_sig_mask, SIGSEGV); pthread_sigmask(SIG_SETMASK, &thread_sig_mask, &curr_sig_mask); if ((r = pthread_create(&worker->thread, NULL, red_worker_main, worker))) { spice_error("create thread failed %d", r); } pthread_sigmask(SIG_SETMASK, &curr_sig_mask, NULL); return r == 0; } RedChannel* red_worker_get_cursor_channel(RedWorker *worker) { spice_return_val_if_fail(worker, NULL); return RED_CHANNEL(worker->cursor_channel); } RedChannel* red_worker_get_display_channel(RedWorker *worker) { spice_return_val_if_fail(worker, NULL); return RED_CHANNEL(worker->display_channel); } clockid_t red_worker_get_clockid(RedWorker *worker) { spice_return_val_if_fail(worker, 0); return worker->clockid; }