/* -*- 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include #include "dcc-encoders.h" #include "display-channel.h" #define ZLIB_DEFAULT_COMPRESSION_LEVEL 3 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; va_start(ap, fmt); vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap); va_end(ap); spice_critical("%s", usr_data->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; va_start(ap, fmt); vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap); va_end(ap); spice_critical("%s", usr_data->message_buf); longjmp(usr_data->jmp_env, 1); } static SPICE_GNUC_PRINTF(2, 3) void glz_usr_error(GlzEncoderUsrContext *usr, const char *fmt, ...) { EncoderData *usr_data = &(((GlzData *)usr)->data); va_list ap; va_start(ap, fmt); vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap); va_end(ap); spice_critical("%s", usr_data->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, ...) { EncoderData *usr_data = &(((QuicData *)usr)->data); va_list ap; va_start(ap, fmt); vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap); va_end(ap); spice_warning("%s", usr_data->message_buf); } static SPICE_GNUC_PRINTF(2, 3) void lz_usr_warn(LzUsrContext *usr, const char *fmt, ...) { EncoderData *usr_data = &(((LzData *)usr)->data); va_list ap; va_start(ap, fmt); vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap); va_end(ap); spice_warning("%s", usr_data->message_buf); } static SPICE_GNUC_PRINTF(2, 3) void glz_usr_warn(GlzEncoderUsrContext *usr, const char *fmt, ...) { EncoderData *usr_data = &(((GlzData *)usr)->data); va_list ap; va_start(ap, fmt); vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap); va_end(ap); spice_warning("%s", usr_data->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); } RedCompressBuf* compress_buf_new(void) { RedCompressBuf *buf = g_slice_new(RedCompressBuf); buf->send_next = NULL; return buf; } void compress_buf_free(RedCompressBuf *buf) { g_slice_free(RedCompressBuf, buf); } /* 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 = compress_buf_new(); 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 dcc_init_quic(DisplayChannelClient *dcc) { dcc->quic_data.usr.error = quic_usr_error; dcc->quic_data.usr.warn = quic_usr_warn; dcc->quic_data.usr.info = quic_usr_warn; dcc->quic_data.usr.malloc = quic_usr_malloc; dcc->quic_data.usr.free = quic_usr_free; dcc->quic_data.usr.more_space = quic_usr_more_space; dcc->quic_data.usr.more_lines = quic_usr_more_lines; dcc->quic = quic_create(&dcc->quic_data.usr); if (!dcc->quic) { spice_critical("create quic failed"); } } static void dcc_init_lz(DisplayChannelClient *dcc) { dcc->lz_data.usr.error = lz_usr_error; dcc->lz_data.usr.warn = lz_usr_warn; dcc->lz_data.usr.info = lz_usr_warn; dcc->lz_data.usr.malloc = lz_usr_malloc; dcc->lz_data.usr.free = lz_usr_free; dcc->lz_data.usr.more_space = lz_usr_more_space; dcc->lz_data.usr.more_lines = lz_usr_more_lines; dcc->lz = lz_create(&dcc->lz_data.usr); if (!dcc->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; DisplayChannelClient *drawable_cc = glz_drawable_instance->glz_drawable->dcc; DisplayChannelClient *this_cc = SPICE_CONTAINEROF(lz_data, DisplayChannelClient, glz_data); if (this_cc == drawable_cc) { dcc_free_glz_drawable_instance(drawable_cc, 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_cc->glz_drawables_inst_to_free_lock); ring_add_before(&glz_drawable_instance->free_link, &drawable_cc->glz_drawables_inst_to_free); pthread_mutex_unlock(&drawable_cc->glz_drawables_inst_to_free_lock); } } static void dcc_init_glz_data(DisplayChannelClient *dcc) { dcc->glz_data.usr.error = glz_usr_error; dcc->glz_data.usr.warn = glz_usr_warn; dcc->glz_data.usr.info = glz_usr_warn; dcc->glz_data.usr.malloc = glz_usr_malloc; dcc->glz_data.usr.free = glz_usr_free; dcc->glz_data.usr.more_space = glz_usr_more_space; dcc->glz_data.usr.more_lines = glz_usr_more_lines; dcc->glz_data.usr.free_image = glz_usr_free_image; } static void dcc_init_jpeg(DisplayChannelClient *dcc) { dcc->jpeg_data.usr.more_space = jpeg_usr_more_space; dcc->jpeg_data.usr.more_lines = jpeg_usr_more_lines; dcc->jpeg = jpeg_encoder_create(&dcc->jpeg_data.usr); if (!dcc->jpeg) { spice_critical("create jpeg encoder failed"); } } #ifdef USE_LZ4 static inline void dcc_init_lz4(DisplayChannelClient *dcc) { dcc->lz4_data.usr.more_space = lz4_usr_more_space; dcc->lz4_data.usr.more_lines = lz4_usr_more_lines; dcc->lz4 = lz4_encoder_create(&dcc->lz4_data.usr); if (!dcc->lz4) { spice_critical("create lz4 encoder failed"); } } #endif static void dcc_init_zlib(DisplayChannelClient *dcc) { dcc->zlib_data.usr.more_space = zlib_usr_more_space; dcc->zlib_data.usr.more_input = zlib_usr_more_input; dcc->zlib = zlib_encoder_create(&dcc->zlib_data.usr, ZLIB_DEFAULT_COMPRESSION_LEVEL); if (!dcc->zlib) { spice_critical("create zlib encoder failed"); } } void dcc_encoders_init(DisplayChannelClient *dcc) { dcc_init_glz_data(dcc); dcc_init_quic(dcc); dcc_init_lz(dcc); dcc_init_jpeg(dcc); #ifdef USE_LZ4 dcc_init_lz4(dcc); #endif dcc_init_zlib(dcc); // todo: tune level according to bandwidth dcc->zlib_level = ZLIB_DEFAULT_COMPRESSION_LEVEL; } static void marshaller_compress_buf_free(uint8_t *data, void *opaque) { compress_buf_free((RedCompressBuf *) opaque); } void marshaller_add_compressed(SpiceMarshaller *m, RedCompressBuf *comp_buf, size_t size) { size_t max = size; size_t now; do { spice_return_if_fail(comp_buf); now = MIN(sizeof(comp_buf->buf), max); max -= now; spice_marshaller_add_ref_full(m, comp_buf->buf.bytes, now, marshaller_compress_buf_free, comp_buf); comp_buf = comp_buf->send_next; } while (max); } /* 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*/ void dcc_free_glz_drawable_instance(DisplayChannelClient *dcc, GlzDrawableInstanceItem *instance) { DisplayChannel *display_channel = DCC_TO_DC(dcc); RedWorker *worker = display_channel->common.worker; RedGlzDrawable *glz_drawable; spice_assert(instance); spice_assert(instance->glz_drawable); glz_drawable = instance->glz_drawable; spice_assert(glz_drawable->dcc == dcc); 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); Drawable *drawable = glz_drawable->drawable; if (drawable) { ring_remove(&glz_drawable->drawable_link); } red_drawable_unref(worker, glz_drawable->red_drawable, glz_drawable->group_id); display_channel->glz_drawable_count--; if (ring_item_is_linked(&glz_drawable->link)) { ring_remove(&glz_drawable->link); } free(glz_drawable); } } void dcc_free_glz_drawables_to_free(DisplayChannelClient* dcc) { RingItem *ring_link; if (!dcc->glz_dict) { return; } pthread_mutex_lock(&dcc->glz_drawables_inst_to_free_lock); while ((ring_link = ring_get_head(&dcc->glz_drawables_inst_to_free))) { GlzDrawableInstanceItem *drawable_instance = SPICE_CONTAINEROF(ring_link, GlzDrawableInstanceItem, free_link); dcc_free_glz_drawable_instance(dcc, drawable_instance); } pthread_mutex_unlock(&dcc->glz_drawables_inst_to_free_lock); } void dcc_freeze_glz(DisplayChannelClient *dcc) { pthread_rwlock_wrlock(&dcc->glz_dict->encode_lock); if (!dcc->glz_dict->migrate_freeze) { dcc->glz_dict->migrate_freeze = TRUE; } pthread_rwlock_unlock(&dcc->glz_dict->encode_lock); }