See spice-protocol commit f24154b87f92ae65b1aa83b97378f9c687d09017 for complete details. --- configure.ac | 4 +- server/red_dispatcher.c | 50 ++++- server/red_dispatcher.h | 4 + server/red_worker.c | 413 +++++++++++++++++++++++++++++++++---- server/spice-server.syms | 5 + server/spice.h | 11 +- server/tests/test_display_base.c | 82 +++++++- server/tests/test_display_base.h | 4 +- server/tests/test_display_no_ssl.c | 3 +- 9 files changed, 519 insertions(+), 57 deletions(-) diff --git a/configure.ac b/configure.ac index fa1ba31..21831ee 100644 --- a/configure.ac +++ b/configure.ac @@ -13,9 +13,9 @@ AC_PREREQ([2.57]) # 4. Follow the libtool manual for the so version: # http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html -m4_define([SPICE_CURRENT], [9]) +m4_define([SPICE_CURRENT], [10]) m4_define([SPICE_REVISION], [0]) -m4_define([SPICE_AGE], [8]) +m4_define([SPICE_AGE], [9]) # Note on the library name on linux (SONAME) produced by libtool (for reference, gleaned # from looking at libtool 2.4.2) diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c index 03a4c4a..5ab5d26 100644 --- a/server/red_dispatcher.c +++ b/server/red_dispatcher.c @@ -508,7 +508,8 @@ static void red_dispatcher_create_primary_surface_complete(RedDispatcher *dispat static void red_dispatcher_create_primary_surface_async(RedDispatcher *dispatcher, uint32_t surface_id, - QXLDevSurfaceCreate *surface, uint64_t cookie) + QXLDevSurfaceCreate *surface, uint64_t cookie, + uint64_t shm_offset) { RedWorkerMessageCreatePrimarySurfaceAsync payload; RedWorkerMessage message = RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC; @@ -517,18 +518,20 @@ red_dispatcher_create_primary_surface_async(RedDispatcher *dispatcher, uint32_t payload.base.cmd = async_command_alloc(dispatcher, message, cookie); payload.surface_id = surface_id; payload.surface = *surface; + payload.shm_offset = shm_offset; dispatcher_send_message(&dispatcher->dispatcher, message, &payload); } static void red_dispatcher_create_primary_surface_sync(RedDispatcher *dispatcher, uint32_t surface_id, - QXLDevSurfaceCreate *surface) + QXLDevSurfaceCreate *surface, uint64_t shm_offset) { RedWorkerMessageCreatePrimarySurface payload = {0,}; dispatcher->surface_create = *surface; payload.surface_id = surface_id; payload.surface = *surface; + payload.shm_offset = shm_offset; dispatcher_send_message(&dispatcher->dispatcher, RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE, &payload); @@ -537,19 +540,23 @@ red_dispatcher_create_primary_surface_sync(RedDispatcher *dispatcher, uint32_t s static void red_dispatcher_create_primary_surface(RedDispatcher *dispatcher, uint32_t surface_id, - QXLDevSurfaceCreate *surface, int async, uint64_t cookie) + QXLDevSurfaceCreate *surface, int async, + uint64_t cookie, uint64_t shm_offset) { if (async) { - red_dispatcher_create_primary_surface_async(dispatcher, surface_id, surface, cookie); + red_dispatcher_create_primary_surface_async(dispatcher, surface_id, + surface, cookie, shm_offset); } else { - red_dispatcher_create_primary_surface_sync(dispatcher, surface_id, surface); + red_dispatcher_create_primary_surface_sync(dispatcher, surface_id, + surface, shm_offset); } } static void qxl_worker_create_primary_surface(QXLWorker *qxl_worker, uint32_t surface_id, QXLDevSurfaceCreate *surface) { - red_dispatcher_create_primary_surface((RedDispatcher*)qxl_worker, surface_id, surface, 0, 0); + red_dispatcher_create_primary_surface((RedDispatcher*)qxl_worker, + surface_id, surface, 0, 0, 0); } static void red_dispatcher_reset_image_cache(RedDispatcher *dispatcher) @@ -926,7 +933,8 @@ SPICE_GNUC_VISIBLE void spice_qxl_create_primary_surface(QXLInstance *instance, uint32_t surface_id, QXLDevSurfaceCreate *surface) { - red_dispatcher_create_primary_surface(instance->st->dispatcher, surface_id, surface, 0, 0); + red_dispatcher_create_primary_surface(instance->st->dispatcher, surface_id, + surface, 0, 0, 0); } SPICE_GNUC_VISIBLE @@ -983,7 +991,18 @@ SPICE_GNUC_VISIBLE void spice_qxl_create_primary_surface_async(QXLInstance *instance, uint32_t surface_id, QXLDevSurfaceCreate *surface, uint64_t cookie) { - red_dispatcher_create_primary_surface(instance->st->dispatcher, surface_id, surface, 1, cookie); + red_dispatcher_create_primary_surface(instance->st->dispatcher, surface_id, + surface, 1, cookie, 0); +} + +SPICE_GNUC_VISIBLE +void spice_qxl_create_primary_surface_shm(QXLInstance *instance, uint32_t surface_id, + QXLDevSurfaceCreate *surface, uint64_t cookie, + uint64_t shm_offset) +{ + red_dispatcher_create_primary_surface(instance->st->dispatcher, + surface_id, surface, !!cookie, + cookie, shm_offset); } SPICE_GNUC_VISIBLE @@ -1072,6 +1091,17 @@ static RedChannel *red_dispatcher_cursor_channel_create(RedDispatcher *dispatche return cursor_channel; } +int qxl_interface_shared_memory_available(QXLInstance *qin) +{ + if (qin->st->qif->base.major_version < 3 || + (qin->st->qif->base.major_version == 3 && + qin->st->qif->base.minor_version < 4) || + !qin->st->qif->shared_memory_file_name) { + return 0; + } + return qin->st->qif->shared_memory_file_name(qin) != NULL; +} + RedDispatcher *red_dispatcher_init(QXLInstance *qxl) { RedDispatcher *red_dispatcher; @@ -1161,6 +1191,10 @@ RedDispatcher *red_dispatcher_init(QXLInstance *qxl) red_channel_set_data(display_channel, red_dispatcher); red_channel_set_cap(display_channel, SPICE_DISPLAY_CAP_MONITORS_CONFIG); red_channel_set_cap(display_channel, SPICE_DISPLAY_CAP_STREAM_REPORT); + if (qxl_interface_shared_memory_available(qxl)) { + red_channel_set_cap(display_channel, + SPICE_DISPLAY_CAP_SHARED_MEMORY); + } reds_register_channel(display_channel); } diff --git a/server/red_dispatcher.h b/server/red_dispatcher.h index 7d23b11..eece032 100644 --- a/server/red_dispatcher.h +++ b/server/red_dispatcher.h @@ -25,6 +25,8 @@ typedef struct AsyncCommand AsyncCommand; struct RedDispatcher *red_dispatcher_init(QXLInstance *qxl); +int qxl_interface_shared_memory_available(QXLInstance *qxl); + void red_dispatcher_set_mm_time(uint32_t); void red_dispatcher_on_ic_change(void); void red_dispatcher_on_sv_change(void); @@ -130,11 +132,13 @@ typedef struct RedWorkerMessageCreatePrimarySurfaceAsync { RedWorkerMessageAsync base; uint32_t surface_id; QXLDevSurfaceCreate surface; + uint64_t shm_offset; } RedWorkerMessageCreatePrimarySurfaceAsync; typedef struct RedWorkerMessageCreatePrimarySurface { uint32_t surface_id; QXLDevSurfaceCreate surface; + uint64_t shm_offset; } RedWorkerMessageCreatePrimarySurface; typedef struct RedWorkerMessageResetImageCache { diff --git a/server/red_worker.c b/server/red_worker.c index 396be4a..c4ffbe0 100644 --- a/server/red_worker.c +++ b/server/red_worker.c @@ -44,8 +44,10 @@ #include <pthread.h> #include <netinet/tcp.h> #include <setjmp.h> -#include <openssl/ssl.h> #include <inttypes.h> +#include <sys/mman.h> + +#include <openssl/ssl.h> #include <spice/protocol.h> #include <spice/qxl_dev.h> @@ -298,6 +300,8 @@ enum { PIPE_ITEM_TYPE_DESTROY_SURFACE, PIPE_ITEM_TYPE_MONITORS_CONFIG, PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT, + PIPE_ITEM_TYPE_SHM_OFFER, + PIPE_ITEM_TYPE_SHM_DAMAGE, }; typedef struct VerbItem { @@ -336,6 +340,7 @@ struct CacheItem { typedef struct SurfaceCreateItem { SpiceMsgSurfaceCreate surface_create; + uint64_t shm_offset; PipeItem pipe_item; } SurfaceCreateItem; @@ -362,6 +367,17 @@ typedef struct StreamActivateReportItem { uint32_t stream_id; } StreamActivateReportItem; +typedef struct ShmOfferItem { + PipeItem pipe_item; + uint8_t name[128]; +} ShmOfferItem; + +typedef struct ShmDamageItem { + PipeItem pipe_item; + SpiceRect box; + SpiceClip *clip; +} ShmDamageItem; + typedef struct CursorItem { uint32_t group_id; int refs; @@ -677,6 +693,11 @@ struct DisplayChannelClient { int expect_init; + struct { + int use; /* if 0 shared memory is unused */ + int got_reply; /* 0/1 */ + } shm; + PixmapCache *pixmap_cache; uint32_t pixmap_cache_generation; int pending_pixmaps_sync; @@ -882,6 +903,7 @@ typedef struct DrawContext { int32_t stride; uint32_t format; void *line_0; + uint64_t shm_offset; } DrawContext; typedef struct RedSurface { @@ -1077,6 +1099,11 @@ static void cursor_channel_client_release_item_after_push(CursorChannelClient *c static void red_push_monitors_config(DisplayChannelClient *dcc); +static void push_shm_damage_from_drawable(DisplayChannelClient *dcc, + Drawable *drawable); +static void push_shm_damage(DisplayChannelClient *dcc, SpiceRect *box, + SpiceClip *clip); + /* * Macros to make iterating over stuff easier * The two collections we iterate over: @@ -1243,9 +1270,11 @@ static inline int is_primary_surface(RedWorker *worker, uint32_t surface_id) return FALSE; } -static inline void __validate_surface(RedWorker *worker, uint32_t surface_id) +static inline int __validate_surface(RedWorker *worker, uint32_t surface_id) { - spice_warn_if(surface_id >= worker->n_surfaces); + spice_return_val_if_fail(surface_id < worker->n_surfaces, 0); + spice_return_val_if_fail(surface_id >= 1, 0); + return 1; } static inline int validate_surface(RedWorker *worker, uint32_t surface_id) @@ -1379,6 +1408,10 @@ static inline void red_handle_drawable_surfaces_client_synced( RedWorker *worker = DCC_TO_WORKER(dcc); int x; + if (dcc->shm.use) { + return; + } + for (x = 0; x < 3; ++x) { int surface_id; @@ -1433,6 +1466,7 @@ static inline DrawablePipeItem *get_drawable_pipe_item(DisplayChannelClient *dcc { DrawablePipeItem *dpi; + spice_assert(!dcc->shm.use); dpi = spice_malloc0(sizeof(*dpi)); dpi->drawable = drawable; dpi->dcc = dcc; @@ -1455,6 +1489,10 @@ static inline void red_pipe_add_drawable(DisplayChannelClient *dcc, Drawable *dr { DrawablePipeItem *dpi; + if (dcc->shm.use) { + /* we get here via red_current_add */ + return; + } red_handle_drawable_surfaces_client_synced(dcc, drawable); dpi = get_drawable_pipe_item(dcc, drawable); red_channel_client_pipe_add(&dcc->common.base, &dpi->dpi_pipe_item); @@ -1464,10 +1502,28 @@ static inline void red_pipes_add_drawable(RedWorker *worker, Drawable *drawable) { DisplayChannelClient *dcc; RingItem *dcc_ring_item, *next; + int did_draw = 0; spice_warn_if(!ring_is_empty(&drawable->pipes)); WORKER_FOREACH_DCC_SAFE(worker, dcc_ring_item, next, dcc) { - red_pipe_add_drawable(dcc, drawable); + if (dcc->shm.use) { + if (drawable->red_drawable->surface_id != 0) { + continue; + } + if (!dcc->shm.got_reply) { + spice_debug("read a drawable before client replied to shm offer"); + continue; + } + /* TODO: rate limit */ + if (!did_draw) { + red_update_area(worker, &drawable->red_drawable->bbox, 0); + //red_draw_drawable(worker, drawable); + did_draw = 1; + } + push_shm_damage_from_drawable(dcc, drawable); + } else { + red_pipe_add_drawable(dcc, drawable); + } } } @@ -1679,6 +1735,7 @@ static inline void red_destroy_surface_item(RedWorker *worker, SurfaceDestroyItem *destroy; RedChannel *channel; + spice_assert(!dcc->shm.use || surface_id == 0); if (!dcc || worker->display_channel->common.during_target_migrate || !dcc->surface_client_created[surface_id]) { return; @@ -1713,7 +1770,9 @@ static inline void red_destroy_surface(RedWorker *worker, uint32_t surface_id) region_destroy(&surface->draw_dirty_region); surface->context.canvas = NULL; WORKER_FOREACH_DCC_SAFE(worker, link, next, dcc) { - red_destroy_surface_item(worker, dcc, surface_id); + if (surface_id == 0 || !dcc->shm.use) { + red_destroy_surface_item(worker, dcc, surface_id); + } } spice_warn_if(!ring_is_empty(&surface->depend_on_me)); @@ -2501,6 +2560,8 @@ static inline void red_detach_stream(RedWorker *worker, Stream *stream, int deta static StreamClipItem *__new_stream_clip(DisplayChannelClient* dcc, StreamAgent *agent) { StreamClipItem *item = spice_new(StreamClipItem, 1); + + spice_assert(!dcc->shm.use); red_channel_pipe_item_init(dcc->common.base.channel, (PipeItem *)item, PIPE_ITEM_TYPE_STREAM_CLIP); @@ -3017,6 +3078,7 @@ static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream) { StreamAgent *agent = &dcc->stream_agents[get_stream_id(dcc->common.worker, stream)]; + spice_assert(!dcc->shm.use); stream->refs++; spice_assert(region_is_empty(&agent->vis_region)); if (stream->current) { @@ -3140,6 +3202,7 @@ static void red_display_client_init_streams(DisplayChannelClient *dcc) RedWorker *worker = dcc->common.worker; RedChannel *channel = dcc->common.base.channel; + spice_assert(!dcc->shm.use); for (i = 0; i < NUM_STREAMS; i++) { StreamAgent *agent = &dcc->stream_agents[i]; agent->stream = &worker->streams_buf[i]; @@ -4218,7 +4281,8 @@ cleanup: static inline void red_create_surface(RedWorker *worker, 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 *line_0, int data_is_valid, int send_client, + uint64_t shm_offset); static inline void red_process_surface(RedWorker *worker, RedSurfaceCmd *surface, uint32_t group_id, int loadvm) @@ -4228,7 +4292,10 @@ static inline void red_process_surface(RedWorker *worker, RedSurfaceCmd *surface uint8_t *data; surface_id = surface->surface_id; - __validate_surface(worker, surface_id); + if (!__validate_surface(worker, surface_id)) { + exit(-1); + return; + } red_surface = &worker->surfaces[surface_id]; @@ -4246,7 +4313,8 @@ static inline void red_process_surface(RedWorker *worker, RedSurfaceCmd *surface height, stride, surface->u.surface_create.format, data, reloaded_surface, // reloaded surfaces will be sent on demand - !reloaded_surface); + !reloaded_surface, + 0 /* shm_offset for reloaded surface TODO */); set_surface_release_info(worker, surface_id, 1, surface->release_info, group_id); break; } @@ -4345,6 +4413,56 @@ static void localize_mask(RedWorker *worker, SpiceQMask *mask, SpiceImage *image } } +int write_ppm_32(uint8_t *d_data, int d_width, int d_height) +{ + FILE *fp; + uint8_t *p; + int n; + static int i; + char outf[128]; + + snprintf(outf, sizeof(outf), "dump.%010d.ppm", i++); + + fp = fopen(outf,"w"); + if (NULL == fp) { + fprintf(stderr, "can't open %s: %s\n", outf, strerror(errno)); + return -1; + } + fprintf(fp, "P6\n%d %d\n255\n", + d_width, d_height); + n = d_width * d_height; + p = d_data; + while (n > 0) { + fputc(p[2], fp); + fputc(p[1], fp); + fputc(p[0], fp); + p += 4; + n--; + } + fclose(fp); + return 0; +} + +uint8_t *worker_surface_start(RedWorker *worker, RedSurface *in_surface, + int surface_id) +{ + RedSurface *surface = in_surface ? in_surface + : &worker->surfaces[surface_id]; + DrawContext *context = &surface->context; + uint8_t *data; + + if (!context->canvas) { + return NULL; + } + + if (context->stride < 0) { + data = (uint8_t *)context->line_0 - (context->height - 1) * (-context->stride); + } else { + data = context->line_0; + } + return data; +} + static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable) { RedSurface *surface; @@ -4482,6 +4600,15 @@ static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable) default: spice_warning("invalid type"); } + { +#if 0 + DrawContext *context = &surface->context; + write_ppm_32(worker_surface_start(worker, NULL, 0), + context->width, context->width); +#else + (void)write_ppm_32; +#endif + } } #ifndef DRAW_ALL @@ -5130,6 +5257,7 @@ static ImageItem *red_add_surface_area_image(DisplayChannelClient *dcc, int surf int all_set; spice_assert(area); + spice_assert(!dcc->shm.use); width = area->right - area->left; height = area->bottom - area->top; @@ -5196,10 +5324,14 @@ static void red_push_surface_image(DisplayChannelClient *dcc, int surface_id) area.right = surface->context.width; area.bottom = surface->context.height; - /* not allowing lossy compression because probably, especially if it is a primary surface, - it combines both "picture-like" areas with areas that are more "artificial"*/ - red_add_surface_area_image(dcc, surface_id, &area, NULL, FALSE); - red_channel_client_push(&dcc->common.base); + if (surface_id == 0 && dcc->shm.use) { + push_shm_damage(dcc, &area, NULL); + } else { + /* not allowing lossy compression because probably, especially if it is a primary surface, + it combines both "picture-like" areas with areas that are more "artificial"*/ + red_add_surface_area_image(dcc, surface_id, &area, NULL, FALSE); + red_channel_client_push(&dcc->common.base); + } } typedef struct { @@ -8962,14 +9094,28 @@ static void red_marshall_cursor(RedChannelClient *rcc, } static void red_marshall_surface_create(RedChannelClient *rcc, - SpiceMarshaller *base_marshaller, SpiceMsgSurfaceCreate *surface_create) + SpiceMarshaller *base_marshaller, SpiceMsgSurfaceCreate *surface_create, + uint64_t shm_offset) { 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); + if (dcc->shm.use) { + SpiceMsgSurfaceCreateShm create_shm; + create_shm.shm_offset = shm_offset; + create_shm.surface_id = surface_create->surface_id; + create_shm.width = surface_create->width; + create_shm.height = surface_create->height; + create_shm.format = surface_create->format; + create_shm.flags = surface_create->flags; + /* newer versions support a message containing shared memory offsets */ + red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SURFACE_CREATE_SHM, NULL); + spice_marshall_msg_display_surface_create_shm(base_marshaller, &create_shm); + } else { + 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, @@ -9029,12 +9175,37 @@ static void red_marshall_stream_activate_report(RedChannelClient *rcc, spice_marshall_msg_display_stream_activate_report(base_marshaller, &msg); } +static void red_marshall_shm_offer(RedChannelClient *rcc, + SpiceMarshaller *base_marshaller, + ShmOfferItem *offer) +{ + SpiceMsgDisplayShmOffer msg; + + red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SHM_OFFER, NULL); + memcpy(msg.name, offer->name, sizeof(msg.name)); + spice_marshall_msg_display_shm_offer(base_marshaller, &msg); +} + +static void red_marshall_shm_damage(RedChannelClient *rcc, + SpiceMarshaller *base_marshaller, + ShmDamageItem *damage) +{ + SpiceMsgDisplayShmDamage msg; + + red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SHM_DAMAGE, NULL); + msg.box = damage->box; + msg.clip.type = damage->clip->type; + msg.clip.rects = damage->clip->rects; + spice_marshall_msg_display_shm_damage(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); @@ -9084,7 +9255,9 @@ static void display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item 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); + spice_assert(!dcc->shm.use || surface_create->surface_create.surface_id == 0); + red_marshall_surface_create(rcc, m, &surface_create->surface_create, + surface_create->shm_offset); break; } case PIPE_ITEM_TYPE_DESTROY_SURFACE: { @@ -9106,6 +9279,14 @@ static void display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item red_marshall_stream_activate_report(rcc, m, report_item->stream_id); break; } + case PIPE_ITEM_TYPE_SHM_OFFER: + red_marshall_shm_offer(rcc, m, SPICE_CONTAINEROF(pipe_item, + ShmOfferItem, pipe_item)); + break; + case PIPE_ITEM_TYPE_SHM_DAMAGE: + red_marshall_shm_damage(rcc, m, SPICE_CONTAINEROF(pipe_item, + ShmDamageItem, pipe_item)); + break; default: spice_error("invalid pipe item type"); } @@ -9395,7 +9576,8 @@ static inline void *create_canvas_for_surface(RedWorker *worker, RedSurface *sur static SurfaceCreateItem *get_surface_create_item( RedChannel* channel, uint32_t surface_id, uint32_t width, - uint32_t height, uint32_t format, uint32_t flags) + uint32_t height, uint32_t format, uint32_t flags, + uint64_t shm_offset) { SurfaceCreateItem *create; @@ -9407,6 +9589,7 @@ static SurfaceCreateItem *get_surface_create_item( create->surface_create.height = height; create->surface_create.flags = flags; create->surface_create.format = format; + create->shm_offset = shm_offset; red_channel_pipe_item_init(channel, &create->pipe_item, PIPE_ITEM_TYPE_CREATE_SURFACE); @@ -9418,17 +9601,24 @@ static inline void red_create_surface_item(DisplayChannelClient *dcc, int surfac RedSurface *surface; SurfaceCreateItem *create; RedWorker *worker = dcc ? dcc->common.worker : NULL; - uint32_t flags = is_primary_surface(worker, surface_id) ? SPICE_SURFACE_FLAGS_PRIMARY : 0; + uint32_t is_primary = is_primary_surface(worker, surface_id) ? + SPICE_SURFACE_FLAGS_PRIMARY : 0; + uint32_t is_upside_down; + uint32_t flags; + spice_assert(!dcc->shm.use || surface_id == 0); /* don't send redundant create surface commands to client */ if (!dcc || worker->display_channel->common.during_target_migrate || dcc->surface_client_created[surface_id]) { return; } surface = &worker->surfaces[surface_id]; + is_upside_down = surface->context.top_down ? + SPICE_SURFACE_FLAGS_TOP_DOWN : 0; + flags = is_primary | is_upside_down; create = get_surface_create_item(dcc->common.base.channel, surface_id, surface->context.width, surface->context.height, - surface->context.format, flags); + surface->context.format, flags, surface->context.shm_offset); dcc->surface_client_created[surface_id] = TRUE; red_channel_client_pipe_add(&dcc->common.base, &create->pipe_item); } @@ -9439,7 +9629,9 @@ static void red_worker_create_surface_item(RedWorker *worker, int surface_id) RingItem *item, *next; WORKER_FOREACH_DCC_SAFE(worker, item, next, dcc) { - red_create_surface_item(dcc, surface_id); + if (surface_id == 0 || !dcc->shm.use) { + red_create_surface_item(dcc, surface_id); + } } } @@ -9450,15 +9642,19 @@ static void red_worker_push_surface_image(RedWorker *worker, int surface_id) RingItem *item, *next; WORKER_FOREACH_DCC_SAFE(worker, item, next, dcc) { - red_push_surface_image(dcc, surface_id); + if (!dcc->shm.use) { + red_push_surface_image(dcc, surface_id); + } } } static inline void red_create_surface(RedWorker *worker, 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 *line_0, int data_is_valid, int send_client, + uint64_t shm_offset) { RedSurface *surface = &worker->surfaces[surface_id]; + uint8_t *surface_start; uint32_t i; spice_warn_if(surface->context.canvas); @@ -9469,8 +9665,14 @@ static inline void red_create_surface(RedWorker *worker, uint32_t surface_id, ui surface->context.format = format; surface->context.stride = stride; surface->context.line_0 = line_0; + surface->context.shm_offset = shm_offset; if (!data_is_valid) { - memset((char *)line_0 + (int32_t)(stride * (height - 1)), 0, height*abs(stride)); + if (stride < 0) { + surface_start = (uint8_t *)line_0 + (int32_t)(stride * (height - 1)); + } else { + surface_start = line_0; + } + memset(surface_start, 0, height * abs(stride)); } surface->create.info = NULL; surface->destroy.info = NULL; @@ -9614,7 +9816,9 @@ static void push_new_primary_surface(DisplayChannelClient *dcc) { RedChannelClient *rcc = &dcc->common.base; - red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE); + if (!dcc->shm.use) { + red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE); + } red_create_surface_item(dcc, 0); red_channel_client_push(rcc); } @@ -9649,22 +9853,97 @@ static int display_channel_client_wait_for_init(DisplayChannelClient *dcc) return FALSE; } -static void on_new_display_channel_client(DisplayChannelClient *dcc) +static SpiceClip *clip_copy(const SpiceClip *clip) +{ + SpiceClipRects *rects = clip && clip->rects ? clip->rects : NULL; + int num_rects = rects ? rects->num_rects : 0; + SpiceClip *ret = spice_malloc(sizeof(*ret) + + sizeof(*ret->rects) + num_rects * sizeof(SpiceRect)); + + ret->type = clip ? clip->type : 0; + ret->rects = (SpiceClipRects *)&ret[1]; + ret->rects->num_rects = num_rects; + if (clip && clip->rects) { + memcpy(ret->rects->rects, clip->rects->rects, + sizeof(SpiceRect) * clip->rects->num_rects); + } + return ret; +} + +static void push_shm_damage(DisplayChannelClient *dcc, SpiceRect *box, + SpiceClip *clip) { - DisplayChannel *display_channel = DCC_TO_DC(dcc); - RedWorker *worker = display_channel->common.worker; RedChannelClient *rcc = &dcc->common.base; + ShmDamageItem *shm_damage_item; - red_channel_client_push_set_ack(&dcc->common.base); + shm_damage_item = spice_malloc0(sizeof(*shm_damage_item)); + /* TODO: not really really required, but if the drawable is destroyed + * before the pipe item is marshalled, we are sending a redundant damage + * message - so keeping a drawable here and checking during marshalling + * time makes sense. Also avoids the copy of the clips. (so should do this) + */ + shm_damage_item->box = *box; + shm_damage_item->clip = clip_copy(clip); + spice_debug("pushing shm_damage"); + red_channel_pipe_item_init(dcc->common.base.channel, + &shm_damage_item->pipe_item, + PIPE_ITEM_TYPE_SHM_DAMAGE); + red_channel_client_pipe_add(&dcc->common.base, + &shm_damage_item->pipe_item); + red_channel_client_push(rcc); +} - if (red_channel_client_waits_for_migrate_data(rcc)) { +static void push_shm_damage_from_drawable(DisplayChannelClient *dcc, + Drawable *drawable) +{ + push_shm_damage(dcc, &drawable->red_drawable->bbox, + &drawable->red_drawable->clip); +} + +static void push_shm_offer(DisplayChannelClient *dcc) +{ + RedChannelClient *rcc = &dcc->common.base; + ShmOfferItem *shm_offer_item; + RedWorker *worker = DCC_TO_WORKER(RCC_TO_DCC(rcc)); + const char *qxl_name; + int qxl_name_length; + + if (!dcc->shm.use) { return; } - if (!display_channel_client_wait_for_init(dcc)) { + if (!qxl_interface_shared_memory_available(worker->qxl)) { + spice_info("shared memory not supported by qxl."); return; } - red_channel_client_ack_zero_messages_window(&dcc->common.base); + qxl_name = worker->qxl->st->qif->shared_memory_file_name(worker->qxl); + if (!qxl_name) { + spice_info("shared memory supported by qxl but not available."); + return; + } + qxl_name_length = strlen(qxl_name); + if (qxl_name_length >= sizeof(shm_offer_item->name)) { + spice_warning("qxl interface shared memory file name length %d > %zd", + qxl_name_length, sizeof(shm_offer_item->name)); + return; + } + spice_info("pushing shm offer for %s", qxl_name); + shm_offer_item = spice_malloc0(sizeof(*shm_offer_item)); + memcpy(shm_offer_item->name, qxl_name, qxl_name_length); + red_channel_pipe_item_init(dcc->common.base.channel, + &shm_offer_item->pipe_item, + PIPE_ITEM_TYPE_SHM_OFFER); + red_channel_client_pipe_add(&dcc->common.base, + &shm_offer_item->pipe_item); + red_channel_client_push(rcc); +} + +static void on_new_display_channel_client_after_shm_offer( + DisplayChannelClient *dcc) +{ + RedChannelClient *rcc = &dcc->common.base; + RedWorker *worker = DCC_TO_WORKER(dcc); + if (worker->surfaces[0].context.canvas) { red_current_flush(worker, 0); push_new_primary_surface(dcc); @@ -9675,6 +9954,35 @@ static void on_new_display_channel_client(DisplayChannelClient *dcc) } } +static void on_new_display_channel_client(DisplayChannelClient *dcc) +{ + RedChannelClient *rcc = &dcc->common.base; + + red_channel_client_push_set_ack(&dcc->common.base); + + if (red_channel_client_waits_for_migrate_data(rcc)) { + return; + } + + if (!display_channel_client_wait_for_init(dcc)) { + return; + } + red_channel_client_ack_zero_messages_window(&dcc->common.base); + + if (red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_SHARED_MEMORY)) { + RedWorker *worker = DCC_TO_WORKER(dcc); + if (qxl_interface_shared_memory_available(worker->qxl)) { + dcc->shm.use = 1; + push_shm_offer(dcc); + } else { + dcc->shm.use = 0; + on_new_display_channel_client_after_shm_offer(dcc); + } + } else { + on_new_display_channel_client_after_shm_offer(dcc); + } +} + static GlzSharedDictionary *_red_find_glz_dictionary(RedClient *client, uint8_t dict_id) { RingItem *now; @@ -10094,6 +10402,20 @@ static int display_channel_handle_stream_report(DisplayChannelClient *dcc, return TRUE; } +static int display_channel_handle_shm_reply(DisplayChannelClient *dcc, + SpiceMsgcDisplayShmReply *shm_reply) +{ + dcc->shm.got_reply = 1; + dcc->shm.use = !!shm_reply->accepted; + if (!dcc->shm.use) { + spice_info("client doesn't want to use shared memory"); + } else { + spice_info("got reply from client, using shared memory!"); + on_new_display_channel_client_after_shm_offer(dcc); + } + return TRUE; +} + static int display_channel_handle_message(RedChannelClient *rcc, uint32_t size, uint16_t type, void *message) { @@ -10110,6 +10432,9 @@ static int display_channel_handle_message(RedChannelClient *rcc, uint32_t size, case SPICE_MSGC_DISPLAY_STREAM_REPORT: return display_channel_handle_stream_report(dcc, (SpiceMsgcDisplayStreamReport *)message); + case SPICE_MSGC_DISPLAY_SHM_REPLY: + return display_channel_handle_shm_reply( + dcc, (SpiceMsgcDisplayShmReply *)message); default: return red_channel_client_handle_message(rcc, size, type, message); } @@ -10460,6 +10785,13 @@ static void display_channel_client_release_item_before_push(DisplayChannelClient free(item); break; } + case PIPE_ITEM_TYPE_SHM_DAMAGE: { + ShmDamageItem *damage = SPICE_CONTAINEROF(item, ShmDamageItem, pipe_item); + + free(damage->clip); + free(damage); + break; + } case PIPE_ITEM_TYPE_INVAL_ONE: case PIPE_ITEM_TYPE_VERB: case PIPE_ITEM_TYPE_MIGRATE_DATA: @@ -10467,6 +10799,7 @@ static void display_channel_client_release_item_before_push(DisplayChannelClient case PIPE_ITEM_TYPE_PIXMAP_RESET: case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE: case PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT: + case PIPE_ITEM_TYPE_SHM_OFFER: free(item); break; default: @@ -10544,6 +10877,7 @@ static void guest_set_client_capabilities(RedWorker *worker) SPICE_DISPLAY_CAP_MONITORS_CONFIG, SPICE_DISPLAY_CAP_COMPOSITE, SPICE_DISPLAY_CAP_A8_SURFACE, + SPICE_DISPLAY_CAP_SHARED_MEMORY, }; if (worker->qxl->st->qif->base.major_version < 3 || @@ -11120,21 +11454,24 @@ static void set_monitors_config_to_primary(RedWorker *worker) } static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id, - QXLDevSurfaceCreate surface) + QXLDevSurfaceCreate surface, + uint64_t shm_offset) { uint8_t *line_0; int error; + int surface_size; 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); + surface_size = surface.height * abs(surface.stride); line_0 = (uint8_t*)get_virt(&worker->mem_slots, surface.mem, - surface.height * abs(surface.stride), - surface.group_id, &error); + surface_size, surface.group_id, &error); if (error) { + spice_warning("error getting memory address for primary surface"); return; } if (surface.stride < 0) { @@ -11142,7 +11479,7 @@ static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id, } red_create_surface(worker, 0, surface.width, surface.height, surface.stride, surface.format, - line_0, surface.flags & QXL_SURF_FLAG_KEEP_DATA, TRUE); + line_0, surface.flags & QXL_SURF_FLAG_KEEP_DATA, TRUE, shm_offset); set_monitors_config_to_primary(worker); if (display_is_connected(worker) && !worker->display_channel->common.during_target_migrate) { @@ -11167,7 +11504,8 @@ 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); + dev_create_primary_surface(worker, msg->surface_id, msg->surface, + msg->shm_offset); } static void dev_destroy_primary_surface(RedWorker *worker, uint32_t surface_id) @@ -11377,7 +11715,8 @@ 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); + dev_create_primary_surface(worker, msg->surface_id, msg->surface, + msg->shm_offset); } /* exception for Dispatcher, data going from red_worker to main thread, diff --git a/server/spice-server.syms b/server/spice-server.syms index 4f2dc37..27baff2 100644 --- a/server/spice-server.syms +++ b/server/spice-server.syms @@ -145,3 +145,8 @@ SPICE_SERVER_0.12.4 { global: spice_server_set_agent_file_xfer; } SPICE_SERVER_0.12.3; + +SPICE_SERVER_0.12.5 { +global: + spice_qxl_create_primary_surface_shm; +} SPICE_SERVER_0.12.4; diff --git a/server/spice.h b/server/spice.h index 6fbb7b2..5c4676e 100644 --- a/server/spice.h +++ b/server/spice.h @@ -23,7 +23,7 @@ #include <spice/qxl_dev.h> #include <spice/vd_agent.h> -#define SPICE_SERVER_VERSION 0x000c04 /* release 0.12.4 */ +#define SPICE_SERVER_VERSION 0x000c05 /* release 0.12.5 */ /* interface base type */ @@ -97,7 +97,7 @@ struct SpiceCoreInterface { #define SPICE_INTERFACE_QXL "qxl" #define SPICE_INTERFACE_QXL_MAJOR 3 -#define SPICE_INTERFACE_QXL_MINOR 3 +#define SPICE_INTERFACE_QXL_MINOR 4 typedef struct QXLInterface QXLInterface; typedef struct QXLInstance QXLInstance; typedef struct QXLState QXLState; @@ -169,6 +169,10 @@ void spice_qxl_monitors_config_async(QXLInstance *instance, QXLPHYSICAL monitors int group_id, uint64_t cookie); /* since spice 0.12.3 */ void spice_qxl_driver_unload(QXLInstance *instance); +/* since spice 0.12.5 */ +void spice_qxl_create_primary_surface_shm(QXLInstance *instance, uint32_t surface_id, + QXLDevSurfaceCreate *surface, uint64_t cookie, + uint64_t shm_offset); typedef struct QXLDrawArea { uint8_t *buf; @@ -250,6 +254,9 @@ struct QXLInterface { * return code. */ int (*client_monitors_config)(QXLInstance *qin, VDAgentMonitorsConfig *monitors_config); + + /* Returns NULL if no file is avilable, or file if it is available */ + const char *(*shared_memory_file_name)(QXLInstance *qin); }; struct QXLInstance { diff --git a/server/tests/test_display_base.c b/server/tests/test_display_base.c index 20c0e47..4359414 100644 --- a/server/tests/test_display_base.c +++ b/server/tests/test_display_base.c @@ -9,6 +9,8 @@ #include <sys/select.h> #include <sys/types.h> #include <getopt.h> +#include <fcntl.h> +#include <sys/mman.h> #include "spice.h" #include <spice/qxl_dev.h> @@ -61,7 +63,18 @@ static int c_i = 0; /* Used for automated tests */ static int control = 3; //used to know when we can take a screenshot static int rects = 16; //number of rects that will be draw -static int has_automated_tests = 0; //automated test flag +static int has_automated_tests; //automated test flag + +/* Shared memory for primary surface */ +static int has_shared_memory; +static struct { + char name[128]; + int fd; + void *ptr; +} shm; + +/* primary surface stride control */ +static int g_primary_stride = 1; __attribute__((noreturn)) static void sigchld_handler(int signal_num) // wait for the child process and exit @@ -337,8 +350,11 @@ static SimpleSurfaceCmd *create_surface(int surface_id, int format, int width, i surface_cmd->u.surface_create.format = format; surface_cmd->u.surface_create.width = width; surface_cmd->u.surface_create.height = height; - surface_cmd->u.surface_create.stride = -width * bpp; + surface_cmd->u.surface_create.stride = width * bpp; surface_cmd->u.surface_create.data = (intptr_t)data; + printf("creating surface with %s stride\n", surface_cmd->u.surface_create.stride > 0 ? + "positive" : "negative"); + return simple_cmd; } @@ -369,17 +385,28 @@ static void create_primary_surface(Test *test, uint32_t width, surface.format = SPICE_SURFACE_FMT_32_xRGB; surface.width = test->primary_width = width; surface.height = test->primary_height = height; - surface.stride = -width * 4; /* negative? */ + printf("g_primary_stride = %d\n", g_primary_stride); + surface.stride = g_primary_stride * width * 4; /* negative? */ surface.mouse_mode = TRUE; /* unused by red_worker */ surface.flags = 0; surface.type = 0; /* unused by red_worker */ surface.position = 0; /* unused by red_worker */ - surface.mem = (uint64_t)&test->primary_surface; + if (has_shared_memory) { + surface.mem = (uint64_t)shm.ptr; + } else { + surface.mem = (uint64_t)test->primary_surface; + } + if (g_primary_stride < 0) { + surface.mem += (g_primary_stride > 0 ? (width * 4 * (height - 1)) : 0); + } surface.group_id = MEM_SLOT_GROUP_ID; test->width = width; test->height = height; + printf("creating primary with %s stride\n", surface.stride > 0 ? + "positive" : "negative"); + qxl_worker->create_primary_surface(qxl_worker, 0, &surface); } @@ -756,6 +783,14 @@ static void set_client_capabilities(QXLInstance *qin, } } +const char *interface_shared_memory_file_name(QXLInstance *sin) +{ + if (has_shared_memory) { + return shm.name; + } + return 0; +} + QXLInterface display_sif = { .base = { .type = SPICE_INTERFACE_QXL, @@ -778,6 +813,7 @@ QXLInterface display_sif = { .flush_resources = flush_resources, .client_monitors_config = client_monitors_config, .set_client_capabilities = set_client_capabilities, + .shared_memory_file_name = interface_shared_memory_file_name, }; /* interface for tests */ @@ -844,6 +880,30 @@ void test_set_command_list(Test *test, Command *commands, int num_commands) test->num_commands = num_commands; } +void init_shared_memory(void) +{ + int size = MAX_HEIGHT * MAX_WIDTH * 4 + SURF_WIDTH * SURF_HEIGHT * 4; + + snprintf((char *)shm.name, sizeof(shm.name), "spice.primary.debug"); + printf("setting up shared memory using %s\n", shm.name); + /* O_EXCL removed for easier testing - unsecure */ + shm.fd = shm_open((char *)shm.name, O_CREAT|O_RDWR, + S_IRWXU|S_IRWXG|S_IRWXO); + if (shm.fd <= 0) { + fprintf(stderr, "test: failed to allocate shared memory\n"); + exit(-1); + } + if (ftruncate(shm.fd, size) != 0) { + fprintf(stderr, "test: failed to ftruncate shared memory to %d\n", + size); + exit(-1); + } + shm.ptr = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, shm.fd, 0); + if (!shm.ptr) { + fprintf(stderr, "test: failed to mmap the shared memory file\n"); + exit(-1); + } +} Test *test_new(SpiceCoreInterface *core) { @@ -868,10 +928,19 @@ Test *test_new(SpiceCoreInterface *core) path_init(&path, 0, angle_parts); test->has_secondary = 0; test->wakeup_timer = core->timer_add(do_wakeup, test); + + if (has_shared_memory) { + init_shared_memory(); + test->primary_surface = shm.ptr; + test->secondary_surface = shm.ptr + MAX_HEIGHT * MAX_WIDTH * 4; + } else { + test->primary_surface = spice_malloc(MAX_HEIGHT * MAX_WIDTH * 4); + test->secondary_surface = spice_malloc(SURF_HEIGHT * SURF_WIDTH * 4); + } return test; } -void init_automated() +void init_automated(void) { struct sigaction sa; @@ -899,6 +968,9 @@ void spice_test_config_parse_args(int argc, char **argv) #ifdef AUTOMATED_TESTS {"automated-tests", no_argument, &has_automated_tests, 1}, #endif + {"shared-memory", no_argument, &has_shared_memory, 1}, + {"primary-stride-positive", no_argument, &g_primary_stride, 1}, + {"primary-stride-negative", no_argument, &g_primary_stride, -1}, {NULL, 0, NULL, 0}, }; int option_index; diff --git a/server/tests/test_display_base.h b/server/tests/test_display_base.h index d2823a7..6d22ea7 100644 --- a/server/tests/test_display_base.h +++ b/server/tests/test_display_base.h @@ -87,7 +87,7 @@ struct Test { QXLInstance qxl_instance; QXLWorker *qxl_worker; - uint8_t primary_surface[MAX_HEIGHT * MAX_WIDTH * 4]; + uint8_t *primary_surface; int primary_height; int primary_width; @@ -96,7 +96,7 @@ struct Test { int cursor_notify; - uint8_t secondary_surface[SURF_WIDTH * SURF_HEIGHT * 4]; + uint8_t *secondary_surface; int has_secondary; // Current mode (set by create_primary) diff --git a/server/tests/test_display_no_ssl.c b/server/tests/test_display_no_ssl.c index 83ab3dc..a5351bb 100644 --- a/server/tests/test_display_no_ssl.c +++ b/server/tests/test_display_no_ssl.c @@ -35,10 +35,11 @@ int simple_commands[] = { SIMPLE_UPDATE, }; -int main(void) +int main(int argc, char **argv) { Test *test; + spice_test_config_parse_args(argc, argv); core = basic_event_loop_init(); test = test_new(core); //spice_server_set_image_compression(server, SPICE_IMAGE_COMPRESS_OFF); -- 1.8.3.1 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel