diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/Makefile.am | 2 | ||||
-rw-r--r-- | server/jpeg_encoder.c | 242 | ||||
-rw-r--r-- | server/jpeg_encoder.h | 61 | ||||
-rw-r--r-- | server/red_worker.c | 309 |
4 files changed, 577 insertions, 37 deletions
diff --git a/server/Makefile.am b/server/Makefile.am index 77f533a5..8d936183 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -66,6 +66,8 @@ libspice_server_la_SOURCES = \ glz_encoder_dictionary.h \ glz_encoder_dictionary_protected.h \ glz_encoder.h \ + jpeg_encoder.c \ + jpeg_encoder.h \ mjpeg_encoder.h \ mjpeg_encoder.c \ red_bitmap_utils.h \ diff --git a/server/jpeg_encoder.c b/server/jpeg_encoder.c new file mode 100644 index 00000000..95cf2400 --- /dev/null +++ b/server/jpeg_encoder.c @@ -0,0 +1,242 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "red_common.h" +#include "jpeg_encoder.h" +#include <jpeglib.h> + +typedef struct JpegEncoder { + JpegEncoderUsrContext *usr; + + struct jpeg_destination_mgr dest_mgr; + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + struct { + JpegEncoderImageType type; + int width; + int height; + int stride; + unsigned int out_size; + void (*convert_line_to_RGB24) (uint8_t *line, int width, uint8_t **out_line); + } cur_image; +} JpegEncoder; + +/* jpeg destination manager callbacks */ + +static void dest_mgr_init_destination(j_compress_ptr cinfo) +{ + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; + if (enc->dest_mgr.free_in_buffer == 0) { + enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, + &enc->dest_mgr.next_output_byte); + + if (enc->dest_mgr.free_in_buffer == 0) { + red_error("not enough space"); + } + } + + enc->cur_image.out_size = enc->dest_mgr.free_in_buffer; +} + +static boolean dest_mgr_empty_output_buffer(j_compress_ptr cinfo) +{ + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; + enc->dest_mgr.free_in_buffer = enc->usr->more_space(enc->usr, + &enc->dest_mgr.next_output_byte); + + if (enc->dest_mgr.free_in_buffer == 0) { + red_error("not enough space"); + } + enc->cur_image.out_size += enc->dest_mgr.free_in_buffer; + return TRUE; +} + +static void dest_mgr_term_destination(j_compress_ptr cinfo) +{ + JpegEncoder *enc = (JpegEncoder *)cinfo->client_data; + enc->cur_image.out_size -= enc->dest_mgr.free_in_buffer; +} + +JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr) +{ + JpegEncoder *enc; + if (!usr->more_space || !usr->more_lines) { + return NULL; + } + + enc = spice_new0(JpegEncoder, 1); + + enc->usr = usr; + + enc->dest_mgr.init_destination = dest_mgr_init_destination; + enc->dest_mgr.empty_output_buffer = dest_mgr_empty_output_buffer; + enc->dest_mgr.term_destination = dest_mgr_term_destination; + + enc->cinfo.err = jpeg_std_error(&enc->jerr); + + jpeg_create_compress(&enc->cinfo); + enc->cinfo.client_data = enc; + enc->cinfo.dest = &enc->dest_mgr; + return (JpegEncoderContext*)enc; +} + +void jpeg_encoder_destroy(JpegEncoderContext* encoder) +{ + jpeg_destroy_compress(&((JpegEncoder*)encoder)->cinfo); + free(encoder); +} + +static void convert_RGB16_to_RGB24(uint8_t *line, int width, uint8_t **out_line) +{ + uint16_t *src_line = (uint16_t *)line; + uint8_t *out_pix; + int x; + + ASSERT(out_line && *out_line); + + out_pix = *out_line; + + for (x = 0; x < width; x++) { + uint16_t pixel = *src_line++; + *out_pix++ = ((pixel >> 7) & 0xf8) | ((pixel >> 12) & 0x7); + *out_pix++ = ((pixel >> 2) & 0xf8) | ((pixel >> 7) & 0x7); + *out_pix++ = ((pixel << 3) & 0xf8) | ((pixel >> 2) & 0x7); + } +} + +static void convert_BGR24_to_RGB24(uint8_t *line, int width, uint8_t **out_line) +{ + int x; + uint8_t *out_pix; + + ASSERT(out_line && *out_line); + + out_pix = *out_line; + + for (x = 0; x < width; x++) { + *out_pix++ = line[2]; + *out_pix++ = line[1]; + *out_pix++ = line[0]; + line += 3; + } +} + +static void convert_BGRX32_to_RGB24(uint8_t *line, int width, uint8_t **out_line) +{ + uint32_t *src_line = (uint32_t *)line; + uint8_t *out_pix; + int x; + + ASSERT(out_line && *out_line); + + out_pix = *out_line; + + for (x = 0; x < width; x++) { + uint32_t pixel = *src_line++; + *out_pix++ = (pixel >> 16) & 0xff; + *out_pix++ = (pixel >> 8) & 0xff; + *out_pix++ = pixel & 0xff; + } +} + +static void convert_RGB24_to_RGB24(uint8_t *line, int width, uint8_t **out_line) +{ + *out_line = line; +} + + +#define FILL_LINES() { \ + if (lines == lines_end) { \ + int n = jpeg->usr->more_lines(jpeg->usr, &lines); \ + if (n <= 0) { \ + red_error("more lines failed\n"); \ + } \ + lines_end = lines + n * stride; \ + } \ +} + +static void do_jpeg_encode(JpegEncoder *jpeg, uint8_t *lines, unsigned int num_lines) +{ + uint8_t *lines_end; + uint8_t *RGB24_line; + int stride, width, height; + JSAMPROW row_pointer[1]; + width = jpeg->cur_image.width; + height = jpeg->cur_image.height; + stride = jpeg->cur_image.stride; + + if (jpeg->cur_image.type != JPEG_IMAGE_TYPE_RGB24) { + RGB24_line = (uint8_t *)spice_malloc(width*3); + } + + lines_end = lines + (stride * num_lines); + + for (;jpeg->cinfo.next_scanline < jpeg->cinfo.image_height; lines += stride) { + FILL_LINES(); + jpeg->cur_image.convert_line_to_RGB24(lines, width, &RGB24_line); + row_pointer[0] = RGB24_line; + jpeg_write_scanlines(&jpeg->cinfo, row_pointer, 1); + } +} + +int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type, + int width, int height, uint8_t *lines, unsigned int num_lines, int stride, + uint8_t *io_ptr, unsigned int num_io_bytes) +{ + JpegEncoder *enc = (JpegEncoder *)jpeg; + + enc->cur_image.type = type; + enc->cur_image.width = width; + enc->cur_image.height = height; + enc->cur_image.stride = stride; + enc->cur_image.out_size = 0; + + switch (type) { + case JPEG_IMAGE_TYPE_RGB16: + enc->cur_image.convert_line_to_RGB24 = convert_RGB16_to_RGB24; + break; + case JPEG_IMAGE_TYPE_RGB24: + enc->cur_image.convert_line_to_RGB24 = convert_RGB24_to_RGB24; + break; + case JPEG_IMAGE_TYPE_BGR24: + enc->cur_image.convert_line_to_RGB24 = convert_BGR24_to_RGB24; + break; + case JPEG_IMAGE_TYPE_BGRX32: + enc->cur_image.convert_line_to_RGB24 = convert_BGRX32_to_RGB24; + break; + default: + red_error("bad image type"); + } + + enc->cinfo.image_width = width; + enc->cinfo.image_height = height; + enc->cinfo.input_components = 3; + enc->cinfo.in_color_space = JCS_RGB; + jpeg_set_defaults(&enc->cinfo); + jpeg_set_quality(&enc->cinfo, quality, TRUE); + + enc->dest_mgr.next_output_byte = io_ptr; + enc->dest_mgr.free_in_buffer = num_io_bytes; + + jpeg_start_compress(&enc->cinfo, TRUE); + + do_jpeg_encode(enc, lines, num_lines); + + jpeg_finish_compress(&enc->cinfo); + return enc->cur_image.out_size; +} diff --git a/server/jpeg_encoder.h b/server/jpeg_encoder.h new file mode 100644 index 00000000..2b8d8b20 --- /dev/null +++ b/server/jpeg_encoder.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef _H_JPEG_ENCODER +#define _H_JPEG_ENCODER + +#include <spice/types.h> + +typedef enum { + JPEG_IMAGE_TYPE_INVALID, + JPEG_IMAGE_TYPE_RGB16, + /* in byte per color types, the notation is according to the order of the + colors in the memory */ + JPEG_IMAGE_TYPE_RGB24, + JPEG_IMAGE_TYPE_BGR24, + JPEG_IMAGE_TYPE_BGRX32, +} JpegEncoderImageType; + +typedef void* JpegEncoderContext; +typedef struct JpegEncoderUsrContext JpegEncoderUsrContext; + +struct JpegEncoderUsrContext { + int (*more_space)(JpegEncoderUsrContext *usr, uint8_t **io_ptr); + int (*more_lines)(JpegEncoderUsrContext *usr, uint8_t **lines); +}; + +JpegEncoderContext* jpeg_encoder_create(JpegEncoderUsrContext *usr); +void jpeg_encoder_destroy(JpegEncoderContext *encoder); + +/* returns the total size of the encoded data. Images must be supplied from the the + top line to the bottom */ +int jpeg_encode(JpegEncoderContext *jpeg, int quality, JpegEncoderImageType type, + int width, int height, uint8_t *lines, unsigned int num_lines, int stride, + uint8_t *io_ptr, unsigned int num_io_bytes); +#endif diff --git a/server/red_worker.c b/server/red_worker.c index c2c78ea3..aac1d27c 100644 --- a/server/red_worker.c +++ b/server/red_worker.c @@ -48,6 +48,7 @@ #include "ring.h" #include "mjpeg_encoder.h" #include "red_memslots.h" +#include "jpeg_encoder.h" //#define COMPRESS_STAT //#define DUMP_BITMAP @@ -167,6 +168,7 @@ static inline void stat_add(stat_info_t *info, stat_time_t start) static const char *lz_stat_name = "lz"; static const char *glz_stat_name = "glz"; static const char *quic_stat_name = "quic"; +static const char *jpeg_stat_name = "jpeg"; static inline void stat_compress_init(stat_info_t *info, const char *name) { @@ -464,6 +466,7 @@ typedef struct __attribute__ ((__packed__)) RedImage { SpiceLZRGBData lz_rgb; SpiceLZPLTData lz_plt; SpiceSurface surface; + SpiceJPEGData jpeg; }; } RedImage; @@ -577,6 +580,11 @@ typedef struct { EncoderData data; } GlzData; +typedef struct { + JpegEncoderUsrContext usr; + EncoderData data; +} JpegData; + /**********************************/ /* LZ dictionary related entities */ /**********************************/ @@ -708,6 +716,7 @@ struct DisplayChannel { stat_info_t lz_stat; stat_info_t glz_stat; stat_info_t quic_stat; + stat_info_t jpeg_stat; #endif }; @@ -987,6 +996,9 @@ typedef struct RedWorker { LzData lz_data; LzContext *lz; + JpegData jpeg_data; + JpegEncoderContext *jpeg; + #ifdef PIPE_DEBUG uint32_t last_id; #endif @@ -1003,6 +1015,9 @@ typedef struct RedWorker { uint64_t *command_counter; #endif SpiceVirtMapping preload_group_virt_mapping; + + int enable_jpeg; + int jpeg_quality; } RedWorker; static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable); @@ -1068,19 +1083,29 @@ static void print_compress_stats(DisplayChannel *display_channel) stat_byte_to_mega(display_channel->lz_stat.comp_size), stat_cpu_time_to_sec(display_channel->lz_stat.total) ); + red_printf("JPEG \t%8d\t%13.2f\t%12.2f\t%12.2f", + display_channel->jpeg_stat.count, + stat_byte_to_mega(display_channel->jpeg_stat.orig_size), + stat_byte_to_mega(display_channel->jpeg_stat.comp_size), + stat_cpu_time_to_sec(display_channel->jpeg_stat.total) + ); red_printf("-------------------------------------------------------------------"); red_printf("Total \t%8d\t%13.2f\t%12.2f\t%12.2f", display_channel->lz_stat.count + display_channel->glz_stat.count + - display_channel->quic_stat.count, + display_channel->quic_stat.count + + display_channel->jpeg_stat.count, stat_byte_to_mega(display_channel->lz_stat.orig_size + display_channel->glz_stat.orig_size + - display_channel->quic_stat.orig_size), + display_channel->quic_stat.orig_size + + display_channel->jpeg_stat.orig_size), stat_byte_to_mega(display_channel->lz_stat.comp_size + display_channel->glz_stat.comp_size + - display_channel->quic_stat.comp_size), + display_channel->quic_stat.comp_size + + display_channel->jpeg_stat.comp_size), stat_cpu_time_to_sec(display_channel->lz_stat.total + display_channel->glz_stat.total + - display_channel->quic_stat.total) + display_channel->quic_stat.total + + display_channel->jpeg_stat.total) ); } @@ -5517,6 +5542,12 @@ static int glz_usr_more_space(GlzEncoderUsrContext *usr, uint8_t **io_ptr) return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2); } +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, (uint32_t **)io_ptr) << 2); +} + static inline int encoder_usr_more_lines(EncoderData *enc_data, uint8_t **lines) { uint32_t data_size; @@ -5564,63 +5595,91 @@ static int glz_usr_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines) return encoder_usr_more_lines(usr_data, lines); } -static int quic_usr_more_lines_revers(QuicUsrContext *usr, uint8_t **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); +} + +static int encoder_usr_more_lines_reverse(EncoderData *enc_data, uint8_t **lines) { uint8_t *data; uint32_t data_size; - EncoderData *quic_data = &(((QuicData *)usr)->data); - if (!quic_data->u.lines_data.next) { + if (!enc_data->u.lines_data.next) { return 0; } - QXLDataChunk *chunk = (QXLDataChunk *)quic_data->u.lines_data.enc_get_virt( - quic_data->u.lines_data.enc_get_virt_opaque, - quic_data->u.lines_data.next, - sizeof(QXLDataChunk), quic_data->u.lines_data.group_id); + QXLDataChunk *chunk = (QXLDataChunk *)enc_data->u.lines_data.enc_get_virt( + enc_data->u.lines_data.enc_get_virt_opaque, + enc_data->u.lines_data.next, + sizeof(QXLDataChunk), enc_data->u.lines_data.group_id); data_size = chunk->data_size; data = chunk->data; - if (data_size % quic_data->u.lines_data.stride) { + if (data_size % enc_data->u.lines_data.stride) { return 0; } - quic_data->u.lines_data.enc_validate_virt(quic_data->u.lines_data.enc_validate_virt_opaque, - (unsigned long)data, - quic_data->u.lines_data.next, data_size, - quic_data->u.lines_data.group_id); + enc_data->u.lines_data.enc_validate_virt(enc_data->u.lines_data.enc_validate_virt_opaque, + (unsigned long)data, + enc_data->u.lines_data.next, data_size, + enc_data->u.lines_data.group_id); - quic_data->u.lines_data.next = chunk->prev_chunk; - *lines = data + data_size - quic_data->u.lines_data.stride; - return data_size / quic_data->u.lines_data.stride; + enc_data->u.lines_data.next = chunk->prev_chunk; + *lines = data + data_size - enc_data->u.lines_data.stride; + return data_size / enc_data->u.lines_data.stride; } -static int quic_usr_more_lines_unstable(QuicUsrContext *usr, uint8_t **out_lines) +static int quic_usr_more_lines_reverse(QuicUsrContext *usr, uint8_t **lines) { - EncoderData *quic_data = &(((QuicData *)usr)->data); + EncoderData *usr_data = &(((QuicData *)usr)->data); + return encoder_usr_more_lines_reverse(usr_data, lines); + +} + +static int jpeg_usr_more_lines_reverse(JpegEncoderUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((JpegData *)usr)->data); + return encoder_usr_more_lines_reverse(usr_data, lines); +} - if (!quic_data->u.unstable_lines_data.lines) { +static int encoder_usr_more_lines_unstable(EncoderData *enc_data, uint8_t **out_lines) +{ + if (!enc_data->u.unstable_lines_data.lines) { return 0; } - uint8_t *src = quic_data->u.unstable_lines_data.next; - int lines = MIN(quic_data->u.unstable_lines_data.lines, - quic_data->u.unstable_lines_data.max_lines_bunch); - quic_data->u.unstable_lines_data.lines -= lines; - uint8_t *end = src + lines * quic_data->u.unstable_lines_data.src_stride; - quic_data->u.unstable_lines_data.next = end; + uint8_t *src = enc_data->u.unstable_lines_data.next; + int lines = MIN(enc_data->u.unstable_lines_data.lines, + enc_data->u.unstable_lines_data.max_lines_bunch); + enc_data->u.unstable_lines_data.lines -= lines; + uint8_t *end = src + lines * enc_data->u.unstable_lines_data.src_stride; + enc_data->u.unstable_lines_data.next = end; - uint8_t *out = (uint8_t *)quic_data->u.unstable_lines_data.input_bufs[ - quic_data->u.unstable_lines_data.input_bufs_pos++ & 1]->buf; + uint8_t *out = (uint8_t *)enc_data->u.unstable_lines_data.input_bufs[ + enc_data->u.unstable_lines_data.input_bufs_pos++ & 1]->buf; uint8_t *dest = out; - for (; src != end; src += quic_data->u.unstable_lines_data.src_stride, - dest += quic_data->u.unstable_lines_data.dest_stride) { - memcpy(dest, src, quic_data->u.unstable_lines_data.dest_stride); + for (; src != end; src += enc_data->u.unstable_lines_data.src_stride, + dest += enc_data->u.unstable_lines_data.dest_stride) { + memcpy(dest, src, enc_data->u.unstable_lines_data.dest_stride); } *out_lines = out; return lines; } +static int quic_usr_more_lines_unstable(QuicUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((QuicData *)usr)->data); + return encoder_usr_more_lines_unstable(usr_data, lines); +} + +static int jpeg_usr_more_lines_unstable(JpegEncoderUsrContext *usr, uint8_t **lines) +{ + EncoderData *usr_data = &(((JpegData *)usr)->data); + return encoder_usr_more_lines_unstable(usr_data, lines); +} + static int quic_usr_no_more_lines(QuicUsrContext *usr, uint8_t **lines) { return 0; @@ -5636,6 +5695,11 @@ static int glz_usr_no_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines) return 0; } +static int jpeg_usr_no_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines) +{ + return 0; +} + static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image) { GlzData *lz_data = (GlzData *)usr; @@ -5698,6 +5762,22 @@ static inline void red_display_init_glz_data(DisplayChannel *display) display->glz_data.usr.free_image = glz_usr_free_image; } +static inline void red_init_jpeg(RedWorker *worker) +{ + worker->jpeg_data.usr.more_space = jpeg_usr_more_space; + worker->jpeg_data.usr.more_lines = jpeg_usr_more_lines; + + worker->jpeg = jpeg_encoder_create(&worker->jpeg_data.usr); + + if (!worker->jpeg) { + PANIC("create jpeg encoder failed"); + } + + // TODO: configure via qemu command line and monitor, and activate only on WAN + worker->enable_jpeg = TRUE; + worker->jpeg_quality = 85; +} + #ifdef __GNUC__ #define ATTR_PACKED __attribute__ ((__packed__)) #else @@ -6046,6 +6126,148 @@ static inline int red_lz_compress_image(DisplayChannel *display_channel, return TRUE; } +static int red_jpeg_compress_image(DisplayChannel *display_channel, RedImage *dest, + SpiceBitmap *src, compress_send_data_t* o_comp_data, + uint32_t group_id) +{ + RedWorker *worker = display_channel->base.worker; + JpegData *jpeg_data = &worker->jpeg_data; + JpegEncoderContext *jpeg = worker->jpeg; + JpegEncoderImageType jpeg_in_type; + int size; +#ifdef COMPRESS_STAT + stat_time_t start_time = stat_now(); +#endif + switch (src->format) { + case SPICE_BITMAP_FMT_32BIT: + jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32; + break; + 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; + default: + return FALSE; + } + + jpeg_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel); + jpeg_data->data.bufs_head = jpeg_data->data.bufs_tail; + + if (!jpeg_data->data.bufs_head) { + red_printf("failed to allocate compress buffer"); + return FALSE; + } + + jpeg_data->data.bufs_head->send_next = NULL; + jpeg_data->data.display_channel = display_channel; + + if (setjmp(jpeg_data->data.jmp_env)) { + while (jpeg_data->data.bufs_head) { + RedCompressBuf *buf = jpeg_data->data.bufs_head; + jpeg_data->data.bufs_head = buf->send_next; + red_display_free_compress_buf(display_channel, buf); + } + return FALSE; + } + + if ((src->flags & QXL_BITMAP_DIRECT)) { + int stride; + uint8_t *data; + + if (!(src->flags & QXL_BITMAP_TOP_DOWN)) { + data = (uint8_t*)get_virt(&worker->mem_slots, src->data, src->stride * src->y, group_id) + + src->stride * (src->y - 1); + stride = -src->stride; + } else { + data = (uint8_t*)get_virt(&worker->mem_slots, src->data, src->stride * src->y, group_id); + stride = src->stride; + } + + if ((src->flags & QXL_BITMAP_UNSTABLE)) { + jpeg_data->data.u.unstable_lines_data.next = data; + jpeg_data->data.u.unstable_lines_data.src_stride = stride; + jpeg_data->data.u.unstable_lines_data.dest_stride = src->stride; + jpeg_data->data.u.unstable_lines_data.lines = src->y; + jpeg_data->data.u.unstable_lines_data.input_bufs_pos = 0; + if (!(jpeg_data->data.u.unstable_lines_data.input_bufs[0] = + red_display_alloc_compress_buf(display_channel)) || + !(jpeg_data->data.u.unstable_lines_data.input_bufs[1] = + red_display_alloc_compress_buf(display_channel))) { + return FALSE; + } + jpeg_data->data.u.unstable_lines_data.max_lines_bunch = + sizeof(jpeg_data->data.u.unstable_lines_data.input_bufs[0]->buf) / + jpeg_data->data.u.unstable_lines_data.dest_stride; + jpeg_data->usr.more_lines = jpeg_usr_more_lines_unstable; + size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, NULL, 0, src->stride, + (uint8_t*)jpeg_data->data.bufs_head->buf, + sizeof(jpeg_data->data.bufs_head->buf)); + } else { + jpeg_data->usr.more_lines = jpeg_usr_no_more_lines; + size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, data, src->y, stride, + (uint8_t*)jpeg_data->data.bufs_head->buf, + sizeof(jpeg_data->data.bufs_head->buf)); + } + } else { + int stride; + + if ((src->flags & QXL_BITMAP_UNSTABLE)) { + red_printf_once("unexpected unstable bitmap"); + return FALSE; + } + jpeg_data->data.u.lines_data.enc_get_virt = cb_get_virt; + jpeg_data->data.u.lines_data.enc_get_virt_opaque = &worker->mem_slots; + jpeg_data->data.u.lines_data.enc_validate_virt = cb_validate_virt; + jpeg_data->data.u.lines_data.enc_validate_virt_opaque = &worker->mem_slots; + jpeg_data->data.u.lines_data.stride = src->stride; + jpeg_data->data.u.lines_data.group_id = group_id; + + if ((src->flags & QXL_BITMAP_TOP_DOWN)) { + jpeg_data->data.u.lines_data.next = src->data; + jpeg_data->usr.more_lines = jpeg_usr_more_lines; + stride = src->stride; + } else { + SPICE_ADDRESS prev_addr = src->data; + QXLDataChunk *chunk = (QXLDataChunk *)get_virt(&worker->mem_slots, src->data, + sizeof(QXLDataChunk), group_id); + while (chunk->next_chunk) { + prev_addr = chunk->next_chunk; + chunk = (QXLDataChunk *)get_virt(&worker->mem_slots, chunk->next_chunk, sizeof(QXLDataChunk), + group_id); + ASSERT(chunk->prev_chunk); + } + jpeg_data->data.u.lines_data.next = (SPICE_ADDRESS)prev_addr - + get_virt_delta(&worker->mem_slots, + get_memslot_id(&worker->mem_slots, src->data), + group_id); + jpeg_data->usr.more_lines = jpeg_usr_more_lines_reverse; + stride = -src->stride; + } + size = jpeg_encode(jpeg, worker->jpeg_quality, jpeg_in_type, src->x, src->y, NULL, 0, stride, + (uint8_t*)jpeg_data->data.bufs_head->buf, + sizeof(jpeg_data->data.bufs_head->buf)); + } + + // the compressed buffer is bigger than the original data + if (size > (src->y * src->stride)) { + longjmp(jpeg_data->data.jmp_env, 1); + } + + dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG; + dest->jpeg.data_size = size; + + o_comp_data->raw_size = sizeof(SpiceJPEGImage); + o_comp_data->comp_buf = jpeg_data->data.bufs_head; + o_comp_data->comp_buf_size = size; + o_comp_data->plt_ptr = NULL; + o_comp_data->flags_ptr = NULL; + stat_compress_add(&display_channel->jpeg_stat, start_time, src->stride * src->y, + o_comp_data->comp_buf_size); + return TRUE; +} + static inline int red_quic_compress_image(DisplayChannel *display_channel, RedImage *dest, SpiceBitmap *src, compress_send_data_t* o_comp_data, uint32_t group_id) @@ -6166,7 +6388,7 @@ static inline int red_quic_compress_image(DisplayChannel *display_channel, RedIm get_virt_delta(&worker->mem_slots, get_memslot_id(&worker->mem_slots, src->data), group_id); - quic_data->usr.more_lines = quic_usr_more_lines_revers; + quic_data->usr.more_lines = quic_usr_more_lines_reverse; stride = -src->stride; } size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride, @@ -6249,7 +6471,17 @@ static inline int red_compress_image(DisplayChannel *display_channel, #ifdef COMPRESS_DEBUG red_printf("QUIC compress"); #endif - return red_quic_compress_image(display_channel, dest, src, o_comp_data, drawable->group_id); + // if bitmaps is picture-like, compress it using jpeg + if (display_channel->base.worker->enable_jpeg && + ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) || + (image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ))) { + if (src->format != SPICE_BITMAP_FMT_RGBA) { + return red_jpeg_compress_image(display_channel, dest, + src, o_comp_data, drawable->group_id); + } + } + return red_quic_compress_image(display_channel, dest, + src, o_comp_data, drawable->group_id); } else { int glz; int ret; @@ -7915,7 +8147,7 @@ static SpiceCanvas *create_ogl_context_common(RedWorker *worker, OGLCtx *ctx, ui oglctx_make_current(ctx); if (!(canvas = gl_canvas_create(width, height, depth, &worker->image_cache.base, - &worker->image_surfaces, NULL, + &worker->image_surfaces, NULL, NULL, &worker->preload_group_virt_mapping))) { return NULL; } @@ -7973,7 +8205,7 @@ static inline void *create_canvas_for_surface(RedWorker *worker, RedSurface *sur canvas = canvas_create_for_data(width, height, format, line_0, stride, &worker->image_cache.base, - &worker->image_surfaces, NULL, + &worker->image_surfaces, NULL, NULL, &worker->preload_group_virt_mapping); surface->context.top_down = TRUE; surface->context.canvas_draws_on_surface = TRUE; @@ -8773,6 +9005,7 @@ static void handle_new_display_channel(RedWorker *worker, RedsStreamContext *pee stat_compress_init(&display_channel->lz_stat, lz_stat_name); stat_compress_init(&display_channel->glz_stat, glz_stat_name); stat_compress_init(&display_channel->quic_stat, quic_stat_name); + stat_compress_init(&display_channel->jpeg_stat, jpeg_stat_name); } static void red_disconnect_cursor(RedChannel *channel) @@ -9321,6 +9554,7 @@ static void handle_dev_input(EventListener *listener, uint32_t events) stat_reset(&worker->display_channel->quic_stat); stat_reset(&worker->display_channel->lz_stat); stat_reset(&worker->display_channel->glz_stat); + stat_reset(&worker->display_channel->jpeg_stat); } #endif break; @@ -9483,6 +9717,7 @@ void *red_worker_main(void *arg) red_init(&worker, (WorkerInitData *)arg); red_init_quic(&worker); red_init_lz(&worker); + red_init_jpeg(&worker); worker.epoll_timeout = INF_EPOLL_WAIT; for (;;) { struct epoll_event events[MAX_EPOLL_SOURCES]; |