diff options
author | Marc-André Lureau <marcandre.lureau@gmail.com> | 2013-09-17 14:52:53 +0200 |
---|---|---|
committer | Pavel Grunt <pgrunt@redhat.com> | 2015-11-13 15:17:23 +0100 |
commit | 2be6bcf3698ed32a2ef987f5aff4460722e9759f (patch) | |
tree | c6507557024ff658f3e3b5622befc41c79e1e17f /server | |
parent | 21b5ce69684ab47805f5fab3a99c8887448a5a67 (diff) |
worker: painfully move display_channel_add_drawable
Diffstat (limited to 'server')
-rw-r--r-- | server/display-channel.c | 541 | ||||
-rw-r--r-- | server/display-channel.h | 53 | ||||
-rw-r--r-- | server/red_worker.c | 1054 | ||||
-rw-r--r-- | server/red_worker.h | 1 | ||||
-rw-r--r-- | server/stream.c | 360 | ||||
-rw-r--r-- | server/stream.h | 9 | ||||
-rw-r--r-- | server/tree.c | 50 | ||||
-rw-r--r-- | server/tree.h | 5 |
8 files changed, 1050 insertions, 1023 deletions
diff --git a/server/display-channel.c b/server/display-channel.c index 824f6016..4f6cfc42 100644 --- a/server/display-channel.c +++ b/server/display-channel.c @@ -20,6 +20,21 @@ #include "display-channel.h" +static stat_time_t display_channel_stat_now(DisplayChannel *display) +{ +#ifdef RED_WORKER_STAT + RedWorker *worker = COMMON_CHANNEL(display)->worker; + + return stat_now(red_worker_get_clockid(worker)); + +#else + return 0; +#endif +} + +#define stat_start(display, var) \ + G_GNUC_UNUSED stat_time_t var = display_channel_stat_now((display)); + void display_channel_compress_stats_reset(DisplayChannel *display) { spice_return_if_fail(display); @@ -148,6 +163,21 @@ DisplayChannelClient *dcc_new(DisplayChannel *display, return dcc; } +void dcc_add_stream_agent_clip(DisplayChannelClient* dcc, StreamAgent *agent) +{ + StreamClipItem *item = stream_clip_item_new(dcc, agent); + int n_rects; + + item->clip_type = SPICE_CLIP_TYPE_RECTS; + + n_rects = pixman_region32_n_rects(&agent->clip); + item->rects = spice_malloc_n_m(n_rects, sizeof(SpiceRect), sizeof(SpiceClipRects)); + item->rects->num_rects = n_rects; + region_ret_rects(&agent->clip, item->rects->rects, n_rects); + + red_channel_client_pipe_add(RED_CHANNEL_CLIENT(dcc), (PipeItem *)item); +} + MonitorsConfig* monitors_config_ref(MonitorsConfig *monitors_config) { monitors_config->refs++; @@ -391,3 +421,514 @@ bool display_channel_surface_has_canvas(DisplayChannel *display, { return display->surfaces[surface_id].context.canvas != NULL; } + +static void streams_update_visible_region(DisplayChannel *display, Drawable *drawable) +{ + Ring *ring; + RingItem *item; + RingItem *dcc_ring_item, *next; + DisplayChannelClient *dcc; + + if (!red_channel_is_connected(RED_CHANNEL(display))) { + return; + } + + if (!is_primary_surface(display, drawable->surface_id)) { + return; + } + + ring = &display->streams; + item = ring_get_head(ring); + + while (item) { + Stream *stream = SPICE_CONTAINEROF(item, Stream, link); + StreamAgent *agent; + + item = ring_next(ring, item); + + if (stream->current == drawable) { + continue; + } + + FOREACH_DCC(display, dcc_ring_item, next, dcc) { + agent = &dcc->stream_agents[get_stream_id(display, stream)]; + + if (region_intersects(&agent->vis_region, &drawable->tree_item.base.rgn)) { + region_exclude(&agent->vis_region, &drawable->tree_item.base.rgn); + region_exclude(&agent->clip, &drawable->tree_item.base.rgn); + dcc_add_stream_agent_clip(dcc, agent); + } + } + } +} + + +static void current_add_drawable(DisplayChannel *display, + Drawable *drawable, RingItem *pos) +{ + RedSurface *surface; + uint32_t surface_id = drawable->surface_id; + + surface = &display->surfaces[surface_id]; + ring_add_after(&drawable->tree_item.base.siblings_link, pos); + ring_add(&display->current_list, &drawable->list_link); + ring_add(&surface->current_list, &drawable->surface_list_link); + display->current_size++; + drawable->refs++; +} + +static int current_add_equal(DisplayChannel *display, DrawItem *item, TreeItem *other) +{ + DrawItem *other_draw_item; + Drawable *drawable; + Drawable *other_drawable; + + if (other->type != TREE_ITEM_TYPE_DRAWABLE) { + return FALSE; + } + other_draw_item = (DrawItem *)other; + + if (item->shadow || other_draw_item->shadow || item->effect != other_draw_item->effect) { + return FALSE; + } + + drawable = SPICE_CONTAINEROF(item, Drawable, tree_item); + other_drawable = SPICE_CONTAINEROF(other_draw_item, Drawable, tree_item); + + if (item->effect == QXL_EFFECT_OPAQUE) { + int add_after = !!other_drawable->stream && + is_drawable_independent_from_surfaces(drawable); + stream_maintenance(display, drawable, other_drawable); + current_add_drawable(display, drawable, &other->siblings_link); + other_drawable->refs++; + current_remove_drawable(display, other_drawable); + if (add_after) { + red_pipes_add_drawable_after(display, drawable, other_drawable); + } else { + red_pipes_add_drawable(display, drawable); + } + red_pipes_remove_drawable(other_drawable); + display_channel_drawable_unref(display, other_drawable); + return TRUE; + } + + switch (item->effect) { + case QXL_EFFECT_REVERT_ON_DUP: + if (is_same_drawable(drawable, other_drawable)) { + + DisplayChannelClient *dcc; + DrawablePipeItem *dpi; + RingItem *worker_ring_item, *dpi_ring_item; + + other_drawable->refs++; + current_remove_drawable(display, other_drawable); + + /* sending the drawable to clients that already received + * (or will receive) other_drawable */ + worker_ring_item = ring_get_head(&RED_CHANNEL(display)->clients); + dpi_ring_item = ring_get_head(&other_drawable->pipes); + /* dpi contains a sublist of dcc's, ordered the same */ + while (worker_ring_item) { + dcc = SPICE_CONTAINEROF(worker_ring_item, DisplayChannelClient, + common.base.channel_link); + dpi = SPICE_CONTAINEROF(dpi_ring_item, DrawablePipeItem, base); + while (worker_ring_item && (!dpi || dcc != dpi->dcc)) { + dcc_add_drawable(dcc, drawable); + worker_ring_item = ring_next(&RED_CHANNEL(display)->clients, + worker_ring_item); + dcc = SPICE_CONTAINEROF(worker_ring_item, DisplayChannelClient, + common.base.channel_link); + } + + if (dpi_ring_item) { + dpi_ring_item = ring_next(&other_drawable->pipes, dpi_ring_item); + } + if (worker_ring_item) { + worker_ring_item = ring_next(&RED_CHANNEL(display)->clients, + worker_ring_item); + } + } + /* not sending other_drawable where possible */ + red_pipes_remove_drawable(other_drawable); + + display_channel_drawable_unref(display, other_drawable); + return TRUE; + } + break; + case QXL_EFFECT_OPAQUE_BRUSH: + if (is_same_geometry(drawable, other_drawable)) { + current_add_drawable(display, drawable, &other->siblings_link); + red_pipes_remove_drawable(other_drawable); + current_remove_drawable(display, other_drawable); + red_pipes_add_drawable(display, drawable); + return TRUE; + } + break; + case QXL_EFFECT_NOP_ON_DUP: + if (is_same_drawable(drawable, other_drawable)) { + return TRUE; + } + break; + } + return FALSE; +} + +static void __exclude_region(DisplayChannel *display, Ring *ring, TreeItem *item, QRegion *rgn, + Ring **top_ring, Drawable *frame_candidate) +{ + QRegion and_rgn; + stat_start(display, start_time); + + region_clone(&and_rgn, rgn); + region_and(&and_rgn, &item->rgn); + if (!region_is_empty(&and_rgn)) { + if (IS_DRAW_ITEM(item)) { + DrawItem *draw = (DrawItem *)item; + + if (draw->effect == QXL_EFFECT_OPAQUE) { + region_exclude(rgn, &and_rgn); + } + + if (draw->shadow) { + Shadow *shadow; + int32_t x = item->rgn.extents.x1; + int32_t y = item->rgn.extents.y1; + + region_exclude(&draw->base.rgn, &and_rgn); + shadow = draw->shadow; + region_offset(&and_rgn, shadow->base.rgn.extents.x1 - x, + shadow->base.rgn.extents.y1 - y); + region_exclude(&shadow->base.rgn, &and_rgn); + region_and(&and_rgn, &shadow->on_hold); + if (!region_is_empty(&and_rgn)) { + region_exclude(&shadow->on_hold, &and_rgn); + region_or(rgn, &and_rgn); + // in flat representation of current, shadow is always his owner next + if (!tree_item_contained_by((TreeItem*)shadow, *top_ring)) { + *top_ring = tree_item_container_items((TreeItem*)shadow, ring); + } + } + } else { + if (frame_candidate) { + Drawable *drawable = SPICE_CONTAINEROF(draw, Drawable, tree_item); + stream_maintenance(display, frame_candidate, drawable); + } + region_exclude(&draw->base.rgn, &and_rgn); + } + } else if (item->type == TREE_ITEM_TYPE_CONTAINER) { + region_exclude(&item->rgn, &and_rgn); + + if (region_is_empty(&item->rgn)) { //assume container removal will follow + Shadow *shadow; + + region_exclude(rgn, &and_rgn); + if ((shadow = tree_item_find_shadow(item))) { + region_or(rgn, &shadow->on_hold); + if (!tree_item_contained_by((TreeItem*)shadow, *top_ring)) { + *top_ring = tree_item_container_items((TreeItem*)shadow, ring); + } + } + } + } else { + Shadow *shadow; + + spice_assert(item->type == TREE_ITEM_TYPE_SHADOW); + shadow = (Shadow *)item; + region_exclude(rgn, &and_rgn); + region_or(&shadow->on_hold, &and_rgn); + } + } + region_destroy(&and_rgn); + stat_add(&display->__exclude_stat, start_time); +} + +static void exclude_region(DisplayChannel *display, Ring *ring, RingItem *ring_item, + QRegion *rgn, TreeItem **last, Drawable *frame_candidate) +{ + Ring *top_ring; + stat_start(display, start_time); + + if (!ring_item) { + return; + } + + top_ring = ring; + + for (;;) { + TreeItem *now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link); + Container *container = now->container; + + spice_assert(!region_is_empty(&now->rgn)); + + if (region_intersects(rgn, &now->rgn)) { + __exclude_region(display, ring, now, rgn, &top_ring, frame_candidate); + + if (region_is_empty(&now->rgn)) { + spice_assert(now->type != TREE_ITEM_TYPE_SHADOW); + ring_item = now->siblings_link.prev; + current_remove(display, now); + if (last && *last == now) { + *last = (TreeItem *)ring_next(ring, ring_item); + } + } else if (now->type == TREE_ITEM_TYPE_CONTAINER) { + Container *container = (Container *)now; + if ((ring_item = ring_get_head(&container->items))) { + ring = &container->items; + spice_assert(((TreeItem *)ring_item)->container); + continue; + } + ring_item = &now->siblings_link; + } + + if (region_is_empty(rgn)) { + stat_add(&display->exclude_stat, start_time); + return; + } + } + + while ((last && *last == (TreeItem *)ring_item) || + !(ring_item = ring_next(ring, ring_item))) { + if (ring == top_ring) { + stat_add(&display->exclude_stat, start_time); + return; + } + ring_item = &container->base.siblings_link; + container = container->base.container; + ring = (container) ? &container->items : top_ring; + } + } +} + +static int current_add_with_shadow(DisplayChannel *display, Ring *ring, Drawable *item) +{ + stat_start(display, start_time); + ++display->add_with_shadow_count; + + RedDrawable *red_drawable = item->red_drawable; + SpicePoint delta = { + .x = red_drawable->u.copy_bits.src_pos.x - red_drawable->bbox.left, + .y = red_drawable->u.copy_bits.src_pos.y - red_drawable->bbox.top + }; + + Shadow *shadow = shadow_new(&item->tree_item, &delta); + if (!shadow) { + stat_add(&display->add_stat, start_time); + return FALSE; + } + // item and his shadow must initially be placed in the same container. + // for now putting them on root. + + // only primary surface streams are supported + if (is_primary_surface(display, item->surface_id)) { + detach_streams_behind(display, &shadow->base.rgn, NULL); + } + + ring_add(ring, &shadow->base.siblings_link); + current_add_drawable(display, item, ring); + if (item->tree_item.effect == QXL_EFFECT_OPAQUE) { + QRegion exclude_rgn; + region_clone(&exclude_rgn, &item->tree_item.base.rgn); + exclude_region(display, ring, &shadow->base.siblings_link, &exclude_rgn, NULL, NULL); + region_destroy(&exclude_rgn); + streams_update_visible_region(display, item); + } else { + if (is_primary_surface(display, item->surface_id)) { + detach_streams_behind(display, &item->tree_item.base.rgn, item); + } + } + stat_add(&display->add_stat, start_time); + return TRUE; +} + +static int current_add(DisplayChannel *display, Ring *ring, Drawable *drawable) +{ + DrawItem *item = &drawable->tree_item; + RingItem *now; + QRegion exclude_rgn; + RingItem *exclude_base = NULL; + stat_start(display, start_time); + + spice_return_val_if_fail(!region_is_empty(&item->base.rgn), FALSE); + region_init(&exclude_rgn); + now = ring_next(ring, ring); + + while (now) { + TreeItem *sibling = SPICE_CONTAINEROF(now, TreeItem, siblings_link); + int test_res; + + if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) { + now = ring_next(ring, now); + continue; + } + test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL); + if (!(test_res & REGION_TEST_SHARED)) { + now = ring_next(ring, now); + continue; + } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) { + if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && + !(test_res & REGION_TEST_LEFT_EXCLUSIVE) && + current_add_equal(display, item, sibling)) { + stat_add(&display->add_stat, start_time); + return FALSE; + } + + if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) { + Shadow *shadow; + int skip = now == exclude_base; + + if ((shadow = tree_item_find_shadow(sibling))) { + if (exclude_base) { + TreeItem *next = sibling; + exclude_region(display, ring, exclude_base, &exclude_rgn, &next, NULL); + if (next != sibling) { + now = next ? &next->siblings_link : NULL; + exclude_base = NULL; + continue; + } + } + region_or(&exclude_rgn, &shadow->on_hold); + } + now = now->prev; + current_remove(display, sibling); + now = ring_next(ring, now); + if (shadow || skip) { + exclude_base = now; + } + continue; + } + + if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) { + Container *container; + + if (exclude_base) { + exclude_region(display, ring, exclude_base, &exclude_rgn, NULL, NULL); + region_clear(&exclude_rgn); + exclude_base = NULL; + } + if (sibling->type == TREE_ITEM_TYPE_CONTAINER) { + container = (Container *)sibling; + ring = &container->items; + item->base.container = container; + now = ring_next(ring, ring); + continue; + } + spice_assert(IS_DRAW_ITEM(sibling)); + if (!DRAW_ITEM(sibling)->container_root) { + container = container_new(DRAW_ITEM(sibling)); + if (!container) { + spice_warning("create new container failed"); + region_destroy(&exclude_rgn); + return FALSE; + } + item->base.container = container; + ring = &container->items; + } + } + } + if (!exclude_base) { + exclude_base = now; + } + break; + } + if (item->effect == QXL_EFFECT_OPAQUE) { + region_or(&exclude_rgn, &item->base.rgn); + exclude_region(display, ring, exclude_base, &exclude_rgn, NULL, drawable); + stream_trace_update(display, drawable); + streams_update_visible_region(display, drawable); + /* + * Performing the insertion after exclude_region for + * safety (todo: Not sure if exclude_region can affect the drawable + * if it is added to the tree before calling exclude_region). + */ + current_add_drawable(display, drawable, ring); + } else { + /* + * red_detach_streams_behind can affect the current tree since it may + * trigger calls to update_area. Thus, the drawable should be added to the tree + * before calling red_detach_streams_behind + */ + current_add_drawable(display, drawable, ring); + if (is_primary_surface(display, drawable->surface_id)) { + detach_streams_behind(display, &drawable->tree_item.base.rgn, drawable); + } + } + region_destroy(&exclude_rgn); + stat_add(&display->add_stat, start_time); + return TRUE; +} + +static bool drawable_can_stream(DisplayChannel *display, Drawable *drawable) +{ + RedDrawable *red_drawable = drawable->red_drawable; + SpiceImage *image; + + if (display->stream_video == SPICE_STREAM_VIDEO_OFF) + return FALSE; + + if (!is_primary_surface(display, drawable->surface_id)) + return FALSE; + + if (drawable->tree_item.effect != QXL_EFFECT_OPAQUE || + red_drawable->type != QXL_DRAW_COPY || + red_drawable->u.copy.rop_descriptor != SPICE_ROPD_OP_PUT) + return FALSE; + + image = red_drawable->u.copy.src_bitmap; + if (image == NULL || + image->descriptor.type != SPICE_IMAGE_TYPE_BITMAP) + return FALSE; + + if (display->stream_video == SPICE_STREAM_VIDEO_FILTER) { + SpiceRect* rect; + int size; + + rect = &drawable->red_drawable->u.copy.src_area; + size = (rect->right - rect->left) * (rect->bottom - rect->top); + if (size < RED_STREAM_MIN_SIZE) + return FALSE; + } + + return TRUE; +} + +void display_channel_print_stats(DisplayChannel *display) +{ +#ifdef RED_WORKER_STAT + stat_time_t total = display->add_stat.total; + spice_info("add with shadow count %u", + display->add_with_shadow_count); + display->add_with_shadow_count = 0; + spice_info("add[%u] %f exclude[%u] %f __exclude[%u] %f", + display->add_stat.count, + stat_cpu_time_to_sec(total), + display->exclude_stat.count, + stat_cpu_time_to_sec(display->exclude_stat.total), + display->__exclude_stat.count, + stat_cpu_time_to_sec(display->__exclude_stat.total)); + spice_info("add %f%% exclude %f%% exclude2 %f%% __exclude %f%%", + (double)(total - display->exclude_stat.total) / total * 100, + (double)(display->exclude_stat.total) / total * 100, + (double)(display->exclude_stat.total - + display->__exclude_stat.total) / display->exclude_stat.total * 100, + (double)(display->__exclude_stat.total) / display->exclude_stat.total * 100); + stat_reset(&display->add_stat); + stat_reset(&display->exclude_stat); + stat_reset(&display->__exclude_stat); +#endif +} + +int display_channel_add_drawable(DisplayChannel *display, Drawable *drawable) +{ + int ret = FALSE, surface_id = drawable->surface_id; + RedDrawable *red_drawable = drawable->red_drawable; + Ring *ring = &display->surfaces[surface_id].current; + + if (has_shadow(red_drawable)) { + ret = current_add_with_shadow(display, ring, drawable); + } else { + drawable->streamable = drawable_can_stream(display, drawable); + ret = current_add(display, ring, drawable); + } + + return ret; +} diff --git a/server/display-channel.h b/server/display-channel.h index 599cce79..432cc618 100644 --- a/server/display-channel.h +++ b/server/display-channel.h @@ -158,6 +158,11 @@ struct Drawable { uint32_t process_commands_generation; }; +#define LINK_TO_DPI(ptr) SPICE_CONTAINEROF((ptr), DrawablePipeItem, base) +#define DRAWABLE_FOREACH_DPI_SAFE(drawable, link, next, dpi) \ + SAFE_FOREACH(link, next, drawable, &(drawable)->pipes, dpi, LINK_TO_DPI(link)) + + struct DisplayChannelClient { CommonChannelClient common; SpiceImageCompression image_compression; @@ -242,6 +247,10 @@ DisplayChannelClient* dcc_new (DisplayCha void dcc_push_monitors_config (DisplayChannelClient *dcc); void dcc_push_destroy_surface (DisplayChannelClient *dcc, uint32_t surface_id); +void dcc_add_stream_agent_clip (DisplayChannelClient* dcc, + StreamAgent *agent); +void dcc_create_stream (DisplayChannelClient *dcc, + Stream *stream); typedef struct DrawablePipeItem { RingItem base; /* link for a list of pipe items held by Drawable */ @@ -347,12 +356,12 @@ struct DisplayChannel { RedCompressBuf *free_compress_bufs; /* TODO: some day unify this, make it more runtime.. */ + uint32_t add_count; + uint32_t add_with_shadow_count; #ifdef RED_WORKER_STAT stat_info_t add_stat; stat_info_t exclude_stat; stat_info_t __exclude_stat; - uint32_t add_count; - uint32_t add_with_shadow_count; #endif #ifdef RED_STATISTICS uint64_t *cache_hits_counter; @@ -397,9 +406,6 @@ typedef struct SurfaceCreateItem { void display_channel_set_stream_video (DisplayChannel *display, int stream_video); -void display_channel_attach_stream (DisplayChannel *display, - Drawable *drawable, - Stream *stream); int display_channel_get_streams_timeout (DisplayChannel *display); void display_channel_compress_stats_print (const DisplayChannel *display); void display_channel_compress_stats_reset (DisplayChannel *display); @@ -409,6 +415,8 @@ void display_channel_surface_unref (DisplayCha bool display_channel_surface_has_canvas (DisplayChannel *display, uint32_t surface_id); void display_channel_show_tree (DisplayChannel *display); +int display_channel_add_drawable (DisplayChannel *display, + Drawable *drawable); static inline int is_equal_path(SpicePath *path1, SpicePath *path2) { @@ -491,6 +499,23 @@ static inline int is_same_drawable(Drawable *d1, Drawable *d2) } } +static inline int is_drawable_independent_from_surfaces(Drawable *drawable) +{ + int x; + + for (x = 0; x < 3; ++x) { + if (drawable->surface_deps[x] != -1) { + return FALSE; + } + } + return TRUE; +} + +static inline int has_shadow(RedDrawable *drawable) +{ + return drawable->type == QXL_COPY_BITS; +} + static inline int is_primary_surface(DisplayChannel *display, uint32_t surface_id) { if (surface_id == 0) { @@ -499,4 +524,22 @@ static inline int is_primary_surface(DisplayChannel *display, uint32_t surface_i return FALSE; } +static inline void region_add_clip_rects(QRegion *rgn, SpiceClipRects *data) +{ + int i; + + for (i = 0; i < data->num_rects; i++) { + region_add(rgn, data->rects + i); + } +} + +void red_pipes_add_drawable(DisplayChannel *display, Drawable *drawable); +void current_remove_drawable(DisplayChannel *display, Drawable *item); +void red_pipes_add_drawable_after(DisplayChannel *display, + Drawable *drawable, Drawable *pos_after); +void red_pipes_remove_drawable(Drawable *drawable); +void dcc_add_drawable(DisplayChannelClient *dcc, Drawable *drawable); +void current_remove(DisplayChannel *display, TreeItem *item); +void detach_streams_behind(DisplayChannel *display, QRegion *region, Drawable *drawable); + #endif /* DISPLAY_CHANNEL_H_ */ diff --git a/server/red_worker.c b/server/red_worker.c index 59586bf0..569080a5 100644 --- a/server/red_worker.c +++ b/server/red_worker.c @@ -79,9 +79,6 @@ #define DISPLAY_FREE_LIST_DEFAULT_SIZE 128 -#define FPS_TEST_INTERVAL 1 -#define MAX_FPS 30 - #define ZLIB_DEFAULT_COMPRESSION_LEVEL 3 #define MIN_GLZ_SIZE_FOR_ZLIB 100 @@ -286,8 +283,6 @@ static void red_draw_drawable(DisplayChannel *display, Drawable *item); 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 void detach_stream(DisplayChannel *display, Stream *stream, int detach_sized); -static inline void display_channel_stream_maintenance(DisplayChannel *display, Drawable *candidate, Drawable *sect); static inline void display_begin_send_message(RedChannelClient *rcc); static void red_release_glz(DisplayChannelClient *dcc); static void red_freeze_glz(DisplayChannelClient *dcc); @@ -305,10 +300,6 @@ static void red_create_surface(DisplayChannel *display, uint32_t surface_id, uin uint32_t height, int32_t stride, uint32_t format, void *line_0, int data_is_valid, int send_client); -#define LINK_TO_DPI(ptr) SPICE_CONTAINEROF((ptr), DrawablePipeItem, base) -#define DRAWABLE_FOREACH_DPI_SAFE(drawable, link, next, dpi) \ - SAFE_FOREACH(link, next, drawable, &(drawable)->pipes, dpi, LINK_TO_DPI(link)) - #define LINK_TO_GLZ(ptr) SPICE_CONTAINEROF((ptr), RedGlzDrawable, \ drawable_link) @@ -326,22 +317,6 @@ static void display_stream_clip_unref(DisplayChannel *display, StreamClipItem *i free(item); } -static void dcc_push_stream_agent_clip(DisplayChannelClient* dcc, StreamAgent *agent) -{ - StreamClipItem *item = stream_clip_item_new(dcc, agent); - int n_rects; - - item->clip_type = SPICE_CLIP_TYPE_RECTS; - - n_rects = pixman_region32_n_rects(&agent->clip); - item->rects = spice_malloc_n_m(n_rects, sizeof(SpiceRect), sizeof(SpiceClipRects)); - item->rects->num_rects = n_rects; - region_ret_rects(&agent->clip, item->rects->rects, n_rects); - - red_channel_client_pipe_add(RED_CHANNEL_CLIENT(dcc), (PipeItem *)item); -} - - void attach_stream(DisplayChannel *display, Drawable *drawable, Stream *stream) { DisplayChannelClient *dcc; @@ -378,7 +353,7 @@ void attach_stream(DisplayChannel *display, Drawable *drawable, Stream *stream) 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_push_stream_agent_clip(dcc, agent); + dcc_add_stream_agent_clip(dcc, agent); } #ifdef STREAM_STATS agent->stats.num_input_frames++; @@ -517,7 +492,7 @@ static int cursor_is_connected(RedWorker *worker) red_channel_is_connected(RED_CHANNEL(worker->cursor_channel)); } -static void dcc_add_drawable(DisplayChannelClient *dcc, Drawable *drawable) +void dcc_add_drawable(DisplayChannelClient *dcc, Drawable *drawable) { DrawablePipeItem *dpi; @@ -526,7 +501,7 @@ static void dcc_add_drawable(DisplayChannelClient *dcc, Drawable *drawable) red_channel_client_pipe_add(RED_CHANNEL_CLIENT(dcc), &dpi->dpi_pipe_item); } -static void red_pipes_add_drawable(DisplayChannel *display, Drawable *drawable) +void red_pipes_add_drawable(DisplayChannel *display, Drawable *drawable) { DisplayChannelClient *dcc; RingItem *dcc_ring_item, *next; @@ -549,8 +524,8 @@ static void dcc_add_drawable_to_tail(DisplayChannelClient *dcc, Drawable *drawab red_channel_client_pipe_add_tail(RED_CHANNEL_CLIENT(dcc), &dpi->dpi_pipe_item); } -static inline void red_pipes_add_drawable_after(DisplayChannel *display, - Drawable *drawable, Drawable *pos_after) +void red_pipes_add_drawable_after(DisplayChannel *display, + Drawable *drawable, Drawable *pos_after) { DrawablePipeItem *dpi, *dpi_pos_after; RingItem *dpi_link, *dpi_next; @@ -596,7 +571,7 @@ static inline PipeItem *red_pipe_get_tail(DisplayChannelClient *dcc) return (PipeItem*)ring_get_tail(&RED_CHANNEL_CLIENT(dcc)->pipe); } -static inline void red_pipes_remove_drawable(Drawable *drawable) +void red_pipes_remove_drawable(Drawable *drawable) { DrawablePipeItem *dpi; RingItem *item, *next; @@ -803,21 +778,6 @@ void display_channel_drawable_unref(DisplayChannel *display, Drawable *drawable) display->drawable_count--; } -static inline void remove_shadow(DrawItem *item) -{ - Shadow *shadow; - - if (!item->shadow) { - return; - } - shadow = item->shadow; - item->shadow = NULL; - ring_remove(&shadow->base.siblings_link); - region_destroy(&shadow->base.rgn); - region_destroy(&shadow->on_hold); - free(shadow); -} - static void display_stream_trace_add_drawable(DisplayChannel *display, Drawable *item) { ItemTrace *trace; @@ -856,10 +816,11 @@ static void red_flush_source_surfaces(DisplayChannel *display, Drawable *drawabl } } -static inline void current_remove_drawable(DisplayChannel *display, Drawable *item) +void current_remove_drawable(DisplayChannel *display, Drawable *item) { + /* todo: move all to unref? */ display_stream_trace_add_drawable(display, item); - remove_shadow(&item->tree_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); @@ -867,13 +828,7 @@ static inline void current_remove_drawable(DisplayChannel *display, Drawable *it display->current_size--; } -static void remove_drawable(DisplayChannel *display, Drawable *drawable) -{ - red_pipes_remove_drawable(drawable); - current_remove_drawable(display, drawable); -} - -static inline void current_remove(DisplayChannel *display, TreeItem *item) +void current_remove(DisplayChannel *display, TreeItem *item) { TreeItem *now = item; @@ -883,8 +838,10 @@ static inline void current_remove(DisplayChannel *display, TreeItem *item) RingItem *ring_item; if (now->type == TREE_ITEM_TYPE_DRAWABLE) { + Drawable *drawable = SPICE_CONTAINEROF(now, Drawable, tree_item); ring_item = now->siblings_link.prev; - remove_drawable(display, SPICE_CONTAINEROF(now, Drawable, tree_item)); + red_pipes_remove_drawable(drawable); + current_remove_drawable(display, drawable); } else { Container *container = (Container *)now; @@ -909,7 +866,7 @@ static inline void current_remove(DisplayChannel *display, TreeItem *item) } } -static void current_clear(DisplayChannel *display, int surface_id) +static void current_remove_all(DisplayChannel *display, int surface_id) { Ring *ring = &display->surfaces[surface_id].current; RingItem *ring_item; @@ -1015,187 +972,8 @@ static void red_clear_surface_drawables_from_pipes(DisplayChannel *display, } } -static inline Shadow *__find_shadow(TreeItem *item) -{ - while (item->type == TREE_ITEM_TYPE_CONTAINER) { - if (!(item = (TreeItem *)ring_get_tail(&((Container *)item)->items))) { - return NULL; - } - } - - if (item->type != TREE_ITEM_TYPE_DRAWABLE) { - return NULL; - } - - return ((DrawItem *)item)->shadow; -} - -static inline Ring *ring_of(Ring *ring, TreeItem *item) -{ - return (item->container) ? &item->container->items : ring; -} - -static inline int __contained_by(TreeItem *item, Ring *ring) -{ - spice_assert(item && ring); - do { - Ring *now = ring_of(ring, item); - if (now == ring) { - return TRUE; - } - } while ((item = (TreeItem *)item->container)); - - return FALSE; -} - -static void __exclude_region(DisplayChannel *display, Ring *ring, TreeItem *item, QRegion *rgn, - Ring **top_ring, Drawable *frame_candidate) -{ - QRegion and_rgn; -#ifdef RED_WORKER_STAT - RedWorker *worker = COMMON_CHANNEL(display)->worker; - stat_time_t start_time = stat_now(worker->clockid); -#endif - - region_clone(&and_rgn, rgn); - region_and(&and_rgn, &item->rgn); - if (!region_is_empty(&and_rgn)) { - if (IS_DRAW_ITEM(item)) { - DrawItem *draw = (DrawItem *)item; - - if (draw->effect == QXL_EFFECT_OPAQUE) { - region_exclude(rgn, &and_rgn); - } - - if (draw->shadow) { - Shadow *shadow; - int32_t x = item->rgn.extents.x1; - int32_t y = item->rgn.extents.y1; - - region_exclude(&draw->base.rgn, &and_rgn); - shadow = draw->shadow; - region_offset(&and_rgn, shadow->base.rgn.extents.x1 - x, - shadow->base.rgn.extents.y1 - y); - region_exclude(&shadow->base.rgn, &and_rgn); - region_and(&and_rgn, &shadow->on_hold); - if (!region_is_empty(&and_rgn)) { - region_exclude(&shadow->on_hold, &and_rgn); - region_or(rgn, &and_rgn); - // in flat representation of current, shadow is always his owner next - if (!__contained_by((TreeItem*)shadow, *top_ring)) { - *top_ring = ring_of(ring, (TreeItem*)shadow); - } - } - } else { - if (frame_candidate) { - Drawable *drawable = SPICE_CONTAINEROF(draw, Drawable, tree_item); - display_channel_stream_maintenance(display, frame_candidate, drawable); - } - region_exclude(&draw->base.rgn, &and_rgn); - } - } else if (item->type == TREE_ITEM_TYPE_CONTAINER) { - region_exclude(&item->rgn, &and_rgn); - - if (region_is_empty(&item->rgn)) { //assume container removal will follow - Shadow *shadow; - - region_exclude(rgn, &and_rgn); - if ((shadow = __find_shadow(item))) { - region_or(rgn, &shadow->on_hold); - if (!__contained_by((TreeItem*)shadow, *top_ring)) { - *top_ring = ring_of(ring, (TreeItem*)shadow); - } - } - } - } else { - Shadow *shadow; - - spice_assert(item->type == TREE_ITEM_TYPE_SHADOW); - shadow = (Shadow *)item; - region_exclude(rgn, &and_rgn); - region_or(&shadow->on_hold, &and_rgn); - } - } - region_destroy(&and_rgn); - stat_add(&display->__exclude_stat, start_time); -} - -static void exclude_region(DisplayChannel *display, Ring *ring, RingItem *ring_item, - QRegion *rgn, TreeItem **last, Drawable *frame_candidate) -{ -#ifdef RED_WORKER_STAT - RedWorker *worker = COMMON_CHANNEL(display)->worker; - stat_time_t start_time = stat_now(worker->clockid); -#endif - Ring *top_ring; - - if (!ring_item) { - return; - } - - top_ring = ring; - - for (;;) { - TreeItem *now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link); - Container *container = now->container; - - spice_assert(!region_is_empty(&now->rgn)); - - if (region_intersects(rgn, &now->rgn)) { - __exclude_region(display, ring, now, rgn, &top_ring, frame_candidate); - - if (region_is_empty(&now->rgn)) { - spice_assert(now->type != TREE_ITEM_TYPE_SHADOW); - ring_item = now->siblings_link.prev; - current_remove(display, now); - if (last && *last == now) { - *last = (TreeItem *)ring_next(ring, ring_item); - } - } else if (now->type == TREE_ITEM_TYPE_CONTAINER) { - Container *container = (Container *)now; - if ((ring_item = ring_get_head(&container->items))) { - ring = &container->items; - spice_assert(((TreeItem *)ring_item)->container); - continue; - } - ring_item = &now->siblings_link; - } - - if (region_is_empty(rgn)) { - stat_add(&display->exclude_stat, start_time); - return; - } - } - - while ((last && *last == (TreeItem *)ring_item) || - !(ring_item = ring_next(ring, ring_item))) { - if (ring == top_ring) { - stat_add(&display->exclude_stat, start_time); - return; - } - ring_item = &container->base.siblings_link; - container = container->base.container; - ring = (container) ? &container->items : top_ring; - } - } -} - -static void current_add_drawable(DisplayChannel *display, - Drawable *drawable, RingItem *pos) -{ - RedSurface *surface; - uint32_t surface_id = drawable->surface_id; - - surface = &display->surfaces[surface_id]; - ring_add_after(&drawable->tree_item.base.siblings_link, pos); - ring_add(&display->current_list, &drawable->list_link); - ring_add(&surface->current_list, &drawable->surface_list_link); - display->current_size++; - drawable->refs++; -} - -static void detach_stream(DisplayChannel *display, Stream *stream, - int detach_sized) +void detach_stream(DisplayChannel *display, Stream *stream, + int detach_sized) { spice_assert(stream->current && stream->current->stream); spice_assert(stream->current->stream == stream); @@ -1234,7 +1012,7 @@ static void dcc_detach_stream_gracefully(DisplayChannelClient *dcc, /* stopping the client from playing older frames at once*/ region_clear(&agent->clip); - dcc_push_stream_agent_clip(dcc, agent); + dcc_add_stream_agent_clip(dcc, agent); if (region_is_empty(&agent->vis_region)) { spice_debug("stream %d: vis region empty", stream_id); @@ -1316,7 +1094,7 @@ static void detach_stream_gracefully(DisplayChannel *display, Stream *stream, * involves sending an upgrade image to the client, this drawable won't be rendered * (see dcc_detach_stream_gracefully). */ -static void detach_streams_behind(DisplayChannel *display, QRegion *region, Drawable *drawable) +void detach_streams_behind(DisplayChannel *display, QRegion *region, Drawable *drawable) { Ring *ring = &display->streams; RingItem *item = ring_get_head(ring); @@ -1349,46 +1127,6 @@ static void detach_streams_behind(DisplayChannel *display, QRegion *region, Draw } } -static void streams_update_visible_region(DisplayChannel *display, Drawable *drawable) -{ - Ring *ring; - RingItem *item; - RingItem *dcc_ring_item, *next; - DisplayChannelClient *dcc; - - if (!red_channel_is_connected(RED_CHANNEL(display))) { - return; - } - - if (!is_primary_surface(display, drawable->surface_id)) { - return; - } - - ring = &display->streams; - item = ring_get_head(ring); - - while (item) { - Stream *stream = SPICE_CONTAINEROF(item, Stream, link); - StreamAgent *agent; - - item = ring_next(ring, item); - - if (stream->current == drawable) { - continue; - } - - FOREACH_DCC(display, dcc_ring_item, next, dcc) { - agent = &dcc->stream_agents[get_stream_id(display, stream)]; - - if (region_intersects(&agent->vis_region, &drawable->tree_item.base.rgn)) { - region_exclude(&agent->vis_region, &drawable->tree_item.base.rgn); - region_exclude(&agent->clip, &drawable->tree_item.base.rgn); - dcc_push_stream_agent_clip(dcc, agent); - } - } - } -} - static void display_channel_streams_timeout(DisplayChannel *display) { Ring *ring = &display->streams; @@ -1406,17 +1144,6 @@ static void display_channel_streams_timeout(DisplayChannel *display) } } -static Stream *display_channel_stream_try_new(DisplayChannel *display) -{ - Stream *stream; - if (!display->free_streams) { - return NULL; - } - stream = display->free_streams; - display->free_streams = display->free_streams->next; - return stream; -} - static uint64_t red_stream_get_initial_bit_rate(DisplayChannelClient *dcc, Stream *stream) { @@ -1543,7 +1270,7 @@ static void red_stream_update_client_playback_latency(void *opaque, uint32_t del main_dispatcher_set_mm_time_latency(RED_CHANNEL_CLIENT(agent->dcc)->client, agent->dcc->streams_max_latency); } -static void dcc_create_stream(DisplayChannelClient *dcc, Stream *stream) +void dcc_create_stream(DisplayChannelClient *dcc, Stream *stream) { StreamAgent *agent = &dcc->stream_agents[get_stream_id(DCC_TO_DC(dcc), stream)]; @@ -1592,47 +1319,6 @@ static void dcc_create_stream(DisplayChannelClient *dcc, Stream *stream) #endif } -static void display_channel_create_stream(DisplayChannel *display, Drawable *drawable) -{ - DisplayChannelClient *dcc; - RingItem *dcc_ring_item, *next; - Stream *stream; - SpiceRect* src_rect; - - spice_assert(!drawable->stream); - - if (!(stream = display_channel_stream_try_new(display))) { - return; - } - - spice_assert(drawable->red_drawable->type == QXL_DRAW_COPY); - src_rect = &drawable->red_drawable->u.copy.src_area; - - ring_add(&display->streams, &stream->link); - stream->current = drawable; - stream->last_time = drawable->creation_time; - stream->width = src_rect->right - src_rect->left; - stream->height = src_rect->bottom - src_rect->top; - stream->dest_area = drawable->red_drawable->bbox; - stream->refs = 1; - SpiceBitmap *bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap; - stream->top_down = !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN); - drawable->stream = stream; - stream->input_fps = MAX_FPS; - stream->num_input_frames = 0; - stream->input_fps_start_time = drawable->creation_time; - display->streams_size_total += stream->width * stream->height; - display->stream_count++; - FOREACH_DCC(display, dcc_ring_item, next, dcc) { - dcc_create_stream(dcc, stream); - } - spice_debug("stream %d %dx%d (%d, %d) (%d, %d)", - (int)(stream - display->streams_buf), stream->width, - stream->height, stream->dest_area.left, stream->dest_area.top, - stream->dest_area.right, stream->dest_area.bottom); - return; -} - static void dcc_create_all_streams(DisplayChannelClient *dcc) { Ring *ring = &DCC_TO_DC(dcc)->streams; @@ -1677,681 +1363,6 @@ static void dcc_destroy_stream_agents(DisplayChannelClient *dcc) } } -static int is_next_stream_frame(DisplayChannel *display, - const Drawable *candidate, - const int other_src_width, - const int other_src_height, - const SpiceRect *other_dest, - const red_time_t other_time, - const Stream *stream, - int container_candidate_allowed) -{ - RedDrawable *red_drawable; - int is_frame_container = FALSE; - - if (!candidate->streamable) { - return STREAM_FRAME_NONE; - } - - if (candidate->creation_time - other_time > - (stream ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) { - return STREAM_FRAME_NONE; - } - - red_drawable = candidate->red_drawable; - if (!container_candidate_allowed) { - SpiceRect* candidate_src; - - if (!rect_is_equal(&red_drawable->bbox, other_dest)) { - return STREAM_FRAME_NONE; - } - - candidate_src = &red_drawable->u.copy.src_area; - if (candidate_src->right - candidate_src->left != other_src_width || - candidate_src->bottom - candidate_src->top != other_src_height) { - return STREAM_FRAME_NONE; - } - } else { - if (rect_contains(&red_drawable->bbox, other_dest)) { - int candidate_area = rect_get_area(&red_drawable->bbox); - int other_area = rect_get_area(other_dest); - /* do not stream drawables that are significantly - * bigger than the original frame */ - if (candidate_area > 2 * other_area) { - spice_debug("too big candidate:"); - spice_debug("prev box ==>"); - rect_debug(other_dest); - spice_debug("new box ==>"); - rect_debug(&red_drawable->bbox); - return STREAM_FRAME_NONE; - } - - if (candidate_area > other_area) { - is_frame_container = TRUE; - } - } else { - return STREAM_FRAME_NONE; - } - } - - if (stream) { - SpiceBitmap *bitmap = &red_drawable->u.copy.src_bitmap->u.bitmap; - if (stream->top_down != !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { - return STREAM_FRAME_NONE; - } - } - if (is_frame_container) { - return STREAM_FRAME_CONTAINER; - } else { - return STREAM_FRAME_NATIVE; - } -} - -static void before_reattach_stream(DisplayChannel *display, - Stream *stream, Drawable *new_frame) -{ - DrawablePipeItem *dpi; - DisplayChannelClient *dcc; - int index; - StreamAgent *agent; - RingItem *ring_item, *next; - - spice_return_if_fail(stream->current); - - if (!red_channel_is_connected(RED_CHANNEL(display))) { - return; - } - - if (new_frame->process_commands_generation == stream->current->process_commands_generation) { - spice_debug("ignoring drop, same process_commands_generation as previous frame"); - return; - } - - index = get_stream_id(display, stream); - DRAWABLE_FOREACH_DPI_SAFE(stream->current, ring_item, next, dpi) { - dcc = dpi->dcc; - agent = &dcc->stream_agents[index]; - - if (!dcc->use_mjpeg_encoder_rate_control && - !dcc->common.is_low_bandwidth) { - continue; - } - - if (pipe_item_is_linked(&dpi->dpi_pipe_item)) { -#ifdef STREAM_STATS - agent->stats.num_drops_pipe++; -#endif - if (dcc->use_mjpeg_encoder_rate_control) { - mjpeg_encoder_notify_server_frame_drop(agent->mjpeg_encoder); - } else { - ++agent->drops; - } - } - } - - - FOREACH_DCC(display, ring_item, next, dcc) { - double drop_factor; - - agent = &dcc->stream_agents[index]; - - if (dcc->use_mjpeg_encoder_rate_control) { - continue; - } - if (agent->frames / agent->fps < FPS_TEST_INTERVAL) { - agent->frames++; - continue; - } - drop_factor = ((double)agent->frames - (double)agent->drops) / - (double)agent->frames; - spice_debug("stream %d: #frames %u #drops %u", index, agent->frames, agent->drops); - if (drop_factor == 1) { - if (agent->fps < MAX_FPS) { - agent->fps++; - spice_debug("stream %d: fps++ %u", index, agent->fps); - } - } else if (drop_factor < 0.9) { - if (agent->fps > 1) { - agent->fps--; - spice_debug("stream %d: fps--%u", index, agent->fps); - } - } - agent->frames = 1; - agent->drops = 0; - } -} - -static void update_copy_graduality(DisplayChannel *display, Drawable *drawable) -{ - SpiceBitmap *bitmap; - spice_return_if_fail(drawable->red_drawable->type == QXL_DRAW_COPY); - - if (display->stream_video != SPICE_STREAM_VIDEO_FILTER) { - drawable->copy_bitmap_graduality = BITMAP_GRADUAL_INVALID; - return; - } - - if (drawable->copy_bitmap_graduality != BITMAP_GRADUAL_INVALID) { - return; // already set - } - - bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap; - - if (!bitmap_fmt_has_graduality(bitmap->format) || bitmap_has_extra_stride(bitmap) || - (bitmap->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE)) { - drawable->copy_bitmap_graduality = BITMAP_GRADUAL_NOT_AVAIL; - } else { - drawable->copy_bitmap_graduality = bitmap_get_graduality_level(bitmap); - } -} - -static int is_stream_start(Drawable *drawable) -{ - return ((drawable->frames_count >= RED_STREAM_FRAMES_START_CONDITION) && - (drawable->gradual_frames_count >= - (RED_STREAM_GRADUAL_FRAMES_START_CONDITION * drawable->frames_count))); -} - -// returns whether a stream was created -static int display_channel_stream_add_frame(DisplayChannel *display, - Drawable *frame_drawable, - int frames_count, - int gradual_frames_count, - int last_gradual_frame) -{ - update_copy_graduality(display, frame_drawable); - frame_drawable->frames_count = frames_count + 1; - frame_drawable->gradual_frames_count = gradual_frames_count; - - if (frame_drawable->copy_bitmap_graduality != BITMAP_GRADUAL_LOW) { - if ((frame_drawable->frames_count - last_gradual_frame) > - RED_STREAM_FRAMES_RESET_CONDITION) { - frame_drawable->frames_count = 1; - frame_drawable->gradual_frames_count = 1; - } else { - frame_drawable->gradual_frames_count++; - } - - frame_drawable->last_gradual_frame = frame_drawable->frames_count; - } else { - frame_drawable->last_gradual_frame = last_gradual_frame; - } - - if (is_stream_start(frame_drawable)) { - display_channel_create_stream(display, frame_drawable); - return TRUE; - } - return FALSE; -} - -static void display_channel_stream_maintenance(DisplayChannel *display, - Drawable *candidate, Drawable *prev) -{ - int is_next_frame; - - if (candidate->stream) { - return; - } - - if (prev->stream) { - Stream *stream = prev->stream; - - is_next_frame = is_next_stream_frame(display, candidate, - stream->width, stream->height, - &stream->dest_area, stream->last_time, - stream, TRUE); - if (is_next_frame != STREAM_FRAME_NONE) { - before_reattach_stream(display, stream, candidate); - detach_stream(display, stream, FALSE); - prev->streamable = FALSE; //prevent item trace - attach_stream(display, candidate, stream); - if (is_next_frame == STREAM_FRAME_CONTAINER) { - candidate->sized_stream = stream; - } - } - } else if (candidate->streamable) { - SpiceRect* prev_src = &prev->red_drawable->u.copy.src_area; - - is_next_frame = - is_next_stream_frame(display, candidate, prev_src->right - prev_src->left, - prev_src->bottom - prev_src->top, - &prev->red_drawable->bbox, prev->creation_time, - prev->stream, - FALSE); - if (is_next_frame != STREAM_FRAME_NONE) { - display_channel_stream_add_frame(display, candidate, - prev->frames_count, - prev->gradual_frames_count, - prev->last_gradual_frame); - } - } -} - -static inline int is_drawable_independent_from_surfaces(Drawable *drawable) -{ - int x; - - for (x = 0; x < 3; ++x) { - if (drawable->surface_deps[x] != -1) { - return FALSE; - } - } - return TRUE; -} - -static inline int red_current_add_equal(DisplayChannel *display, DrawItem *item, TreeItem *other) -{ - DrawItem *other_draw_item; - Drawable *drawable; - Drawable *other_drawable; - - if (other->type != TREE_ITEM_TYPE_DRAWABLE) { - return FALSE; - } - other_draw_item = (DrawItem *)other; - - if (item->shadow || other_draw_item->shadow || item->effect != other_draw_item->effect) { - return FALSE; - } - - drawable = SPICE_CONTAINEROF(item, Drawable, tree_item); - other_drawable = SPICE_CONTAINEROF(other_draw_item, Drawable, tree_item); - - if (item->effect == QXL_EFFECT_OPAQUE) { - int add_after = !!other_drawable->stream && - is_drawable_independent_from_surfaces(drawable); - display_channel_stream_maintenance(display, drawable, other_drawable); - current_add_drawable(display, drawable, &other->siblings_link); - other_drawable->refs++; - current_remove_drawable(display, other_drawable); - if (add_after) { - red_pipes_add_drawable_after(display, drawable, other_drawable); - } else { - red_pipes_add_drawable(display, drawable); - } - red_pipes_remove_drawable(other_drawable); - display_channel_drawable_unref(display, other_drawable); - return TRUE; - } - - switch (item->effect) { - case QXL_EFFECT_REVERT_ON_DUP: - if (is_same_drawable(drawable, other_drawable)) { - - DisplayChannelClient *dcc; - DrawablePipeItem *dpi; - RingItem *worker_ring_item, *dpi_ring_item; - - other_drawable->refs++; - current_remove_drawable(display, other_drawable); - - /* sending the drawable to clients that already received - * (or will receive) other_drawable */ - worker_ring_item = ring_get_head(&RED_CHANNEL(display)->clients); - dpi_ring_item = ring_get_head(&other_drawable->pipes); - /* dpi contains a sublist of dcc's, ordered the same */ - while (worker_ring_item) { - dcc = SPICE_CONTAINEROF(worker_ring_item, DisplayChannelClient, - common.base.channel_link); - dpi = SPICE_CONTAINEROF(dpi_ring_item, DrawablePipeItem, base); - while (worker_ring_item && (!dpi || dcc != dpi->dcc)) { - dcc_add_drawable(dcc, drawable); - worker_ring_item = ring_next(&RED_CHANNEL(display)->clients, - worker_ring_item); - dcc = SPICE_CONTAINEROF(worker_ring_item, DisplayChannelClient, - common.base.channel_link); - } - - if (dpi_ring_item) { - dpi_ring_item = ring_next(&other_drawable->pipes, dpi_ring_item); - } - if (worker_ring_item) { - worker_ring_item = ring_next(&RED_CHANNEL(display)->clients, - worker_ring_item); - } - } - /* not sending other_drawable where possible */ - red_pipes_remove_drawable(other_drawable); - - display_channel_drawable_unref(display, other_drawable); - return TRUE; - } - break; - case QXL_EFFECT_OPAQUE_BRUSH: - if (is_same_geometry(drawable, other_drawable)) { - current_add_drawable(display, drawable, &other->siblings_link); - remove_drawable(display, other_drawable); - red_pipes_add_drawable(display, drawable); - return TRUE; - } - break; - case QXL_EFFECT_NOP_ON_DUP: - if (is_same_drawable(drawable, other_drawable)) { - return TRUE; - } - break; - } - return FALSE; -} - -#define FOREACH_STREAMS(display, item) \ - for (item = ring_get_head(&(display)->streams); \ - item != NULL; \ - item = ring_next(&(display)->streams, item)) - -static void red_use_stream_trace(DisplayChannel *display, Drawable *drawable) -{ - ItemTrace *trace; - ItemTrace *trace_end; - RingItem *item; - - if (drawable->stream || !drawable->streamable || drawable->frames_count) { - return; - } - - FOREACH_STREAMS(display, item) { - Stream *stream = SPICE_CONTAINEROF(item, Stream, link); - int is_next_frame = is_next_stream_frame(display, - drawable, - stream->width, - stream->height, - &stream->dest_area, - stream->last_time, - stream, - TRUE); - if (is_next_frame != STREAM_FRAME_NONE) { - if (stream->current) { - stream->current->streamable = FALSE; //prevent item trace - before_reattach_stream(display, stream, drawable); - detach_stream(display, stream, FALSE); - } - attach_stream(display, drawable, stream); - if (is_next_frame == STREAM_FRAME_CONTAINER) { - drawable->sized_stream = stream; - } - return; - } - } - - trace = display->items_trace; - trace_end = trace + NUM_TRACE_ITEMS; - for (; trace < trace_end; trace++) { - if (is_next_stream_frame(display, drawable, trace->width, trace->height, - &trace->dest_area, trace->time, NULL, FALSE) != - STREAM_FRAME_NONE) { - if (display_channel_stream_add_frame(display, drawable, - trace->frames_count, - trace->gradual_frames_count, - trace->last_gradual_frame)) { - return; - } - } - } -} - -static int current_add(DisplayChannel *display, Ring *ring, Drawable *drawable) -{ - DrawItem *item = &drawable->tree_item; -#ifdef RED_WORKER_STAT - RedWorker *worker = COMMON_CHANNEL(display)->worker; - stat_time_t start_time = stat_now(worker->clockid); -#endif - RingItem *now; - QRegion exclude_rgn; - RingItem *exclude_base = NULL; - - spice_return_val_if_fail(!region_is_empty(&item->base.rgn), FALSE); - region_init(&exclude_rgn); - now = ring_next(ring, ring); - - while (now) { - TreeItem *sibling = SPICE_CONTAINEROF(now, TreeItem, siblings_link); - int test_res; - - if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) { - now = ring_next(ring, now); - continue; - } - test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL); - if (!(test_res & REGION_TEST_SHARED)) { - now = ring_next(ring, now); - continue; - } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) { - if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && - !(test_res & REGION_TEST_LEFT_EXCLUSIVE) && - red_current_add_equal(display, item, sibling)) { - stat_add(&display->add_stat, start_time); - return FALSE; - } - - if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) { - Shadow *shadow; - int skip = now == exclude_base; - - if ((shadow = __find_shadow(sibling))) { - if (exclude_base) { - TreeItem *next = sibling; - exclude_region(display, ring, exclude_base, &exclude_rgn, &next, NULL); - if (next != sibling) { - now = next ? &next->siblings_link : NULL; - exclude_base = NULL; - continue; - } - } - region_or(&exclude_rgn, &shadow->on_hold); - } - now = now->prev; - current_remove(display, sibling); - now = ring_next(ring, now); - if (shadow || skip) { - exclude_base = now; - } - continue; - } - - if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) { - Container *container; - - if (exclude_base) { - exclude_region(display, ring, exclude_base, &exclude_rgn, NULL, NULL); - region_clear(&exclude_rgn); - exclude_base = NULL; - } - if (sibling->type == TREE_ITEM_TYPE_CONTAINER) { - container = (Container *)sibling; - ring = &container->items; - item->base.container = container; - now = ring_next(ring, ring); - continue; - } - spice_assert(IS_DRAW_ITEM(sibling)); - if (!DRAW_ITEM(sibling)->container_root) { - container = container_new(DRAW_ITEM(sibling)); - if (!container) { - spice_warning("create new container failed"); - region_destroy(&exclude_rgn); - return FALSE; - } - item->base.container = container; - ring = &container->items; - } - } - } - if (!exclude_base) { - exclude_base = now; - } - break; - } - if (item->effect == QXL_EFFECT_OPAQUE) { - region_or(&exclude_rgn, &item->base.rgn); - exclude_region(display, ring, exclude_base, &exclude_rgn, NULL, drawable); - red_use_stream_trace(display, drawable); - streams_update_visible_region(display, drawable); - /* - * Performing the insertion after exclude_region for - * safety (todo: Not sure if exclude_region can affect the drawable - * if it is added to the tree before calling exclude_region). - */ - current_add_drawable(display, drawable, ring); - } else { - /* - * red_detach_streams_behind can affect the current tree since it may - * trigger calls to update_area. Thus, the drawable should be added to the tree - * before calling red_detach_streams_behind - */ - current_add_drawable(display, drawable, ring); - if (is_primary_surface(display, drawable->surface_id)) { - detach_streams_behind(display, &drawable->tree_item.base.rgn, drawable); - } - } - region_destroy(&exclude_rgn); - stat_add(&display->add_stat, start_time); - return TRUE; -} - -static void add_clip_rects(QRegion *rgn, SpiceClipRects *data) -{ - int i; - - for (i = 0; i < data->num_rects; i++) { - region_add(rgn, data->rects + i); - } -} - -static int current_add_with_shadow(DisplayChannel *display, Ring *ring, Drawable *item) -{ -#ifdef RED_WORKER_STAT - RedWorker *worker = COMMON_CHANNEL(display)->worker; - stat_time_t start_time = stat_now(worker->clockid); - ++display->add_with_shadow_count; -#endif - - RedDrawable *red_drawable = item->red_drawable; - SpicePoint delta = { - .x = red_drawable->u.copy_bits.src_pos.x - red_drawable->bbox.left, - .y = red_drawable->u.copy_bits.src_pos.y - red_drawable->bbox.top - }; - - Shadow *shadow = shadow_new(&item->tree_item, &delta); - if (!shadow) { - stat_add(&display->add_stat, start_time); - return FALSE; - } - // item and his shadow must initially be placed in the same container. - // for now putting them on root. - - // only primary surface streams are supported - if (is_primary_surface(display, item->surface_id)) { - detach_streams_behind(display, &shadow->base.rgn, NULL); - } - - ring_add(ring, &shadow->base.siblings_link); - current_add_drawable(display, item, ring); - if (item->tree_item.effect == QXL_EFFECT_OPAQUE) { - QRegion exclude_rgn; - region_clone(&exclude_rgn, &item->tree_item.base.rgn); - exclude_region(display, ring, &shadow->base.siblings_link, &exclude_rgn, NULL, NULL); - region_destroy(&exclude_rgn); - streams_update_visible_region(display, item); - } else { - if (is_primary_surface(display, item->surface_id)) { - detach_streams_behind(display, &item->tree_item.base.rgn, item); - } - } - stat_add(&display->add_stat, start_time); - return TRUE; -} - -static inline int has_shadow(RedDrawable *drawable) -{ - return drawable->type == QXL_COPY_BITS; -} - -static void drawable_update_streamable(DisplayChannel *display, Drawable *drawable) -{ - RedDrawable *red_drawable = drawable->red_drawable; - SpiceImage *image; - - if (display->stream_video == SPICE_STREAM_VIDEO_OFF) { - return; - } - - if (!is_primary_surface(display, drawable->surface_id)) { - return; - } - - if (drawable->tree_item.effect != QXL_EFFECT_OPAQUE || - red_drawable->type != QXL_DRAW_COPY || - red_drawable->u.copy.rop_descriptor != SPICE_ROPD_OP_PUT) { - return; - } - - image = red_drawable->u.copy.src_bitmap; - if (image == NULL || - image->descriptor.type != SPICE_IMAGE_TYPE_BITMAP) { - return; - } - - if (display->stream_video == SPICE_STREAM_VIDEO_FILTER) { - SpiceRect* rect; - int size; - - rect = &drawable->red_drawable->u.copy.src_area; - size = (rect->right - rect->left) * (rect->bottom - rect->top); - if (size < RED_STREAM_MIN_SIZE) { - return; - } - } - - drawable->streamable = TRUE; -} - -void print_stats(DisplayChannel *display) -{ -#ifdef RED_WORKER_STAT - stat_time_t total = display->add_stat.total; - spice_info("add with shadow count %u", - display->add_with_shadow_count); - display->add_with_shadow_count = 0; - spice_info("add[%u] %f exclude[%u] %f __exclude[%u] %f", - display->add_stat.count, - stat_cpu_time_to_sec(total), - display->exclude_stat.count, - stat_cpu_time_to_sec(display->exclude_stat.total), - display->__exclude_stat.count, - stat_cpu_time_to_sec(display->__exclude_stat.total)); - spice_info("add %f%% exclude %f%% exclude2 %f%% __exclude %f%%", - (double)(total - display->exclude_stat.total) / total * 100, - (double)(display->exclude_stat.total) / total * 100, - (double)(display->exclude_stat.total - - display->__exclude_stat.total) / display->exclude_stat.total * 100, - (double)(display->__exclude_stat.total) / display->exclude_stat.total * 100); - stat_reset(&display->add_stat); - stat_reset(&display->exclude_stat); - stat_reset(&display->__exclude_stat); -#endif -} - - static int red_add_drawable(DisplayChannel *display, Drawable *drawable) -{ - int ret = FALSE, surface_id = drawable->surface_id; - RedDrawable *red_drawable = drawable->red_drawable; - Ring *ring = &display->surfaces[surface_id].current; - - if (has_shadow(red_drawable)) { - ret = current_add_with_shadow(display, ring, drawable); - } else { - drawable_update_streamable(display, drawable); - ret = current_add(display, ring, drawable); - } - -#ifdef RED_WORKER_STAT - if ((++display->add_count % 100) == 0) - print_stats(display); -#endif - return ret; -} - static void red_get_area(DisplayChannel *display, int surface_id, const SpiceRect *area, uint8_t *dest, int dest_stride, int update) { @@ -2620,7 +1631,7 @@ static inline void red_process_draw(RedWorker *worker, RedDrawable *red_drawable QRegion rgn; region_init(&rgn); - add_clip_rects(&rgn, red_drawable->clip.rects); + region_add_clip_rects(&rgn, red_drawable->clip.rects); region_and(&drawable->tree_item.base.rgn, &rgn); region_destroy(&rgn); } @@ -2649,7 +1660,7 @@ static inline void red_process_draw(RedWorker *worker, RedDrawable *red_drawable goto cleanup; } - if (red_add_drawable(worker->display_channel, drawable)) { + if (display_channel_add_drawable(worker->display_channel, drawable)) { red_pipes_add_drawable(worker->display_channel, drawable); } cleanup: @@ -2701,10 +1712,10 @@ static inline void red_process_surface(RedWorker *worker, RedSurfaceCmd *surface } 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_clear. + /* 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 - red_current_clear will remove them from the pipe. */ - current_clear(display, surface_id); + 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; @@ -3247,7 +2258,7 @@ static void red_current_flush(DisplayChannel *display, int surface_id) while (!ring_is_empty(&display->surfaces[surface_id].current_list)) { free_one_drawable(display, FALSE); } - current_clear(display, surface_id); + current_remove_all(display, surface_id); } // adding the pipe item after pos. If pos == NULL, adding to head. @@ -5044,7 +4055,7 @@ static void surface_lossy_region_update(DisplayChannelClient *dcc, region_init(&clip_rgn); region_init(&draw_region); region_add(&draw_region, &drawable->bbox); - add_clip_rects(&clip_rgn, drawable->clip.rects); + region_add_clip_rects(&clip_rgn, drawable->clip.rects); region_and(&draw_region, &clip_rgn); if (lossy) { region_or(surface_lossy_region, &draw_region); @@ -8591,10 +7602,10 @@ void display_channel_destroy_surface_wait(DisplayChannel *display, int surface_i return; red_handle_depends_on_target_surface(display, surface_id); - /* note that red_handle_depends_on_target_surface must be called before current_clear. + /* 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_clear will remove them from the pipe. */ - current_clear(display, surface_id); + 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); } @@ -9628,3 +8639,10 @@ RedChannel* red_worker_get_display_channel(RedWorker *worker) return RED_CHANNEL(worker->display_channel); } + +clockid_t red_worker_get_clockid(RedWorker *worker) +{ + spice_return_val_if_fail(worker, 0); + + return worker->clockid; +} diff --git a/server/red_worker.h b/server/red_worker.h index 3604dfd3..d70e8627 100644 --- a/server/red_worker.h +++ b/server/red_worker.h @@ -110,6 +110,7 @@ QXLInstance* red_worker_get_qxl(RedWorker *worker); RedChannel* red_worker_get_cursor_channel(RedWorker *worker); RedChannel* red_worker_get_display_channel(RedWorker *worker); void red_worker_print_stats(RedWorker *worker); +clockid_t red_worker_get_clockid(RedWorker *worker); RedChannel *red_worker_new_channel(RedWorker *worker, int size, const char *name, diff --git a/server/stream.c b/server/stream.c index d61cec18..19ffa709 100644 --- a/server/stream.c +++ b/server/stream.c @@ -21,6 +21,12 @@ #include "stream.h" #include "display-channel.h" +#define FPS_TEST_INTERVAL 1 +#define FOREACH_STREAMS(display, item) \ + for (item = ring_get_head(&(display)->streams); \ + item != NULL; \ + item = ring_next(&(display)->streams, item)) + void stream_agent_stats_print(StreamAgent *agent) { #ifdef STREAM_STATS @@ -138,3 +144,357 @@ StreamClipItem *stream_clip_item_new(DisplayChannelClient* dcc, StreamAgent *age item->refs = 1; return item; } + +static int is_stream_start(Drawable *drawable) +{ + return ((drawable->frames_count >= RED_STREAM_FRAMES_START_CONDITION) && + (drawable->gradual_frames_count >= + (RED_STREAM_GRADUAL_FRAMES_START_CONDITION * drawable->frames_count))); +} + +static void update_copy_graduality(Drawable *drawable) +{ + SpiceBitmap *bitmap; + spice_return_if_fail(drawable->red_drawable->type == QXL_DRAW_COPY); + + /* TODO: global property -> per dc/dcc */ + if (streaming_video != SPICE_STREAM_VIDEO_FILTER) { + drawable->copy_bitmap_graduality = BITMAP_GRADUAL_INVALID; + return; + } + + if (drawable->copy_bitmap_graduality != BITMAP_GRADUAL_INVALID) { + return; // already set + } + + bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap; + + if (!bitmap_fmt_has_graduality(bitmap->format) || bitmap_has_extra_stride(bitmap) || + (bitmap->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE)) { + drawable->copy_bitmap_graduality = BITMAP_GRADUAL_NOT_AVAIL; + } else { + drawable->copy_bitmap_graduality = bitmap_get_graduality_level(bitmap); + } +} + +static int is_next_stream_frame(DisplayChannel *display, + const Drawable *candidate, + const int other_src_width, + const int other_src_height, + const SpiceRect *other_dest, + const red_time_t other_time, + const Stream *stream, + int container_candidate_allowed) +{ + RedDrawable *red_drawable; + int is_frame_container = FALSE; + + if (!candidate->streamable) { + return STREAM_FRAME_NONE; + } + + if (candidate->creation_time - other_time > + (stream ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) { + return STREAM_FRAME_NONE; + } + + red_drawable = candidate->red_drawable; + if (!container_candidate_allowed) { + SpiceRect* candidate_src; + + if (!rect_is_equal(&red_drawable->bbox, other_dest)) { + return STREAM_FRAME_NONE; + } + + candidate_src = &red_drawable->u.copy.src_area; + if (candidate_src->right - candidate_src->left != other_src_width || + candidate_src->bottom - candidate_src->top != other_src_height) { + return STREAM_FRAME_NONE; + } + } else { + if (rect_contains(&red_drawable->bbox, other_dest)) { + int candidate_area = rect_get_area(&red_drawable->bbox); + int other_area = rect_get_area(other_dest); + /* do not stream drawables that are significantly + * bigger than the original frame */ + if (candidate_area > 2 * other_area) { + spice_debug("too big candidate:"); + spice_debug("prev box ==>"); + rect_debug(other_dest); + spice_debug("new box ==>"); + rect_debug(&red_drawable->bbox); + return STREAM_FRAME_NONE; + } + + if (candidate_area > other_area) { + is_frame_container = TRUE; + } + } else { + return STREAM_FRAME_NONE; + } + } + + if (stream) { + SpiceBitmap *bitmap = &red_drawable->u.copy.src_bitmap->u.bitmap; + if (stream->top_down != !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { + return STREAM_FRAME_NONE; + } + } + if (is_frame_container) { + return STREAM_FRAME_CONTAINER; + } else { + return STREAM_FRAME_NATIVE; + } +} + +static void before_reattach_stream(DisplayChannel *display, + Stream *stream, Drawable *new_frame) +{ + DrawablePipeItem *dpi; + DisplayChannelClient *dcc; + int index; + StreamAgent *agent; + RingItem *ring_item, *next; + + spice_return_if_fail(stream->current); + + if (!red_channel_is_connected(RED_CHANNEL(display))) { + return; + } + + if (new_frame->process_commands_generation == stream->current->process_commands_generation) { + spice_debug("ignoring drop, same process_commands_generation as previous frame"); + return; + } + + index = get_stream_id(display, stream); + DRAWABLE_FOREACH_DPI_SAFE(stream->current, ring_item, next, dpi) { + dcc = dpi->dcc; + agent = &dcc->stream_agents[index]; + + if (!dcc->use_mjpeg_encoder_rate_control && + !dcc->common.is_low_bandwidth) { + continue; + } + + if (pipe_item_is_linked(&dpi->dpi_pipe_item)) { +#ifdef STREAM_STATS + agent->stats.num_drops_pipe++; +#endif + if (dcc->use_mjpeg_encoder_rate_control) { + mjpeg_encoder_notify_server_frame_drop(agent->mjpeg_encoder); + } else { + ++agent->drops; + } + } + } + + + FOREACH_DCC(display, ring_item, next, dcc) { + double drop_factor; + + agent = &dcc->stream_agents[index]; + + if (dcc->use_mjpeg_encoder_rate_control) { + continue; + } + if (agent->frames / agent->fps < FPS_TEST_INTERVAL) { + agent->frames++; + continue; + } + drop_factor = ((double)agent->frames - (double)agent->drops) / + (double)agent->frames; + spice_debug("stream %d: #frames %u #drops %u", index, agent->frames, agent->drops); + if (drop_factor == 1) { + if (agent->fps < MAX_FPS) { + agent->fps++; + spice_debug("stream %d: fps++ %u", index, agent->fps); + } + } else if (drop_factor < 0.9) { + if (agent->fps > 1) { + agent->fps--; + spice_debug("stream %d: fps--%u", index, agent->fps); + } + } + agent->frames = 1; + agent->drops = 0; + } +} + +static Stream *display_channel_stream_try_new(DisplayChannel *display) +{ + Stream *stream; + if (!display->free_streams) { + return NULL; + } + stream = display->free_streams; + display->free_streams = display->free_streams->next; + return stream; +} + +static void display_channel_create_stream(DisplayChannel *display, Drawable *drawable) +{ + DisplayChannelClient *dcc; + RingItem *dcc_ring_item, *next; + Stream *stream; + SpiceRect* src_rect; + + spice_assert(!drawable->stream); + + if (!(stream = display_channel_stream_try_new(display))) { + return; + } + + spice_assert(drawable->red_drawable->type == QXL_DRAW_COPY); + src_rect = &drawable->red_drawable->u.copy.src_area; + + ring_add(&display->streams, &stream->link); + stream->current = drawable; + stream->last_time = drawable->creation_time; + stream->width = src_rect->right - src_rect->left; + stream->height = src_rect->bottom - src_rect->top; + stream->dest_area = drawable->red_drawable->bbox; + stream->refs = 1; + SpiceBitmap *bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap; + stream->top_down = !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN); + drawable->stream = stream; + stream->input_fps = MAX_FPS; + stream->num_input_frames = 0; + stream->input_fps_start_time = drawable->creation_time; + display->streams_size_total += stream->width * stream->height; + display->stream_count++; + FOREACH_DCC(display, dcc_ring_item, next, dcc) { + dcc_create_stream(dcc, stream); + } + spice_debug("stream %d %dx%d (%d, %d) (%d, %d)", + (int)(stream - display->streams_buf), stream->width, + stream->height, stream->dest_area.left, stream->dest_area.top, + stream->dest_area.right, stream->dest_area.bottom); + return; +} + +// returns whether a stream was created +static int stream_add_frame(DisplayChannel *display, + Drawable *frame_drawable, + int frames_count, + int gradual_frames_count, + int last_gradual_frame) +{ + update_copy_graduality(frame_drawable); + frame_drawable->frames_count = frames_count + 1; + frame_drawable->gradual_frames_count = gradual_frames_count; + + if (frame_drawable->copy_bitmap_graduality != BITMAP_GRADUAL_LOW) { + if ((frame_drawable->frames_count - last_gradual_frame) > + RED_STREAM_FRAMES_RESET_CONDITION) { + frame_drawable->frames_count = 1; + frame_drawable->gradual_frames_count = 1; + } else { + frame_drawable->gradual_frames_count++; + } + + frame_drawable->last_gradual_frame = frame_drawable->frames_count; + } else { + frame_drawable->last_gradual_frame = last_gradual_frame; + } + + if (is_stream_start(frame_drawable)) { + display_channel_create_stream(display, frame_drawable); + return TRUE; + } + return FALSE; +} + +/* TODO: document the difference between the 2 functions below */ +void stream_trace_update(DisplayChannel *display, Drawable *drawable) +{ + ItemTrace *trace; + ItemTrace *trace_end; + RingItem *item; + + if (drawable->stream || !drawable->streamable || drawable->frames_count) { + return; + } + + FOREACH_STREAMS(display, item) { + Stream *stream = SPICE_CONTAINEROF(item, Stream, link); + int is_next_frame = is_next_stream_frame(display, + drawable, + stream->width, + stream->height, + &stream->dest_area, + stream->last_time, + stream, + TRUE); + if (is_next_frame != STREAM_FRAME_NONE) { + if (stream->current) { + stream->current->streamable = FALSE; //prevent item trace + before_reattach_stream(display, stream, drawable); + detach_stream(display, stream, FALSE); + } + attach_stream(display, drawable, stream); + if (is_next_frame == STREAM_FRAME_CONTAINER) { + drawable->sized_stream = stream; + } + return; + } + } + + trace = display->items_trace; + trace_end = trace + NUM_TRACE_ITEMS; + for (; trace < trace_end; trace++) { + if (is_next_stream_frame(display, drawable, trace->width, trace->height, + &trace->dest_area, trace->time, NULL, FALSE) != + STREAM_FRAME_NONE) { + if (stream_add_frame(display, drawable, + trace->frames_count, + trace->gradual_frames_count, + trace->last_gradual_frame)) { + return; + } + } + } +} + +void stream_maintenance(DisplayChannel *display, + Drawable *candidate, Drawable *prev) +{ + int is_next_frame; + + if (candidate->stream) { + return; + } + + if (prev->stream) { + Stream *stream = prev->stream; + + is_next_frame = is_next_stream_frame(display, candidate, + stream->width, stream->height, + &stream->dest_area, stream->last_time, + stream, TRUE); + if (is_next_frame != STREAM_FRAME_NONE) { + before_reattach_stream(display, stream, candidate); + detach_stream(display, stream, FALSE); + prev->streamable = FALSE; //prevent item trace + attach_stream(display, candidate, stream); + if (is_next_frame == STREAM_FRAME_CONTAINER) { + candidate->sized_stream = stream; + } + } + } else if (candidate->streamable) { + SpiceRect* prev_src = &prev->red_drawable->u.copy.src_area; + + is_next_frame = + is_next_stream_frame(display, candidate, prev_src->right - prev_src->left, + prev_src->bottom - prev_src->top, + &prev->red_drawable->bbox, prev->creation_time, + prev->stream, + FALSE); + if (is_next_frame != STREAM_FRAME_NONE) { + stream_add_frame(display, candidate, + prev->frames_count, + prev->gradual_frames_count, + prev->last_gradual_frame); + } + } +} diff --git a/server/stream.h b/server/stream.h index 47049370..bf78137e 100644 --- a/server/stream.h +++ b/server/stream.h @@ -39,6 +39,7 @@ #define RED_STREAM_CLIENT_REPORT_TIMEOUT 1000 // milliseconds #define RED_STREAM_DEFAULT_HIGH_START_BIT_RATE (10 * 1024 * 1024) // 10Mbps #define RED_STREAM_DEFAULT_LOW_START_BIT_RATE (2.5 * 1024 * 1024) // 2.5Mbps +#define MAX_FPS 30 /* move back to display_channel once struct private */ typedef struct DisplayChannel DisplayChannel; @@ -143,5 +144,13 @@ void stream_unref (DisplayChan void stream_agent_unref (DisplayChannel *display, StreamAgent *agent); void stream_agent_stats_print (StreamAgent *agent); +void stream_trace_update (DisplayChannel *display, + Drawable *drawable); +void stream_maintenance (DisplayChannel *display, + Drawable *candidate, + Drawable *prev); + +void attach_stream(DisplayChannel *display, Drawable *drawable, Stream *stream); +void detach_stream(DisplayChannel *display, Stream *stream, int detach_sized); #endif /* STREAM_H */ diff --git a/server/tree.c b/server/tree.c index ad31f09b..1daa90c2 100644 --- a/server/tree.c +++ b/server/tree.c @@ -250,3 +250,53 @@ void container_cleanup(Container *container) container = next; } } + +/* FIXME: document weird function: go down containers, and return drawable->shadow? */ +Shadow* tree_item_find_shadow(TreeItem *item) +{ + while (item->type == TREE_ITEM_TYPE_CONTAINER) { + if (!(item = (TreeItem *)ring_get_tail(&((Container *)item)->items))) { + return NULL; + } + } + + if (item->type != TREE_ITEM_TYPE_DRAWABLE) { + return NULL; + } + + return ((DrawItem *)item)->shadow; +} + +Ring *tree_item_container_items(TreeItem *item, Ring *ring) +{ + return (item->container) ? &item->container->items : ring; +} + +int tree_item_contained_by(TreeItem *item, Ring *ring) +{ + spice_assert(item && ring); + do { + Ring *now = tree_item_container_items(item, ring); + if (now == ring) { + return TRUE; + } + } while ((item = (TreeItem *)item->container)); + + return FALSE; +} + +void draw_item_remove_shadow(DrawItem *item) +{ + Shadow *shadow; + + if (!item->shadow) { + return; + } + shadow = item->shadow; + item->shadow = NULL; + /* shadow_free? */ + ring_remove(&shadow->base.siblings_link); + region_destroy(&shadow->base.rgn); + region_destroy(&shadow->on_hold); + free(shadow); +} diff --git a/server/tree.h b/server/tree.h index 01d4ff9e..8b9c6baa 100644 --- a/server/tree.h +++ b/server/tree.h @@ -80,6 +80,11 @@ static inline int is_opaque_item(TreeItem *item) } void tree_item_dump (TreeItem *item); +Shadow* tree_item_find_shadow (TreeItem *item); +int tree_item_contained_by (TreeItem *item, Ring *ring); +Ring* tree_item_container_items (TreeItem *item, Ring *ring); + +void draw_item_remove_shadow (DrawItem *item); Shadow* shadow_new (DrawItem *item, const SpicePoint *delta); Container* container_new (DrawItem *item); void container_free (Container *container); |