Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> --- server/Makefile.am | 4 +- server/dcc-encoders.c | 1388 ----------------------------------------------- server/dcc-encoders.h | 217 -------- server/dcc.h | 2 +- server/image-encoders.c | 1388 +++++++++++++++++++++++++++++++++++++++++++++++ server/image-encoders.h | 217 ++++++++ 6 files changed, 1608 insertions(+), 1608 deletions(-) delete mode 100644 server/dcc-encoders.c delete mode 100644 server/dcc-encoders.h create mode 100644 server/image-encoders.c create mode 100644 server/image-encoders.h diff --git a/server/Makefile.am b/server/Makefile.am index 0af8a1b..921b082 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -148,8 +148,8 @@ libserver_la_SOURCES = \ dcc-send.c \ dcc.h \ display-limits.h \ - dcc-encoders.c \ - dcc-encoders.h \ + image-encoders.c \ + image-encoders.h \ $(NULL) if HAVE_LZ4 diff --git a/server/dcc-encoders.c b/server/dcc-encoders.c deleted file mode 100644 index 984f2d4..0000000 --- a/server/dcc-encoders.c +++ /dev/null @@ -1,1388 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <glib.h> - -#include "dcc-encoders.h" -#include "spice-bitmap-utils.h" -#include "red-worker.h" // red_drawable_unref -#include "pixmap-cache.h" // MAX_CACHE_CLIENTS - -#define ZLIB_DEFAULT_COMPRESSION_LEVEL 3 - -#define ENCODER_MESSAGE_SIZE 512 - -#define MAX_GLZ_DRAWABLE_INSTANCES 2 - -typedef struct GlzDrawableInstanceItem GlzDrawableInstanceItem; - -struct GlzSharedDictionary { - RingItem base; - GlzEncDictContext *dict; - uint32_t refs; - uint8_t id; - pthread_rwlock_t encode_lock; - int migrate_freeze; - RedClient *client; // channel clients of the same client share the dict -}; - -/* for each qxl drawable, there may be several instances of lz drawables */ -/* TODO - reuse this stuff for the top level. I just added a second level of multiplicity - * at the Drawable by keeping a ring, so: - * Drawable -> (ring of) RedGlzDrawable -> (up to 2) GlzDrawableInstanceItem - * and it should probably (but need to be sure...) be - * Drawable -> ring of GlzDrawableInstanceItem. - */ -struct GlzDrawableInstanceItem { - RingItem glz_link; - RingItem free_link; - GlzEncDictImageContext *context; - RedGlzDrawable *glz_drawable; -}; - -struct RedGlzDrawable { - RingItem link; // ordered by the time it was encoded - RingItem drawable_link; - RedDrawable *red_drawable; - GlzDrawableInstanceItem instances_pool[MAX_GLZ_DRAWABLE_INSTANCES]; - Ring instances; - uint8_t instances_count; - gboolean has_drawable; - ImageEncoders *encoders; -}; - -#define LINK_TO_GLZ(ptr) SPICE_CONTAINEROF((ptr), RedGlzDrawable, \ - drawable_link) -#define DRAWABLE_FOREACH_GLZ_SAFE(drawable, link, next, glz) \ - SAFE_FOREACH(link, next, drawable, &(drawable)->glz_retention.ring, glz, LINK_TO_GLZ(link)) - -static void glz_drawable_instance_item_free(GlzDrawableInstanceItem *instance); -static void encoder_data_init(EncoderData *data); -static void encoder_data_reset(EncoderData *data); -static void image_encoders_release_glz(ImageEncoders *enc); - - -static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void -quic_usr_error(QuicUsrContext *usr, const char *fmt, ...) -{ - EncoderData *usr_data = &(((QuicData *)usr)->data); - va_list ap; - char message_buf[ENCODER_MESSAGE_SIZE]; - - va_start(ap, fmt); - vsnprintf(message_buf, sizeof(message_buf), fmt, ap); - va_end(ap); - spice_critical("%s", message_buf); - - longjmp(usr_data->jmp_env, 1); -} - -static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void -lz_usr_error(LzUsrContext *usr, const char *fmt, ...) -{ - EncoderData *usr_data = &(((LzData *)usr)->data); - va_list ap; - char message_buf[ENCODER_MESSAGE_SIZE]; - - va_start(ap, fmt); - vsnprintf(message_buf, sizeof(message_buf), fmt, ap); - va_end(ap); - spice_critical("%s", message_buf); - - longjmp(usr_data->jmp_env, 1); -} - -static SPICE_GNUC_PRINTF(2, 3) void -glz_usr_error(GlzEncoderUsrContext *usr, const char *fmt, ...) -{ - va_list ap; - char message_buf[ENCODER_MESSAGE_SIZE]; - - va_start(ap, fmt); - vsnprintf(message_buf, sizeof(message_buf), fmt, ap); - va_end(ap); - - spice_critical("%s", message_buf); // if global lz fails in the middle - // the consequences are not predictable since the window - // can turn to be unsynchronized between the server and - // and the client -} - -static SPICE_GNUC_PRINTF(2, 3) void -quic_usr_warn(QuicUsrContext *usr, const char *fmt, ...) -{ - va_list ap; - char message_buf[ENCODER_MESSAGE_SIZE]; - - va_start(ap, fmt); - vsnprintf(message_buf, sizeof(message_buf), fmt, ap); - va_end(ap); - spice_warning("%s", message_buf); -} - -static SPICE_GNUC_PRINTF(2, 3) void -lz_usr_warn(LzUsrContext *usr, const char *fmt, ...) -{ - va_list ap; - char message_buf[ENCODER_MESSAGE_SIZE]; - - va_start(ap, fmt); - vsnprintf(message_buf, sizeof(message_buf), fmt, ap); - va_end(ap); - spice_warning("%s", message_buf); -} - -static SPICE_GNUC_PRINTF(2, 3) void -glz_usr_warn(GlzEncoderUsrContext *usr, const char *fmt, ...) -{ - va_list ap; - char message_buf[ENCODER_MESSAGE_SIZE]; - - va_start(ap, fmt); - vsnprintf(message_buf, sizeof(message_buf), fmt, ap); - va_end(ap); - spice_warning("%s", message_buf); -} - -static void *quic_usr_malloc(QuicUsrContext *usr, int size) -{ - return spice_malloc(size); -} - -static void *lz_usr_malloc(LzUsrContext *usr, int size) -{ - return spice_malloc(size); -} - -static void *glz_usr_malloc(GlzEncoderUsrContext *usr, int size) -{ - return spice_malloc(size); -} - -static void quic_usr_free(QuicUsrContext *usr, void *ptr) -{ - free(ptr); -} - -static void lz_usr_free(LzUsrContext *usr, void *ptr) -{ - free(ptr); -} - -static void glz_usr_free(GlzEncoderUsrContext *usr, void *ptr) -{ - free(ptr); -} - -static void encoder_data_init(EncoderData *data) -{ - data->bufs_tail = g_new(RedCompressBuf, 1); - data->bufs_head = data->bufs_tail; - data->bufs_head->send_next = NULL; -} - -static void encoder_data_reset(EncoderData *data) -{ - RedCompressBuf *buf = data->bufs_head; - while (buf) { - RedCompressBuf *next = buf->send_next; - g_free(buf); - buf = next; - } - data->bufs_head = data->bufs_tail = NULL; -} - -/* Allocate more space for compressed buffer. - * The pointer returned in io_ptr is garanteed to be aligned to 4 bytes. - */ -static int encoder_usr_more_space(EncoderData *enc_data, uint8_t **io_ptr) -{ - RedCompressBuf *buf; - - buf = g_new(RedCompressBuf, 1); - enc_data->bufs_tail->send_next = buf; - enc_data->bufs_tail = buf; - buf->send_next = NULL; - *io_ptr = buf->buf.bytes; - return sizeof(buf->buf); -} - -static int quic_usr_more_space(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed) -{ - EncoderData *usr_data = &(((QuicData *)usr)->data); - return encoder_usr_more_space(usr_data, (uint8_t **)io_ptr) / sizeof(uint32_t); -} - -static int lz_usr_more_space(LzUsrContext *usr, uint8_t **io_ptr) -{ - EncoderData *usr_data = &(((LzData *)usr)->data); - return encoder_usr_more_space(usr_data, io_ptr); -} - -static int glz_usr_more_space(GlzEncoderUsrContext *usr, uint8_t **io_ptr) -{ - EncoderData *usr_data = &(((GlzData *)usr)->data); - return encoder_usr_more_space(usr_data, io_ptr); -} - -static int jpeg_usr_more_space(JpegEncoderUsrContext *usr, uint8_t **io_ptr) -{ - EncoderData *usr_data = &(((JpegData *)usr)->data); - return encoder_usr_more_space(usr_data, io_ptr); -} - -#ifdef USE_LZ4 -static int lz4_usr_more_space(Lz4EncoderUsrContext *usr, uint8_t **io_ptr) -{ - EncoderData *usr_data = &(((Lz4Data *)usr)->data); - return encoder_usr_more_space(usr_data, io_ptr); -} -#endif - -static int zlib_usr_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr) -{ - EncoderData *usr_data = &(((ZlibData *)usr)->data); - return encoder_usr_more_space(usr_data, io_ptr); -} - -static inline int encoder_usr_more_lines(EncoderData *enc_data, uint8_t **lines) -{ - struct SpiceChunk *chunk; - - if (enc_data->u.lines_data.reverse) { - if (!(enc_data->u.lines_data.next >= 0)) { - return 0; - } - } else { - if (!(enc_data->u.lines_data.next < enc_data->u.lines_data.chunks->num_chunks)) { - return 0; - } - } - - chunk = &enc_data->u.lines_data.chunks->chunk[enc_data->u.lines_data.next]; - if (chunk->len % enc_data->u.lines_data.stride) { - return 0; - } - - if (enc_data->u.lines_data.reverse) { - enc_data->u.lines_data.next--; - *lines = chunk->data + chunk->len - enc_data->u.lines_data.stride; - } else { - enc_data->u.lines_data.next++; - *lines = chunk->data; - } - - return chunk->len / enc_data->u.lines_data.stride; -} - -static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines) -{ - EncoderData *usr_data = &(((QuicData *)usr)->data); - return encoder_usr_more_lines(usr_data, lines); -} - -static int lz_usr_more_lines(LzUsrContext *usr, uint8_t **lines) -{ - EncoderData *usr_data = &(((LzData *)usr)->data); - return encoder_usr_more_lines(usr_data, lines); -} - -static int glz_usr_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines) -{ - EncoderData *usr_data = &(((GlzData *)usr)->data); - return encoder_usr_more_lines(usr_data, lines); -} - -static int jpeg_usr_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines) -{ - EncoderData *usr_data = &(((JpegData *)usr)->data); - return encoder_usr_more_lines(usr_data, lines); -} - -#ifdef USE_LZ4 -static int lz4_usr_more_lines(Lz4EncoderUsrContext *usr, uint8_t **lines) -{ - EncoderData *usr_data = &(((Lz4Data *)usr)->data); - return encoder_usr_more_lines(usr_data, lines); -} -#endif - -static int zlib_usr_more_input(ZlibEncoderUsrContext *usr, uint8_t** input) -{ - EncoderData *usr_data = &(((ZlibData *)usr)->data); - int buf_size; - - if (!usr_data->u.compressed_data.next) { - spice_assert(usr_data->u.compressed_data.size_left == 0); - return 0; - } - - *input = usr_data->u.compressed_data.next->buf.bytes; - buf_size = MIN(sizeof(usr_data->u.compressed_data.next->buf), - usr_data->u.compressed_data.size_left); - - usr_data->u.compressed_data.next = usr_data->u.compressed_data.next->send_next; - usr_data->u.compressed_data.size_left -= buf_size; - return buf_size; -} - -static void image_encoders_init_quic(ImageEncoders *enc) -{ - enc->quic_data.usr.error = quic_usr_error; - enc->quic_data.usr.warn = quic_usr_warn; - enc->quic_data.usr.info = quic_usr_warn; - enc->quic_data.usr.malloc = quic_usr_malloc; - enc->quic_data.usr.free = quic_usr_free; - enc->quic_data.usr.more_space = quic_usr_more_space; - enc->quic_data.usr.more_lines = quic_usr_more_lines; - - enc->quic = quic_create(&enc->quic_data.usr); - - if (!enc->quic) { - spice_critical("create quic failed"); - } -} - -static void image_encoders_init_lz(ImageEncoders *enc) -{ - enc->lz_data.usr.error = lz_usr_error; - enc->lz_data.usr.warn = lz_usr_warn; - enc->lz_data.usr.info = lz_usr_warn; - enc->lz_data.usr.malloc = lz_usr_malloc; - enc->lz_data.usr.free = lz_usr_free; - enc->lz_data.usr.more_space = lz_usr_more_space; - enc->lz_data.usr.more_lines = lz_usr_more_lines; - - enc->lz = lz_create(&enc->lz_data.usr); - - if (!enc->lz) { - spice_critical("create lz failed"); - } -} - -static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image) -{ - GlzData *lz_data = (GlzData *)usr; - GlzDrawableInstanceItem *glz_drawable_instance = (GlzDrawableInstanceItem *)image; - ImageEncoders *drawable_enc = glz_drawable_instance->glz_drawable->encoders; - ImageEncoders *this_enc = SPICE_CONTAINEROF(lz_data, ImageEncoders, glz_data); - if (this_enc == drawable_enc) { - glz_drawable_instance_item_free(glz_drawable_instance); - } else { - /* The glz dictionary is shared between all DisplayChannelClient - * instances that belong to the same client, and glz_usr_free_image - * can be called by the dictionary code - * (glz_dictionary_window_remove_head). Thus this function can be - * called from any DisplayChannelClient thread, hence the need for - * this check. - */ - pthread_mutex_lock(&drawable_enc->glz_drawables_inst_to_free_lock); - ring_add_before(&glz_drawable_instance->free_link, - &drawable_enc->glz_drawables_inst_to_free); - pthread_mutex_unlock(&drawable_enc->glz_drawables_inst_to_free_lock); - } -} - -static void image_encoders_init_glz_data(ImageEncoders *enc) -{ - enc->glz_data.usr.error = glz_usr_error; - enc->glz_data.usr.warn = glz_usr_warn; - enc->glz_data.usr.info = glz_usr_warn; - enc->glz_data.usr.malloc = glz_usr_malloc; - enc->glz_data.usr.free = glz_usr_free; - enc->glz_data.usr.more_space = glz_usr_more_space; - enc->glz_data.usr.more_lines = glz_usr_more_lines; - enc->glz_data.usr.free_image = glz_usr_free_image; -} - -static void image_encoders_init_jpeg(ImageEncoders *enc) -{ - enc->jpeg_data.usr.more_space = jpeg_usr_more_space; - enc->jpeg_data.usr.more_lines = jpeg_usr_more_lines; - - enc->jpeg = jpeg_encoder_create(&enc->jpeg_data.usr); - - if (!enc->jpeg) { - spice_critical("create jpeg encoder failed"); - } -} - -#ifdef USE_LZ4 -static inline void image_encoders_init_lz4(ImageEncoders *enc) -{ - enc->lz4_data.usr.more_space = lz4_usr_more_space; - enc->lz4_data.usr.more_lines = lz4_usr_more_lines; - - enc->lz4 = lz4_encoder_create(&enc->lz4_data.usr); - - if (!enc->lz4) { - spice_critical("create lz4 encoder failed"); - } -} -#endif - -static void image_encoders_init_zlib(ImageEncoders *enc) -{ - enc->zlib_data.usr.more_space = zlib_usr_more_space; - enc->zlib_data.usr.more_input = zlib_usr_more_input; - - enc->zlib = zlib_encoder_create(&enc->zlib_data.usr, ZLIB_DEFAULT_COMPRESSION_LEVEL); - - if (!enc->zlib) { - spice_critical("create zlib encoder failed"); - } -} - -void image_encoders_init(ImageEncoders *enc, ImageEncoderSharedData *shared_data) -{ - spice_assert(shared_data); - enc->shared_data = shared_data; - - ring_init(&enc->glz_drawables); - ring_init(&enc->glz_drawables_inst_to_free); - pthread_mutex_init(&enc->glz_drawables_inst_to_free_lock, NULL); - - image_encoders_init_glz_data(enc); - image_encoders_init_quic(enc); - image_encoders_init_lz(enc); - image_encoders_init_jpeg(enc); -#ifdef USE_LZ4 - image_encoders_init_lz4(enc); -#endif - image_encoders_init_zlib(enc); - - // todo: tune level according to bandwidth - enc->zlib_level = ZLIB_DEFAULT_COMPRESSION_LEVEL; -} - -void image_encoders_free(ImageEncoders *enc) -{ - image_encoders_release_glz(enc); - quic_destroy(enc->quic); - enc->quic = NULL; - lz_destroy(enc->lz); - enc->lz = NULL; - jpeg_encoder_destroy(enc->jpeg); - enc->jpeg = NULL; -#ifdef USE_LZ4 - lz4_encoder_destroy(enc->lz4); - enc->lz4 = NULL; -#endif - zlib_encoder_destroy(enc->zlib); - enc->zlib = NULL; -} - -/* Remove from the to_free list and the instances_list. - When no instance is left - the RedGlzDrawable is released too. (and the qxl drawable too, if - it is not used by Drawable). - NOTE - 1) can be called only by the display channel that created the drawable - 2) it is assumed that the instance was already removed from the dictionary*/ -static void glz_drawable_instance_item_free(GlzDrawableInstanceItem *instance) -{ - RedGlzDrawable *glz_drawable; - - spice_assert(instance); - spice_assert(instance->glz_drawable); - - glz_drawable = instance->glz_drawable; - - spice_assert(glz_drawable->instances_count > 0); - - ring_remove(&instance->glz_link); - glz_drawable->instances_count--; - - // when the remove callback is performed from the channel that the - // drawable belongs to, the instance is not added to the 'to_free' list - if (ring_item_is_linked(&instance->free_link)) { - ring_remove(&instance->free_link); - } - - if (ring_is_empty(&glz_drawable->instances)) { - spice_assert(glz_drawable->instances_count == 0); - - if (glz_drawable->has_drawable) { - ring_remove(&glz_drawable->drawable_link); - } - red_drawable_unref(glz_drawable->red_drawable); - glz_drawable->encoders->shared_data->glz_drawable_count--; - if (ring_item_is_linked(&glz_drawable->link)) { - ring_remove(&glz_drawable->link); - } - free(glz_drawable); - } -} - -/* - * Releases all the instances of the drawable from the dictionary and the display channel client. - * The release of the last instance will also release the drawable itself and the qxl drawable - * if possible. - * NOTE - the caller should prevent encoding using the dictionary during this operation - */ -static void red_glz_drawable_free(RedGlzDrawable *glz_drawable) -{ - ImageEncoders *enc = glz_drawable->encoders; - RingItem *head_instance = ring_get_head(&glz_drawable->instances); - int cont = (head_instance != NULL); - - while (cont) { - if (glz_drawable->instances_count == 1) { - /* Last instance: glz_drawable_instance_item_free will free the glz_drawable */ - cont = FALSE; - } - GlzDrawableInstanceItem *instance = SPICE_CONTAINEROF(head_instance, - GlzDrawableInstanceItem, - glz_link); - if (!ring_item_is_linked(&instance->free_link)) { - // the instance didn't get out from window yet - glz_enc_dictionary_remove_image(enc->glz_dict->dict, - instance->context, - &enc->glz_data.usr); - } - glz_drawable_instance_item_free(instance); - - if (cont) { - head_instance = ring_get_head(&glz_drawable->instances); - } - } -} - -gboolean image_encoders_glz_encode_lock(ImageEncoders *enc) -{ - if (enc->glz_dict) { - pthread_rwlock_wrlock(&enc->glz_dict->encode_lock); - return TRUE; - } - return FALSE; -} - -void image_encoders_glz_encode_unlock(ImageEncoders *enc) -{ - if (enc->glz_dict) { - pthread_rwlock_unlock(&enc->glz_dict->encode_lock); - } -} - -/* - * Remove from the global lz dictionary some glz_drawables that have no reference to - * Drawable (their qxl drawables are released too). - * NOTE - the caller should prevent encoding using the dictionary during the operation - */ -int image_encoders_free_some_independent_glz_drawables(ImageEncoders *enc) -{ - RingItem *ring_link; - int n = 0; - - if (!enc) { - return 0; - } - ring_link = ring_get_head(&enc->glz_drawables); - while ((n < RED_RELEASE_BUNCH_SIZE) && (ring_link != NULL)) { - RedGlzDrawable *glz_drawable = SPICE_CONTAINEROF(ring_link, RedGlzDrawable, link); - ring_link = ring_next(&enc->glz_drawables, ring_link); - if (!glz_drawable->has_drawable) { - red_glz_drawable_free(glz_drawable); - n++; - } - } - return n; -} - -void image_encoders_free_glz_drawables_to_free(ImageEncoders* enc) -{ - RingItem *ring_link; - - if (!enc->glz_dict) { - return; - } - pthread_mutex_lock(&enc->glz_drawables_inst_to_free_lock); - while ((ring_link = ring_get_head(&enc->glz_drawables_inst_to_free))) { - GlzDrawableInstanceItem *drawable_instance = SPICE_CONTAINEROF(ring_link, - GlzDrawableInstanceItem, - free_link); - glz_drawable_instance_item_free(drawable_instance); - } - pthread_mutex_unlock(&enc->glz_drawables_inst_to_free_lock); -} - -/* Clear all lz drawables - enforce their removal from the global dictionary. - NOTE - prevents encoding using the dictionary during the operation*/ -void image_encoders_free_glz_drawables(ImageEncoders *enc) -{ - RingItem *ring_link; - GlzSharedDictionary *glz_dict = enc ? enc->glz_dict : NULL; - - if (!glz_dict) { - return; - } - - // assure no display channel is during global lz encoding - pthread_rwlock_wrlock(&glz_dict->encode_lock); - while ((ring_link = ring_get_head(&enc->glz_drawables))) { - RedGlzDrawable *drawable = SPICE_CONTAINEROF(ring_link, RedGlzDrawable, link); - // no need to lock the to_free list, since we assured no other thread is encoding and - // thus not other thread access the to_free list of the channel - red_glz_drawable_free(drawable); - } - pthread_rwlock_unlock(&glz_dict->encode_lock); -} - -void glz_retention_free_drawables(GlzImageRetention *ret) -{ - RingItem *glz_item, *next_item; - RedGlzDrawable *glz; - SAFE_FOREACH(glz_item, next_item, TRUE, &ret->ring, glz, LINK_TO_GLZ(glz_item)) { - red_glz_drawable_free(glz); - } -} - -void glz_retention_detach_drawables(GlzImageRetention *ret) -{ - RingItem *item, *next; - - RING_FOREACH_SAFE(item, next, &ret->ring) { - SPICE_CONTAINEROF(item, RedGlzDrawable, drawable_link)->has_drawable = FALSE; - ring_remove(item); - } -} - -static void image_encoders_freeze_glz(ImageEncoders *enc) -{ - pthread_rwlock_wrlock(&enc->glz_dict->encode_lock); - enc->glz_dict->migrate_freeze = TRUE; - pthread_rwlock_unlock(&enc->glz_dict->encode_lock); -} - -void image_encoders_glz_get_restore_data(ImageEncoders *enc, - uint8_t *out_id, GlzEncDictRestoreData *out_data) -{ - spice_assert(enc->glz_dict); - image_encoders_freeze_glz(enc); - *out_id = enc->glz_dict->id; - glz_enc_dictionary_get_restore_data(enc->glz_dict->dict, out_data, - &enc->glz_data.usr); -} - -static GlzSharedDictionary *glz_shared_dictionary_new(RedClient *client, uint8_t id, - GlzEncDictContext *dict) -{ - spice_return_val_if_fail(dict != NULL, NULL); - - GlzSharedDictionary *shared_dict = spice_new0(GlzSharedDictionary, 1); - - shared_dict->dict = dict; - shared_dict->id = id; - shared_dict->refs = 1; - shared_dict->migrate_freeze = FALSE; - shared_dict->client = client; - ring_item_init(&shared_dict->base); - pthread_rwlock_init(&shared_dict->encode_lock, NULL); - - return shared_dict; -} - -static pthread_mutex_t glz_dictionary_list_lock = PTHREAD_MUTEX_INITIALIZER; -static Ring glz_dictionary_list = {&glz_dictionary_list, &glz_dictionary_list}; - -static GlzSharedDictionary *find_glz_dictionary(RedClient *client, uint8_t dict_id) -{ - RingItem *now; - GlzSharedDictionary *ret = NULL; - - now = &glz_dictionary_list; - while ((now = ring_next(&glz_dictionary_list, now))) { - GlzSharedDictionary *dict = SPICE_UPCAST(GlzSharedDictionary, now); - if ((dict->client == client) && (dict->id == dict_id)) { - ret = dict; - break; - } - } - - return ret; -} - -#define MAX_LZ_ENCODERS MAX_CACHE_CLIENTS - -static GlzSharedDictionary *create_glz_dictionary(ImageEncoders *enc, - RedClient *client, - uint8_t id, int window_size) -{ - spice_info("Lz Window %d Size=%d", id, window_size); - - GlzEncDictContext *glz_dict = - glz_enc_dictionary_create(window_size, MAX_LZ_ENCODERS, &enc->glz_data.usr); - - return glz_shared_dictionary_new(client, id, glz_dict); -} - -gboolean image_encoders_get_glz_dictionary(ImageEncoders *enc, - RedClient *client, - uint8_t id, int window_size) -{ - GlzSharedDictionary *shared_dict; - - spice_return_val_if_fail(!enc->glz_dict, FALSE); - - pthread_mutex_lock(&glz_dictionary_list_lock); - - shared_dict = find_glz_dictionary(client, id); - if (shared_dict) { - shared_dict->refs++; - } else { - shared_dict = create_glz_dictionary(enc, client, id, window_size); - ring_add(&glz_dictionary_list, &shared_dict->base); - } - - pthread_mutex_unlock(&glz_dictionary_list_lock); - enc->glz_dict = shared_dict; - return shared_dict != NULL; -} - -static GlzSharedDictionary *restore_glz_dictionary(ImageEncoders *enc, - RedClient *client, - uint8_t id, - GlzEncDictRestoreData *restore_data) -{ - GlzEncDictContext *glz_dict = - glz_enc_dictionary_restore(restore_data, &enc->glz_data.usr); - - return glz_shared_dictionary_new(client, id, glz_dict); -} - -gboolean image_encoders_restore_glz_dictionary(ImageEncoders *enc, - RedClient *client, - uint8_t id, - GlzEncDictRestoreData *restore_data) -{ - GlzSharedDictionary *shared_dict = NULL; - - spice_return_val_if_fail(!enc->glz_dict, FALSE); - - pthread_mutex_lock(&glz_dictionary_list_lock); - - shared_dict = find_glz_dictionary(client, id); - - if (shared_dict) { - shared_dict->refs++; - } else { - shared_dict = restore_glz_dictionary(enc, client, id, restore_data); - ring_add(&glz_dictionary_list, &shared_dict->base); - } - - pthread_mutex_unlock(&glz_dictionary_list_lock); - enc->glz_dict = shared_dict; - return shared_dict != NULL; -} - -gboolean image_encoders_glz_create(ImageEncoders *enc, uint8_t id) -{ - enc->glz = glz_encoder_create(id, enc->glz_dict->dict, &enc->glz_data.usr); - return enc->glz != NULL; -} - -/* destroy encoder, and dictionary if no one uses it*/ -static void image_encoders_release_glz(ImageEncoders *enc) -{ - GlzSharedDictionary *shared_dict; - - image_encoders_free_glz_drawables(enc); - - glz_encoder_destroy(enc->glz); - enc->glz = NULL; - - if (!(shared_dict = enc->glz_dict)) { - return; - } - - enc->glz_dict = NULL; - pthread_mutex_lock(&glz_dictionary_list_lock); - if (--shared_dict->refs != 0) { - pthread_mutex_unlock(&glz_dictionary_list_lock); - return; - } - ring_remove(&shared_dict->base); - pthread_mutex_unlock(&glz_dictionary_list_lock); - glz_enc_dictionary_destroy(shared_dict->dict, &enc->glz_data.usr); - free(shared_dict); -} - -int image_encoders_compress_quic(ImageEncoders *enc, SpiceImage *dest, - SpiceBitmap *src, compress_send_data_t* o_comp_data) -{ - QuicData *quic_data = &enc->quic_data; - QuicContext *quic = enc->quic; - volatile QuicImageType type; - int size, stride; - stat_start_time_t start_time; - stat_start_time_init(&start_time, &enc->shared_data->quic_stat); - -#ifdef COMPRESS_DEBUG - spice_info("QUIC compress"); -#endif - - switch (src->format) { - case SPICE_BITMAP_FMT_32BIT: - type = QUIC_IMAGE_TYPE_RGB32; - break; - case SPICE_BITMAP_FMT_RGBA: - type = QUIC_IMAGE_TYPE_RGBA; - break; - case SPICE_BITMAP_FMT_16BIT: - type = QUIC_IMAGE_TYPE_RGB16; - break; - case SPICE_BITMAP_FMT_24BIT: - type = QUIC_IMAGE_TYPE_RGB24; - break; - default: - return FALSE; - } - - encoder_data_init(&quic_data->data); - - if (setjmp(quic_data->data.jmp_env)) { - encoder_data_reset(&quic_data->data); - return FALSE; - } - - if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) { - spice_chunks_linearize(src->data); - } - - quic_data->data.u.lines_data.chunks = src->data; - quic_data->data.u.lines_data.stride = src->stride; - if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { - quic_data->data.u.lines_data.next = 0; - quic_data->data.u.lines_data.reverse = 0; - stride = src->stride; - } else { - quic_data->data.u.lines_data.next = src->data->num_chunks - 1; - quic_data->data.u.lines_data.reverse = 1; - stride = -src->stride; - } - size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride, - quic_data->data.bufs_head->buf.words, - G_N_ELEMENTS(quic_data->data.bufs_head->buf.words)); - - // the compressed buffer is bigger than the original data - if ((size << 2) > (src->y * src->stride)) { - longjmp(quic_data->data.jmp_env, 1); - } - - dest->descriptor.type = SPICE_IMAGE_TYPE_QUIC; - dest->u.quic.data_size = size << 2; - - o_comp_data->comp_buf = quic_data->data.bufs_head; - o_comp_data->comp_buf_size = size << 2; - - stat_compress_add(&enc->shared_data->quic_stat, start_time, src->stride * src->y, - o_comp_data->comp_buf_size); - return TRUE; -} - -static const LzImageType bitmap_fmt_to_lz_image_type[] = { - LZ_IMAGE_TYPE_INVALID, - LZ_IMAGE_TYPE_PLT1_LE, - LZ_IMAGE_TYPE_PLT1_BE, - LZ_IMAGE_TYPE_PLT4_LE, - LZ_IMAGE_TYPE_PLT4_BE, - LZ_IMAGE_TYPE_PLT8, - LZ_IMAGE_TYPE_RGB16, - LZ_IMAGE_TYPE_RGB24, - LZ_IMAGE_TYPE_RGB32, - LZ_IMAGE_TYPE_RGBA, - LZ_IMAGE_TYPE_A8 -}; - -int image_encoders_compress_lz(ImageEncoders *enc, - SpiceImage *dest, SpiceBitmap *src, - compress_send_data_t* o_comp_data) -{ - LzData *lz_data = &enc->lz_data; - LzContext *lz = enc->lz; - LzImageType type = bitmap_fmt_to_lz_image_type[src->format]; - int size; // size of the compressed data - - stat_start_time_t start_time; - stat_start_time_init(&start_time, &enc->shared_data->lz_stat); - -#ifdef COMPRESS_DEBUG - spice_info("LZ LOCAL compress"); -#endif - - encoder_data_init(&lz_data->data); - - if (setjmp(lz_data->data.jmp_env)) { - encoder_data_reset(&lz_data->data); - return FALSE; - } - - lz_data->data.u.lines_data.chunks = src->data; - lz_data->data.u.lines_data.stride = src->stride; - lz_data->data.u.lines_data.next = 0; - lz_data->data.u.lines_data.reverse = 0; - - size = lz_encode(lz, type, src->x, src->y, - !!(src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), - NULL, 0, src->stride, - lz_data->data.bufs_head->buf.bytes, - sizeof(lz_data->data.bufs_head->buf)); - - // the compressed buffer is bigger than the original data - if (size > (src->y * src->stride)) { - longjmp(lz_data->data.jmp_env, 1); - } - - if (bitmap_fmt_is_rgb(src->format)) { - dest->descriptor.type = SPICE_IMAGE_TYPE_LZ_RGB; - dest->u.lz_rgb.data_size = size; - - o_comp_data->comp_buf = lz_data->data.bufs_head; - o_comp_data->comp_buf_size = size; - } else { - /* masks are 1BIT bitmaps without palettes, but they are not compressed - * (see fill_mask) */ - spice_assert(src->palette); - dest->descriptor.type = SPICE_IMAGE_TYPE_LZ_PLT; - dest->u.lz_plt.data_size = size; - dest->u.lz_plt.flags = src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN; - dest->u.lz_plt.palette = src->palette; - dest->u.lz_plt.palette_id = src->palette->unique; - o_comp_data->comp_buf = lz_data->data.bufs_head; - o_comp_data->comp_buf_size = size; - - o_comp_data->lzplt_palette = dest->u.lz_plt.palette; - } - - stat_compress_add(&enc->shared_data->lz_stat, start_time, src->stride * src->y, - o_comp_data->comp_buf_size); - return TRUE; -} - -int image_encoders_compress_jpeg(ImageEncoders *enc, SpiceImage *dest, - SpiceBitmap *src, compress_send_data_t* o_comp_data) -{ - JpegData *jpeg_data = &enc->jpeg_data; - LzData *lz_data = &enc->lz_data; - JpegEncoderContext *jpeg = enc->jpeg; - LzContext *lz = enc->lz; - volatile JpegEncoderImageType jpeg_in_type; - int jpeg_size = 0; - volatile int has_alpha = FALSE; - int alpha_lz_size = 0; - int comp_head_filled; - int comp_head_left; - int stride; - uint8_t *lz_out_start_byte; - stat_start_time_t start_time; - stat_start_time_init(&start_time, &enc->shared_data->jpeg_alpha_stat); - -#ifdef COMPRESS_DEBUG - spice_info("JPEG compress"); -#endif - - switch (src->format) { - case SPICE_BITMAP_FMT_16BIT: - jpeg_in_type = JPEG_IMAGE_TYPE_RGB16; - break; - case SPICE_BITMAP_FMT_24BIT: - jpeg_in_type = JPEG_IMAGE_TYPE_BGR24; - break; - case SPICE_BITMAP_FMT_32BIT: - jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32; - break; - case SPICE_BITMAP_FMT_RGBA: - jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32; - has_alpha = TRUE; - break; - default: - return FALSE; - } - - encoder_data_init(&jpeg_data->data); - - if (setjmp(jpeg_data->data.jmp_env)) { - encoder_data_reset(&jpeg_data->data); - return FALSE; - } - - if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) { - spice_chunks_linearize(src->data); - } - - jpeg_data->data.u.lines_data.chunks = src->data; - jpeg_data->data.u.lines_data.stride = src->stride; - if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { - jpeg_data->data.u.lines_data.next = 0; - jpeg_data->data.u.lines_data.reverse = 0; - stride = src->stride; - } else { - jpeg_data->data.u.lines_data.next = src->data->num_chunks - 1; - jpeg_data->data.u.lines_data.reverse = 1; - stride = -src->stride; - } - jpeg_size = jpeg_encode(jpeg, enc->jpeg_quality, jpeg_in_type, - src->x, src->y, NULL, - 0, stride, jpeg_data->data.bufs_head->buf.bytes, - sizeof(jpeg_data->data.bufs_head->buf)); - - // the compressed buffer is bigger than the original data - if (jpeg_size > (src->y * src->stride)) { - longjmp(jpeg_data->data.jmp_env, 1); - } - - if (!has_alpha) { - dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG; - dest->u.jpeg.data_size = jpeg_size; - - o_comp_data->comp_buf = jpeg_data->data.bufs_head; - o_comp_data->comp_buf_size = jpeg_size; - o_comp_data->is_lossy = TRUE; - - stat_compress_add(&enc->shared_data->jpeg_stat, start_time, src->stride * src->y, - o_comp_data->comp_buf_size); - return TRUE; - } - - lz_data->data.bufs_head = jpeg_data->data.bufs_tail; - lz_data->data.bufs_tail = lz_data->data.bufs_head; - - comp_head_filled = jpeg_size % sizeof(lz_data->data.bufs_head->buf); - comp_head_left = sizeof(lz_data->data.bufs_head->buf) - comp_head_filled; - lz_out_start_byte = lz_data->data.bufs_head->buf.bytes + comp_head_filled; - - lz_data->data.u.lines_data.chunks = src->data; - lz_data->data.u.lines_data.stride = src->stride; - lz_data->data.u.lines_data.next = 0; - lz_data->data.u.lines_data.reverse = 0; - - alpha_lz_size = lz_encode(lz, LZ_IMAGE_TYPE_XXXA, src->x, src->y, - !!(src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), - NULL, 0, src->stride, - lz_out_start_byte, - comp_head_left); - - // the compressed buffer is bigger than the original data - if ((jpeg_size + alpha_lz_size) > (src->y * src->stride)) { - longjmp(jpeg_data->data.jmp_env, 1); - } - - dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG_ALPHA; - dest->u.jpeg_alpha.flags = 0; - if (src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN) { - dest->u.jpeg_alpha.flags |= SPICE_JPEG_ALPHA_FLAGS_TOP_DOWN; - } - - dest->u.jpeg_alpha.jpeg_size = jpeg_size; - dest->u.jpeg_alpha.data_size = jpeg_size + alpha_lz_size; - - o_comp_data->comp_buf = jpeg_data->data.bufs_head; - o_comp_data->comp_buf_size = jpeg_size + alpha_lz_size; - o_comp_data->is_lossy = TRUE; - stat_compress_add(&enc->shared_data->jpeg_alpha_stat, start_time, src->stride * src->y, - o_comp_data->comp_buf_size); - return TRUE; -} - -#ifdef USE_LZ4 -int image_encoders_compress_lz4(ImageEncoders *enc, SpiceImage *dest, - SpiceBitmap *src, compress_send_data_t* o_comp_data) -{ - Lz4Data *lz4_data = &enc->lz4_data; - Lz4EncoderContext *lz4 = enc->lz4; - int lz4_size = 0; - stat_start_time_t start_time; - stat_start_time_init(&start_time, &enc->shared_data->lz4_stat); - -#ifdef COMPRESS_DEBUG - spice_info("LZ4 compress"); -#endif - - encoder_data_init(&lz4_data->data); - - if (setjmp(lz4_data->data.jmp_env)) { - encoder_data_reset(&lz4_data->data); - return FALSE; - } - - if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) { - spice_chunks_linearize(src->data); - } - - lz4_data->data.u.lines_data.chunks = src->data; - lz4_data->data.u.lines_data.stride = src->stride; - lz4_data->data.u.lines_data.next = 0; - lz4_data->data.u.lines_data.reverse = 0; - - lz4_size = lz4_encode(lz4, src->y, src->stride, lz4_data->data.bufs_head->buf.bytes, - sizeof(lz4_data->data.bufs_head->buf), - src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN, src->format); - - // the compressed buffer is bigger than the original data - if (lz4_size > (src->y * src->stride)) { - longjmp(lz4_data->data.jmp_env, 1); - } - - dest->descriptor.type = SPICE_IMAGE_TYPE_LZ4; - dest->u.lz4.data_size = lz4_size; - - o_comp_data->comp_buf = lz4_data->data.bufs_head; - o_comp_data->comp_buf_size = lz4_size; - - stat_compress_add(&enc->shared_data->lz4_stat, start_time, src->stride * src->y, - o_comp_data->comp_buf_size); - return TRUE; -} -#endif - -/* if already exists, returns it. Otherwise allocates and adds it (1) to the ring tail - in the channel (2) to the Drawable*/ -static RedGlzDrawable *get_glz_drawable(ImageEncoders *enc, RedDrawable *red_drawable, - GlzImageRetention *glz_retention) -{ - RedGlzDrawable *ret; - RingItem *item, *next; - - // TODO - I don't really understand what's going on here, so doing the technical equivalent - // now that we have multiple glz_dicts, so the only way to go from dcc to drawable glz is to go - // over the glz_ring (unless adding some better data structure then a ring) - SAFE_FOREACH(item, next, TRUE, &glz_retention->ring, ret, LINK_TO_GLZ(item)) { - if (ret->encoders == enc) { - return ret; - } - } - - ret = spice_new(RedGlzDrawable, 1); - - ret->encoders = enc; - ret->red_drawable = red_drawable_ref(red_drawable); - ret->has_drawable = TRUE; - ret->instances_count = 0; - ring_init(&ret->instances); - - ring_item_init(&ret->link); - ring_item_init(&ret->drawable_link); - ring_add_before(&ret->link, &enc->glz_drawables); - ring_add(&glz_retention->ring, &ret->drawable_link); - enc->shared_data->glz_drawable_count++; - return ret; -} - -/* allocates new instance and adds it to instances in the given drawable. - NOTE - the caller should set the glz_instance returned by the encoder by itself.*/ -static GlzDrawableInstanceItem *add_glz_drawable_instance(RedGlzDrawable *glz_drawable) -{ - spice_assert(glz_drawable->instances_count < MAX_GLZ_DRAWABLE_INSTANCES); - // NOTE: We assume the additions are performed consecutively, without removals in the middle - GlzDrawableInstanceItem *ret = glz_drawable->instances_pool + glz_drawable->instances_count; - glz_drawable->instances_count++; - - ring_item_init(&ret->free_link); - ring_item_init(&ret->glz_link); - ring_add(&glz_drawable->instances, &ret->glz_link); - ret->context = NULL; - ret->glz_drawable = glz_drawable; - - return ret; -} - -#define MIN_GLZ_SIZE_FOR_ZLIB 100 - -int image_encoders_compress_glz(ImageEncoders *enc, - SpiceImage *dest, SpiceBitmap *src, - RedDrawable *red_drawable, - GlzImageRetention *glz_retention, - compress_send_data_t* o_comp_data, - gboolean enable_zlib_glz_wrap) -{ - stat_start_time_t start_time; - stat_start_time_init(&start_time, &enc->shared_data->zlib_glz_stat); - spice_assert(bitmap_fmt_is_rgb(src->format)); - GlzData *glz_data = &enc->glz_data; - ZlibData *zlib_data; - LzImageType type = bitmap_fmt_to_lz_image_type[src->format]; - RedGlzDrawable *glz_drawable; - GlzDrawableInstanceItem *glz_drawable_instance; - int glz_size; - int zlib_size; - -#ifdef COMPRESS_DEBUG - spice_info("LZ global compress fmt=%d", src->format); -#endif - - if ((src->x * src->y) >= glz_enc_dictionary_get_size(enc->glz_dict->dict)) { - return FALSE; - } - - pthread_rwlock_rdlock(&enc->glz_dict->encode_lock); - /* using the global dictionary only if it is not frozen */ - if (enc->glz_dict->migrate_freeze) { - pthread_rwlock_unlock(&enc->glz_dict->encode_lock); - return FALSE; - } - - encoder_data_init(&glz_data->data); - - glz_drawable = get_glz_drawable(enc, red_drawable, glz_retention); - glz_drawable_instance = add_glz_drawable_instance(glz_drawable); - - glz_data->data.u.lines_data.chunks = src->data; - glz_data->data.u.lines_data.stride = src->stride; - glz_data->data.u.lines_data.next = 0; - glz_data->data.u.lines_data.reverse = 0; - - glz_size = glz_encode(enc->glz, type, src->x, src->y, - (src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), NULL, 0, - src->stride, glz_data->data.bufs_head->buf.bytes, - sizeof(glz_data->data.bufs_head->buf), - glz_drawable_instance, - &glz_drawable_instance->context); - - stat_compress_add(&enc->shared_data->glz_stat, start_time, src->stride * src->y, glz_size); - - if (!enable_zlib_glz_wrap || (glz_size < MIN_GLZ_SIZE_FOR_ZLIB)) { - goto glz; - } - stat_start_time_init(&start_time, &enc->shared_data->zlib_glz_stat); - zlib_data = &enc->zlib_data; - - encoder_data_init(&zlib_data->data); - - zlib_data->data.u.compressed_data.next = glz_data->data.bufs_head; - zlib_data->data.u.compressed_data.size_left = glz_size; - - zlib_size = zlib_encode(enc->zlib, enc->zlib_level, - glz_size, zlib_data->data.bufs_head->buf.bytes, - sizeof(zlib_data->data.bufs_head->buf)); - - // the compressed buffer is bigger than the original data - if (zlib_size >= glz_size) { - encoder_data_reset(&zlib_data->data); - goto glz; - } else { - encoder_data_reset(&glz_data->data); - } - - dest->descriptor.type = SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB; - dest->u.zlib_glz.glz_data_size = glz_size; - dest->u.zlib_glz.data_size = zlib_size; - - o_comp_data->comp_buf = zlib_data->data.bufs_head; - o_comp_data->comp_buf_size = zlib_size; - - stat_compress_add(&enc->shared_data->zlib_glz_stat, start_time, glz_size, zlib_size); - pthread_rwlock_unlock(&enc->glz_dict->encode_lock); - return TRUE; - -glz: - pthread_rwlock_unlock(&enc->glz_dict->encode_lock); - - dest->descriptor.type = SPICE_IMAGE_TYPE_GLZ_RGB; - dest->u.lz_rgb.data_size = glz_size; - - o_comp_data->comp_buf = glz_data->data.bufs_head; - o_comp_data->comp_buf_size = glz_size; - - return TRUE; -} - -void image_encoder_shared_init(ImageEncoderSharedData *shared_data) -{ - clockid_t stat_clock = CLOCK_THREAD_CPUTIME_ID; - - stat_compress_init(&shared_data->off_stat, "off", stat_clock); - stat_compress_init(&shared_data->lz_stat, "lz", stat_clock); - stat_compress_init(&shared_data->glz_stat, "glz", stat_clock); - stat_compress_init(&shared_data->quic_stat, "quic", stat_clock); - stat_compress_init(&shared_data->jpeg_stat, "jpeg", stat_clock); - stat_compress_init(&shared_data->zlib_glz_stat, "zlib", stat_clock); - stat_compress_init(&shared_data->jpeg_alpha_stat, "jpeg_alpha", stat_clock); - stat_compress_init(&shared_data->lz4_stat, "lz4", stat_clock); -} - -void image_encoder_shared_stat_reset(ImageEncoderSharedData *shared_data) -{ - stat_reset(&shared_data->off_stat); - stat_reset(&shared_data->quic_stat); - stat_reset(&shared_data->lz_stat); - stat_reset(&shared_data->glz_stat); - stat_reset(&shared_data->jpeg_stat); - stat_reset(&shared_data->zlib_glz_stat); - stat_reset(&shared_data->jpeg_alpha_stat); - stat_reset(&shared_data->lz4_stat); -} - -#define STAT_FMT "%s\t%8u\t%13.8g\t%12.8g\t%12.8g" - -#ifdef COMPRESS_STAT -static void stat_print_one(const char *name, const stat_info_t *stat) -{ - spice_info(STAT_FMT, name, stat->count, - stat_byte_to_mega(stat->orig_size), - stat_byte_to_mega(stat->comp_size), - stat_cpu_time_to_sec(stat->total)); -} - -static void stat_sum(stat_info_t *total, const stat_info_t *stat) -{ - total->count += stat->count; - total->orig_size += stat->orig_size; - total->comp_size += stat->comp_size; - total->total += stat->total; -} -#endif - -void image_encoder_shared_stat_print(const ImageEncoderSharedData *shared_data) -{ -#ifdef COMPRESS_STAT - /* sum all statistics */ - stat_info_t total = { - .count = 0, - .orig_size = 0, - .comp_size = 0, - .total = 0 - }; - stat_sum(&total, &shared_data->off_stat); - stat_sum(&total, &shared_data->quic_stat); - stat_sum(&total, &shared_data->glz_stat); - stat_sum(&total, &shared_data->lz_stat); - stat_sum(&total, &shared_data->jpeg_stat); - stat_sum(&total, &shared_data->jpeg_alpha_stat); - stat_sum(&total, &shared_data->lz4_stat); - - /* fix for zlib glz */ - total.total += shared_data->zlib_glz_stat.total; - if (shared_data->zlib_glz_stat.count) { - total.comp_size = total.comp_size - shared_data->glz_stat.comp_size + - shared_data->zlib_glz_stat.comp_size; - } - - spice_info("Method \t count \torig_size(MB)\tenc_size(MB)\tenc_time(s)"); - stat_print_one("OFF ", &shared_data->off_stat); - stat_print_one("QUIC ", &shared_data->quic_stat); - stat_print_one("GLZ ", &shared_data->glz_stat); - stat_print_one("ZLIB GLZ ", &shared_data->zlib_glz_stat); - stat_print_one("LZ ", &shared_data->lz_stat); - stat_print_one("JPEG ", &shared_data->jpeg_stat); - stat_print_one("JPEG-RGBA", &shared_data->jpeg_alpha_stat); - stat_print_one("LZ4 ", &shared_data->lz4_stat); - spice_info("-------------------------------------------------------------------"); - stat_print_one("Total ", &total); -#endif -} diff --git a/server/dcc-encoders.h b/server/dcc-encoders.h deleted file mode 100644 index 9286970..0000000 --- a/server/dcc-encoders.h +++ /dev/null @@ -1,217 +0,0 @@ -/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - Copyright (C) 2009-2015 Red Hat, Inc. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see <http://www.gnu.org/licenses/>. -*/ -#ifndef DCC_ENCODERS_H_ -#define DCC_ENCODERS_H_ - -#include <setjmp.h> -#include <common/quic.h> -#include <common/lz.h> - -#include "stat.h" -#include "red-parse-qxl.h" -#include "glz-encoder.h" -#include "jpeg-encoder.h" -#ifdef USE_LZ4 -#include "lz4-encoder.h" -#endif -#include "zlib-encoder.h" - -struct RedClient; - -typedef struct RedCompressBuf RedCompressBuf; -typedef struct RedGlzDrawable RedGlzDrawable; -typedef struct ImageEncoders ImageEncoders; -typedef struct ImageEncoderSharedData ImageEncoderSharedData; -typedef struct GlzSharedDictionary GlzSharedDictionary; -typedef struct GlzImageRetention GlzImageRetention; - -void image_encoder_shared_init(ImageEncoderSharedData *shared_data); -void image_encoder_shared_stat_reset(ImageEncoderSharedData *shared_data); -void image_encoder_shared_stat_print(const ImageEncoderSharedData *shared_data); - -void image_encoders_init(ImageEncoders *enc, ImageEncoderSharedData *shared_data); -void image_encoders_free(ImageEncoders *enc); -int image_encoders_free_some_independent_glz_drawables(ImageEncoders *enc); -void image_encoders_free_glz_drawables(ImageEncoders *enc); -void image_encoders_free_glz_drawables_to_free(ImageEncoders* enc); -gboolean image_encoders_glz_create(ImageEncoders *enc, uint8_t id); -void image_encoders_glz_get_restore_data(ImageEncoders *enc, - uint8_t *out_id, GlzEncDictRestoreData *out_data); -gboolean image_encoders_glz_encode_lock(ImageEncoders *enc); -void image_encoders_glz_encode_unlock(ImageEncoders *enc); -void glz_retention_free_drawables(GlzImageRetention *ret); -void glz_retention_detach_drawables(GlzImageRetention *ret); - -#define RED_COMPRESS_BUF_SIZE (1024 * 64) -struct RedCompressBuf { - /* This buffer provide space for compression algorithms. - * Some algorithms access the buffer as an array of 32 bit words - * so is defined to make sure is always aligned that way. - */ - union { - uint8_t bytes[RED_COMPRESS_BUF_SIZE]; - uint32_t words[RED_COMPRESS_BUF_SIZE / 4]; - } buf; - RedCompressBuf *send_next; -}; - -static inline void compress_buf_free(RedCompressBuf *buf) -{ - g_free(buf); -} - -gboolean image_encoders_get_glz_dictionary(ImageEncoders *enc, - struct RedClient *client, - uint8_t id, int window_size); -gboolean image_encoders_restore_glz_dictionary(ImageEncoders *enc, - struct RedClient *client, - uint8_t id, - GlzEncDictRestoreData *restore_data); - -typedef struct { - RedCompressBuf *bufs_head; - RedCompressBuf *bufs_tail; - jmp_buf jmp_env; - union { - struct { - SpiceChunks *chunks; - int next; - int stride; - int reverse; - } lines_data; - struct { - RedCompressBuf* next; - int size_left; - } compressed_data; // for encoding data that was already compressed by another method - } u; -} EncoderData; - -typedef struct { - QuicUsrContext usr; - EncoderData data; -} QuicData; - -typedef struct { - LzUsrContext usr; - EncoderData data; -} LzData; - -typedef struct { - JpegEncoderUsrContext usr; - EncoderData data; -} JpegData; - -#ifdef USE_LZ4 -typedef struct { - Lz4EncoderUsrContext usr; - EncoderData data; -} Lz4Data; -#endif - -typedef struct { - ZlibEncoderUsrContext usr; - EncoderData data; -} ZlibData; - -typedef struct { - GlzEncoderUsrContext usr; - EncoderData data; -} GlzData; - -struct GlzImageRetention { - Ring ring; -}; - -static inline void glz_retention_init(GlzImageRetention *ret) -{ - ring_init(&ret->ring); -} - -struct ImageEncoderSharedData { - uint32_t glz_drawable_count; - - stat_info_t off_stat; - stat_info_t lz_stat; - stat_info_t glz_stat; - stat_info_t quic_stat; - stat_info_t jpeg_stat; - stat_info_t zlib_glz_stat; - stat_info_t jpeg_alpha_stat; - stat_info_t lz4_stat; -}; - -struct ImageEncoders { - ImageEncoderSharedData *shared_data; - - QuicData quic_data; - QuicContext *quic; - - LzData lz_data; - LzContext *lz; - - int jpeg_quality; - - JpegData jpeg_data; - JpegEncoderContext *jpeg; - -#ifdef USE_LZ4 - Lz4Data lz4_data; - Lz4EncoderContext *lz4; -#endif - - int zlib_level; - - ZlibData zlib_data; - ZlibEncoder *zlib; - - /* global lz encoding entities */ - GlzSharedDictionary *glz_dict; - GlzEncoderContext *glz; - GlzData glz_data; - - Ring glz_drawables; // all the living lz drawable, ordered by encoding time - Ring glz_drawables_inst_to_free; // list of instances to be freed - pthread_mutex_t glz_drawables_inst_to_free_lock; -}; - -typedef struct compress_send_data_t { - void* comp_buf; - uint32_t comp_buf_size; - SpicePalette *lzplt_palette; - int is_lossy; -} compress_send_data_t; - -int image_encoders_compress_quic(ImageEncoders *enc, SpiceImage *dest, - SpiceBitmap *src, compress_send_data_t* o_comp_data); -int image_encoders_compress_lz(ImageEncoders *enc, - SpiceImage *dest, SpiceBitmap *src, - compress_send_data_t* o_comp_data); -int image_encoders_compress_jpeg(ImageEncoders *enc, SpiceImage *dest, - SpiceBitmap *src, compress_send_data_t* o_comp_data); -int image_encoders_compress_lz4(ImageEncoders *enc, SpiceImage *dest, - SpiceBitmap *src, compress_send_data_t* o_comp_data); -int image_encoders_compress_glz(ImageEncoders *enc, - SpiceImage *dest, SpiceBitmap *src, - RedDrawable *red_drawable, - GlzImageRetention *glz_retention, - compress_send_data_t* o_comp_data, - gboolean enable_zlib_glz_wrap); - -#define RED_RELEASE_BUNCH_SIZE 64 - -#endif /* DCC_ENCODERS_H_ */ diff --git a/server/dcc.h b/server/dcc.h index 0c19c02..5f7b16f 100644 --- a/server/dcc.h +++ b/server/dcc.h @@ -21,7 +21,7 @@ #include "red-worker.h" #include "pixmap-cache.h" #include "cache-item.h" -#include "dcc-encoders.h" +#include "image-encoders.h" #include "stream.h" #include "display-limits.h" diff --git a/server/image-encoders.c b/server/image-encoders.c new file mode 100644 index 0000000..b47ae35 --- /dev/null +++ b/server/image-encoders.c @@ -0,0 +1,1388 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +#include "image-encoders.h" +#include "spice-bitmap-utils.h" +#include "red-worker.h" // red_drawable_unref +#include "pixmap-cache.h" // MAX_CACHE_CLIENTS + +#define ZLIB_DEFAULT_COMPRESSION_LEVEL 3 + +#define ENCODER_MESSAGE_SIZE 512 + +#define MAX_GLZ_DRAWABLE_INSTANCES 2 + +typedef struct GlzDrawableInstanceItem GlzDrawableInstanceItem; + +struct GlzSharedDictionary { + RingItem base; + GlzEncDictContext *dict; + uint32_t refs; + uint8_t id; + pthread_rwlock_t encode_lock; + int migrate_freeze; + RedClient *client; // channel clients of the same client share the dict +}; + +/* for each qxl drawable, there may be several instances of lz drawables */ +/* TODO - reuse this stuff for the top level. I just added a second level of multiplicity + * at the Drawable by keeping a ring, so: + * Drawable -> (ring of) RedGlzDrawable -> (up to 2) GlzDrawableInstanceItem + * and it should probably (but need to be sure...) be + * Drawable -> ring of GlzDrawableInstanceItem. + */ +struct GlzDrawableInstanceItem { + RingItem glz_link; + RingItem free_link; + GlzEncDictImageContext *context; + RedGlzDrawable *glz_drawable; +}; + +struct RedGlzDrawable { + RingItem link; // ordered by the time it was encoded + RingItem drawable_link; + RedDrawable *red_drawable; + GlzDrawableInstanceItem instances_pool[MAX_GLZ_DRAWABLE_INSTANCES]; + Ring instances; + uint8_t instances_count; + gboolean has_drawable; + ImageEncoders *encoders; +}; + +#define LINK_TO_GLZ(ptr) SPICE_CONTAINEROF((ptr), RedGlzDrawable, \ + drawable_link) +#define DRAWABLE_FOREACH_GLZ_SAFE(drawable, link, next, glz) \ + SAFE_FOREACH(link, next, drawable, &(drawable)->glz_retention.ring, glz, LINK_TO_GLZ(link)) + +static void glz_drawable_instance_item_free(GlzDrawableInstanceItem *instance); +static void encoder_data_init(EncoderData *data); +static void encoder_data_reset(EncoderData *data); +static void image_encoders_release_glz(ImageEncoders *enc); + + +static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void +quic_usr_error(QuicUsrContext *usr, const char *fmt, ...) +{ + EncoderData *usr_data = &(((QuicData *)usr)->data); + va_list ap; + char message_buf[ENCODER_MESSAGE_SIZE]; + + va_start(ap, fmt); + vsnprintf(message_buf, sizeof(message_buf), fmt, ap); + va_end(ap); + spice_critical("%s", message_buf); + + longjmp(usr_data->jmp_env, 1); +} + +static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void +lz_usr_error(LzUsrContext *usr, const char *fmt, ...) +{ + EncoderData *usr_data = &(((LzData *)usr)->data); + va_list ap; + char message_buf[ENCODER_MESSAGE_SIZE]; + + va_start(ap, fmt); + vsnprintf(message_buf, sizeof(message_buf), fmt, ap); + va_end(ap); + spice_critical("%s", message_buf); + + longjmp(usr_data->jmp_env, 1); +} + +static SPICE_GNUC_PRINTF(2, 3) void +glz_usr_error(GlzEncoderUsrContext *usr, const char *fmt, ...) +{ + va_list ap; + char message_buf[ENCODER_MESSAGE_SIZE]; + + va_start(ap, fmt); + vsnprintf(message_buf, sizeof(message_buf), fmt, ap); + va_end(ap); + + spice_critical("%s", message_buf); // if global lz fails in the middle + // the consequences are not predictable since the window + // can turn to be unsynchronized between the server and + // and the client +} + +static SPICE_GNUC_PRINTF(2, 3) void +quic_usr_warn(QuicUsrContext *usr, const char *fmt, ...) +{ + va_list ap; + char message_buf[ENCODER_MESSAGE_SIZE]; + + va_start(ap, fmt); + vsnprintf(message_buf, sizeof(message_buf), fmt, ap); + va_end(ap); + spice_warning("%s", message_buf); +} + +static SPICE_GNUC_PRINTF(2, 3) void +lz_usr_warn(LzUsrContext *usr, const char *fmt, ...) +{ + va_list ap; + char message_buf[ENCODER_MESSAGE_SIZE]; + + va_start(ap, fmt); + vsnprintf(message_buf, sizeof(message_buf), fmt, ap); + va_end(ap); + spice_warning("%s", message_buf); +} + +static SPICE_GNUC_PRINTF(2, 3) void +glz_usr_warn(GlzEncoderUsrContext *usr, const char *fmt, ...) +{ + va_list ap; + char message_buf[ENCODER_MESSAGE_SIZE]; + + va_start(ap, fmt); + vsnprintf(message_buf, sizeof(message_buf), fmt, ap); + va_end(ap); + spice_warning("%s", message_buf); +} + +static void *quic_usr_malloc(QuicUsrContext *usr, int size) +{ + return spice_malloc(size); +} + +static void *lz_usr_malloc(LzUsrContext *usr, int size) +{ + return spice_malloc(size); +} + +static void *glz_usr_malloc(GlzEncoderUsrContext *usr, int size) +{ + return spice_malloc(size); +} + +static void quic_usr_free(QuicUsrContext *usr, void *ptr) +{ + free(ptr); +} + +static void lz_usr_free(LzUsrContext *usr, void *ptr) +{ + free(ptr); +} + +static void glz_usr_free(GlzEncoderUsrContext *usr, void *ptr) +{ + free(ptr); +} + +static void encoder_data_init(EncoderData *data) +{ + data->bufs_tail = g_new(RedCompressBuf, 1); + data->bufs_head = data->bufs_tail; + data->bufs_head->send_next = NULL; +} + +static void encoder_data_reset(EncoderData *data) +{ + RedCompressBuf *buf = data->bufs_head; + while (buf) { + RedCompressBuf *next = buf->send_next; + g_free(buf); + buf = next; + } + data->bufs_head = data->bufs_tail = NULL; +} + +/* Allocate more space for compressed buffer. + * The pointer returned in io_ptr is garanteed to be aligned to 4 bytes. + */ +static int encoder_usr_more_space(EncoderData *enc_data, uint8_t **io_ptr) +{ + RedCompressBuf *buf; + + buf = g_new(RedCompressBuf, 1); + enc_data->bufs_tail->send_next = buf; + enc_data->bufs_tail = buf; + buf->send_next = NULL; + *io_ptr = buf->buf.bytes; + return sizeof(buf->buf); +} + +static int quic_usr_more_space(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed) +{ + EncoderData *usr_data = &(((QuicData *)usr)->data); + return encoder_usr_more_space(usr_data, (uint8_t **)io_ptr) / sizeof(uint32_t); +} + +static int lz_usr_more_space(LzUsrContext *usr, uint8_t **io_ptr) +{ + EncoderData *usr_data = &(((LzData *)usr)->data); + return encoder_usr_more_space(usr_data, io_ptr); +} + +static int glz_usr_more_space(GlzEncoderUsrContext *usr, uint8_t **io_ptr) +{ + EncoderData *usr_data = &(((GlzData *)usr)->data); + return encoder_usr_more_space(usr_data, io_ptr); +} + +static int jpeg_usr_more_space(JpegEncoderUsrContext *usr, uint8_t **io_ptr) +{ + EncoderData *usr_data = &(((JpegData *)usr)->data); + return encoder_usr_more_space(usr_data, io_ptr); +} + +#ifdef USE_LZ4 +static int lz4_usr_more_space(Lz4EncoderUsrContext *usr, uint8_t **io_ptr) +{ + EncoderData *usr_data = &(((Lz4Data *)usr)->data); + return encoder_usr_more_space(usr_data, io_ptr); +} +#endif + +static int zlib_usr_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr) +{ + EncoderData *usr_data = &(((ZlibData *)usr)->data); + return encoder_usr_more_space(usr_data, io_ptr); +} + +static inline int encoder_usr_more_lines(EncoderData *enc_data, uint8_t **lines) +{ + struct SpiceChunk *chunk; + + if (enc_data->u.lines_data.reverse) { + if (!(enc_data->u.lines_data.next >= 0)) { + return 0; + } + } else { + if (!(enc_data->u.lines_data.next < enc_data->u.lines_data.chunks->num_chunks)) { + return 0; + } + } + + chunk = &enc_data->u.lines_data.chunks->chunk[enc_data->u.lines_data.next]; + if (chunk->len % enc_data->u.lines_data.stride) { + return 0; + } + + if (enc_data->u.lines_data.reverse) { + enc_data->u.lines_data.next--; + *lines = chunk->data + chunk->len - enc_data->u.lines_data.stride; + } else { + enc_data->u.lines_data.next++; + *lines = chunk->data; + } + + return chunk->len / enc_data->u.lines_data.stride; +} + +static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((QuicData *)usr)->data); + return encoder_usr_more_lines(usr_data, lines); +} + +static int lz_usr_more_lines(LzUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((LzData *)usr)->data); + return encoder_usr_more_lines(usr_data, lines); +} + +static int glz_usr_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((GlzData *)usr)->data); + return encoder_usr_more_lines(usr_data, lines); +} + +static int jpeg_usr_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((JpegData *)usr)->data); + return encoder_usr_more_lines(usr_data, lines); +} + +#ifdef USE_LZ4 +static int lz4_usr_more_lines(Lz4EncoderUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((Lz4Data *)usr)->data); + return encoder_usr_more_lines(usr_data, lines); +} +#endif + +static int zlib_usr_more_input(ZlibEncoderUsrContext *usr, uint8_t** input) +{ + EncoderData *usr_data = &(((ZlibData *)usr)->data); + int buf_size; + + if (!usr_data->u.compressed_data.next) { + spice_assert(usr_data->u.compressed_data.size_left == 0); + return 0; + } + + *input = usr_data->u.compressed_data.next->buf.bytes; + buf_size = MIN(sizeof(usr_data->u.compressed_data.next->buf), + usr_data->u.compressed_data.size_left); + + usr_data->u.compressed_data.next = usr_data->u.compressed_data.next->send_next; + usr_data->u.compressed_data.size_left -= buf_size; + return buf_size; +} + +static void image_encoders_init_quic(ImageEncoders *enc) +{ + enc->quic_data.usr.error = quic_usr_error; + enc->quic_data.usr.warn = quic_usr_warn; + enc->quic_data.usr.info = quic_usr_warn; + enc->quic_data.usr.malloc = quic_usr_malloc; + enc->quic_data.usr.free = quic_usr_free; + enc->quic_data.usr.more_space = quic_usr_more_space; + enc->quic_data.usr.more_lines = quic_usr_more_lines; + + enc->quic = quic_create(&enc->quic_data.usr); + + if (!enc->quic) { + spice_critical("create quic failed"); + } +} + +static void image_encoders_init_lz(ImageEncoders *enc) +{ + enc->lz_data.usr.error = lz_usr_error; + enc->lz_data.usr.warn = lz_usr_warn; + enc->lz_data.usr.info = lz_usr_warn; + enc->lz_data.usr.malloc = lz_usr_malloc; + enc->lz_data.usr.free = lz_usr_free; + enc->lz_data.usr.more_space = lz_usr_more_space; + enc->lz_data.usr.more_lines = lz_usr_more_lines; + + enc->lz = lz_create(&enc->lz_data.usr); + + if (!enc->lz) { + spice_critical("create lz failed"); + } +} + +static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image) +{ + GlzData *lz_data = (GlzData *)usr; + GlzDrawableInstanceItem *glz_drawable_instance = (GlzDrawableInstanceItem *)image; + ImageEncoders *drawable_enc = glz_drawable_instance->glz_drawable->encoders; + ImageEncoders *this_enc = SPICE_CONTAINEROF(lz_data, ImageEncoders, glz_data); + if (this_enc == drawable_enc) { + glz_drawable_instance_item_free(glz_drawable_instance); + } else { + /* The glz dictionary is shared between all DisplayChannelClient + * instances that belong to the same client, and glz_usr_free_image + * can be called by the dictionary code + * (glz_dictionary_window_remove_head). Thus this function can be + * called from any DisplayChannelClient thread, hence the need for + * this check. + */ + pthread_mutex_lock(&drawable_enc->glz_drawables_inst_to_free_lock); + ring_add_before(&glz_drawable_instance->free_link, + &drawable_enc->glz_drawables_inst_to_free); + pthread_mutex_unlock(&drawable_enc->glz_drawables_inst_to_free_lock); + } +} + +static void image_encoders_init_glz_data(ImageEncoders *enc) +{ + enc->glz_data.usr.error = glz_usr_error; + enc->glz_data.usr.warn = glz_usr_warn; + enc->glz_data.usr.info = glz_usr_warn; + enc->glz_data.usr.malloc = glz_usr_malloc; + enc->glz_data.usr.free = glz_usr_free; + enc->glz_data.usr.more_space = glz_usr_more_space; + enc->glz_data.usr.more_lines = glz_usr_more_lines; + enc->glz_data.usr.free_image = glz_usr_free_image; +} + +static void image_encoders_init_jpeg(ImageEncoders *enc) +{ + enc->jpeg_data.usr.more_space = jpeg_usr_more_space; + enc->jpeg_data.usr.more_lines = jpeg_usr_more_lines; + + enc->jpeg = jpeg_encoder_create(&enc->jpeg_data.usr); + + if (!enc->jpeg) { + spice_critical("create jpeg encoder failed"); + } +} + +#ifdef USE_LZ4 +static inline void image_encoders_init_lz4(ImageEncoders *enc) +{ + enc->lz4_data.usr.more_space = lz4_usr_more_space; + enc->lz4_data.usr.more_lines = lz4_usr_more_lines; + + enc->lz4 = lz4_encoder_create(&enc->lz4_data.usr); + + if (!enc->lz4) { + spice_critical("create lz4 encoder failed"); + } +} +#endif + +static void image_encoders_init_zlib(ImageEncoders *enc) +{ + enc->zlib_data.usr.more_space = zlib_usr_more_space; + enc->zlib_data.usr.more_input = zlib_usr_more_input; + + enc->zlib = zlib_encoder_create(&enc->zlib_data.usr, ZLIB_DEFAULT_COMPRESSION_LEVEL); + + if (!enc->zlib) { + spice_critical("create zlib encoder failed"); + } +} + +void image_encoders_init(ImageEncoders *enc, ImageEncoderSharedData *shared_data) +{ + spice_assert(shared_data); + enc->shared_data = shared_data; + + ring_init(&enc->glz_drawables); + ring_init(&enc->glz_drawables_inst_to_free); + pthread_mutex_init(&enc->glz_drawables_inst_to_free_lock, NULL); + + image_encoders_init_glz_data(enc); + image_encoders_init_quic(enc); + image_encoders_init_lz(enc); + image_encoders_init_jpeg(enc); +#ifdef USE_LZ4 + image_encoders_init_lz4(enc); +#endif + image_encoders_init_zlib(enc); + + // todo: tune level according to bandwidth + enc->zlib_level = ZLIB_DEFAULT_COMPRESSION_LEVEL; +} + +void image_encoders_free(ImageEncoders *enc) +{ + image_encoders_release_glz(enc); + quic_destroy(enc->quic); + enc->quic = NULL; + lz_destroy(enc->lz); + enc->lz = NULL; + jpeg_encoder_destroy(enc->jpeg); + enc->jpeg = NULL; +#ifdef USE_LZ4 + lz4_encoder_destroy(enc->lz4); + enc->lz4 = NULL; +#endif + zlib_encoder_destroy(enc->zlib); + enc->zlib = NULL; +} + +/* Remove from the to_free list and the instances_list. + When no instance is left - the RedGlzDrawable is released too. (and the qxl drawable too, if + it is not used by Drawable). + NOTE - 1) can be called only by the display channel that created the drawable + 2) it is assumed that the instance was already removed from the dictionary*/ +static void glz_drawable_instance_item_free(GlzDrawableInstanceItem *instance) +{ + RedGlzDrawable *glz_drawable; + + spice_assert(instance); + spice_assert(instance->glz_drawable); + + glz_drawable = instance->glz_drawable; + + spice_assert(glz_drawable->instances_count > 0); + + ring_remove(&instance->glz_link); + glz_drawable->instances_count--; + + // when the remove callback is performed from the channel that the + // drawable belongs to, the instance is not added to the 'to_free' list + if (ring_item_is_linked(&instance->free_link)) { + ring_remove(&instance->free_link); + } + + if (ring_is_empty(&glz_drawable->instances)) { + spice_assert(glz_drawable->instances_count == 0); + + if (glz_drawable->has_drawable) { + ring_remove(&glz_drawable->drawable_link); + } + red_drawable_unref(glz_drawable->red_drawable); + glz_drawable->encoders->shared_data->glz_drawable_count--; + if (ring_item_is_linked(&glz_drawable->link)) { + ring_remove(&glz_drawable->link); + } + free(glz_drawable); + } +} + +/* + * Releases all the instances of the drawable from the dictionary and the display channel client. + * The release of the last instance will also release the drawable itself and the qxl drawable + * if possible. + * NOTE - the caller should prevent encoding using the dictionary during this operation + */ +static void red_glz_drawable_free(RedGlzDrawable *glz_drawable) +{ + ImageEncoders *enc = glz_drawable->encoders; + RingItem *head_instance = ring_get_head(&glz_drawable->instances); + int cont = (head_instance != NULL); + + while (cont) { + if (glz_drawable->instances_count == 1) { + /* Last instance: glz_drawable_instance_item_free will free the glz_drawable */ + cont = FALSE; + } + GlzDrawableInstanceItem *instance = SPICE_CONTAINEROF(head_instance, + GlzDrawableInstanceItem, + glz_link); + if (!ring_item_is_linked(&instance->free_link)) { + // the instance didn't get out from window yet + glz_enc_dictionary_remove_image(enc->glz_dict->dict, + instance->context, + &enc->glz_data.usr); + } + glz_drawable_instance_item_free(instance); + + if (cont) { + head_instance = ring_get_head(&glz_drawable->instances); + } + } +} + +gboolean image_encoders_glz_encode_lock(ImageEncoders *enc) +{ + if (enc->glz_dict) { + pthread_rwlock_wrlock(&enc->glz_dict->encode_lock); + return TRUE; + } + return FALSE; +} + +void image_encoders_glz_encode_unlock(ImageEncoders *enc) +{ + if (enc->glz_dict) { + pthread_rwlock_unlock(&enc->glz_dict->encode_lock); + } +} + +/* + * Remove from the global lz dictionary some glz_drawables that have no reference to + * Drawable (their qxl drawables are released too). + * NOTE - the caller should prevent encoding using the dictionary during the operation + */ +int image_encoders_free_some_independent_glz_drawables(ImageEncoders *enc) +{ + RingItem *ring_link; + int n = 0; + + if (!enc) { + return 0; + } + ring_link = ring_get_head(&enc->glz_drawables); + while ((n < RED_RELEASE_BUNCH_SIZE) && (ring_link != NULL)) { + RedGlzDrawable *glz_drawable = SPICE_CONTAINEROF(ring_link, RedGlzDrawable, link); + ring_link = ring_next(&enc->glz_drawables, ring_link); + if (!glz_drawable->has_drawable) { + red_glz_drawable_free(glz_drawable); + n++; + } + } + return n; +} + +void image_encoders_free_glz_drawables_to_free(ImageEncoders* enc) +{ + RingItem *ring_link; + + if (!enc->glz_dict) { + return; + } + pthread_mutex_lock(&enc->glz_drawables_inst_to_free_lock); + while ((ring_link = ring_get_head(&enc->glz_drawables_inst_to_free))) { + GlzDrawableInstanceItem *drawable_instance = SPICE_CONTAINEROF(ring_link, + GlzDrawableInstanceItem, + free_link); + glz_drawable_instance_item_free(drawable_instance); + } + pthread_mutex_unlock(&enc->glz_drawables_inst_to_free_lock); +} + +/* Clear all lz drawables - enforce their removal from the global dictionary. + NOTE - prevents encoding using the dictionary during the operation*/ +void image_encoders_free_glz_drawables(ImageEncoders *enc) +{ + RingItem *ring_link; + GlzSharedDictionary *glz_dict = enc ? enc->glz_dict : NULL; + + if (!glz_dict) { + return; + } + + // assure no display channel is during global lz encoding + pthread_rwlock_wrlock(&glz_dict->encode_lock); + while ((ring_link = ring_get_head(&enc->glz_drawables))) { + RedGlzDrawable *drawable = SPICE_CONTAINEROF(ring_link, RedGlzDrawable, link); + // no need to lock the to_free list, since we assured no other thread is encoding and + // thus not other thread access the to_free list of the channel + red_glz_drawable_free(drawable); + } + pthread_rwlock_unlock(&glz_dict->encode_lock); +} + +void glz_retention_free_drawables(GlzImageRetention *ret) +{ + RingItem *glz_item, *next_item; + RedGlzDrawable *glz; + SAFE_FOREACH(glz_item, next_item, TRUE, &ret->ring, glz, LINK_TO_GLZ(glz_item)) { + red_glz_drawable_free(glz); + } +} + +void glz_retention_detach_drawables(GlzImageRetention *ret) +{ + RingItem *item, *next; + + RING_FOREACH_SAFE(item, next, &ret->ring) { + SPICE_CONTAINEROF(item, RedGlzDrawable, drawable_link)->has_drawable = FALSE; + ring_remove(item); + } +} + +static void image_encoders_freeze_glz(ImageEncoders *enc) +{ + pthread_rwlock_wrlock(&enc->glz_dict->encode_lock); + enc->glz_dict->migrate_freeze = TRUE; + pthread_rwlock_unlock(&enc->glz_dict->encode_lock); +} + +void image_encoders_glz_get_restore_data(ImageEncoders *enc, + uint8_t *out_id, GlzEncDictRestoreData *out_data) +{ + spice_assert(enc->glz_dict); + image_encoders_freeze_glz(enc); + *out_id = enc->glz_dict->id; + glz_enc_dictionary_get_restore_data(enc->glz_dict->dict, out_data, + &enc->glz_data.usr); +} + +static GlzSharedDictionary *glz_shared_dictionary_new(RedClient *client, uint8_t id, + GlzEncDictContext *dict) +{ + spice_return_val_if_fail(dict != NULL, NULL); + + GlzSharedDictionary *shared_dict = spice_new0(GlzSharedDictionary, 1); + + shared_dict->dict = dict; + shared_dict->id = id; + shared_dict->refs = 1; + shared_dict->migrate_freeze = FALSE; + shared_dict->client = client; + ring_item_init(&shared_dict->base); + pthread_rwlock_init(&shared_dict->encode_lock, NULL); + + return shared_dict; +} + +static pthread_mutex_t glz_dictionary_list_lock = PTHREAD_MUTEX_INITIALIZER; +static Ring glz_dictionary_list = {&glz_dictionary_list, &glz_dictionary_list}; + +static GlzSharedDictionary *find_glz_dictionary(RedClient *client, uint8_t dict_id) +{ + RingItem *now; + GlzSharedDictionary *ret = NULL; + + now = &glz_dictionary_list; + while ((now = ring_next(&glz_dictionary_list, now))) { + GlzSharedDictionary *dict = SPICE_UPCAST(GlzSharedDictionary, now); + if ((dict->client == client) && (dict->id == dict_id)) { + ret = dict; + break; + } + } + + return ret; +} + +#define MAX_LZ_ENCODERS MAX_CACHE_CLIENTS + +static GlzSharedDictionary *create_glz_dictionary(ImageEncoders *enc, + RedClient *client, + uint8_t id, int window_size) +{ + spice_info("Lz Window %d Size=%d", id, window_size); + + GlzEncDictContext *glz_dict = + glz_enc_dictionary_create(window_size, MAX_LZ_ENCODERS, &enc->glz_data.usr); + + return glz_shared_dictionary_new(client, id, glz_dict); +} + +gboolean image_encoders_get_glz_dictionary(ImageEncoders *enc, + RedClient *client, + uint8_t id, int window_size) +{ + GlzSharedDictionary *shared_dict; + + spice_return_val_if_fail(!enc->glz_dict, FALSE); + + pthread_mutex_lock(&glz_dictionary_list_lock); + + shared_dict = find_glz_dictionary(client, id); + if (shared_dict) { + shared_dict->refs++; + } else { + shared_dict = create_glz_dictionary(enc, client, id, window_size); + ring_add(&glz_dictionary_list, &shared_dict->base); + } + + pthread_mutex_unlock(&glz_dictionary_list_lock); + enc->glz_dict = shared_dict; + return shared_dict != NULL; +} + +static GlzSharedDictionary *restore_glz_dictionary(ImageEncoders *enc, + RedClient *client, + uint8_t id, + GlzEncDictRestoreData *restore_data) +{ + GlzEncDictContext *glz_dict = + glz_enc_dictionary_restore(restore_data, &enc->glz_data.usr); + + return glz_shared_dictionary_new(client, id, glz_dict); +} + +gboolean image_encoders_restore_glz_dictionary(ImageEncoders *enc, + RedClient *client, + uint8_t id, + GlzEncDictRestoreData *restore_data) +{ + GlzSharedDictionary *shared_dict = NULL; + + spice_return_val_if_fail(!enc->glz_dict, FALSE); + + pthread_mutex_lock(&glz_dictionary_list_lock); + + shared_dict = find_glz_dictionary(client, id); + + if (shared_dict) { + shared_dict->refs++; + } else { + shared_dict = restore_glz_dictionary(enc, client, id, restore_data); + ring_add(&glz_dictionary_list, &shared_dict->base); + } + + pthread_mutex_unlock(&glz_dictionary_list_lock); + enc->glz_dict = shared_dict; + return shared_dict != NULL; +} + +gboolean image_encoders_glz_create(ImageEncoders *enc, uint8_t id) +{ + enc->glz = glz_encoder_create(id, enc->glz_dict->dict, &enc->glz_data.usr); + return enc->glz != NULL; +} + +/* destroy encoder, and dictionary if no one uses it*/ +static void image_encoders_release_glz(ImageEncoders *enc) +{ + GlzSharedDictionary *shared_dict; + + image_encoders_free_glz_drawables(enc); + + glz_encoder_destroy(enc->glz); + enc->glz = NULL; + + if (!(shared_dict = enc->glz_dict)) { + return; + } + + enc->glz_dict = NULL; + pthread_mutex_lock(&glz_dictionary_list_lock); + if (--shared_dict->refs != 0) { + pthread_mutex_unlock(&glz_dictionary_list_lock); + return; + } + ring_remove(&shared_dict->base); + pthread_mutex_unlock(&glz_dictionary_list_lock); + glz_enc_dictionary_destroy(shared_dict->dict, &enc->glz_data.usr); + free(shared_dict); +} + +int image_encoders_compress_quic(ImageEncoders *enc, SpiceImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data) +{ + QuicData *quic_data = &enc->quic_data; + QuicContext *quic = enc->quic; + volatile QuicImageType type; + int size, stride; + stat_start_time_t start_time; + stat_start_time_init(&start_time, &enc->shared_data->quic_stat); + +#ifdef COMPRESS_DEBUG + spice_info("QUIC compress"); +#endif + + switch (src->format) { + case SPICE_BITMAP_FMT_32BIT: + type = QUIC_IMAGE_TYPE_RGB32; + break; + case SPICE_BITMAP_FMT_RGBA: + type = QUIC_IMAGE_TYPE_RGBA; + break; + case SPICE_BITMAP_FMT_16BIT: + type = QUIC_IMAGE_TYPE_RGB16; + break; + case SPICE_BITMAP_FMT_24BIT: + type = QUIC_IMAGE_TYPE_RGB24; + break; + default: + return FALSE; + } + + encoder_data_init(&quic_data->data); + + if (setjmp(quic_data->data.jmp_env)) { + encoder_data_reset(&quic_data->data); + return FALSE; + } + + if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) { + spice_chunks_linearize(src->data); + } + + quic_data->data.u.lines_data.chunks = src->data; + quic_data->data.u.lines_data.stride = src->stride; + if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { + quic_data->data.u.lines_data.next = 0; + quic_data->data.u.lines_data.reverse = 0; + stride = src->stride; + } else { + quic_data->data.u.lines_data.next = src->data->num_chunks - 1; + quic_data->data.u.lines_data.reverse = 1; + stride = -src->stride; + } + size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride, + quic_data->data.bufs_head->buf.words, + G_N_ELEMENTS(quic_data->data.bufs_head->buf.words)); + + // the compressed buffer is bigger than the original data + if ((size << 2) > (src->y * src->stride)) { + longjmp(quic_data->data.jmp_env, 1); + } + + dest->descriptor.type = SPICE_IMAGE_TYPE_QUIC; + dest->u.quic.data_size = size << 2; + + o_comp_data->comp_buf = quic_data->data.bufs_head; + o_comp_data->comp_buf_size = size << 2; + + stat_compress_add(&enc->shared_data->quic_stat, start_time, src->stride * src->y, + o_comp_data->comp_buf_size); + return TRUE; +} + +static const LzImageType bitmap_fmt_to_lz_image_type[] = { + LZ_IMAGE_TYPE_INVALID, + LZ_IMAGE_TYPE_PLT1_LE, + LZ_IMAGE_TYPE_PLT1_BE, + LZ_IMAGE_TYPE_PLT4_LE, + LZ_IMAGE_TYPE_PLT4_BE, + LZ_IMAGE_TYPE_PLT8, + LZ_IMAGE_TYPE_RGB16, + LZ_IMAGE_TYPE_RGB24, + LZ_IMAGE_TYPE_RGB32, + LZ_IMAGE_TYPE_RGBA, + LZ_IMAGE_TYPE_A8 +}; + +int image_encoders_compress_lz(ImageEncoders *enc, + SpiceImage *dest, SpiceBitmap *src, + compress_send_data_t* o_comp_data) +{ + LzData *lz_data = &enc->lz_data; + LzContext *lz = enc->lz; + LzImageType type = bitmap_fmt_to_lz_image_type[src->format]; + int size; // size of the compressed data + + stat_start_time_t start_time; + stat_start_time_init(&start_time, &enc->shared_data->lz_stat); + +#ifdef COMPRESS_DEBUG + spice_info("LZ LOCAL compress"); +#endif + + encoder_data_init(&lz_data->data); + + if (setjmp(lz_data->data.jmp_env)) { + encoder_data_reset(&lz_data->data); + return FALSE; + } + + lz_data->data.u.lines_data.chunks = src->data; + lz_data->data.u.lines_data.stride = src->stride; + lz_data->data.u.lines_data.next = 0; + lz_data->data.u.lines_data.reverse = 0; + + size = lz_encode(lz, type, src->x, src->y, + !!(src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), + NULL, 0, src->stride, + lz_data->data.bufs_head->buf.bytes, + sizeof(lz_data->data.bufs_head->buf)); + + // the compressed buffer is bigger than the original data + if (size > (src->y * src->stride)) { + longjmp(lz_data->data.jmp_env, 1); + } + + if (bitmap_fmt_is_rgb(src->format)) { + dest->descriptor.type = SPICE_IMAGE_TYPE_LZ_RGB; + dest->u.lz_rgb.data_size = size; + + o_comp_data->comp_buf = lz_data->data.bufs_head; + o_comp_data->comp_buf_size = size; + } else { + /* masks are 1BIT bitmaps without palettes, but they are not compressed + * (see fill_mask) */ + spice_assert(src->palette); + dest->descriptor.type = SPICE_IMAGE_TYPE_LZ_PLT; + dest->u.lz_plt.data_size = size; + dest->u.lz_plt.flags = src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN; + dest->u.lz_plt.palette = src->palette; + dest->u.lz_plt.palette_id = src->palette->unique; + o_comp_data->comp_buf = lz_data->data.bufs_head; + o_comp_data->comp_buf_size = size; + + o_comp_data->lzplt_palette = dest->u.lz_plt.palette; + } + + stat_compress_add(&enc->shared_data->lz_stat, start_time, src->stride * src->y, + o_comp_data->comp_buf_size); + return TRUE; +} + +int image_encoders_compress_jpeg(ImageEncoders *enc, SpiceImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data) +{ + JpegData *jpeg_data = &enc->jpeg_data; + LzData *lz_data = &enc->lz_data; + JpegEncoderContext *jpeg = enc->jpeg; + LzContext *lz = enc->lz; + volatile JpegEncoderImageType jpeg_in_type; + int jpeg_size = 0; + volatile int has_alpha = FALSE; + int alpha_lz_size = 0; + int comp_head_filled; + int comp_head_left; + int stride; + uint8_t *lz_out_start_byte; + stat_start_time_t start_time; + stat_start_time_init(&start_time, &enc->shared_data->jpeg_alpha_stat); + +#ifdef COMPRESS_DEBUG + spice_info("JPEG compress"); +#endif + + switch (src->format) { + case SPICE_BITMAP_FMT_16BIT: + jpeg_in_type = JPEG_IMAGE_TYPE_RGB16; + break; + case SPICE_BITMAP_FMT_24BIT: + jpeg_in_type = JPEG_IMAGE_TYPE_BGR24; + break; + case SPICE_BITMAP_FMT_32BIT: + jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32; + break; + case SPICE_BITMAP_FMT_RGBA: + jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32; + has_alpha = TRUE; + break; + default: + return FALSE; + } + + encoder_data_init(&jpeg_data->data); + + if (setjmp(jpeg_data->data.jmp_env)) { + encoder_data_reset(&jpeg_data->data); + return FALSE; + } + + if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) { + spice_chunks_linearize(src->data); + } + + jpeg_data->data.u.lines_data.chunks = src->data; + jpeg_data->data.u.lines_data.stride = src->stride; + if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) { + jpeg_data->data.u.lines_data.next = 0; + jpeg_data->data.u.lines_data.reverse = 0; + stride = src->stride; + } else { + jpeg_data->data.u.lines_data.next = src->data->num_chunks - 1; + jpeg_data->data.u.lines_data.reverse = 1; + stride = -src->stride; + } + jpeg_size = jpeg_encode(jpeg, enc->jpeg_quality, jpeg_in_type, + src->x, src->y, NULL, + 0, stride, jpeg_data->data.bufs_head->buf.bytes, + sizeof(jpeg_data->data.bufs_head->buf)); + + // the compressed buffer is bigger than the original data + if (jpeg_size > (src->y * src->stride)) { + longjmp(jpeg_data->data.jmp_env, 1); + } + + if (!has_alpha) { + dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG; + dest->u.jpeg.data_size = jpeg_size; + + o_comp_data->comp_buf = jpeg_data->data.bufs_head; + o_comp_data->comp_buf_size = jpeg_size; + o_comp_data->is_lossy = TRUE; + + stat_compress_add(&enc->shared_data->jpeg_stat, start_time, src->stride * src->y, + o_comp_data->comp_buf_size); + return TRUE; + } + + lz_data->data.bufs_head = jpeg_data->data.bufs_tail; + lz_data->data.bufs_tail = lz_data->data.bufs_head; + + comp_head_filled = jpeg_size % sizeof(lz_data->data.bufs_head->buf); + comp_head_left = sizeof(lz_data->data.bufs_head->buf) - comp_head_filled; + lz_out_start_byte = lz_data->data.bufs_head->buf.bytes + comp_head_filled; + + lz_data->data.u.lines_data.chunks = src->data; + lz_data->data.u.lines_data.stride = src->stride; + lz_data->data.u.lines_data.next = 0; + lz_data->data.u.lines_data.reverse = 0; + + alpha_lz_size = lz_encode(lz, LZ_IMAGE_TYPE_XXXA, src->x, src->y, + !!(src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), + NULL, 0, src->stride, + lz_out_start_byte, + comp_head_left); + + // the compressed buffer is bigger than the original data + if ((jpeg_size + alpha_lz_size) > (src->y * src->stride)) { + longjmp(jpeg_data->data.jmp_env, 1); + } + + dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG_ALPHA; + dest->u.jpeg_alpha.flags = 0; + if (src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN) { + dest->u.jpeg_alpha.flags |= SPICE_JPEG_ALPHA_FLAGS_TOP_DOWN; + } + + dest->u.jpeg_alpha.jpeg_size = jpeg_size; + dest->u.jpeg_alpha.data_size = jpeg_size + alpha_lz_size; + + o_comp_data->comp_buf = jpeg_data->data.bufs_head; + o_comp_data->comp_buf_size = jpeg_size + alpha_lz_size; + o_comp_data->is_lossy = TRUE; + stat_compress_add(&enc->shared_data->jpeg_alpha_stat, start_time, src->stride * src->y, + o_comp_data->comp_buf_size); + return TRUE; +} + +#ifdef USE_LZ4 +int image_encoders_compress_lz4(ImageEncoders *enc, SpiceImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data) +{ + Lz4Data *lz4_data = &enc->lz4_data; + Lz4EncoderContext *lz4 = enc->lz4; + int lz4_size = 0; + stat_start_time_t start_time; + stat_start_time_init(&start_time, &enc->shared_data->lz4_stat); + +#ifdef COMPRESS_DEBUG + spice_info("LZ4 compress"); +#endif + + encoder_data_init(&lz4_data->data); + + if (setjmp(lz4_data->data.jmp_env)) { + encoder_data_reset(&lz4_data->data); + return FALSE; + } + + if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) { + spice_chunks_linearize(src->data); + } + + lz4_data->data.u.lines_data.chunks = src->data; + lz4_data->data.u.lines_data.stride = src->stride; + lz4_data->data.u.lines_data.next = 0; + lz4_data->data.u.lines_data.reverse = 0; + + lz4_size = lz4_encode(lz4, src->y, src->stride, lz4_data->data.bufs_head->buf.bytes, + sizeof(lz4_data->data.bufs_head->buf), + src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN, src->format); + + // the compressed buffer is bigger than the original data + if (lz4_size > (src->y * src->stride)) { + longjmp(lz4_data->data.jmp_env, 1); + } + + dest->descriptor.type = SPICE_IMAGE_TYPE_LZ4; + dest->u.lz4.data_size = lz4_size; + + o_comp_data->comp_buf = lz4_data->data.bufs_head; + o_comp_data->comp_buf_size = lz4_size; + + stat_compress_add(&enc->shared_data->lz4_stat, start_time, src->stride * src->y, + o_comp_data->comp_buf_size); + return TRUE; +} +#endif + +/* if already exists, returns it. Otherwise allocates and adds it (1) to the ring tail + in the channel (2) to the Drawable*/ +static RedGlzDrawable *get_glz_drawable(ImageEncoders *enc, RedDrawable *red_drawable, + GlzImageRetention *glz_retention) +{ + RedGlzDrawable *ret; + RingItem *item, *next; + + // TODO - I don't really understand what's going on here, so doing the technical equivalent + // now that we have multiple glz_dicts, so the only way to go from dcc to drawable glz is to go + // over the glz_ring (unless adding some better data structure then a ring) + SAFE_FOREACH(item, next, TRUE, &glz_retention->ring, ret, LINK_TO_GLZ(item)) { + if (ret->encoders == enc) { + return ret; + } + } + + ret = spice_new(RedGlzDrawable, 1); + + ret->encoders = enc; + ret->red_drawable = red_drawable_ref(red_drawable); + ret->has_drawable = TRUE; + ret->instances_count = 0; + ring_init(&ret->instances); + + ring_item_init(&ret->link); + ring_item_init(&ret->drawable_link); + ring_add_before(&ret->link, &enc->glz_drawables); + ring_add(&glz_retention->ring, &ret->drawable_link); + enc->shared_data->glz_drawable_count++; + return ret; +} + +/* allocates new instance and adds it to instances in the given drawable. + NOTE - the caller should set the glz_instance returned by the encoder by itself.*/ +static GlzDrawableInstanceItem *add_glz_drawable_instance(RedGlzDrawable *glz_drawable) +{ + spice_assert(glz_drawable->instances_count < MAX_GLZ_DRAWABLE_INSTANCES); + // NOTE: We assume the additions are performed consecutively, without removals in the middle + GlzDrawableInstanceItem *ret = glz_drawable->instances_pool + glz_drawable->instances_count; + glz_drawable->instances_count++; + + ring_item_init(&ret->free_link); + ring_item_init(&ret->glz_link); + ring_add(&glz_drawable->instances, &ret->glz_link); + ret->context = NULL; + ret->glz_drawable = glz_drawable; + + return ret; +} + +#define MIN_GLZ_SIZE_FOR_ZLIB 100 + +int image_encoders_compress_glz(ImageEncoders *enc, + SpiceImage *dest, SpiceBitmap *src, + RedDrawable *red_drawable, + GlzImageRetention *glz_retention, + compress_send_data_t* o_comp_data, + gboolean enable_zlib_glz_wrap) +{ + stat_start_time_t start_time; + stat_start_time_init(&start_time, &enc->shared_data->zlib_glz_stat); + spice_assert(bitmap_fmt_is_rgb(src->format)); + GlzData *glz_data = &enc->glz_data; + ZlibData *zlib_data; + LzImageType type = bitmap_fmt_to_lz_image_type[src->format]; + RedGlzDrawable *glz_drawable; + GlzDrawableInstanceItem *glz_drawable_instance; + int glz_size; + int zlib_size; + +#ifdef COMPRESS_DEBUG + spice_info("LZ global compress fmt=%d", src->format); +#endif + + if ((src->x * src->y) >= glz_enc_dictionary_get_size(enc->glz_dict->dict)) { + return FALSE; + } + + pthread_rwlock_rdlock(&enc->glz_dict->encode_lock); + /* using the global dictionary only if it is not frozen */ + if (enc->glz_dict->migrate_freeze) { + pthread_rwlock_unlock(&enc->glz_dict->encode_lock); + return FALSE; + } + + encoder_data_init(&glz_data->data); + + glz_drawable = get_glz_drawable(enc, red_drawable, glz_retention); + glz_drawable_instance = add_glz_drawable_instance(glz_drawable); + + glz_data->data.u.lines_data.chunks = src->data; + glz_data->data.u.lines_data.stride = src->stride; + glz_data->data.u.lines_data.next = 0; + glz_data->data.u.lines_data.reverse = 0; + + glz_size = glz_encode(enc->glz, type, src->x, src->y, + (src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), NULL, 0, + src->stride, glz_data->data.bufs_head->buf.bytes, + sizeof(glz_data->data.bufs_head->buf), + glz_drawable_instance, + &glz_drawable_instance->context); + + stat_compress_add(&enc->shared_data->glz_stat, start_time, src->stride * src->y, glz_size); + + if (!enable_zlib_glz_wrap || (glz_size < MIN_GLZ_SIZE_FOR_ZLIB)) { + goto glz; + } + stat_start_time_init(&start_time, &enc->shared_data->zlib_glz_stat); + zlib_data = &enc->zlib_data; + + encoder_data_init(&zlib_data->data); + + zlib_data->data.u.compressed_data.next = glz_data->data.bufs_head; + zlib_data->data.u.compressed_data.size_left = glz_size; + + zlib_size = zlib_encode(enc->zlib, enc->zlib_level, + glz_size, zlib_data->data.bufs_head->buf.bytes, + sizeof(zlib_data->data.bufs_head->buf)); + + // the compressed buffer is bigger than the original data + if (zlib_size >= glz_size) { + encoder_data_reset(&zlib_data->data); + goto glz; + } else { + encoder_data_reset(&glz_data->data); + } + + dest->descriptor.type = SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB; + dest->u.zlib_glz.glz_data_size = glz_size; + dest->u.zlib_glz.data_size = zlib_size; + + o_comp_data->comp_buf = zlib_data->data.bufs_head; + o_comp_data->comp_buf_size = zlib_size; + + stat_compress_add(&enc->shared_data->zlib_glz_stat, start_time, glz_size, zlib_size); + pthread_rwlock_unlock(&enc->glz_dict->encode_lock); + return TRUE; + +glz: + pthread_rwlock_unlock(&enc->glz_dict->encode_lock); + + dest->descriptor.type = SPICE_IMAGE_TYPE_GLZ_RGB; + dest->u.lz_rgb.data_size = glz_size; + + o_comp_data->comp_buf = glz_data->data.bufs_head; + o_comp_data->comp_buf_size = glz_size; + + return TRUE; +} + +void image_encoder_shared_init(ImageEncoderSharedData *shared_data) +{ + clockid_t stat_clock = CLOCK_THREAD_CPUTIME_ID; + + stat_compress_init(&shared_data->off_stat, "off", stat_clock); + stat_compress_init(&shared_data->lz_stat, "lz", stat_clock); + stat_compress_init(&shared_data->glz_stat, "glz", stat_clock); + stat_compress_init(&shared_data->quic_stat, "quic", stat_clock); + stat_compress_init(&shared_data->jpeg_stat, "jpeg", stat_clock); + stat_compress_init(&shared_data->zlib_glz_stat, "zlib", stat_clock); + stat_compress_init(&shared_data->jpeg_alpha_stat, "jpeg_alpha", stat_clock); + stat_compress_init(&shared_data->lz4_stat, "lz4", stat_clock); +} + +void image_encoder_shared_stat_reset(ImageEncoderSharedData *shared_data) +{ + stat_reset(&shared_data->off_stat); + stat_reset(&shared_data->quic_stat); + stat_reset(&shared_data->lz_stat); + stat_reset(&shared_data->glz_stat); + stat_reset(&shared_data->jpeg_stat); + stat_reset(&shared_data->zlib_glz_stat); + stat_reset(&shared_data->jpeg_alpha_stat); + stat_reset(&shared_data->lz4_stat); +} + +#define STAT_FMT "%s\t%8u\t%13.8g\t%12.8g\t%12.8g" + +#ifdef COMPRESS_STAT +static void stat_print_one(const char *name, const stat_info_t *stat) +{ + spice_info(STAT_FMT, name, stat->count, + stat_byte_to_mega(stat->orig_size), + stat_byte_to_mega(stat->comp_size), + stat_cpu_time_to_sec(stat->total)); +} + +static void stat_sum(stat_info_t *total, const stat_info_t *stat) +{ + total->count += stat->count; + total->orig_size += stat->orig_size; + total->comp_size += stat->comp_size; + total->total += stat->total; +} +#endif + +void image_encoder_shared_stat_print(const ImageEncoderSharedData *shared_data) +{ +#ifdef COMPRESS_STAT + /* sum all statistics */ + stat_info_t total = { + .count = 0, + .orig_size = 0, + .comp_size = 0, + .total = 0 + }; + stat_sum(&total, &shared_data->off_stat); + stat_sum(&total, &shared_data->quic_stat); + stat_sum(&total, &shared_data->glz_stat); + stat_sum(&total, &shared_data->lz_stat); + stat_sum(&total, &shared_data->jpeg_stat); + stat_sum(&total, &shared_data->jpeg_alpha_stat); + stat_sum(&total, &shared_data->lz4_stat); + + /* fix for zlib glz */ + total.total += shared_data->zlib_glz_stat.total; + if (shared_data->zlib_glz_stat.count) { + total.comp_size = total.comp_size - shared_data->glz_stat.comp_size + + shared_data->zlib_glz_stat.comp_size; + } + + spice_info("Method \t count \torig_size(MB)\tenc_size(MB)\tenc_time(s)"); + stat_print_one("OFF ", &shared_data->off_stat); + stat_print_one("QUIC ", &shared_data->quic_stat); + stat_print_one("GLZ ", &shared_data->glz_stat); + stat_print_one("ZLIB GLZ ", &shared_data->zlib_glz_stat); + stat_print_one("LZ ", &shared_data->lz_stat); + stat_print_one("JPEG ", &shared_data->jpeg_stat); + stat_print_one("JPEG-RGBA", &shared_data->jpeg_alpha_stat); + stat_print_one("LZ4 ", &shared_data->lz4_stat); + spice_info("-------------------------------------------------------------------"); + stat_print_one("Total ", &total); +#endif +} diff --git a/server/image-encoders.h b/server/image-encoders.h new file mode 100644 index 0000000..9286970 --- /dev/null +++ b/server/image-encoders.h @@ -0,0 +1,217 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef DCC_ENCODERS_H_ +#define DCC_ENCODERS_H_ + +#include <setjmp.h> +#include <common/quic.h> +#include <common/lz.h> + +#include "stat.h" +#include "red-parse-qxl.h" +#include "glz-encoder.h" +#include "jpeg-encoder.h" +#ifdef USE_LZ4 +#include "lz4-encoder.h" +#endif +#include "zlib-encoder.h" + +struct RedClient; + +typedef struct RedCompressBuf RedCompressBuf; +typedef struct RedGlzDrawable RedGlzDrawable; +typedef struct ImageEncoders ImageEncoders; +typedef struct ImageEncoderSharedData ImageEncoderSharedData; +typedef struct GlzSharedDictionary GlzSharedDictionary; +typedef struct GlzImageRetention GlzImageRetention; + +void image_encoder_shared_init(ImageEncoderSharedData *shared_data); +void image_encoder_shared_stat_reset(ImageEncoderSharedData *shared_data); +void image_encoder_shared_stat_print(const ImageEncoderSharedData *shared_data); + +void image_encoders_init(ImageEncoders *enc, ImageEncoderSharedData *shared_data); +void image_encoders_free(ImageEncoders *enc); +int image_encoders_free_some_independent_glz_drawables(ImageEncoders *enc); +void image_encoders_free_glz_drawables(ImageEncoders *enc); +void image_encoders_free_glz_drawables_to_free(ImageEncoders* enc); +gboolean image_encoders_glz_create(ImageEncoders *enc, uint8_t id); +void image_encoders_glz_get_restore_data(ImageEncoders *enc, + uint8_t *out_id, GlzEncDictRestoreData *out_data); +gboolean image_encoders_glz_encode_lock(ImageEncoders *enc); +void image_encoders_glz_encode_unlock(ImageEncoders *enc); +void glz_retention_free_drawables(GlzImageRetention *ret); +void glz_retention_detach_drawables(GlzImageRetention *ret); + +#define RED_COMPRESS_BUF_SIZE (1024 * 64) +struct RedCompressBuf { + /* This buffer provide space for compression algorithms. + * Some algorithms access the buffer as an array of 32 bit words + * so is defined to make sure is always aligned that way. + */ + union { + uint8_t bytes[RED_COMPRESS_BUF_SIZE]; + uint32_t words[RED_COMPRESS_BUF_SIZE / 4]; + } buf; + RedCompressBuf *send_next; +}; + +static inline void compress_buf_free(RedCompressBuf *buf) +{ + g_free(buf); +} + +gboolean image_encoders_get_glz_dictionary(ImageEncoders *enc, + struct RedClient *client, + uint8_t id, int window_size); +gboolean image_encoders_restore_glz_dictionary(ImageEncoders *enc, + struct RedClient *client, + uint8_t id, + GlzEncDictRestoreData *restore_data); + +typedef struct { + RedCompressBuf *bufs_head; + RedCompressBuf *bufs_tail; + jmp_buf jmp_env; + union { + struct { + SpiceChunks *chunks; + int next; + int stride; + int reverse; + } lines_data; + struct { + RedCompressBuf* next; + int size_left; + } compressed_data; // for encoding data that was already compressed by another method + } u; +} EncoderData; + +typedef struct { + QuicUsrContext usr; + EncoderData data; +} QuicData; + +typedef struct { + LzUsrContext usr; + EncoderData data; +} LzData; + +typedef struct { + JpegEncoderUsrContext usr; + EncoderData data; +} JpegData; + +#ifdef USE_LZ4 +typedef struct { + Lz4EncoderUsrContext usr; + EncoderData data; +} Lz4Data; +#endif + +typedef struct { + ZlibEncoderUsrContext usr; + EncoderData data; +} ZlibData; + +typedef struct { + GlzEncoderUsrContext usr; + EncoderData data; +} GlzData; + +struct GlzImageRetention { + Ring ring; +}; + +static inline void glz_retention_init(GlzImageRetention *ret) +{ + ring_init(&ret->ring); +} + +struct ImageEncoderSharedData { + uint32_t glz_drawable_count; + + stat_info_t off_stat; + stat_info_t lz_stat; + stat_info_t glz_stat; + stat_info_t quic_stat; + stat_info_t jpeg_stat; + stat_info_t zlib_glz_stat; + stat_info_t jpeg_alpha_stat; + stat_info_t lz4_stat; +}; + +struct ImageEncoders { + ImageEncoderSharedData *shared_data; + + QuicData quic_data; + QuicContext *quic; + + LzData lz_data; + LzContext *lz; + + int jpeg_quality; + + JpegData jpeg_data; + JpegEncoderContext *jpeg; + +#ifdef USE_LZ4 + Lz4Data lz4_data; + Lz4EncoderContext *lz4; +#endif + + int zlib_level; + + ZlibData zlib_data; + ZlibEncoder *zlib; + + /* global lz encoding entities */ + GlzSharedDictionary *glz_dict; + GlzEncoderContext *glz; + GlzData glz_data; + + Ring glz_drawables; // all the living lz drawable, ordered by encoding time + Ring glz_drawables_inst_to_free; // list of instances to be freed + pthread_mutex_t glz_drawables_inst_to_free_lock; +}; + +typedef struct compress_send_data_t { + void* comp_buf; + uint32_t comp_buf_size; + SpicePalette *lzplt_palette; + int is_lossy; +} compress_send_data_t; + +int image_encoders_compress_quic(ImageEncoders *enc, SpiceImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data); +int image_encoders_compress_lz(ImageEncoders *enc, + SpiceImage *dest, SpiceBitmap *src, + compress_send_data_t* o_comp_data); +int image_encoders_compress_jpeg(ImageEncoders *enc, SpiceImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data); +int image_encoders_compress_lz4(ImageEncoders *enc, SpiceImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data); +int image_encoders_compress_glz(ImageEncoders *enc, + SpiceImage *dest, SpiceBitmap *src, + RedDrawable *red_drawable, + GlzImageRetention *glz_retention, + compress_send_data_t* o_comp_data, + gboolean enable_zlib_glz_wrap); + +#define RED_RELEASE_BUNCH_SIZE 64 + +#endif /* DCC_ENCODERS_H_ */ -- 2.7.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel