/* Copyright (C) 2009 Red Hat, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qxl_dev.h" #include "vd_interface.h" #include "region.h" #include "red.h" #include "red_worker.h" #include "cairo.h" #include "cairo_canvas.h" #include "gl_canvas.h" #include "ogl_ctx.h" #include "ffmpeg_inc.h" #include "quic.h" #include "lz.h" #include "glz_encoder_dictionary.h" #include "glz_encoder.h" #include "stat.h" #include "reds.h" #include "ring.h" //#define COMPRESS_STAT //#define DUMP_BITMAP #define STREAM_TRACE //#define PIPE_DEBUG #define USE_EXCLUDE_RGN //#define RED_WORKER_STAT //#define DRAW_ALL //#define COMPRESS_DEBUG //#define UPDATE_AREA_BY_TREE #define CMD_RING_POLL_TIMEOUT 10 //milli #define CMD_RING_POLL_RETRIES 200 #define DETACH_TIMEOUT 15000000000ULL //nano #define DETACH_SLEEP_DURATION 10000 //micro #define DISPLAY_CLIENT_TIMEOUT 15000000000ULL //nano #define DISPLAY_CLIENT_RETRY_INTERVAL 10000 //micro #define DISPLAY_MAX_SUB_MESSAGES 10 #define DISPLAY_FREE_LIST_DEFAULT_SIZE 128 #define RED_STREAM_DETACTION_MAX_DELTA ((1000 * 1000 * 1000) / 5) // 1/5 sec #define RED_STREAM_CONTINUS_MAX_DELTA ((1000 * 1000 * 1000) / 2) // 1/2 sec #define RED_STREAM_TIMOUT (1000 * 1000 * 1000) #define RED_STREAM_FRAMES_START_CONDITION 20 #define RED_STREAM_GRADUAL_FRAMES_START_CONDITION 0.2 #define RED_STREAM_FRAMES_RESET_CONDITION 100 #define RED_STREAM_MIN_SIZE (96 * 96) #define FPS_TEST_INTERVAL 1 #define MAX_FPS 30 //best bit rate per pixel base on 13000000 bps for frame size 720x576 pixels and 25 fps #define BEST_BIT_RATE_PER_PIXEL 38 #define WARST_BIT_RATE_PER_PIXEL 4 #define RED_COMPRESS_BUF_SIZE (1024 * 64) typedef int64_t red_time_t; static inline red_time_t timespec_to_red_time(struct timespec *time) { return time->tv_sec * (1000 * 1000 * 1000) + time->tv_nsec; } #if defined(RED_WORKER_STAT) || defined(COMPRESS_STAT) static clockid_t clock_id; typedef unsigned long stat_time_t; inline stat_time_t stat_now() { struct timespec ts; clock_gettime(clock_id, &ts); return ts.tv_nsec + ts.tv_sec * 1000 * 1000 * 1000; } double stat_cpu_time_to_sec(stat_time_t time) { return (double)time / (1000 * 1000 * 1000); } typedef struct stat_info_s { const char *name; uint32_t count; stat_time_t max; stat_time_t min; stat_time_t total; #ifdef COMPRESS_STAT uint64_t orig_size; uint64_t comp_size; #endif } stat_info_t; static inline void stat_reset(stat_info_t *info) { info->count = info->max = info->total = 0; info->min = ~(stat_time_t)0; #ifdef COMPRESS_STAT info->orig_size = info->comp_size = 0; #endif } #endif #ifdef RED_WORKER_STAT static const char *add_stat_name = "add"; static const char *exclude_stat_name = "exclude"; static const char *__exclude_stat_name = "__exclude"; static inline void stat_init(stat_info_t *info, const char *name) { info->name = name; stat_reset(info); } static inline void stat_add(stat_info_t *info, stat_time_t start) { stat_time_t time; ++info->count; time = stat_now() - start; info->total += time; info->max = MAX(info->max, time); info->min = MIN(info->min, time); } #else #define stat_add(a, b) #define stat_init(a, b) #endif #ifdef COMPRESS_STAT static const char *lz_stat_name = "lz"; static const char *glz_stat_name = "glz"; static const char *quic_stat_name = "quic"; static inline void stat_compress_init(stat_info_t *info, const char *name) { info->name = name; stat_reset(info); } static inline void stat_compress_add(stat_info_t *info, stat_time_t start, int orig_size, int comp_size) { stat_time_t time; ++info->count; time = stat_now() - start; info->total += time; info->max = MAX(info->max, time); info->min = MIN(info->min, time); info->orig_size += orig_size; info->comp_size += comp_size; } double inline stat_byte_to_mega(uint64_t size) { return (double)size / (1000 * 1000); } #else #define stat_compress_init(a, b) #define stat_compress_add(a, b, c, d) #endif #define MAX_EPOLL_SOURCES 10 #define INF_EPOLL_WAIT ~0 typedef struct EventListener EventListener; typedef void (*event_listener_action_proc)(EventListener *ctx, uint32_t events); struct EventListener { uint32_t refs; event_listener_action_proc action; }; enum { BUF_TYPE_RAW = 1, BUF_TYPE_COMPRESS_BUF, BUF_TYPE_CHUNK, }; typedef struct BufDescriptor { uint32_t type; uint32_t size; uint8_t *data; } BufDescriptor; enum { PIPE_ITEM_TYPE_DRAW, PIPE_ITEM_TYPE_MODE, PIPE_ITEM_TYPE_INVAL_ONE, PIPE_ITEM_TYPE_CURSOR, PIPE_ITEM_TYPE_MIGRATE, PIPE_ITEM_TYPE_LOCAL_CURSOR, PIPE_ITEM_TYPE_SET_ACK, PIPE_ITEM_TYPE_CURSOR_INIT, PIPE_ITEM_TYPE_IMAGE, PIPE_ITEM_TYPE_STREAM_CREATE, PIPE_ITEM_TYPE_STREAM_CLIP, PIPE_ITEM_TYPE_STREAM_DESTROY, PIPE_ITEM_TYPE_UPGRADE, PIPE_ITEM_TYPE_VERB, PIPE_ITEM_TYPE_MIGRATE_DATA, PIPE_ITEM_TYPE_PIXMAP_SYNC, PIPE_ITEM_TYPE_PIXMAP_RESET, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE, }; typedef struct PipeItem { RingItem link; int type; } PipeItem; typedef struct VerbItem { PipeItem base; uint16_t verb; } VerbItem; #define MAX_CACHE_CLIENTS 4 #define MAX_LZ_ENCODERS MAX_CACHE_CLIENTS typedef struct NewCacheItem NewCacheItem; struct NewCacheItem { RingItem lru_link; NewCacheItem *next; uint64_t id; uint64_t sync[MAX_CACHE_CLIENTS]; size_t size; }; typedef struct CacheItem CacheItem; struct CacheItem { union { PipeItem pipe_data; struct { RingItem lru_link; CacheItem *next; } cache_data; } u; uint64_t id; size_t size; uint32_t inval_type; }; enum { CURSOR_TYPE_INVALID, CURSOR_TYPE_DEV, CURSOR_TYPE_LOCAL, }; typedef struct CursorItem { PipeItem pipe_data; int refs; int type; } CursorItem; typedef struct LocalCursor { CursorItem base; Point16 position; uint32_t data_size; RedCursor red_cursor; } LocalCursor; #define MAX_SEND_BUFS 20 #define MAX_BITMAPS 4 #define MAX_PIPE_SIZE 50 #define RECIVE_BUF_SIZE 1024 #define WIDE_CLIENT_ACK_WINDOW 40 #define NARROW_CLIENT_ACK_WINDOW 20 #define BITS_CACHE_HASH_SHIFT 10 #define BITS_CACHE_HASH_SIZE (1 << BITS_CACHE_HASH_SHIFT) #define BITS_CACHE_HASH_MASK (BITS_CACHE_HASH_SIZE - 1) #define BITS_CACHE_HASH_KEY(id) ((id) & BITS_CACHE_HASH_MASK) #define CLIENT_CURSOR_CACHE_SIZE 256 #define CURSOR_CACHE_HASH_SHIFT 8 #define CURSOR_CACHE_HASH_SIZE (1 << CURSOR_CACHE_HASH_SHIFT) #define CURSOR_CACHE_HASH_MASK (CURSOR_CACHE_HASH_SIZE - 1) #define CURSOR_CACHE_HASH_KEY(id) ((id) & CURSOR_CACHE_HASH_MASK) #define CLIENT_PALETTE_CACHE_SIZE 128 #define PALETTE_CACHE_HASH_SHIFT 8 #define PALETTE_CACHE_HASH_SIZE (1 << PALETTE_CACHE_HASH_SHIFT) #define PALETTE_CACHE_HASH_MASK (PALETTE_CACHE_HASH_SIZE - 1) #define PALETTE_CACHE_HASH_KEY(id) ((id) & PALETTE_CACHE_HASH_MASK) typedef struct RedChannel RedChannel; typedef void (*disconnect_channel_proc)(RedChannel *channel); typedef void (*hold_item_proc)(void *item); typedef void (*release_item_proc)(RedChannel *channel, void *item); typedef int (*handle_message_proc)(RedChannel *channel, RedDataHeader *message); struct RedChannel { EventListener listener; uint32_t id; struct RedWorker *worker; RedsStreamContext *peer; int migrate; Ring pipe; uint32_t pipe_size; uint32_t client_ack_window; uint32_t ack_generation; uint32_t client_ack_generation; uint32_t messages_window; struct { int blocked; RedDataHeader header; union { RedSetAck ack; } u; uint32_t n_bufs; BufDescriptor bufs[MAX_SEND_BUFS]; uint32_t size; uint32_t pos; void *item; } send_data; struct { uint8_t buf[RECIVE_BUF_SIZE]; RedDataHeader *message; uint8_t *now; uint8_t *end; } recive_data; disconnect_channel_proc disconnect; hold_item_proc hold_item; release_item_proc release_item; handle_message_proc handle_message; #ifdef RED_STATISTICS uint64_t *out_bytes_counter; #endif }; typedef struct ImageItem { PipeItem link; int refs; Point pos; int width; int height; int stride; int top_down; uint8_t data[0]; } ImageItem; typedef struct Drawable Drawable; typedef struct Stream Stream; struct Stream { uint8_t refs; Drawable *current; #ifdef STREAM_TRACE red_time_t last_time; int width; int height; Rect dest_area; #endif int top_down; Stream *next; RingItem link; AVCodecContext *av_ctx; AVFrame *av_frame; uint8_t* frame_buf; uint8_t* frame_buf_end; int bit_rate; }; typedef struct StreamAgent { QRegion vis_region; PipeItem create_item; PipeItem destroy_item; Stream *stream; uint64_t lats_send_time; int frames; int drops; int fps; } StreamAgent; typedef struct StreamClipItem { PipeItem base; int refs; StreamAgent *stream_agent; int clip_type; QRegion region; } StreamClipItem; typedef struct RedCompressBuf RedCompressBuf; struct RedCompressBuf { uint32_t buf[RED_COMPRESS_BUF_SIZE / 4]; RedCompressBuf *next; RedCompressBuf *send_next; }; static const int BITMAP_FMT_IS_PLT[] = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0}; static const int BITMAP_FMT_IS_RGB[] = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1}; static const int BITMAP_FMP_BYTES_PER_PIXEL[] = {0, 0, 0, 0, 0, 1, 2, 3, 4, 4}; typedef struct __attribute__ ((__packed__)) RedImage { ImageDescriptor descriptor; union { // variable length Bitmap bitmap; QUICData quic; LZ_RGBData lz_rgb; LZ_PLTData lz_plt; }; } RedImage; pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; Ring pixmap_cache_list = {&pixmap_cache_list, &pixmap_cache_list}; typedef struct PixmapCache PixmapCache; struct PixmapCache { RingItem base; pthread_mutex_t lock; uint8_t id; uint32_t refs; NewCacheItem *hash_table[BITS_CACHE_HASH_SIZE]; Ring lru; int64_t available; int64_t size; int32_t items; int freezed; RingItem *freezed_head; RingItem *freezed_tail; uint32_t generation; struct { uint8_t client; uint64_t message; } generation_initiator; uint64_t sync[MAX_CACHE_CLIENTS]; }; #define NUM_STREAMS 50 #define DISPLAY_MIGRATE_DATA_MAGIC (*(uint32_t*)"DMDA") #define DISPLAY_MIGRATE_DATA_VERSION 2 typedef struct __attribute__ ((__packed__)) DisplayChannelMigrateData { //todo: add ack_generation + move common to generic migration data uint32_t magic; uint32_t version; uint64_t message_serial; uint8_t pixmap_cache_freezer; uint8_t pixmap_cache_id; int64_t pixmap_cache_size; uint64_t pixmap_cache_clients[MAX_CACHE_CLIENTS]; uint8_t glz_dict_id; GlzEncDictRestoreData glz_dict_restore_data; } DisplayChannelMigrateData; typedef struct WaitForChannels { RedWaitForChannels header; RedWaitForChannel buf[MAX_CACHE_CLIENTS]; } WaitForChannels; typedef struct FreeList { int res_size; RedResorceList *res; uint64_t sync[MAX_CACHE_CLIENTS]; WaitForChannels wait; } FreeList; typedef struct DisplayChannel DisplayChannel; typedef struct { DisplayChannel *display_channel; RedCompressBuf *bufs_head; RedCompressBuf *bufs_tail; jmp_buf jmp_env; union { struct { ADDRESS next; long address_delta; uint32_t stride; } lines_data; struct { uint8_t* next; int src_stride; uint32_t dest_stride; int lines; int max_lines_bunch; int input_bufs_pos; RedCompressBuf *input_bufs[2]; } unstable_lines_data; } u; char message_buf[512]; } EncoderData; typedef struct { QuicUsrContext usr; EncoderData data; } QuicData; typedef struct { LzUsrContext usr; EncoderData data; } LzData; typedef struct { GlzEncoderUsrContext usr; EncoderData data; } GlzData; /**********************************/ /* LZ dictionary related entities */ /**********************************/ #define MAX_GLZ_DRAWABLE_INSTANCES 2 typedef struct RedGlzDrawable RedGlzDrawable; /* for each qxl drawable, there may be serveral instances of lz drawables */ typedef struct GlzDrawableInstanceItem { RingItem glz_link; RingItem free_link; GlzEncDictImageContext *glz_instance; RedGlzDrawable *red_glz_drawable; } GlzDrawableInstanceItem; struct RedGlzDrawable { RingItem link; // ordered by the time it was encoded QXLDrawable *qxl_drawable; Drawable *drawable; GlzDrawableInstanceItem instances_pool[MAX_GLZ_DRAWABLE_INSTANCES]; Ring instances; uint8_t instances_count; DisplayChannel *display_channel; }; pthread_mutex_t glz_dictionary_list_lock = PTHREAD_MUTEX_INITIALIZER; Ring glz_dictionary_list = {&glz_dictionary_list, &glz_dictionary_list}; typedef struct GlzSharedDictionary { RingItem base; GlzEncDictContext *dict; uint32_t refs; uint8_t id; pthread_rwlock_t encode_lock; int migrate_freeze; } GlzSharedDictionary; struct DisplayChannel { RedChannel base; int expect_init; int expect_migrate_mark; int expect_migrate_data; PixmapCache *pixmap_cache; uint32_t pixmap_cache_generation; int pending_pixmaps_sync; CacheItem *palette_cache[PALETTE_CACHE_HASH_SIZE]; Ring palette_cache_lru; long palette_cache_available; uint32_t palette_cache_items; StreamAgent stream_agents[NUM_STREAMS]; /* 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; struct { union { RedFill fill; RedOpaque opaque; RedCopy copy; RedTransparent transparent; RedAlphaBlend alpha_blend; RedCopyBits copy_bits; RedBlend blend; RedRop3 rop3; RedBlackness blackness; RedWhiteness whiteness; RedInvers invers; RedStroke stroke; RedText text; RedMode mode; RedInvalOne inval_one; WaitForChannels wait; struct { RedStreamCreate message; uint32_t num_rects; } stream_create; RedStreamClip stream_clip; RedStreamData stream_data; RedStreamDestroy stream_destroy; RedMigrate migrate; DisplayChannelMigrateData migrate_data; } u; uint32_t bitmap_pos; RedImage images[MAX_BITMAPS]; uint32_t stream_outbuf_size; uint8_t *stream_outbuf; // caution stream buffer is also used as compress bufs!!! RedCompressBuf *free_compress_bufs; RedCompressBuf *used_compress_bufs; struct { RedSubMessageList sub_list; uint32_t sub_messages[DISPLAY_MAX_SUB_MESSAGES]; } sub_list; RedSubMessage sub_header[DISPLAY_MAX_SUB_MESSAGES]; FreeList free_list; } send_data; #ifdef RED_STATISTICS StatNodeRef stat; uint64_t *cache_hits_counter; uint64_t *add_to_cache_counter; uint64_t *non_cache_counter; #endif #ifdef COMPRESS_STAT stat_info_t lz_stat; stat_info_t glz_stat; stat_info_t quic_stat; #endif }; typedef struct CursorChannel { RedChannel base; CacheItem *cursor_cache[CURSOR_CACHE_HASH_SIZE]; Ring cursor_cache_lru; long cursor_cache_available; uint32_t cursor_cache_items; struct { union { RedCursorInit cursor_init; RedCursorSet cursor_set; RedCursorMove cursor_move; RedCursorTrail cursor_trail; RedInvalOne inval_one; RedMigrate migrate; } u; } send_data; #ifdef RED_STATISTICS StatNodeRef stat; #endif } CursorChannel; typedef struct __attribute__ ((__packed__)) LocalImage { QXLImage qxl_image; uint8_t buf[sizeof(QXLDataChunk *)]; // quic data area } LocalImage; typedef struct ImageCacheItem { RingItem lru_link; uint64_t id; #ifdef IMAGE_CACHE_AGE uint32_t age; #endif struct ImageCacheItem *next; cairo_surface_t *surf; } ImageCacheItem; #define IMAGE_CACHE_HASH_SIZE 1024 typedef struct ImageCache { ImageCacheItem *hash_table[IMAGE_CACHE_HASH_SIZE]; Ring lru; #ifdef IMAGE_CACHE_AGE uint32_t age; #else uint32_t num_items; #endif } ImageCache; enum { TREE_ITEM_TYPE_DRAWABLE, TREE_ITEM_TYPE_CONTAINER, TREE_ITEM_TYPE_SHADOW, }; typedef struct TreeItem { RingItem siblings_link; uint32_t type; struct Container *container; QRegion rgn; #ifdef PIPE_DEBUG uint32_t id; #endif } TreeItem; #define IS_DRAW_ITEM(item) ((item)->type == TREE_ITEM_TYPE_DRAWABLE) typedef struct Shadow { TreeItem base; QRegion on_hold; struct DrawItem* owner; } Shadow; typedef struct Container { TreeItem base; Ring items; } Container; typedef struct DrawItem { TreeItem base; uint8_t effect; uint8_t container_root; Shadow *shadow; } DrawItem; typedef enum { BITMAP_GRADUAL_INVALID, BITMAP_GRADUAL_NOT_AVAIL, BITMAP_GRADUAL_TRUE, BITMAP_GRADUAL_FALSE, } BitmapGradualType; struct Drawable { uint8_t refs; RingItem list_link; DrawItem tree_item; PipeItem pipe_item; #ifdef UPDATE_AREA_BY_TREE RingItem collect_link; #endif QXLDrawable *qxl_drawable; RedGlzDrawable *red_glz_drawable; red_time_t creation_time; int frames_count; int gradual_frames_count; int last_gradual_frame; Stream *stream; #ifdef STREAM_TRACE int streamable; #endif BitmapGradualType copy_bitmap_graduality; }; typedef struct _Drawable _Drawable; struct _Drawable { union { Drawable drawable; _Drawable *next; } u; }; typedef struct UpgradeItem { PipeItem base; int refs; Drawable *drawable; QRegion region; } UpgradeItem; typedef void (*set_access_params_t)(void *canvas, ADDRESS delta); typedef void (*draw_fill_t)(void *canvas, Rect *bbox, Clip *clip, Fill *fill); typedef void (*draw_copy_t)(void *canvas, Rect *bbox, Clip *clip, Copy *copy); typedef void (*draw_opaque_t)(void *canvas, Rect *bbox, Clip *clip, Opaque *opaque); typedef void (*copy_bits_t)(void *canvas, Rect *bbox, Clip *clip, Point *src_pos); typedef void (*draw_text_t)(void *canvas, Rect *bbox, Clip *clip, Text *text); typedef void (*draw_stroke_t)(void *canvas, Rect *bbox, Clip *clip, Stroke *stroke); typedef void (*draw_rop3_t)(void *canvas, Rect *bbox, Clip *clip, Rop3 *rop3); typedef void (*draw_blend_t)(void *canvas, Rect *bbox, Clip *clip, Blend *blend); typedef void (*draw_blackness_t)(void *canvas, Rect *bbox, Clip *clip, Blackness *blackness); typedef void (*draw_whiteness_t)(void *canvas, Rect *bbox, Clip *clip, Whiteness *whiteness); typedef void (*draw_invers_t)(void *canvas, Rect *bbox, Clip *clip, Invers *invers); typedef void (*draw_transparent_t)(void *canvas, Rect *bbox, Clip *clip, Transparent* transparent); typedef void (*draw_alpha_blend_t)(void *canvas, Rect *bbox, Clip *clip, AlphaBlnd* alpha_blend); typedef void (*read_pixels_t)(void *canvas, uint8_t *dest, int dest_stride, const Rect *area); typedef void (*set_top_mask_t)(void *canvas, int num_rect, const Rect *rects); typedef void (*clear_top_mask_t)(void *canvas); typedef void (*validate_area_t)(void *canvas, const DrawArea *draw_area, const Rect *area); typedef void (*destroy_t)(void *canvas); typedef struct DrawContext { void *canvas; int top_down; set_access_params_t set_access_params; draw_fill_t draw_fill; draw_copy_t draw_copy; draw_opaque_t draw_opaque; copy_bits_t copy_bits; draw_text_t draw_text; draw_stroke_t draw_stroke; draw_rop3_t draw_rop3; draw_blend_t draw_blend; draw_blackness_t draw_blackness; draw_whiteness_t draw_whiteness; draw_invers_t draw_invers; draw_transparent_t draw_transparent; draw_alpha_blend_t draw_alpha_blend; read_pixels_t read_pixels; set_top_mask_t set_top_mask; clear_top_mask_t clear_top_mask; validate_area_t validate_area; destroy_t destroy; } DrawContext; #ifdef STREAM_TRACE typedef struct ItemTrace { red_time_t time; int frames_count; int gradual_frames_count; int last_gradual_frame; int width; int height; Rect dest_area; } ItemTrace; #define TRACE_ITEMS_SHIFT 3 #define NUM_TRACE_ITEMS (1 << TRACE_ITEMS_SHIFT) #define ITEMS_TRACE_MASK (NUM_TRACE_ITEMS - 1) #endif #define NUM_DRAWABLES 1000 typedef struct RedWorker { EventListener dev_listener; DisplayChannel *display_channel; CursorChannel *cursor_channel; QXLInterface *qxl; int id; int channel; int attached; int running; uint32_t *pending; QXLDevInfo dev_info; int epoll; unsigned int epoll_timeout; uint32_t repoll_cmd_ring; uint32_t repoll_cursor_ring; DrawContext draw_context; uint32_t num_renderers; uint32_t renderers[RED_MAX_RENDERERS]; Ring current_list; Ring current; uint32_t current_size; uint32_t drawable_count; uint32_t transparent_count; uint32_t shadows_count; uint32_t containers_count; uint32_t bits_unique; CursorItem *cursor; int cursor_visible; Point16 cursor_position; uint16_t cursor_trail_length; uint16_t cursor_trail_frequency; _Drawable drawables[NUM_DRAWABLES]; _Drawable *free_drawables; uint32_t local_images_pos; LocalImage local_images[MAX_BITMAPS]; ImageCache image_cache; spice_image_compression_t image_compression; uint32_t mouse_mode; uint32_t streaming_video; Stream streams_buf[NUM_STREAMS]; Stream *free_streams; Ring streams; #ifdef STREAM_TRACE ItemTrace items_trace[NUM_TRACE_ITEMS]; uint32_t next_item_trace; #endif QuicData quic_data; QuicContext *quic; LzData lz_data; LzContext *lz; #ifdef PIPE_DEBUG uint32_t last_id; #endif #ifdef RED_WORKER_STAT stat_info_t add_stat; stat_info_t exclude_stat; stat_info_t __exclude_stat; uint32_t add_count; uint32_t add_with_shadow_count; #endif #ifdef RED_STATISTICS StatNodeRef stat; uint64_t *wakeup_counter; uint64_t *command_counter; #endif } RedWorker; pthread_mutex_t avcodec_lock = PTHREAD_MUTEX_INITIALIZER; static void red_draw_qxl_drawable(RedWorker *worker, QXLDrawable *drawable); static void red_current_flush(RedWorker *worker); static void display_channel_push(RedWorker *worker); #ifdef DRAW_ALL #define red_update_area(worker, rect) #define red_draw_drawable(worker, item) #else static void red_draw_drawable(RedWorker *worker, Drawable *item); static void red_update_area(RedWorker *worker, const Rect *area); #endif static void red_release_cursor(RedWorker *worker, CursorItem *cursor); static inline void release_drawable(RedWorker *worker, Drawable *item); static void red_display_release_stream(DisplayChannel *display, StreamAgent *agent); #ifdef STREAM_TRACE static inline void red_detach_stream(RedWorker *worker, Stream *stream); #endif static void red_stop_stream(RedWorker *worker, Stream *stream); static inline void red_stream_maintenance(RedWorker *worker, Drawable *candidate, Drawable *sect); static inline void red_begin_send_massage(RedChannel *channel, void *item); static inline void display_begin_send_massage(DisplayChannel *channel, void *item); static void red_receive(RedChannel *channel); static void red_release_pixmap_cache(DisplayChannel *channel); static void red_release_glz(DisplayChannel *channel); static void red_freeze_glz(DisplayChannel *channel); static void display_channel_push_release(DisplayChannel *channel, uint8_t type, uint64_t id, uint64_t* sync_data); static void red_display_release_stream_clip(DisplayChannel* channel, StreamClipItem *item); static int red_display_free_some_independent_glz_drawables(DisplayChannel *channel); static void red_display_free_glz_drawable(DisplayChannel *channel, RedGlzDrawable *drawable); static void reset_rate(StreamAgent *stream_agent); static int _bitmap_is_gradual(RedWorker *worker, Bitmap *bitmap); static inline int _stride_is_extra(Bitmap *bitmap); #ifdef DUMP_BITMAP static void dump_bitmap(RedWorker *worker, Bitmap *bitmap); #endif #ifdef COMPRESS_STAT static void print_compress_stats(DisplayChannel *display_channel) { if (!display_channel) { return; } red_printf("==> Compression stats for display %u", display_channel->base.id); red_printf("Method \t count \torig_size(MB)\tenc_size(MB)\tenc_time(s)"); red_printf("QUIC \t%8d\t%13.2f\t%12.2f\t%12.2f", display_channel->quic_stat.count, stat_byte_to_mega(display_channel->quic_stat.orig_size), stat_byte_to_mega(display_channel->quic_stat.comp_size), stat_cpu_time_to_sec(display_channel->quic_stat.total) ); red_printf("GLZ \t%8d\t%13.2f\t%12.2f\t%12.2f", display_channel->glz_stat.count, stat_byte_to_mega(display_channel->glz_stat.orig_size), stat_byte_to_mega(display_channel->glz_stat.comp_size), stat_cpu_time_to_sec(display_channel->glz_stat.total) ); red_printf("LZ \t%8d\t%13.2f\t%12.2f\t%12.2f", display_channel->lz_stat.count, stat_byte_to_mega(display_channel->lz_stat.orig_size), stat_byte_to_mega(display_channel->lz_stat.comp_size), stat_cpu_time_to_sec(display_channel->lz_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, stat_byte_to_mega(display_channel->lz_stat.orig_size + display_channel->glz_stat.orig_size + display_channel->quic_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), stat_cpu_time_to_sec(display_channel->lz_stat.total + display_channel->glz_stat.total + display_channel->quic_stat.total) ); } #endif char *draw_type_to_str(UINT8 type) { switch (type) { case QXL_DRAW_FILL: return "QXL_DRAW_FILL"; case QXL_DRAW_OPAQUE: return "QXL_DRAW_OPAQUE"; case QXL_DRAW_COPY: return "QXL_DRAW_COPY"; case QXL_DRAW_TRANSPARENT: return "QXL_DRAW_TRANSPARENT"; case QXL_DRAW_ALPHA_BLEND: return "QXL_DRAW_ALPHA_BLEND"; case QXL_COPY_BITS: return "QXL_COPY_BITS"; case QXL_DRAW_BLEND: return "QXL_DRAW_BLEND"; case QXL_DRAW_BLACKNESS: return "QXL_DRAW_BLACKNESS"; case QXL_DRAW_WHITENESS: return "QXL_DRAW_WHITENESS"; case QXL_DRAW_INVERS: return "QXL_DRAW_INVERS"; case QXL_DRAW_ROP3: return "QXL_DRAW_ROP3"; case QXL_DRAW_STROKE: return "QXL_DRAW_STROKE"; case QXL_DRAW_TEXT: return "QXL_DRAW_TEXT"; default: return "?"; } } static void show_qxl_drawable(RedWorker *worker, QXLDrawable *drawable, const char *prefix) { if (prefix) { printf("%s: ", prefix); } printf("%s effect %d bbox(%d %d %d %d)", draw_type_to_str(drawable->type), drawable->effect, drawable->bbox.top, drawable->bbox.left, drawable->bbox.bottom, drawable->bbox.right); switch (drawable->type) { case QXL_DRAW_FILL: case QXL_DRAW_OPAQUE: case QXL_DRAW_COPY: case QXL_DRAW_TRANSPARENT: case QXL_DRAW_ALPHA_BLEND: case QXL_COPY_BITS: case QXL_DRAW_BLEND: case QXL_DRAW_BLACKNESS: case QXL_DRAW_WHITENESS: case QXL_DRAW_INVERS: case QXL_DRAW_ROP3: case QXL_DRAW_STROKE: case QXL_DRAW_TEXT: break; default: red_error("bad rawable type"); } printf("\n"); } static void show_draw_item(RedWorker *worker, DrawItem *draw_item, const char *prefix) { if (prefix) { printf("%s: ", prefix); } printf("effect %d bbox(%d %d %d %d)\n", draw_item->effect, draw_item->base.rgn.bbox.top, draw_item->base.rgn.bbox.left, draw_item->base.rgn.bbox.bottom, draw_item->base.rgn.bbox.right); } static inline void red_pipe_item_init(PipeItem *item, int type) { ring_item_init(&item->link); item->type = type; } static inline void red_pipe_add(RedChannel *channel, PipeItem *item) { ASSERT(channel); channel->pipe_size++; ring_add(&channel->pipe, &item->link); } static inline void red_pipe_add_after(RedChannel *channel, PipeItem *item, PipeItem *pos) { ASSERT(channel && pos); channel->pipe_size++; ring_add_after(&item->link, &pos->link); } static inline int pipe_item_is_linked(PipeItem *item) { return ring_item_is_linked(&item->link); } static inline void pipe_item_remove(PipeItem *item) { ring_remove(&item->link); } static inline void red_pipe_add_tail(RedChannel *channel, PipeItem *item) { ASSERT(channel); channel->pipe_size++; ring_add_before(&item->link, &channel->pipe); } static void red_pipe_add_verb(RedChannel* channel, uint16_t verb) { VerbItem *item = malloc(sizeof(*item)); if (!item) { PANIC("malloc failed"); } red_pipe_item_init(&item->base, PIPE_ITEM_TYPE_VERB); item->verb = verb; red_pipe_add(channel, &item->base); } static void red_pipe_add_type(RedChannel* channel, int pipe_item_type) { PipeItem *item = malloc(sizeof(*item)); if (!item) { PANIC("malloc failed"); } red_pipe_item_init(item, pipe_item_type); red_pipe_add(channel, item); } static inline void red_pipe_add_drawable(RedWorker *worker, Drawable *drawable) { if (!worker->display_channel) { return; } drawable->refs++; red_pipe_add(&worker->display_channel->base, &drawable->pipe_item); } static inline void red_pipe_add_drawable_after(RedWorker *worker, Drawable *drawable, Drawable *pos_after) { if (!worker->display_channel) { return; } if (!pos_after || !pipe_item_is_linked(&pos_after->pipe_item)) { red_pipe_add_drawable(worker, drawable); return; } drawable->refs++; red_pipe_add_after(&worker->display_channel->base, &drawable->pipe_item, &pos_after->pipe_item); } static inline void red_pipe_remove_drawable(RedWorker *worker, Drawable *drawable) { if (ring_item_is_linked(&drawable->pipe_item.link)) { worker->display_channel->base.pipe_size--; ring_remove(&drawable->pipe_item.link); release_drawable(worker, drawable); } } static inline void red_pipe_add_image_item(RedWorker *worker, ImageItem *item) { if (!worker->display_channel) { return; } item->refs++; red_pipe_add(&worker->display_channel->base, &item->link); } static inline uint64_t channel_message_serial(RedChannel *channel) { return channel->send_data.header.serial; } static void release_image_item(ImageItem *item) { if (!--item->refs) { free(item); } } static void release_upgrade_item(RedWorker* worker, UpgradeItem *item) { if (!--item->refs) { release_drawable(worker, item->drawable); region_destroy(&item->region); free(item); } } static void red_pipe_clear_device_resources(RedChannel *channel) { Ring *ring; PipeItem *item; ASSERT(channel); ring = &channel->pipe; item = (PipeItem *) ring; while ((item = (PipeItem *)ring_next(ring, (RingItem *)item))) { PipeItem *tmp_item = item; switch (item->type) { case PIPE_ITEM_TYPE_DRAW: item = (PipeItem *)ring_prev(ring, (RingItem *)item); ring_remove(&tmp_item->link); release_drawable(channel->worker, CONTAINEROF(tmp_item, Drawable, pipe_item)); channel->pipe_size--; break; case PIPE_ITEM_TYPE_CURSOR: item = (PipeItem *)ring_prev(ring, (RingItem *)item); ring_remove(&tmp_item->link); red_release_cursor(channel->worker, (CursorItem *)tmp_item); channel->pipe_size--; break; case PIPE_ITEM_TYPE_UPGRADE: item = (PipeItem *)ring_prev(ring, (RingItem *)item); ring_remove(&tmp_item->link); release_upgrade_item(channel->worker, (UpgradeItem *)tmp_item); channel->pipe_size--; break; } } } static void red_pipe_clear(RedChannel *channel) { PipeItem *item; ASSERT(channel); while ((item = (PipeItem *)ring_get_head(&channel->pipe))) { ring_remove(&item->link); switch (item->type) { case PIPE_ITEM_TYPE_DRAW: release_drawable(channel->worker, CONTAINEROF(item, Drawable, pipe_item)); break; case PIPE_ITEM_TYPE_CURSOR: red_release_cursor(channel->worker, (CursorItem *)item); break; case PIPE_ITEM_TYPE_UPGRADE: release_upgrade_item(channel->worker, (UpgradeItem *)item); break; case PIPE_ITEM_TYPE_PIXMAP_RESET: case PIPE_ITEM_TYPE_PIXMAP_SYNC: case PIPE_ITEM_TYPE_INVAL_ONE: case PIPE_ITEM_TYPE_MODE: case PIPE_ITEM_TYPE_MIGRATE: case PIPE_ITEM_TYPE_SET_ACK: case PIPE_ITEM_TYPE_CURSOR_INIT: case PIPE_ITEM_TYPE_VERB: case PIPE_ITEM_TYPE_MIGRATE_DATA: case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE: case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE: free(item); break; case PIPE_ITEM_TYPE_IMAGE: release_image_item((ImageItem *)item); break; case PIPE_ITEM_TYPE_STREAM_CREATE: red_display_release_stream((DisplayChannel *)channel, CONTAINEROF(item, StreamAgent, create_item)); break; case PIPE_ITEM_TYPE_STREAM_CLIP: red_display_release_stream_clip((DisplayChannel *)channel, (StreamClipItem*)item); break; case PIPE_ITEM_TYPE_STREAM_DESTROY: red_display_release_stream((DisplayChannel *)channel, CONTAINEROF(item, StreamAgent, destroy_item)); break; } } channel->pipe_size = 0; } #define CLIENT_PIXMAPS_CACHE #include "red_client_shared_cache.h" #undef CLIENT_PIXMAPS_CACHE #define CLIENT_CURSOR_CACHE #include "red_client_cache.h" #undef CLIENT_CURSOR_CACHE #define CLIENT_PALETTE_CACHE #include "red_client_cache.h" #undef CLIENT_PALETTE_CACHE static void red_reset_palette_cache(DisplayChannel *display_channel) { red_palette_cache_reset(display_channel, CLIENT_PALETTE_CACHE_SIZE); } static void red_reset_cursor_cache(CursorChannel *channel) { red_cursor_cache_reset(channel, CLIENT_CURSOR_CACHE_SIZE); } static inline Drawable *alloc_drawable(RedWorker *worker) { Drawable *drawable; if (!worker->free_drawables) { return NULL; } drawable = &worker->free_drawables->u.drawable; worker->free_drawables = worker->free_drawables->u.next; return drawable; } static inline void free_drawable(RedWorker *worker, Drawable *item) { ((_Drawable *)item)->u.next = worker->free_drawables; worker->free_drawables = (_Drawable *)item; } static void drawables_init(RedWorker *worker) { int i; worker->free_drawables = NULL; for (i = 0; i < NUM_DRAWABLES; i++) { free_drawable(worker, &worker->drawables[i].u.drawable); } } static inline void free_qxl_drawable(RedWorker *worker, QXLDrawable *drawable) { if (drawable->bitmap_offset) { PHYSICAL *addr = (PHYSICAL *)((uint8_t *)drawable + drawable->bitmap_offset); if (*addr) { free((uint8_t *)*addr + worker->dev_info.phys_delta); } } worker->qxl->release_resource(worker->qxl, &drawable->release_info); } static inline void release_drawable(RedWorker *worker, Drawable *item) { if (!--item->refs) { #ifdef STREAM_TRACE ASSERT(!item->stream); #else if (item->stream) { red_stop_stream(worker, item->stream); } #endif ASSERT(!item->tree_item.shadow); region_destroy(&item->tree_item.base.rgn); if (item->red_glz_drawable) { item->red_glz_drawable->drawable = NULL; } else { // no refernce to the qxl drawable left free_qxl_drawable(worker, item->qxl_drawable); } free_drawable(worker, item); } } static inline void remove_shadow(RedWorker *worker, DrawItem *item) { Shadow *shadow; if (!item->shadow) { return; } shadow = item->shadow; item->shadow = NULL; ring_remove(&shadow->base.siblings_link); region_destroy(&shadow->base.rgn); region_destroy(&shadow->on_hold); free(shadow); worker->shadows_count--; } static inline void current_remove_container(RedWorker *worker, Container *container) { ASSERT(ring_is_empty(&container->items)); worker->containers_count--; ring_remove(&container->base.siblings_link); region_destroy(&container->base.rgn); free(container); } static inline void container_cleanup(RedWorker *worker, Container *container) { while (container && container->items.next == container->items.prev) { Container *next = container->base.container; if (container->items.next != &container->items) { TreeItem *item = (TreeItem *)ring_get_head(&container->items); ASSERT(item); ring_remove(&item->siblings_link); ring_add_after(&item->siblings_link, &container->base.siblings_link); item->container = container->base.container; } current_remove_container(worker, container); container = next; } } #ifdef STREAM_TRACE static inline void red_add_item_trace(RedWorker *worker, Drawable *item) { ItemTrace *trace; if (!item->streamable) { return; } trace = &worker->items_trace[worker->next_item_trace++ & ITEMS_TRACE_MASK]; trace->time = item->creation_time; trace->frames_count = item->frames_count; trace->gradual_frames_count = item->gradual_frames_count; trace->last_gradual_frame = item->last_gradual_frame; Rect* src_area = &item->qxl_drawable->u.copy.src_area; trace->width = src_area->right - src_area->left; trace->height = src_area->bottom - src_area->top; trace->dest_area = item->qxl_drawable->bbox; } #endif static inline void current_remove_drawable(RedWorker *worker, Drawable *item) { worker->drawable_count--; if (item->tree_item.effect != QXL_EFFECT_OPAQUE) { worker->transparent_count--; } #ifdef STREAM_TRACE if (item->stream) { red_detach_stream(worker, item->stream); } else { red_add_item_trace(worker, item); } #endif remove_shadow(worker, &item->tree_item); ring_remove(&item->tree_item.base.siblings_link); ring_remove(&item->list_link); release_drawable(worker, item); worker->current_size--; } static void remove_drawable(RedWorker *worker, Drawable *item) { red_pipe_remove_drawable(worker, item); current_remove_drawable(worker, item); } static inline void current_remove(RedWorker *worker, TreeItem *item) { TreeItem *now = item; for (;;) { Container *container = now->container; RingItem *ring_item; if (now->type == TREE_ITEM_TYPE_DRAWABLE) { ring_item = now->siblings_link.prev; remove_drawable(worker, CONTAINEROF(now, Drawable, tree_item)); } else { Container *container = (Container *)now; ASSERT(now->type == TREE_ITEM_TYPE_CONTAINER); if ((ring_item = ring_get_head(&container->items))) { now = CONTAINEROF(ring_item, TreeItem, siblings_link); continue; } ring_item = now->siblings_link.prev; current_remove_container(worker, container); } if (now == item) { return; } if ((ring_item = ring_next(&container->items, ring_item))) { now = CONTAINEROF(ring_item, TreeItem, siblings_link); } else { now = (TreeItem *)container; } } } static void current_tree_for_each(RedWorker *worker, void (*f)(TreeItem *, void *), void * data) { Ring *ring = &worker->current; RingItem *ring_item; Ring *top_ring; if (!(ring_item = ring_get_head(ring))) { return; } top_ring = ring; for (;;) { TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link); f(now, data); if (now->type == TREE_ITEM_TYPE_CONTAINER) { Container *container = (Container *)now; if ((ring_item = ring_get_head(&container->items))) { ring = &container->items; continue; } } for (;;) { ring_item = ring_next(ring, &now->siblings_link); if (ring_item) { break; } if (ring == top_ring) { return; } now = (TreeItem *)now->container; ring = (now->container) ? &now->container->items : top_ring; } } } static void red_current_clear(RedWorker *worker) { RingItem *ring_item; while ((ring_item = ring_get_head(&worker->current))) { TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link); current_remove(worker, now); } } #ifdef PIPE_DEBUG static void print_rgn(const char* prefix, const QRegion* rgn) { int i; printf("TEST: %s: RGN: bbox %u %u %u %u\n", prefix, rgn->bbox.top, rgn->bbox.left, rgn->bbox.bottom, rgn->bbox.right); for (i = 0; i < rgn->num_rects; i++) { printf("TEST: %s: RECT %u %u %u %u\n", prefix, rgn->rects[i].top, rgn->rects[i].left, rgn->rects[i].bottom, rgn->rects[i].right); } } static void print_draw_item(const char* prefix, const DrawItem *draw_item) { const TreeItem *base = &draw_item->base; const Drawable *drawable = CONTAINEROF(draw_item, Drawable, tree_item); printf("TEST: %s: draw id %u container %u effect %u", prefix, base->id, base->container ? base->container->base.id : 0, draw_item->effect); if (draw_item->shadow) { printf(" shadow %u\n", draw_item->shadow->base.id); } else { printf("\n"); } print_rgn(prefix, &base->rgn); } static void print_shadow_item(const char* prefix, const Shadow *item) { printf("TEST: %s: shadow %p id %d\n", prefix, item, item->base.id); print_rgn(prefix, &item->base.rgn); } static void print_container_item(const char* prefix, const Container *item) { printf("TEST: %s: container %p id %d\n", prefix, item, item->base.id); print_rgn(prefix, &item->base.rgn); } static void print_base_item(const char* prefix, const TreeItem *base) { switch (base->type) { case TREE_ITEM_TYPE_DRAWABLE: print_draw_item(prefix, (const DrawItem *)base); break; case TREE_ITEM_TYPE_SHADOW: print_shadow_item(prefix, (const Shadow *)base); break; case TREE_ITEM_TYPE_CONTAINER: print_container_item(prefix, (const Container *)base); break; default: red_error("invalid type %u", base->type); } } void __show_current(TreeItem *item, void *data) { print_base_item("TREE", item); } static void show_current(RedWorker *worker) { if (ring_is_empty(&worker->current)) { red_printf("TEST: TREE: EMPTY"); return; } current_tree_for_each(worker, __show_current, NULL); } #else #define print_rgn(a, b) #define print_draw_private(a, b) #define show_current(a) #define print_shadow_item(a, b) #define print_base_item(a, b) #endif static inline Shadow *__find_shadow(TreeItem *item) { while (item->type == TREE_ITEM_TYPE_CONTAINER) { if (!(item = (TreeItem *)ring_get_tail(&((Container *)item)->items))) { return NULL; } } if (item->type != TREE_ITEM_TYPE_DRAWABLE) { return NULL; } return ((DrawItem *)item)->shadow; } static inline Ring *ring_of(RedWorker *worker, TreeItem *item) { return (item->container) ? &item->container->items : &worker->current; } static inline int __contained_by(RedWorker *worker, TreeItem *item, Ring *ring) { ASSERT(item && ring); do { Ring *now = ring_of(worker, item); if (now == ring) { return TRUE; } } while ((item = (TreeItem *)item->container)); return FALSE; } static inline void __exclude_region(RedWorker *worker, TreeItem *item, QRegion *rgn, Ring **top_ring, Drawable *frame_candidate) { QRegion and_rgn; #ifdef RED_WORKER_STAT stat_time_t start_time = stat_now(); #endif region_clone(&and_rgn, rgn); region_and(&and_rgn, &item->rgn); if (!region_is_empty(&and_rgn)) { if (IS_DRAW_ITEM(item)) { DrawItem *draw = (DrawItem *)item; if (draw->effect == QXL_EFFECT_OPAQUE) { region_exclude(rgn, &and_rgn); } if (draw->shadow) { Shadow *shadow; int32_t x = item->rgn.bbox.left; int32_t y = item->rgn.bbox.top; region_exclude(&draw->base.rgn, &and_rgn); shadow = draw->shadow; region_offset(&and_rgn, shadow->base.rgn.bbox.left - x, shadow->base.rgn.bbox.top - y); region_exclude(&shadow->base.rgn, &and_rgn); region_and(&and_rgn, &shadow->on_hold); if (!region_is_empty(&and_rgn)) { region_exclude(&shadow->on_hold, &and_rgn); region_or(rgn, &and_rgn); // in flat representation of current, shadow is always his owner next if (!__contained_by(worker, (TreeItem*)shadow, *top_ring)) { *top_ring = ring_of(worker, (TreeItem*)shadow); } } } else { if (frame_candidate) { Drawable *drawable = CONTAINEROF(draw, Drawable, tree_item); red_stream_maintenance(worker, frame_candidate, drawable); } region_exclude(&draw->base.rgn, &and_rgn); } } else if (item->type == TREE_ITEM_TYPE_CONTAINER) { region_exclude(&item->rgn, &and_rgn); if (region_is_empty(&item->rgn)) { //assume container removal will follow Shadow *shadow; region_exclude(rgn, &and_rgn); if ((shadow = __find_shadow(item))) { region_or(rgn, &shadow->on_hold); if (!__contained_by(worker, (TreeItem*)shadow, *top_ring)) { *top_ring = ring_of(worker, (TreeItem*)shadow); } } } } else { Shadow *shadow; ASSERT(item->type == TREE_ITEM_TYPE_SHADOW); shadow = (Shadow *)item; region_exclude(rgn, &and_rgn); region_or(&shadow->on_hold, &and_rgn); } } region_destroy(&and_rgn); stat_add(&worker->__exclude_stat, start_time); } #ifdef USE_EXCLUDE_RGN static void exclude_region(RedWorker *worker, Ring *ring, RingItem *ring_item, QRegion *rgn, TreeItem **last, Drawable *frame_candidate) { #ifdef RED_WORKER_STAT stat_time_t start_time = stat_now(); #endif Ring *top_ring; if (!ring_item) { return; } top_ring = ring; for (;;) { TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link); Container *container = now->container; ASSERT(!region_is_empty(&now->rgn)); if (region_intersects(rgn, &now->rgn)) { print_base_item("EXCLUDE2", now); __exclude_region(worker, now, rgn, &top_ring, frame_candidate); print_base_item("EXCLUDE3", now); if (region_is_empty(&now->rgn)) { ASSERT(now->type != TREE_ITEM_TYPE_SHADOW); ring_item = now->siblings_link.prev; print_base_item("EXCLUDE_REMOVE", now); current_remove(worker, now); if (last && *last == now) { *last = (TreeItem *)ring_next(ring, ring_item); } } else if (now->type == TREE_ITEM_TYPE_CONTAINER) { Container *container = (Container *)now; if ((ring_item = ring_get_head(&container->items))) { ring = &container->items; ASSERT(((TreeItem *)ring_item)->container); continue; } ring_item = &now->siblings_link; } if (region_is_empty(rgn)) { stat_add(&worker->exclude_stat, start_time); return; } } while ((last && *last == (TreeItem *)ring_item) || !(ring_item = ring_next(ring, ring_item))) { if (ring == top_ring) { stat_add(&worker->exclude_stat, start_time); return; } ring_item = &container->base.siblings_link; container = container->base.container; ring = (container) ? &container->items : top_ring; } } } #else static void exclude_region(RedWorker *worker, Ring *ring, RingItem *ring_item, QRegion *rgn) { #ifdef RED_WORKER_STAT stat_time_t start_time = stat_now(); #endif Ring *top_ring; if (!ring_item) { return; } top_ring = ring; for (;;) { TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link); Container *container = now->container; ASSERT(!region_is_empty(&now->rgn)); if (region_test(rgn, &now->rgn, REGION_TEST_SHARED)) { print_base_item("EXCLUDE2", now); __exclude_region(worker, now, rgn, &top_ring); print_base_item("EXCLUDE3", now); if (region_is_empty(&now->rgn)) { ASSERT(now->type != TREE_ITEM_TYPE_SHADOW); ring_item = now->siblings_link.prev; print_base_item("EXCLUDE_REMOVE", now); current_remove(worker, now); } else if (now->type == TREE_ITEM_TYPE_CONTAINER) { Container *container = (Container *)now; if ((ring_item = ring_get_head(&container->items))) { ring = &container->items; ASSERT(((TreeItem *)ring_item)->container); continue; } ring_item = &now->siblings_link; } if (region_is_empty(rgn)) { stat_add(&worker->exclude_stat, start_time); return; } } while (!(ring_item = ring_next(ring, ring_item))) { if (ring == top_ring) { stat_add(&worker->exclude_stat, start_time); return; } ring_item = &container->base.siblings_link; container = container->base.container; ring = (container) ? &container->items : top_ring; } } } #endif static inline Container *__new_container(RedWorker *worker, DrawItem *item) { Container *container = malloc(sizeof(Container)); if (!container) { return NULL; } worker->containers_count++; #ifdef PIPE_DEBUG container->base.id = ++worker->last_id; #endif container->base.type = TREE_ITEM_TYPE_CONTAINER; container->base.container = item->base.container; item->base.container = container; item->container_root = TRUE; region_clone(&container->base.rgn, &item->base.rgn); ring_item_init(&container->base.siblings_link); ring_add_after(&container->base.siblings_link, &item->base.siblings_link); ring_remove(&item->base.siblings_link); ring_init(&container->items); ring_add(&container->items, &item->base.siblings_link); return container; } static inline int is_opaque_item(TreeItem *item) { return item->type == TREE_ITEM_TYPE_CONTAINER || (IS_DRAW_ITEM(item) && ((DrawItem *)item)->effect == QXL_EFFECT_OPAQUE); } static inline void __current_add_drawable(RedWorker *worker, Drawable *drawable, RingItem *pos) { ring_add_after(&drawable->tree_item.base.siblings_link, pos); ring_add(&worker->current_list, &drawable->list_link); drawable->refs++; } #ifdef USE_EXCLUDE_RGN static int is_equal_path(ADDRESS p1, ADDRESS p2, long phys_delta) { QXLPath *path1; QXLPath *path2; QXLDataChunk *chunk1; QXLDataChunk *chunk2; uint8_t *data1; uint8_t *data2; int size; int size1; int size2; ASSERT(p1 && p2); path1 = (QXLPath *)(p1 + phys_delta); path2 = (QXLPath *)(p2 + phys_delta); if ((size = path1->data_size) != path2->data_size) { return FALSE; } ASSERT(size); chunk1 = &path1->chunk; size1 = chunk1->data_size; data1 = chunk1->data; chunk2 = &path2->chunk; size2 = chunk2->data_size; data2 = chunk2->data; for (;;) { int now = MIN(size1, size2); ASSERT(now); if (memcmp(data1, data2, now)) { return FALSE; } if (!(size -= now)) { return TRUE; } if ((size1 -= now) == 0) { ASSERT(chunk1->next_chunk) chunk1 = (QXLDataChunk *)(chunk1->next_chunk + phys_delta); size1 = chunk1->data_size; data1 = chunk1->data; } else { data1 += now; } if ((size2 -= now) == 0) { ASSERT(chunk2->next_chunk) chunk2 = (QXLDataChunk *)(chunk2->next_chunk + phys_delta); size2 = chunk2->data_size; data2 = chunk2->data; } else { data2 += now; } } } // partial imp static int is_equal_brush(Brush *b1, Brush *b2) { return b1->type == b2->type && b1->type == BRUSH_TYPE_SOLID && b1->u.color == b1->u.color; } // partial imp static int is_equal_line_attr(LineAttr *a1, LineAttr *a2) { return a1->flags == a2->flags && a1->join_style == a2->join_style && a1->end_style == a2->end_style && a1->style_nseg == a2->style_nseg && a1->width == a2->width && a1->miter_limit == a2->miter_limit && a1->style_nseg == 0; } static inline int rect_is_equal(const Rect *r1, const Rect *r2) { return r1->top == r2->top && r1->left == r2->left && r1->bottom == r2->bottom && r1->right == r2->right; } // partial imp static int is_same_geometry(QXLDrawable *d1, QXLDrawable *d2, long phys_delta) { if (d1->type != d2->type) { return FALSE; } switch (d1->type) { case QXL_DRAW_STROKE: return is_equal_line_attr(&d1->u.stroke.attr, &d2->u.stroke.attr) && is_equal_path(d1->u.stroke.path, d2->u.stroke.path, phys_delta); case QXL_DRAW_FILL: return rect_is_equal(&d1->bbox, &d2->bbox); default: return FALSE; } } static int is_same_drawable(QXLDrawable *d1, QXLDrawable *d2, long phys_delta) { if (!is_same_geometry(d1, d2, phys_delta)) { return FALSE; } switch (d1->type) { case QXL_DRAW_STROKE: return is_equal_brush(&d1->u.stroke.brush, &d2->u.stroke.brush); case QXL_DRAW_FILL: return is_equal_brush(&d1->u.fill.brush, &d2->u.fill.brush); default: return FALSE; } } static inline void red_free_stream(RedWorker *worker, Stream *stream) { stream->next = worker->free_streams; worker->free_streams = stream; } static void red_release_stream(RedWorker *worker, Stream *stream) { if (!--stream->refs) { #ifdef STREAM_TRACE ASSERT(!ring_item_is_linked(&stream->link)); #else ring_remove(&stream->link); #endif pthread_mutex_lock(&avcodec_lock); avcodec_close(stream->av_ctx); pthread_mutex_unlock(&avcodec_lock); av_free(stream->av_ctx); av_free(stream->av_frame); free(stream->frame_buf); red_free_stream(worker, stream); } } #ifdef STREAM_TRACE static inline void red_detach_stream(RedWorker *worker, Stream *stream) { ASSERT(stream->current && stream->current->stream); ASSERT(stream->current->stream == stream); stream->current->stream = NULL; stream->current = NULL; } static StreamClipItem *__new_stream_clip(DisplayChannel* channel, StreamAgent *agent) { StreamClipItem *item = (StreamClipItem *)malloc(sizeof(*item)); if (!item) { PANIC("alloc failed"); } red_pipe_item_init((PipeItem *)item, PIPE_ITEM_TYPE_STREAM_CLIP); item->stream_agent = agent; agent->stream->refs++; item->refs = 1; return item; } static void push_stream_clip_by_drawable(DisplayChannel* channel, StreamAgent *agent, Drawable *drawable) { StreamClipItem *item = __new_stream_clip(channel, agent); if (!item) { PANIC("alloc failed"); } if (drawable->qxl_drawable->clip.type == CLIP_TYPE_NONE) { region_init(&item->region); item->clip_type = CLIP_TYPE_NONE; } else { item->clip_type = CLIP_TYPE_RECTS; region_clone(&item->region, &drawable->tree_item.base.rgn); } red_pipe_add((RedChannel*)channel, (PipeItem *)item); } static void push_stream_clip(DisplayChannel* channel, StreamAgent *agent) { StreamClipItem *item = __new_stream_clip(channel, agent); if (!item) { PANIC("alloc failed"); } item->clip_type = CLIP_TYPE_RECTS; region_clone(&item->region, &agent->vis_region); red_pipe_add((RedChannel*)channel, (PipeItem *)item); } static void red_display_release_stream_clip(DisplayChannel* channel, StreamClipItem *item) { if (!--item->refs) { red_display_release_stream(channel, item->stream_agent); region_destroy(&item->region); free(item); } } static void red_attach_stream(RedWorker *worker, Drawable *drawable, Stream *stream) { DisplayChannel *channel; ASSERT(!drawable->stream && !stream->current); ASSERT(drawable && stream); stream->current = drawable; drawable->stream = stream; stream->last_time = drawable->creation_time; if ((channel = worker->display_channel)) { StreamAgent *agent = &channel->stream_agents[stream - worker->streams_buf]; if (!region_is_equal(&agent->vis_region, &drawable->tree_item.base.rgn)) { region_destroy(&agent->vis_region); region_clone(&agent->vis_region, &drawable->tree_item.base.rgn); push_stream_clip_by_drawable(channel, agent, drawable); } } } #endif static void red_stop_stream(RedWorker *worker, Stream *stream) { DisplayChannel *channel; #ifdef STREAM_TRACE ASSERT(ring_item_is_linked(&stream->link)); ASSERT(!stream->current); #else ASSERT(stream->current && stream->current->stream); stream->current->stream = NULL; stream->current = NULL; #endif if ((channel = worker->display_channel)) { StreamAgent *stream_agent; stream_agent = &channel->stream_agents[stream - worker->streams_buf]; region_clear(&stream_agent->vis_region); ASSERT(!pipe_item_is_linked(&stream_agent->destroy_item)); stream->refs++; red_pipe_add(&channel->base, &stream_agent->destroy_item); } #ifdef STREAM_TRACE ring_remove(&stream->link); #endif red_release_stream(worker, stream); } #ifdef STREAM_TRACE static inline void red_detach_stream_gracefully(RedWorker *worker, Stream *stream) { DisplayChannel *channel; ASSERT(stream->current); if ((channel = worker->display_channel) && !pipe_item_is_linked(&stream->current->pipe_item)) { UpgradeItem *upgrade_item; if (!(upgrade_item = (UpgradeItem *)malloc(sizeof(*upgrade_item)))) { PANIC("malloc failed"); } upgrade_item->refs = 1; red_pipe_item_init(&upgrade_item->base, PIPE_ITEM_TYPE_UPGRADE); upgrade_item->drawable = stream->current; upgrade_item->drawable->refs++; region_clone(&upgrade_item->region, &upgrade_item->drawable->tree_item.base.rgn); red_pipe_add((RedChannel *)channel, &upgrade_item->base); } red_detach_stream(worker, stream); } #else static inline void red_stop_stream_gracefully(RedWorker *worker, Stream *stream) { ASSERT(stream->current); if (worker->display_channel && !pipe_item_is_linked(&stream->current->pipe_item)) { UpgradeItem *item; if ((item = (UpgradeItem *)malloc(sizeof(*item)))) { item->refs = 1; red_pipe_item_init(&item->base, PIPE_ITEM_TYPE_UPGRADE); item->drawable = stream->current; item->drawable->refs++; region_clone(&item->region, &item->drawable->tree_item.base.rgn); red_pipe_add((RedChannel *)worker->display_channel, &item->base); } } red_stop_stream(worker, stream); } #endif #ifdef STREAM_TRACE static void red_detach_streams_behind(RedWorker *worker, QRegion *region) { Ring *ring = &worker->streams; RingItem *item = ring_get_head(ring); DisplayChannel *channel = worker->display_channel; while (item) { Stream *stream = CONTAINEROF(item, Stream, link); item = ring_next(ring, item); if (channel) { StreamAgent *agent = &channel->stream_agents[stream - worker->streams_buf]; if (region_intersects(&agent->vis_region, region)) { region_clear(&agent->vis_region); push_stream_clip(channel, agent); if (stream->current) { red_detach_stream_gracefully(worker, stream); } } } else if (stream->current && region_intersects(&stream->current->tree_item.base.rgn, region)) { red_detach_stream(worker, stream); } } } #else static void red_stop_streams_behind(RedWorker *worker, QRegion *region) { Ring *ring = &worker->streams; RingItem *item = ring_get_head(ring); while (item) { Stream *stream = CONTAINEROF(item, Stream, link); stream->refs++; if (stream->current && region_intersects(region, &stream->current->tree_item.base.rgn)) { red_stop_stream_gracefully(worker, stream); } item = ring_next(ring, item); red_release_stream(worker, stream); } } #endif static void red_streams_update_clip(RedWorker *worker, Drawable *drawable) { DisplayChannel *channel; Ring *ring; RingItem *item; if (!(channel = worker->display_channel)) { return; } ring = &worker->streams; item = ring_get_head(ring); while (item) { Stream *stream = CONTAINEROF(item, Stream, link); StreamAgent *agent; item = ring_next(ring, item); agent = &channel->stream_agents[stream - worker->streams_buf]; if (stream->current == drawable) { continue; } if (region_intersects(&agent->vis_region, &drawable->tree_item.base.rgn)) { region_exclude(&agent->vis_region, &drawable->tree_item.base.rgn); push_stream_clip(channel, agent); } } } static inline unsigned int red_get_streams_timout(RedWorker *worker) { unsigned int timout = -1; Ring *ring = &worker->streams; RingItem *item = ring; struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); red_time_t now = timespec_to_red_time(&time); while ((item = ring_next(ring, item))) { Stream *stream; stream = CONTAINEROF(item, Stream, link); #ifdef STREAM_TRACE red_time_t delta = (stream->last_time + RED_STREAM_TIMOUT) - now; if (delta < 1000 * 1000) { return 0; } timout = MIN(timout, (unsigned int)(delta / (1000 * 1000))); #else if (stream->current) { red_time_t delta = (stream->current->creation_time + RED_STREAM_TIMOUT) - now; if (delta < 1000 * 1000) { return 0; } timout = MIN(timout, (unsigned int)(delta / (1000 * 1000))); } #endif } return timout; } static inline void red_handle_streams_timout(RedWorker *worker) { Ring *ring = &worker->streams; struct timespec time; RingItem *item; clock_gettime(CLOCK_MONOTONIC, &time); red_time_t now = timespec_to_red_time(&time); item = ring_get_head(ring); while (item) { Stream *stream = CONTAINEROF(item, Stream, link); #ifdef STREAM_TRACE item = ring_next(ring, item); if (now >= (stream->last_time + RED_STREAM_TIMOUT)) { if (stream->current) { red_detach_stream_gracefully(worker, stream); } red_stop_stream(worker, stream); } #else stream->refs++; if (stream->current && now >= (stream->current->creation_time + RED_STREAM_TIMOUT)) { red_stop_stream_gracefully(worker, stream); } item = ring_next(ring, item); red_release_stream(worker, stream); #endif } } static void red_display_release_stream(DisplayChannel *display, StreamAgent *agent) { ASSERT(agent->stream); red_release_stream(display->base.worker, agent->stream); } static inline Stream *red_alloc_stream(RedWorker *worker) { Stream *stream; if (!worker->free_streams) { return NULL; } stream = worker->free_streams; worker->free_streams = worker->free_streams->next; return stream; } static int get_bit_rate(int width, int height) { uint64_t bit_rate = width * height * BEST_BIT_RATE_PER_PIXEL; if (IS_LOW_BANDWIDTH()) { bit_rate = MIN(bitrate_per_sec * 70 / 100, bit_rate); bit_rate = MAX(bit_rate, width * height * WARST_BIT_RATE_PER_PIXEL); } return bit_rate; } static void red_dispaly_create_stream(DisplayChannel *display, Stream *stream) { StreamAgent *agent = &display->stream_agents[stream - display->base.worker->streams_buf]; stream->refs++; ASSERT(region_is_empty(&agent->vis_region)); if (stream->current) { agent->frames = 1; region_clone(&agent->vis_region, &stream->current->tree_item.base.rgn); } else { agent->frames = 0; } agent->drops = 0; agent->fps = MAX_FPS; reset_rate(agent); red_pipe_add(&display->base, &agent->create_item); } static AVCodecContext *red_init_video_encoder(int width, int height) { AVCodec *codec; AVCodecContext *ctx; int r; codec = avcodec_find_encoder(CODEC_ID_MJPEG); if (!codec) { red_printf("codec not found"); return NULL; } if (!(ctx = avcodec_alloc_context())) { red_printf("alloc ctx failed"); return NULL; } ctx->bit_rate = get_bit_rate(width, height); ASSERT(width % 2 == 0); ASSERT(height % 2 == 0); ctx->width = width; ctx->height = height; ctx->time_base = (AVRational){1, MAX_FPS}; ctx->gop_size = 10; ctx->max_b_frames = 0; ctx->pix_fmt = PIX_FMT_YUVJ420P; pthread_mutex_lock(&avcodec_lock); r = avcodec_open(ctx, codec); pthread_mutex_unlock(&avcodec_lock); if (r < 0) { red_printf("avcodec open failed"); av_free(ctx); return NULL; } return ctx; } static void red_create_stream(RedWorker *worker, Drawable *drawable) { AVCodecContext *av_ctx; Stream *stream; AVFrame *frame; uint8_t* frame_buf; Rect* src_rect; int stream_width; int stream_height; int pict_size; ASSERT(!drawable->stream); if (!(stream = red_alloc_stream(worker))) { return; } ASSERT(drawable->qxl_drawable->type == QXL_DRAW_COPY); src_rect = &drawable->qxl_drawable->u.copy.src_area; stream_width = ALIGN(src_rect->right - src_rect->left, 2); stream_height = ALIGN(src_rect->bottom - src_rect->top, 2); if (!(av_ctx = red_init_video_encoder(stream_width, stream_height))) { goto error_1; } if (!(frame = avcodec_alloc_frame())) { goto error_2; } if ((pict_size = avpicture_get_size(av_ctx->pix_fmt, stream_width, stream_height)) < 0) { goto error_3; } if (!(frame_buf = malloc(pict_size))) { goto error_3; } if (avpicture_fill((AVPicture *)frame, frame_buf, av_ctx->pix_fmt, stream_width, stream_height) < 0) { goto error_4; } ring_add(&worker->streams, &stream->link); stream->current = drawable; #ifdef STREAM_TRACE stream->last_time = drawable->creation_time; stream->width = src_rect->right - src_rect->left; stream->height = src_rect->bottom - src_rect->top; stream->dest_area = drawable->qxl_drawable->bbox; #endif stream->refs = 1; stream->av_ctx = av_ctx; stream->bit_rate = av_ctx->bit_rate; stream->av_frame = frame; stream->frame_buf = frame_buf; stream->frame_buf_end = frame_buf + pict_size; QXLImage *qxl_image = (QXLImage *)(drawable->qxl_drawable->u.copy.src_bitmap + worker->dev_info.phys_delta); stream->top_down = !!(qxl_image->bitmap.flags & BITMAP_TOP_DOWN); drawable->stream = stream; if (worker->display_channel) { red_dispaly_create_stream(worker->display_channel, stream); } return; error_4: free(frame_buf); error_3: av_free(frame); error_2: pthread_mutex_lock(&avcodec_lock); avcodec_close(av_ctx); pthread_mutex_unlock(&avcodec_lock); av_free(av_ctx); error_1: red_free_stream(worker, stream); } static void red_disply_start_streams(DisplayChannel *display_channel) { Ring *ring = &display_channel->base.worker->streams; RingItem *item = ring; while ((item = ring_next(ring, item))) { Stream *stream = CONTAINEROF(item, Stream, link); red_dispaly_create_stream(display_channel, stream); } } static void red_display_init_streams(DisplayChannel *display) { int i; for (i = 0; i < NUM_STREAMS; i++) { StreamAgent *agent = &display->stream_agents[i]; agent->stream = &display->base.worker->streams_buf[i]; region_init(&agent->vis_region); red_pipe_item_init(&agent->create_item, PIPE_ITEM_TYPE_STREAM_CREATE); red_pipe_item_init(&agent->destroy_item, PIPE_ITEM_TYPE_STREAM_DESTROY); } } static void red_display_destroy_streams(DisplayChannel *display) { int i; for (i = 0; i < NUM_STREAMS; i++) { StreamAgent *agent = &display->stream_agents[i]; region_destroy(&agent->vis_region); } } static void red_init_streams(RedWorker *worker) { int i; ring_init(&worker->streams); worker->free_streams = NULL; for (i = 0; i < NUM_STREAMS; i++) { Stream *stream = &worker->streams_buf[i]; ring_item_init(&stream->link); red_free_stream(worker, stream); } } #ifdef STREAM_TRACE static inline int __red_is_next_stream_frame(const Drawable *candidate, const int other_src_width, const int other_src_height, const Rect *other_dest, const red_time_t other_time, const Stream *stream, unsigned long phys_delta) { QXLDrawable *qxl_drawable; if (candidate->creation_time - other_time > (stream ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) { return FALSE; } qxl_drawable = candidate->qxl_drawable; if (!rect_is_equal(&qxl_drawable->bbox, other_dest)) { return FALSE; } Rect* candidate_src = &qxl_drawable->u.copy.src_area; if (candidate_src->right - candidate_src->left != other_src_width || candidate_src->bottom - candidate_src->top != other_src_height) { return FALSE; } if (stream) { QXLImage *qxl_image = (QXLImage *)(qxl_drawable->u.copy.src_bitmap + phys_delta); if (stream->top_down != !!(qxl_image->bitmap.flags & BITMAP_TOP_DOWN)) { return FALSE; } } return TRUE; } static inline int red_is_next_stream_frame(const Drawable *candidate, const Drawable *prev, const unsigned long phys_delta) { if (!candidate->streamable) { return FALSE; } Rect* prev_src = &prev->qxl_drawable->u.copy.src_area; return __red_is_next_stream_frame(candidate, prev_src->right - prev_src->left, prev_src->bottom - prev_src->top, &prev->qxl_drawable->bbox, prev->creation_time, prev->stream, phys_delta); } #else static inline int red_is_next_stream_frame(Drawable *candidate, Drawable *prev, unsigned long phys_delta) { QXLImage *qxl_image; QXLDrawable *qxl_drawable; QXLDrawable *prev_qxl_drawable; if (candidate->creation_time - prev->creation_time > ((prev->stream) ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) { return FALSE; } qxl_drawable = candidate->qxl_drawable; prev_qxl_drawable = prev->qxl_drawable; if (qxl_drawable->type != QXL_DRAW_COPY || prev_qxl_drawable->type != QXL_DRAW_COPY) { return FALSE; } if (!rect_is_equal(&qxl_drawable->bbox, &prev_qxl_drawable->bbox)) { return FALSE; } if (!rect_is_same_size(&qxl_drawable->u.copy.src_area, &prev_qxl_drawable->u.copy.src_area)) { return FALSE; } if (qxl_drawable->u.copy.rop_decriptor != ROPD_OP_PUT || prev_qxl_drawable->u.copy.rop_decriptor != ROPD_OP_PUT) { return FALSE; } qxl_image = (QXLImage *)(qxl_drawable->u.copy.src_bitmap + phys_delta); if (qxl_image->descriptor.type != IMAGE_TYPE_BITMAP) { return FALSE; } if (prev->stream && prev->stream->top_down != !!(qxl_image->bitmap.flags & BITMAP_TOP_DOWN)) { return FALSE; } return TRUE; } #endif static void reset_rate(StreamAgent *stream_agent) { Stream *stream = stream_agent->stream; AVCodecContext *new_ctx; int rate; rate = get_bit_rate(stream->width, stream->height); if (rate == stream->bit_rate) { return; } int stream_width = ALIGN(stream->width, 2); int stream_height = ALIGN(stream->height, 2); new_ctx = red_init_video_encoder(stream_width, stream_height); if (!new_ctx) { red_printf("craete ctx failed"); return; } avcodec_close(stream->av_ctx); av_free(stream->av_ctx); stream->av_ctx = new_ctx; stream->bit_rate = rate; } static inline void pre_stream_item_swap(RedWorker *worker, Stream *stream) { ASSERT(stream->current); if (!worker->display_channel || !IS_LOW_BANDWIDTH()) { return; } int index = stream - worker->streams_buf; StreamAgent *agent = &worker->display_channel->stream_agents[index]; if (pipe_item_is_linked(&stream->current->pipe_item)) { ++agent->drops; } if (agent->frames / agent->fps < FPS_TEST_INTERVAL) { agent->frames++; return; } double drop_factor = ((double)agent->frames - (double)agent->drops) / (double)agent->frames; if (drop_factor == 1) { if (agent->fps < MAX_FPS) { agent->fps++; } } else if (drop_factor < 0.9) { if (agent->fps > 1) { agent->fps--; } } agent->frames = 1; agent->drops = 0; } static inline void red_update_copy_graduality(RedWorker* worker, Drawable *drawable) { QXLImage *qxl_image; ASSERT(drawable->qxl_drawable->type == QXL_DRAW_COPY); if (worker->streaming_video != STREAM_VIDEO_FILTER) { drawable->copy_bitmap_graduality = BITMAP_GRADUAL_INVALID; return; } if (drawable->copy_bitmap_graduality != BITMAP_GRADUAL_INVALID) { return; // already set } qxl_image = (QXLImage *)(drawable->qxl_drawable->u.copy.src_bitmap + worker->dev_info.phys_delta); if (!BITMAP_FMT_IS_RGB[qxl_image->bitmap.format] || _stride_is_extra(&qxl_image->bitmap) || (qxl_image->bitmap.flags & QXL_BITMAP_UNSTABLE)) { drawable->copy_bitmap_graduality = BITMAP_GRADUAL_NOT_AVAIL; } else { if (_bitmap_is_gradual(worker, &qxl_image->bitmap)) { drawable->copy_bitmap_graduality = BITMAP_GRADUAL_TRUE; } else { drawable->copy_bitmap_graduality = BITMAP_GRADUAL_FALSE; } } } static inline int red_is_stream_start(Drawable *drawable) { return ((drawable->frames_count >= RED_STREAM_FRAMES_START_CONDITION) && (drawable->gradual_frames_count >= (RED_STREAM_GRADUAL_FRAMES_START_CONDITION * drawable->frames_count))); } static void red_stream_add_frame(RedWorker* worker, Drawable *frame_drawable, int frames_count, int gradual_frames_count, int last_gradual_frame) { red_update_copy_graduality(worker, frame_drawable); frame_drawable->frames_count = frames_count + 1; frame_drawable->gradual_frames_count = gradual_frames_count; if (frame_drawable->copy_bitmap_graduality != BITMAP_GRADUAL_FALSE) { if ((frame_drawable->frames_count - last_gradual_frame) > RED_STREAM_FRAMES_RESET_CONDITION) { frame_drawable->frames_count = 1; frame_drawable->gradual_frames_count = 1; } else { frame_drawable->gradual_frames_count++; } frame_drawable->last_gradual_frame = frame_drawable->frames_count; } else { frame_drawable->last_gradual_frame = last_gradual_frame; } if (red_is_stream_start(frame_drawable)) { red_create_stream(worker, frame_drawable); } } static inline void red_stream_maintenance(RedWorker *worker, Drawable *candidate, Drawable *prev) { Stream *stream; if (candidate->stream) { return; } #ifdef STREAM_TRACE if (!red_is_next_stream_frame(candidate, prev, worker->dev_info.phys_delta)) { return; } #else if (!worker->streaming_video || !red_is_next_stream_frame(candidate, prev, worker->dev_info.phys_delta)) { if ((worker->streaming_video == STREAM_VIDEO_OFF) || !red_is_next_stream_frame(candidate, prev, worker->dev_info.phys_delta) { return; } #endif if ((stream = prev->stream)) { #ifdef STREAM_TRACE pre_stream_item_swap(worker, stream); red_detach_stream(worker, stream); prev->streamable = FALSE; //prevent item trace red_attach_stream(worker, candidate, stream); #else prev->stream = NULL; candidate->stream = stream; stream->current = candidate; if (!region_is_equal(&stream->region, &candidate->tree_item.base.rgn)) { region_destroy(&stream->region); region_clone(&stream->region, &candidate->tree_item.base.rgn); if (worker->display_channel) { int index = stream - worker->streams_buf; StreamAgent *stream_agent = &worker->display_channel->stream_agents[index]; if (!pipe_item_is_linked(&stream_agent->clip_item)) { stream->refs++; red_pipe_add((RedChannel*)worker->display_channel, &stream_agent->clip_item); } } } #endif } else { red_stream_add_frame(worker, candidate, prev->frames_count, prev->gradual_frames_count, prev->last_gradual_frame); } } static inline int red_current_add_equal(RedWorker *worker, DrawItem *item, TreeItem *other) { DrawItem *other_draw_item; Drawable *drawable; Drawable *other_drawable; QXLDrawable *qxl_drawable; QXLDrawable *other_qxl_drawable; if (other->type != TREE_ITEM_TYPE_DRAWABLE) { return FALSE; } other_draw_item = (DrawItem *)other; if (item->shadow || other_draw_item->shadow || item->effect != other_draw_item->effect) { return FALSE; } drawable = CONTAINEROF(item, Drawable, tree_item); other_drawable = CONTAINEROF(other_draw_item, Drawable, tree_item); qxl_drawable = drawable->qxl_drawable; other_qxl_drawable = other_drawable->qxl_drawable; if (item->effect == QXL_EFFECT_OPAQUE) { int add_after = !!other_drawable->stream; red_stream_maintenance(worker, drawable, other_drawable); __current_add_drawable(worker, drawable, &other->siblings_link); if (add_after) { red_pipe_add_drawable_after(worker, drawable, other_drawable); } else { red_pipe_add_drawable(worker, drawable); } remove_drawable(worker, other_drawable); return TRUE; } switch (item->effect) { case QXL_EFFECT_REVERT_ON_DUP: if (is_same_drawable(qxl_drawable, other_qxl_drawable, worker->dev_info.phys_delta)) { if (!ring_item_is_linked(&other_drawable->pipe_item.link)) { red_pipe_add_drawable(worker, drawable); } remove_drawable(worker, other_drawable); return TRUE; } break; case QXL_EFFECT_OPAQUE_BRUSH: if (is_same_geometry(qxl_drawable, other_qxl_drawable, worker->dev_info.phys_delta)) { __current_add_drawable(worker, drawable, &other->siblings_link); remove_drawable(worker, other_drawable); red_pipe_add_drawable(worker, drawable); return TRUE; } break; case QXL_EFFECT_NOP_ON_DUP: if (is_same_drawable(qxl_drawable, other_qxl_drawable, worker->dev_info.phys_delta)) { return TRUE; } break; } return FALSE; } #ifdef STREAM_TRACE static inline void red_use_stream_trace(RedWorker *worker, Drawable *drawable) { ItemTrace *trace; ItemTrace *trace_end; Ring *ring; RingItem *item; if (drawable->stream || !drawable->streamable || drawable->frames_count) { return; } ring = &worker->streams; item = ring_get_head(ring); while (item) { Stream *stream = CONTAINEROF(item, Stream, link); if (!stream->current && __red_is_next_stream_frame(drawable, stream->width, stream->height, &stream->dest_area, stream->last_time, stream, worker->dev_info.phys_delta)) { red_attach_stream(worker, drawable, stream); return; } item = ring_next(ring, item); } trace = worker->items_trace; trace_end = trace + NUM_TRACE_ITEMS; for (; trace < trace_end; trace++) { if (__red_is_next_stream_frame(drawable, trace->width, trace->height, &trace->dest_area, trace->time, NULL, worker->dev_info.phys_delta)) { red_stream_add_frame(worker, drawable, trace->frames_count, trace->gradual_frames_count, trace->last_gradual_frame); } } } static void red_reset_stream_trace(RedWorker *worker) { Ring *ring = &worker->streams; RingItem *item = ring_get_head(ring); while (item) { Stream *stream = CONTAINEROF(item, Stream, link); item = ring_next(ring, item); if (!stream->current) { red_stop_stream(worker, stream); } else { red_printf("attached stream"); } } worker->next_item_trace = 0; memset(worker->items_trace, 0, sizeof(worker->items_trace)); } #endif static inline int red_current_add(RedWorker *worker, Drawable *drawable) { DrawItem *item = &drawable->tree_item; #ifdef RED_WORKER_STAT stat_time_t start_time = stat_now(); #endif RingItem *now; Ring *ring = &worker->current; QRegion exclude_rgn; RingItem *exclude_base = NULL; print_base_item("ADD", &item->base); ASSERT(!region_is_empty(&item->base.rgn)); worker->current_size++; region_init(&exclude_rgn); now = ring_next(ring, ring); while (now) { TreeItem *sibling = CONTAINEROF(now, TreeItem, siblings_link); int test_res; if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) { print_base_item("EMPTY", sibling); now = ring_next(ring, now); continue; } test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL); if (!(test_res & REGION_TEST_SHARED)) { print_base_item("EMPTY", sibling); now = ring_next(ring, now); continue; } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) { if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && !(test_res & REGION_TEST_LEFT_EXCLUSIVE) && red_current_add_equal(worker, item, sibling)) { stat_add(&worker->add_stat, start_time); return FALSE; } if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) { Shadow *shadow; int skip = now == exclude_base; print_base_item("CONTAIN", sibling); if ((shadow = __find_shadow(sibling))) { if (exclude_base) { TreeItem *next = sibling; exclude_region(worker, ring, exclude_base, &exclude_rgn, &next, NULL); if (next != sibling) { now = next ? &next->siblings_link : NULL; exclude_base = NULL; continue; } } region_or(&exclude_rgn, &shadow->on_hold); } now = now->prev; current_remove(worker, sibling); now = ring_next(ring, now); if (shadow || skip) { exclude_base = now; } continue; } if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) { Container *container; if (exclude_base) { exclude_region(worker, ring, exclude_base, &exclude_rgn, NULL, NULL); region_clear(&exclude_rgn); exclude_base = NULL; } print_base_item("IN", sibling); if (sibling->type == TREE_ITEM_TYPE_CONTAINER) { container = (Container *)sibling; ring = &container->items; item->base.container = container; now = ring_next(ring, ring); continue; } ASSERT(IS_DRAW_ITEM(sibling)); if (!((DrawItem *)sibling)->container_root) { container = __new_container(worker, (DrawItem *)sibling); if (!container) { red_printf("create new container failed"); region_destroy(&exclude_rgn); return FALSE; } item->base.container = container; ring = &container->items; } } } if (!exclude_base) { exclude_base = now; } break; } if (item->effect == QXL_EFFECT_OPAQUE) { region_or(&exclude_rgn, &item->base.rgn); exclude_region(worker, ring, exclude_base, &exclude_rgn, NULL, drawable); #ifdef STREAM_TRACE red_use_stream_trace(worker, drawable); #endif red_streams_update_clip(worker, drawable); } else { #ifdef STREAM_TRACE red_detach_streams_behind(worker, &drawable->tree_item.base.rgn); #else red_stop_streams_behind(worker, &drawable->tree_item.base.rgn); #endif } region_destroy(&exclude_rgn); __current_add_drawable(worker, drawable, ring); stat_add(&worker->add_stat, start_time); return TRUE; } #else static inline void __handle_remove_shadow(RedWorker *worker, TreeItem *item) { Shadow *shadow; Ring *ring; while (item->type == TREE_ITEM_TYPE_CONTAINER) { if (!(item = (TreeItem *)ring_get_tail(&((Container *)item)->items))) { return; } } if (item->type != TREE_ITEM_TYPE_DRAWABLE || !(shadow = ((DrawItem *)item)->shadow)) { return; } print_base_item("SHADW", &shadow->base); ring = (shadow->base.container) ? &shadow->base.container->items : &worker->current; exclude_region(worker, ring, ring_next(ring, &shadow->base.siblings_link), &shadow->on_hold); region_clear(&shadow->on_hold); } static inline int red_current_add(RedWorker *worker, Drawable *drawable) { DrawItem *item = &drawable->tree_item; #ifdef RED_WORKER_STAT stat_time_t start_time = stat_now(); #endif RingItem *now; Ring *ring = &worker->current; print_base_item("ADD", &item->base); ASSERT(!region_is_empty(&item->base.rgn)); worker->current_size++; now = ring_next(ring, ring); while (now) { TreeItem *sibling = CONTAINEROF(now, TreeItem, siblings_link); int test_res; if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) { print_base_item("EMPTY", sibling); now = ring_next(ring, now); continue; } test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL); if (!(test_res & REGION_TEST_SHARED)) { print_base_item("EMPTY", sibling); now = ring_next(ring, now); continue; } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) { if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) { print_base_item("CONTAIN", sibling); __handle_remove_shadow(worker, sibling); now = now->prev; current_remove(worker, sibling); now = ring_next(ring, now); continue; } if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) { Container *container; print_base_item("IN", sibling); if (sibling->type == TREE_ITEM_TYPE_CONTAINER) { container = (Container *)sibling; ring = &container->items; item->base.container = container; now = ring_next(ring, ring); continue; } ASSERT(IS_DRAW_ITEM(sibling)); if (!((DrawItem *)sibling)->container_root) { container = __new_container(worker, (DrawItem *)sibling); if (!container) { red_printf("create new container failed"); return FALSE; } item->base.container = container; ring = &container->items; } } } if (item->effect == QXL_EFFECT_OPAQUE) { QRegion exclude_rgn; region_clone(&exclude_rgn, &item->base.rgn); exclude_region(worker, ring, now, &exclude_rgn); region_destroy(&exclude_rgn); } break; } __current_add_drawable(worker, drawable, ring); stat_add(&worker->add_stat, start_time); return TRUE; } #endif static void add_clip_rects(QRegion *rgn, PHYSICAL data, unsigned long phys_delta) { while (data) { QXLDataChunk *chunk; Rect *now; Rect *end; chunk = (QXLDataChunk *)(data + phys_delta); now = (Rect *)chunk->data; end = now + chunk->data_size / sizeof(Rect); for (; now < end; now++) { Rect* r = (Rect *)now; ASSERT(now->top == r->top && now->left == r->left && now->bottom == r->bottom && now->right == r->right); #ifdef PIPE_DEBUG printf("TEST: DRAWABLE: RECT: %u %u %u %u\n", r->top, r->left, r->bottom, r->right); #endif region_add(rgn, r); } data = chunk->next_chunk; } } static inline Shadow *__new_shadow(RedWorker *worker, Drawable *item, Point *delta) { if (!delta->x && !delta->y) { return NULL; } Shadow *shadow = malloc(sizeof(Shadow)); if (!shadow) { return NULL; } worker->shadows_count++; #ifdef PIPE_DEBUG shadow->base.id = ++worker->last_id; #endif shadow->base.type = TREE_ITEM_TYPE_SHADOW; shadow->base.container = NULL; shadow->owner = &item->tree_item; region_clone(&shadow->base.rgn, &item->tree_item.base.rgn); region_offset(&shadow->base.rgn, delta->x, delta->y); ring_item_init(&shadow->base.siblings_link); region_init(&shadow->on_hold); item->tree_item.shadow = shadow; return shadow; } static inline int red_current_add_with_shadow(RedWorker *worker, Drawable *item, Point *delta) { #ifdef RED_WORKER_STAT stat_time_t start_time = stat_now(); #endif Ring *ring; Shadow *shadow = __new_shadow(worker, item, delta); if (!shadow) { stat_add(&worker->add_stat, start_time); return FALSE; } print_base_item("ADDSHADOW", &item->tree_item.base); worker->current_size++; ring = &worker->current; // item and his shadow must initially be placed in the same container. // for now putting them on root. #ifdef STREAM_TRACE red_detach_streams_behind(worker, &shadow->base.rgn); #else red_stop_streams_behind(worker, &shadow->base.rgn); #endif ring_add(ring, &shadow->base.siblings_link); __current_add_drawable(worker, item, ring); if (item->tree_item.effect == QXL_EFFECT_OPAQUE) { QRegion exclude_rgn; region_clone(&exclude_rgn, &item->tree_item.base.rgn); #ifdef USE_EXCLUDE_RGN exclude_region(worker, ring, &shadow->base.siblings_link, &exclude_rgn, NULL, NULL); #else exclude_region(worker, ring, &shadow->base.siblings_link, &exclude_rgn); #endif region_destroy(&exclude_rgn); red_streams_update_clip(worker, item); } else { #ifdef STREAM_TRACE red_detach_streams_behind(worker, &item->tree_item.base.rgn); #else red_stop_streams_behind(worker, &item->tree_item.base.rgn); #endif } stat_add(&worker->add_stat, start_time); return TRUE; } static inline int has_shadow(QXLDrawable *drawable) { return drawable->type == QXL_COPY_BITS; } #ifdef STREAM_TRACE static inline void red_update_streamable(RedWorker *worker, Drawable *drawable, QXLDrawable *qxl_drawable) { QXLImage *qxl_image; if (worker->streaming_video == STREAM_VIDEO_OFF) { return; } if (drawable->tree_item.effect != QXL_EFFECT_OPAQUE || qxl_drawable->type != QXL_DRAW_COPY || qxl_drawable->u.copy.rop_decriptor != ROPD_OP_PUT) { return; } qxl_image = (QXLImage *)(qxl_drawable->u.copy.src_bitmap + worker->dev_info.phys_delta); if (qxl_image->descriptor.type != IMAGE_TYPE_BITMAP) { return; } if (worker->streaming_video == STREAM_VIDEO_FILTER) { Rect* rect; int size; rect = &drawable->qxl_drawable->u.copy.src_area; size = (rect->right - rect->left) * (rect->bottom - rect->top); if (size < RED_STREAM_MIN_SIZE) { return; } } drawable->streamable = TRUE; } #endif static inline int red_current_add_qxl(RedWorker *worker, Drawable *drawable, QXLDrawable *qxl_drawable) { int ret; if (has_shadow(qxl_drawable)) { Point delta; #ifdef RED_WORKER_STAT ++worker->add_with_shadow_count; #endif delta.x = qxl_drawable->u.copy_bits.src_pos.x - qxl_drawable->bbox.left; delta.y = qxl_drawable->u.copy_bits.src_pos.y - qxl_drawable->bbox.top; ret = red_current_add_with_shadow(worker, drawable, &delta); } else { #ifdef STREAM_TRACE red_update_streamable(worker, drawable, qxl_drawable); #endif ret = red_current_add(worker, drawable); } #ifdef RED_WORKER_STAT if ((++worker->add_count % 100) == 0) { stat_time_t total = worker->add_stat.total; red_printf("add with shadow count %u", worker->add_with_shadow_count); worker->add_with_shadow_count = 0; red_printf("add[%u] %f exclude[%u] %f __exclude[%u] %f", worker->add_stat.count, stat_cpu_time_to_sec(total), worker->exclude_stat.count, stat_cpu_time_to_sec(worker->exclude_stat.total), worker->__exclude_stat.count, stat_cpu_time_to_sec(worker->__exclude_stat.total)); red_printf("add %f%% exclude %f%% exclude2 %f%% __exclude %f%%", (double)(total - worker->exclude_stat.total) / total * 100, (double)(worker->exclude_stat.total) / total * 100, (double)(worker->exclude_stat.total - worker->__exclude_stat.total) / worker->exclude_stat.total * 100, (double)(worker->__exclude_stat.total) / worker->exclude_stat.total * 100); stat_reset(&worker->add_stat); stat_reset(&worker->exclude_stat); stat_reset(&worker->__exclude_stat); } #endif return ret; } static void red_get_area(RedWorker *worker, const Rect *area, uint8_t *dest, int dest_stride, int update) { if (update) { red_update_area(worker, area); } worker->draw_context.read_pixels(worker->draw_context.canvas, dest, dest_stride, area); } static inline int red_handle_self_bitmap(RedWorker *worker, QXLDrawable *drawable) { QXLImage *image; int32_t width; int32_t height; uint8_t *dest; int dest_stride; PHYSICAL *addr; if (!drawable->bitmap_offset) { return TRUE; } width = drawable->bbox.right - drawable->bbox.left; height = drawable->bbox.bottom - drawable->bbox.top; dest_stride = width * sizeof(uint32_t); if (!(image = malloc(sizeof(QXLImage) + height * dest_stride))) { red_printf("alloc failed"); return FALSE; } dest = (uint8_t *)(image + 1); image->descriptor.type = IMAGE_TYPE_BITMAP; image->descriptor.flags = 0; QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique); image->bitmap.flags = QXL_BITMAP_DIRECT | (worker->draw_context.top_down ? QXL_BITMAP_TOP_DOWN : 0); image->bitmap.format = BITMAP_FMT_32BIT; image->bitmap.stride = dest_stride; image->descriptor.width = image->bitmap.x = width; image->descriptor.height = image->bitmap.y = height; image->bitmap.data = (PHYSICAL)(dest - worker->dev_info.phys_delta); image->bitmap.palette = 0; red_get_area(worker, &drawable->bitmap_area, dest, dest_stride, TRUE); addr = (PHYSICAL *)((uint8_t *)drawable + drawable->bitmap_offset); ASSERT(*addr == 0); *addr = (PHYSICAL)((uint8_t *)image - worker->dev_info.phys_delta); return TRUE; } static void free_one_drawable(RedWorker *worker, int force_glz_free) { RingItem *ring_item = ring_get_tail(&worker->current_list); Drawable *drawable; Container *container; ASSERT(ring_item); drawable = CONTAINEROF(ring_item, Drawable, list_link); if (drawable->red_glz_drawable && force_glz_free) { ASSERT(worker->display_channel); red_display_free_glz_drawable(worker->display_channel, drawable->red_glz_drawable); } red_draw_drawable(worker, drawable); container = drawable->tree_item.base.container; current_remove_drawable(worker, drawable); container_cleanup(worker, container); } static Drawable *get_drawable(RedWorker *worker, uint8_t effect, QXLDrawable *qxl_drawable) { Drawable *drawable; struct timespec time; while (!(drawable = alloc_drawable(worker))) { free_one_drawable(worker, FALSE); } memset(drawable, 0, sizeof(Drawable)); drawable->refs = 1; clock_gettime(CLOCK_MONOTONIC, &time); drawable->creation_time = timespec_to_red_time(&time); #ifdef PIPE_DEBUG drawable->tree_item.base.id = ++worker->last_id; #endif ring_item_init(&drawable->list_link); #ifdef UPDATE_AREA_BY_TREE ring_item_init(&drawable->collect_link); #endif ring_item_init(&drawable->tree_item.base.siblings_link); drawable->tree_item.base.type = TREE_ITEM_TYPE_DRAWABLE; region_init(&drawable->tree_item.base.rgn); drawable->tree_item.effect = effect; red_pipe_item_init(&drawable->pipe_item, PIPE_ITEM_TYPE_DRAW); drawable->qxl_drawable = qxl_drawable; return drawable; } static inline void red_process_drawable(RedWorker *worker, QXLDrawable *drawable) { Drawable *item = get_drawable(worker, drawable->effect, drawable); ASSERT(item); region_add(&item->tree_item.base.rgn, &drawable->bbox); #ifdef PIPE_DEBUG printf("TEST: DRAWABLE: id %u type %s effect %u bbox %u %u %u %u\n", item->tree_item.base.id, draw_type_to_str(drawable->type), item->tree_item.effect, drawable->bbox.top, drawable->bbox.left, drawable->bbox.bottom, drawable->bbox.right); #endif if (drawable->clip.type == CLIP_TYPE_RECTS) { QRegion rgn; region_init(&rgn); add_clip_rects(&rgn, drawable->clip.data + OFFSETOF(QXLClipRects, chunk), worker->dev_info.phys_delta); region_and(&item->tree_item.base.rgn, &rgn); region_destroy(&rgn); } else if (drawable->clip.type == CLIP_TYPE_PATH) { item->tree_item.effect = QXL_EFFECT_BLEND; #ifdef PIPE_DEBUG printf("TEST: DRAWABLE: QXL_CLIP_TYPE_PATH\n"); #endif } if (region_is_empty(&item->tree_item.base.rgn)) { release_drawable(worker, item); return; } if (!red_handle_self_bitmap(worker, drawable)) { release_drawable(worker, item); return; } if (red_current_add_qxl(worker, item, drawable)) { worker->drawable_count++; if (item->tree_item.effect != QXL_EFFECT_OPAQUE) { worker->transparent_count++; } red_pipe_add_drawable(worker, item); #ifdef DRAW_ALL red_draw_qxl_drawable(worker, drawable); #endif } release_drawable(worker, item); } static void localize_path(PHYSICAL *in_path, long phys_delta) { QXLPath *path; uint8_t *data; QXLDataChunk *chunk; ASSERT(in_path && *in_path); path = (QXLPath *)(*in_path + phys_delta); data = malloc(sizeof(UINT32) + path->data_size); ASSERT(data); *in_path = (PHYSICAL)data; *(UINT32 *)data = path->data_size; data += sizeof(UINT32); chunk = &path->chunk; do { memcpy(data, chunk->data, chunk->data_size); data += chunk->data_size; chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + phys_delta) : NULL; } while (chunk); } static void unlocalize_path(PHYSICAL *path) { ASSERT(path && *path); free((void *)*path); *path = 0; } static void localize_str(PHYSICAL *in_str, long phys_delta) { QXLString *qxl_str = (QXLString *)(*in_str + phys_delta); QXLDataChunk *chunk; String *str; uint8_t *dest; ASSERT(in_str); str = malloc(sizeof(UINT32) + qxl_str->data_size); ASSERT(str); *in_str = (PHYSICAL)str; str->length = qxl_str->length; str->flags = qxl_str->flags; dest = str->data; chunk = &qxl_str->chunk; for (;;) { memcpy(dest, chunk->data, chunk->data_size); if (!chunk->next_chunk) { return; } dest += chunk->data_size; chunk = (QXLDataChunk *)(chunk->next_chunk + phys_delta); } } static void unlocalize_str(PHYSICAL *str) { ASSERT(str && *str); free((void *)*str); *str = 0; } static void localize_clip(Clip *clip, long phys_delta) { switch (clip->type) { case CLIP_TYPE_NONE: return; case CLIP_TYPE_RECTS: { QXLClipRects *clip_rects; QXLDataChunk *chunk; uint8_t *data; clip_rects = (QXLClipRects *)(clip->data + phys_delta); chunk = &clip_rects->chunk; ASSERT(clip->data); data = malloc(sizeof(UINT32) + clip_rects->num_rects * sizeof(Rect)); ASSERT(data); clip->data = (PHYSICAL)data; *(UINT32 *)(data) = clip_rects->num_rects; data += sizeof(UINT32); do { memcpy(data, chunk->data, chunk->data_size); data += chunk->data_size; chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + phys_delta) : NULL; } while (chunk); break; } case CLIP_TYPE_PATH: localize_path(&clip->data, phys_delta); break; default: red_printf("invalid clip type"); } } static void unlocalize_clip(Clip *clip) { switch (clip->type) { case CLIP_TYPE_NONE: return; case CLIP_TYPE_RECTS: free((void *)clip->data); clip->data = 0; break; case CLIP_TYPE_PATH: unlocalize_path(&clip->data); break; default: red_printf("invalid clip type"); } } static LocalImage *alloc_local_image(RedWorker *worker) { ASSERT(worker->local_images_pos < MAX_BITMAPS); return &worker->local_images[worker->local_images_pos++]; } static ImageCacheItem *image_cache_find(ImageCache *cache, UINT64 id) { ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE]; while (item) { if (item->id == id) { return item; } item = item->next; } return NULL; } static int image_cache_hit(ImageCache *cache, UINT64 id) { ImageCacheItem *item; if (!(item = image_cache_find(cache, id))) { return FALSE; } #ifdef IMAGE_CACHE_AGE item->age = cache->age; #endif ring_remove(&item->lru_link); ring_add(&cache->lru, &item->lru_link); return TRUE; } static void image_cache_remove(ImageCache *cache, ImageCacheItem *item) { ImageCacheItem **now; now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE]; for (;;) { ASSERT(*now); if (*now == item) { *now = item->next; break; } now = &(*now)->next; } ring_remove(&item->lru_link); cairo_surface_destroy(item->surf); free(item); #ifndef IMAGE_CACHE_AGE cache->num_items--; #endif } #define IMAGE_CACHE_MAX_ITEMS 2 static void image_cache_put(void *opaque, uint64_t id, cairo_surface_t *surface) { ImageCache *cache = (ImageCache *)opaque; ImageCacheItem *item; #ifndef IMAGE_CACHE_AGE if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) { ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru); ASSERT(tail); image_cache_remove(cache, tail); } #endif if (!(item = (ImageCacheItem *)malloc(sizeof(ImageCacheItem)))) { red_error("alloc failed"); } item->id = id; #ifdef IMAGE_CACHE_AGE item->age = cache->age; #else cache->num_items++; #endif item->surf = cairo_surface_reference(surface); ring_item_init(&item->lru_link); item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE]; cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item; ring_add(&cache->lru, &item->lru_link); } static cairo_surface_t *image_cache_get(void *opaque, uint64_t id) { ImageCache *cache = (ImageCache *)opaque; ImageCacheItem *item = image_cache_find(cache, id); if (!item) { red_error("not found"); } return cairo_surface_reference(item->surf); } static void image_cache_init(ImageCache *cache) { memset(cache->hash_table, 0, sizeof(cache->hash_table)); ring_init(&cache->lru); #ifdef IMAGE_CACHE_AGE cache->age = 0; #else cache->num_items = 0; #endif } static void image_cache_reset(ImageCache *cache) { ImageCacheItem *item; while ((item = (ImageCacheItem *)ring_get_head(&cache->lru))) { image_cache_remove(cache, item); } #ifdef IMAGE_CACHE_AGE cache->age = 0; #endif } #define IMAGE_CACHE_DEPTH 4 static void image_cache_eaging(ImageCache *cache) { #ifdef IMAGE_CACHE_AGE ImageCacheItem *item; cache->age++; while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) && cache->age - item->age > IMAGE_CACHE_DEPTH) { image_cache_remove(cache, item); } #endif } static void localize_bitmap(RedWorker *worker, PHYSICAL *in_bitmap, long phys_delta) { QXLImage *image; QXLImage *local_image; ASSERT(in_bitmap && *in_bitmap); image = (QXLImage *)(*in_bitmap + phys_delta); local_image = (QXLImage *)alloc_local_image(worker); *local_image = *image; *in_bitmap = (PHYSICAL)local_image; local_image->descriptor.flags = 0; if (image_cache_hit(&worker->image_cache, local_image->descriptor.id)) { local_image->descriptor.type = IMAGE_TYPE_FROM_CACHE; return; } switch (local_image->descriptor.type) { case IMAGE_TYPE_QUIC: { QXLDataChunk **chanks_head; #ifdef IMAGE_CACHE_AGE local_image->descriptor.flags |= IMAGE_CACHE_ME; #else if (local_image->descriptor.width * local_image->descriptor.height >= 640 * 480) { local_image->descriptor.flags |= IMAGE_CACHE_ME; } #endif chanks_head = (QXLDataChunk **)local_image->quic.data; *chanks_head = (QXLDataChunk *)image->quic.data; break; } case IMAGE_TYPE_BITMAP: if (image->bitmap.flags & QXL_BITMAP_DIRECT) { local_image->bitmap.data = (PHYSICAL)(image->bitmap.data + phys_delta); } else { PHYSICAL src_data; int size = image->bitmap.y * image->bitmap.stride; uint8_t *data = malloc(size); ASSERT(data); local_image->bitmap.data = (PHYSICAL)data; src_data = image->bitmap.data; while (size) { QXLDataChunk *chunk; int cp_size; ASSERT(src_data); chunk = (QXLDataChunk *)(src_data + phys_delta); cp_size = MIN(chunk->data_size, size); memcpy(data, chunk->data, cp_size); data += cp_size; size -= cp_size; src_data = chunk->next_chunk; } } if (local_image->bitmap.palette) { local_image->bitmap.palette = (local_image->bitmap.palette + phys_delta); } break; default: red_error("invalid image type"); } } static void unlocalize_bitmap(PHYSICAL *bitmap) { QXLImage *image; ASSERT(bitmap && *bitmap); image = (QXLImage *)*bitmap; *bitmap = 0; switch (image->descriptor.type) { case IMAGE_TYPE_BITMAP: if (!(image->bitmap.flags & QXL_BITMAP_DIRECT)) { free((void *)image->bitmap.data); } break; case IMAGE_TYPE_QUIC: case IMAGE_TYPE_FROM_CACHE: *bitmap = 0; break; default: red_error("invalid image type %u", image->descriptor.type); } } static void localize_brush(RedWorker *worker, Brush *brush, long phys_delta) { if (brush->type == BRUSH_TYPE_PATTERN) { localize_bitmap(worker, &brush->u.pattern.pat, phys_delta); } } static void unlocalize_brush(Brush *brush) { if (brush->type == BRUSH_TYPE_PATTERN) { unlocalize_bitmap(&brush->u.pattern.pat); } } static void localize_mask(RedWorker *worker, QMask *mask, long phys_delta) { if (mask->bitmap) { localize_bitmap(worker, &mask->bitmap, phys_delta); } } static void unlocalize_mask(QMask *mask) { if (mask->bitmap) { unlocalize_bitmap(&mask->bitmap); } } static void localize_attr(LineAttr *attr, long phys_delta) { if (attr->style_nseg) { uint8_t *buf; uint8_t *data; ASSERT(attr->style); buf = (uint8_t *)(attr->style + phys_delta); data = malloc(attr->style_nseg * sizeof(uint32_t)); ASSERT(data); memcpy(data, buf, attr->style_nseg * sizeof(uint32_t)); attr->style = (PHYSICAL)data; } } static void unlocalize_attr(LineAttr *attr) { if (attr->style_nseg) { free((void *)attr->style); attr->style = 0; } } static void red_draw_qxl_drawable(RedWorker *worker, QXLDrawable *drawable) { Clip clip = drawable->clip; worker->local_images_pos = 0; image_cache_eaging(&worker->image_cache); worker->draw_context.set_access_params(worker->draw_context.canvas, worker->dev_info.phys_delta); // todo: use delta // instead of // localize_x localize_clip(&clip, worker->dev_info.phys_delta); switch (drawable->type) { case QXL_DRAW_FILL: { Fill fill = drawable->u.fill; localize_brush(worker, &fill.brush, worker->dev_info.phys_delta); localize_mask(worker, &fill.mask, worker->dev_info.phys_delta); worker->draw_context.draw_fill(worker->draw_context.canvas, &drawable->bbox, &clip, &fill); unlocalize_mask(&fill.mask); unlocalize_brush(&fill.brush); break; } case QXL_DRAW_OPAQUE: { Opaque opaque = drawable->u.opaque; localize_brush(worker, &opaque.brush, worker->dev_info.phys_delta); localize_bitmap(worker, &opaque.src_bitmap, worker->dev_info.phys_delta); localize_mask(worker, &opaque.mask, worker->dev_info.phys_delta); worker->draw_context.draw_opaque(worker->draw_context.canvas, &drawable->bbox, &clip, &opaque); unlocalize_mask(&opaque.mask); unlocalize_bitmap(&opaque.src_bitmap); unlocalize_brush(&opaque.brush); break; } case QXL_DRAW_COPY: { Copy copy = drawable->u.copy; localize_bitmap(worker, ©.src_bitmap, worker->dev_info.phys_delta); localize_mask(worker, ©.mask, worker->dev_info.phys_delta); worker->draw_context.draw_copy(worker->draw_context.canvas, &drawable->bbox, &clip, ©); unlocalize_mask(©.mask); unlocalize_bitmap(©.src_bitmap); break; } case QXL_DRAW_TRANSPARENT: { Transparent transparent = drawable->u.transparent; localize_bitmap(worker, &transparent.src_bitmap, worker->dev_info.phys_delta); worker->draw_context.draw_transparent(worker->draw_context.canvas, &drawable->bbox, &clip, &transparent); unlocalize_bitmap(&transparent.src_bitmap); break; } case QXL_DRAW_ALPHA_BLEND: { AlphaBlnd alpha_blend = drawable->u.alpha_blend; localize_bitmap(worker, &alpha_blend.src_bitmap, worker->dev_info.phys_delta); worker->draw_context.draw_alpha_blend(worker->draw_context.canvas, &drawable->bbox, &clip, &alpha_blend); unlocalize_bitmap(&alpha_blend.src_bitmap); break; } case QXL_COPY_BITS: { worker->draw_context.copy_bits(worker->draw_context.canvas, &drawable->bbox, &clip, &drawable->u.copy_bits.src_pos); break; } case QXL_DRAW_BLEND: { Blend blend = drawable->u.blend; localize_bitmap(worker, &blend.src_bitmap, worker->dev_info.phys_delta); localize_mask(worker, &blend.mask, worker->dev_info.phys_delta); worker->draw_context.draw_blend(worker->draw_context.canvas, &drawable->bbox, &clip, &blend); unlocalize_mask(&blend.mask); unlocalize_bitmap(&blend.src_bitmap); break; } case QXL_DRAW_BLACKNESS: { Blackness blackness = drawable->u.blackness; localize_mask(worker, &blackness.mask, worker->dev_info.phys_delta); worker->draw_context.draw_blackness(worker->draw_context.canvas, &drawable->bbox, &clip, &blackness); unlocalize_mask(&blackness.mask); break; } case QXL_DRAW_WHITENESS: { Whiteness whiteness = drawable->u.whiteness; localize_mask(worker, &whiteness.mask, worker->dev_info.phys_delta); worker->draw_context.draw_whiteness(worker->draw_context.canvas, &drawable->bbox, &clip, &whiteness); unlocalize_mask(&whiteness.mask); break; } case QXL_DRAW_INVERS: { Invers invers = drawable->u.invers; localize_mask(worker, &invers.mask, worker->dev_info.phys_delta); worker->draw_context.draw_invers(worker->draw_context.canvas, &drawable->bbox, &clip, &invers); unlocalize_mask(&invers.mask); break; } case QXL_DRAW_ROP3: { Rop3 rop3 = drawable->u.rop3; localize_brush(worker, &rop3.brush, worker->dev_info.phys_delta); localize_bitmap(worker, &rop3.src_bitmap, worker->dev_info.phys_delta); localize_mask(worker, &rop3.mask, worker->dev_info.phys_delta); worker->draw_context.draw_rop3(worker->draw_context.canvas, &drawable->bbox, &clip, &rop3); unlocalize_mask(&rop3.mask); unlocalize_bitmap(&rop3.src_bitmap); unlocalize_brush(&rop3.brush); break; } case QXL_DRAW_STROKE: { Stroke stroke = drawable->u.stroke; localize_brush(worker, &stroke.brush, worker->dev_info.phys_delta); localize_path(&stroke.path, worker->dev_info.phys_delta); localize_attr(&stroke.attr, worker->dev_info.phys_delta); worker->draw_context.draw_stroke(worker->draw_context.canvas, &drawable->bbox, &clip, &stroke); unlocalize_attr(&stroke.attr); unlocalize_path(&stroke.path); unlocalize_brush(&stroke.brush); break; } case QXL_DRAW_TEXT: { Text text = drawable->u.text; localize_brush(worker, &text.fore_brush, worker->dev_info.phys_delta); localize_brush(worker, &text.back_brush, worker->dev_info.phys_delta); localize_str(&text.str, worker->dev_info.phys_delta); worker->draw_context.draw_text(worker->draw_context.canvas, &drawable->bbox, &clip, &text); unlocalize_str(&text.str); unlocalize_brush(&text.back_brush); unlocalize_brush(&text.fore_brush); break; } default: red_printf("invlaid type"); } unlocalize_clip(&clip); } #ifndef DRAW_ALL static void red_draw_drawable(RedWorker *worker, Drawable *drawable) { #ifdef UPDATE_AREA_BY_TREE //todo: add need top mask flag worker->draw_context.set_top_mask(worker->draw_context.canvas, drawable->tree_item.base.rgn.num_rects, drawable->tree_item.base.rgn.rects); #endif red_draw_qxl_drawable(worker, drawable->qxl_drawable); #ifdef UPDATE_AREA_BY_TREE worker->draw_context.clear_top_mask(worker->draw_context.canvas); #endif } #ifdef UPDATE_AREA_BY_TREE static inline void __red_collect_for_update(RedWorker *worker, Ring *ring, RingItem *ring_item, QRegion *rgn, Ring *items) { Ring *top_ring = ring; for (;;) { TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link); Container *container = now->container; if (region_intersects(rgn, &now->rgn)) { if (IS_DRAW_ITEM(now)) { Drawable *drawable = CONTAINEROF(now, Drawable, tree_item); ring_add(items, &drawable->collect_link); region_or(rgn, &now->rgn); if (drawable->tree_item.shadow) { region_or(rgn, &drawable->tree_item.shadow->base.rgn); } } else if (now->type == TREE_ITEM_TYPE_SHADOW) { Drawable *owner = CONTAINEROF(((Shadow *)now)->owner, Drawable, tree_item); if (!ring_item_is_linked(&owner->collect_link)) { region_or(rgn, &now->rgn); region_or(rgn, &owner->tree_item.base.rgn); ring_add(items, &owner->collect_link); } } else if (now->type == TREE_ITEM_TYPE_CONTAINER) { Container *container = (Container *)now; if ((ring_item = ring_get_head(&container->items))) { ring = &container->items; ASSERT(((TreeItem *)ring_item)->container); continue; } ring_item = &now->siblings_link; } } while (!(ring_item = ring_next(ring, ring_item))) { if (ring == top_ring) { return; } ring_item = &container->base.siblings_link; container = container->base.container; ring = (container) ? &container->items : top_ring; } } } static void red_update_area(RedWorker *worker, const Rect *area) { Ring *ring = &worker->current; RingItem *ring_item; Ring items; QRegion rgn; if (!(ring_item = ring_get_head(ring))) { worker->draw_context.validate_area(worker->draw_context.canvas, &worker->dev_info.draw_area, area); return; } region_init(&rgn); region_add(&rgn, area); ring_init(&items); __red_collect_for_update(worker, ring, ring_item, &rgn, &items); region_destroy(&rgn); while ((ring_item = ring_get_head(&items))) { Drawable *drawable = CONTAINEROF(ring_item, Drawable, collect_link); Container *container; ring_remove(ring_item); red_draw_drawable(worker, drawable); container = drawable->tree_item.base.container; current_remove_drawable(worker, drawable); container_cleanup(worker, container); } worker->draw_context.validate_area(worker->draw_context.canvas, &worker->dev_info.draw_area, area); } #else static void red_update_area(RedWorker *worker, const Rect *area) { Ring *ring = &worker->current_list; RingItem *ring_item = ring; QRegion rgn; Drawable *last = NULL; Drawable *now; region_init(&rgn); region_add(&rgn, area); while ((ring_item = ring_next(ring, ring_item))) { now = CONTAINEROF(ring_item, Drawable, list_link); if (region_intersects(&rgn, &now->tree_item.base.rgn)) { last = now; break; } } region_destroy(&rgn); if (!last) { worker->draw_context.validate_area(worker->draw_context.canvas, &worker->dev_info.draw_area, area); return; } do { Container *container; ring_item = ring_get_tail(&worker->current_list); now = CONTAINEROF(ring_item, Drawable, list_link); red_draw_drawable(worker, now); container = now->tree_item.base.container; current_remove_drawable(worker, now); container_cleanup(worker, container); } while (now != last); worker->draw_context.validate_area(worker->draw_context.canvas, &worker->dev_info.draw_area, area); } #endif #endif static void red_release_cursor(RedWorker *worker, CursorItem *cursor) { if (!--cursor->refs) { QXLCursorCmd *cursor_cmd; if (cursor->type == CURSOR_TYPE_LOCAL) { free(cursor); return; } cursor_cmd = CONTAINEROF(cursor, QXLCursorCmd, device_data); worker->qxl->release_resource(worker->qxl, &cursor_cmd->release_info); } } static void red_set_cursor(RedWorker *worker, CursorItem *cursor) { if (worker->cursor) { red_release_cursor(worker, worker->cursor); } ++cursor->refs; worker->cursor = cursor; } void qxl_process_cursor(RedWorker *worker, QXLCursorCmd *cursor_cmd) { CursorItem *item = (CursorItem *)cursor_cmd->device_data; int cursor_show = FALSE; red_pipe_item_init(&item->pipe_data, PIPE_ITEM_TYPE_CURSOR); item->refs = 1; item->type = CURSOR_TYPE_INVALID; switch (cursor_cmd->type) { case QXL_CURSOR_SET: worker->cursor_visible = cursor_cmd->u.set.visible; item->type = CURSOR_TYPE_DEV; red_set_cursor(worker, item); break; case QXL_CURSOR_MOVE: cursor_show = !worker->cursor_visible; worker->cursor_visible = TRUE; worker->cursor_position = cursor_cmd->u.position; break; case QXL_CURSOR_HIDE: worker->cursor_visible = FALSE; break; case QXL_CURSOR_TRAIL: worker->cursor_trail_length = cursor_cmd->u.trail.length; worker->cursor_trail_frequency = cursor_cmd->u.trail.frequency; break; default: red_error("invalid cursor command %u", cursor_cmd->type); } if (worker->cursor_channel && (worker->mouse_mode == RED_MOUSE_MODE_SERVER || cursor_cmd->type != QXL_CURSOR_MOVE || cursor_show)) { red_pipe_add(&worker->cursor_channel->base, &item->pipe_data); } else { red_release_cursor(worker, item); } } static inline uint64_t red_now() { struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); return time.tv_sec * 1000000000 + time.tv_nsec; } static int red_process_cursor(RedWorker *worker, uint32_t max_pipe_size) { QXLCommand cmd; int n = 0; while (!worker->cursor_channel || worker->cursor_channel->base.pipe_size <= max_pipe_size) { if (!worker->qxl->get_cursor_command(worker->qxl, &cmd)) { if (worker->repoll_cursor_ring < CMD_RING_POLL_RETRIES) { worker->repoll_cursor_ring++; worker->epoll_timeout = MIN(worker->epoll_timeout, CMD_RING_POLL_TIMEOUT); break; } if (worker->repoll_cursor_ring > CMD_RING_POLL_RETRIES || worker->qxl->req_cursor_notification(worker->qxl)) { worker->repoll_cursor_ring++; break; } continue; } worker->repoll_cursor_ring = 0; switch (cmd.type) { case QXL_CMD_CURSOR: { QXLCursorCmd *cursor_cmd = (QXLCursorCmd *)(cmd.data + worker->dev_info.phys_delta); ASSERT((uint64_t)cursor_cmd >= worker->dev_info.phys_start); ASSERT((uint64_t)cursor_cmd + sizeof(QXLCursorCmd) <= worker->dev_info.phys_end); qxl_process_cursor(worker, cursor_cmd); break; } default: red_error("bad command type"); } n++; } return n; } static int red_process_commands(RedWorker *worker, uint32_t max_pipe_size) { QXLCommand cmd; int n = 0; uint64_t start = red_now(); while (!worker->display_channel || worker->display_channel->base.pipe_size <= max_pipe_size) { if (!worker->qxl->get_command(worker->qxl, &cmd)) { if (worker->repoll_cmd_ring < CMD_RING_POLL_RETRIES) { worker->repoll_cmd_ring++; worker->epoll_timeout = MIN(worker->epoll_timeout, CMD_RING_POLL_TIMEOUT); break; } if (worker->repoll_cmd_ring > CMD_RING_POLL_RETRIES || worker->qxl->req_cmd_notification(worker->qxl)) { worker->repoll_cmd_ring++; break; } continue; } stat_inc_counter(worker->command_counter, 1); worker->repoll_cmd_ring = 0; switch (cmd.type) { case QXL_CMD_DRAW: { QXLDrawable *drawable = (QXLDrawable *)(cmd.data + worker->dev_info.phys_delta); ASSERT((uint64_t)drawable >= worker->dev_info.phys_start); ASSERT((uint64_t)drawable + sizeof(QXLDrawable) <= worker->dev_info.phys_end); red_process_drawable(worker, drawable); break; } case QXL_CMD_UPDATE: { QXLUpdateCmd *draw_cmd = (QXLUpdateCmd *)(cmd.data + worker->dev_info.phys_delta); ASSERT((uint64_t)draw_cmd >= worker->dev_info.phys_start); ASSERT((uint64_t)draw_cmd + sizeof(QXLUpdateCmd) <= worker->dev_info.phys_end); red_update_area(worker, &draw_cmd->area); worker->qxl->notify_update(worker->qxl, draw_cmd->update_id); worker->qxl->release_resource(worker->qxl, &draw_cmd->release_info); break; } case QXL_CMD_CURSOR: { QXLCursorCmd *cursor_cmd = (QXLCursorCmd *)(cmd.data + worker->dev_info.phys_delta); ASSERT((uint64_t)cursor_cmd >= worker->dev_info.phys_start); ASSERT((uint64_t)cursor_cmd + sizeof(QXLCursorCmd) <= worker->dev_info.phys_end); qxl_process_cursor(worker, cursor_cmd); break; } case QXL_CMD_MESSAGE: { QXLMessage *message = (QXLMessage *)(cmd.data + worker->dev_info.phys_delta); ASSERT((uint64_t)message >= worker->dev_info.phys_start); ASSERT((uint64_t)message + sizeof(QXLDrawable) <= worker->dev_info.phys_end); red_printf("MESSAGE: %s", message->data); worker->qxl->release_resource(worker->qxl, &message->release_info); break; } default: red_error("bad command type"); } n++; if ((worker->display_channel && worker->display_channel->base.send_data.blocked) || red_now() - start > 10 * 1000 * 1000) { worker->epoll_timeout = 0; return n; } } return n; } #define RED_RELEASE_BUNCH_SIZE 5 static void red_free_some(RedWorker *worker) { int n = 0; if (worker->display_channel && worker->display_channel->glz_dict) { // encoding using the dictionary is prevented since the following operations might // change the dictionary pthread_rwlock_wrlock(&worker->display_channel->glz_dict->encode_lock); n = red_display_free_some_independent_glz_drawables(worker->display_channel); } while (!ring_is_empty(&worker->current_list) && n++ < RED_RELEASE_BUNCH_SIZE) { free_one_drawable(worker, TRUE); } if (worker->display_channel && worker->display_channel->glz_dict) { pthread_rwlock_unlock(&worker->display_channel->glz_dict->encode_lock); } } static void red_current_flush(RedWorker *worker) { while (!ring_is_empty(&worker->current_list)) { free_one_drawable(worker, FALSE); } red_current_clear(worker); } static void red_add_screen_image(RedWorker *worker) { ImageItem *item; int stride; Rect area; if (!worker->display_channel) { return; } stride = worker->dev_info.x_res << 2; if (!(item = (ImageItem *)malloc(sizeof(ImageItem) + worker->dev_info.y_res * stride))) { //warn return; } red_pipe_item_init(&item->link, PIPE_ITEM_TYPE_IMAGE); item->refs = 1; item->pos.x = item->pos.y = 0; item->width = worker->dev_info.x_res; item->height = worker->dev_info.y_res; item->stride = stride; item->top_down = worker->draw_context.top_down; area.top = area.left = 0; area.right = worker->dev_info.x_res; area.bottom = worker->dev_info.y_res; worker->draw_context.read_pixels(worker->draw_context.canvas, item->data, stride, &area); red_pipe_add_image_item(worker, item); release_image_item(item); display_channel_push(worker); } static void inline __add_buf(RedChannel *channel, uint32_t type, void *data, uint32_t size) { int pos = channel->send_data.n_bufs++; ASSERT(pos < MAX_SEND_BUFS); channel->send_data.bufs[pos].type = type; channel->send_data.bufs[pos].size = size; channel->send_data.bufs[pos].data = data; } static void add_buf(RedChannel *channel, uint32_t type, void *data, uint32_t size) { __add_buf(channel, type, data, size); channel->send_data.header.size += size; } static void fill_path(DisplayChannel *display_channel, PHYSICAL *in_path) { RedChannel *channel = &display_channel->base; ASSERT(in_path && *in_path); QXLPath *path = (QXLPath *)(*in_path + channel->worker->dev_info.phys_delta); *in_path = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, &path->data_size, sizeof(UINT32)); add_buf(channel, BUF_TYPE_CHUNK, &path->chunk, path->data_size); } static void fill_str(DisplayChannel *display_channel, PHYSICAL *in_str) { RedChannel *channel = &display_channel->base; ASSERT(in_str && *in_str); QXLString *str = (QXLString *)(*in_str + channel->worker->dev_info.phys_delta); *in_str = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, &str->length, sizeof(UINT32)); add_buf(channel, BUF_TYPE_CHUNK, &str->chunk, str->data_size); } static inline void fill_rects_clip(RedChannel *channel, PHYSICAL *in_clip) { QXLClipRects *clip; ASSERT(in_clip && *in_clip); clip = (QXLClipRects *)(*in_clip + channel->worker->dev_info.phys_delta); *in_clip = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, &clip->num_rects, sizeof(UINT32)); add_buf(channel, BUF_TYPE_CHUNK, &clip->chunk, clip->num_rects * sizeof(Rect)); } static void fill_base(DisplayChannel *display_channel, RedDrawBase *base, QXLDrawable *drawable, uint32_t size) { RedChannel *channel = &display_channel->base; add_buf(channel, BUF_TYPE_RAW, base, size); base->box = drawable->bbox; base->clip = drawable->clip; if (base->clip.type == CLIP_TYPE_RECTS) { fill_rects_clip(channel, &base->clip.data); } else if (base->clip.type == CLIP_TYPE_PATH) { fill_path(display_channel, &base->clip.data); } } static inline RedImage *alloc_image(DisplayChannel *display_channel) { ASSERT(display_channel->send_data.bitmap_pos < MAX_BITMAPS); return &display_channel->send_data.images[display_channel->send_data.bitmap_pos++]; } /* io_palette is relative address of the palette*/ static inline void fill_palette(DisplayChannel *display_channel, ADDRESS *io_palette, UINT8 *flags) { RedChannel *channel = &display_channel->base; Palette *palette; if (!(*io_palette)) { return; } palette = (Palette *)(*io_palette + channel->worker->dev_info.phys_delta); if (palette->unique) { if (red_palette_cache_find(display_channel, palette->unique)) { *flags |= BITMAP_PAL_FROM_CACHE; *io_palette = palette->unique; return; } if (red_palette_cache_add(display_channel, palette->unique, 1)) { *flags |= BITMAP_PAL_CACHE_ME; } } *io_palette = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, palette, sizeof(Palette) + palette->num_ents * sizeof(UINT32)); } static inline RedCompressBuf *red_display_alloc_compress_buf(DisplayChannel *display_channel) { RedCompressBuf *ret; if (display_channel->send_data.free_compress_bufs) { ret = display_channel->send_data.free_compress_bufs; display_channel->send_data.free_compress_bufs = ret->next; } else { if (!(ret = malloc(sizeof(*ret)))) { return NULL; } } ret->next = display_channel->send_data.used_compress_bufs; display_channel->send_data.used_compress_bufs = ret; return ret; } static inline void __red_display_free_compress_buf(DisplayChannel *display_channel, RedCompressBuf *buf) { buf->next = display_channel->send_data.free_compress_bufs; display_channel->send_data.free_compress_bufs = buf; } static void red_display_free_compress_buf(DisplayChannel *display_channel, RedCompressBuf *buf) { RedCompressBuf **curr_used = &display_channel->send_data.used_compress_bufs; for (;;) { ASSERT(*curr_used); if (*curr_used == buf) { *curr_used = buf->next; break; } curr_used = &(*curr_used)->next; } __red_display_free_compress_buf(display_channel, buf); } static void red_display_reset_compress_buf(DisplayChannel *display_channel) { while (display_channel->send_data.used_compress_bufs) { RedCompressBuf *buf = display_channel->send_data.used_compress_bufs; display_channel->send_data.used_compress_bufs = buf->next; __red_display_free_compress_buf(display_channel, buf); } } /****************************************************** * Global lz red drawables routines *******************************************************/ /* if already exists, returns it. Otherwise allocates and adds it (1) to the ring tail in the channel (2) to the Drawable*/ static RedGlzDrawable *red_display_get_glz_drawable(DisplayChannel *channel, Drawable *drawable) { RedGlzDrawable *ret; if (drawable->red_glz_drawable) { return drawable->red_glz_drawable; } if (!(ret = malloc(sizeof(*ret)))) { PANIC("malloc failed"); } ret->display_channel = channel; ret->qxl_drawable = drawable->qxl_drawable; ret->drawable = drawable; ret->instances_count = 0; ring_init(&ret->instances); ring_item_init(&ret->link); ring_add_before(&ret->link, &channel->glz_drawables); drawable->red_glz_drawable = ret; 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 *red_display_add_glz_drawable_instance(RedGlzDrawable *glz_drawable) { ASSERT(glz_drawable->instances_count < MAX_GLZ_DRAWABLE_INSTANCES); // NOTE: We assume the addtions 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->glz_instance = NULL; ret->red_glz_drawable = glz_drawable; return ret; } /* Remove from the to_free list and the instances_list. When no instance is left - the RedGlzDrawable is released too. (and the qxl drawblae 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 dicitonary*/ static void red_display_free_glz_drawable_instance(DisplayChannel *channel, GlzDrawableInstanceItem *glz_drawable_instance) { RedGlzDrawable *glz_drawable; ASSERT(glz_drawable_instance); ASSERT(glz_drawable_instance->red_glz_drawable); glz_drawable = glz_drawable_instance->red_glz_drawable; ASSERT(glz_drawable->display_channel == channel); ASSERT(glz_drawable->instances_count); ring_remove(&glz_drawable_instance->glz_link); glz_drawable->instances_count--; // whan 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(&glz_drawable_instance->free_link)) { ring_remove(&glz_drawable_instance->free_link); } if (ring_is_empty(&glz_drawable->instances)) { ASSERT(!glz_drawable->instances_count); Drawable *drawable = glz_drawable->drawable; if (drawable) { drawable->red_glz_drawable = NULL; } else { // no reference to the qxl drawable left free_qxl_drawable(channel->base.worker, glz_drawable->qxl_drawable); } if (ring_item_is_linked(&glz_drawable->link)) { ring_remove(&glz_drawable->link); } free(glz_drawable); } } static void red_display_handle_glz_drawables_to_free(DisplayChannel* channel) { RingItem *ring_link; pthread_mutex_lock(&channel->glz_drawables_inst_to_free_lock); while ((ring_link = ring_get_head(&channel->glz_drawables_inst_to_free))) { GlzDrawableInstanceItem *drawable_instance = CONTAINEROF(ring_link, GlzDrawableInstanceItem, free_link); red_display_free_glz_drawable_instance(channel, drawable_instance); } pthread_mutex_unlock(&channel->glz_drawables_inst_to_free_lock); } /* releases all the instances of the drawable from the dictionary and the display channel. 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 dicitonary during this operation*/ static void red_display_free_glz_drawable(DisplayChannel *channel, RedGlzDrawable *drawable) { RingItem *head_instance = ring_get_head(&drawable->instances); int cont = (head_instance != NULL); while (cont) { if (drawable->instances_count == 1) { /* Last instance: red_display_free_glz_drawable_instance will free the drawable */ cont = FALSE; } GlzDrawableInstanceItem *instance = 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(channel->glz_dict->dict, instance->glz_instance, &channel->glz_data.usr); } red_display_free_glz_drawable_instance(channel, instance); if (cont) { head_instance = ring_get_head(&drawable->instances); } } } /* Clear all lz drawables - enforce their removal from the global dictionary. NOTE - prevents encoding using the dicitonary during the operation*/ static void red_display_clear_glz_drawables(DisplayChannel *channel) { RingItem *ring_link; if (!channel || !channel->glz_dict) { return; } // assure no display channel is during global lz encoding pthread_rwlock_wrlock(&channel->glz_dict->encode_lock); while ((ring_link = ring_get_head(&channel->glz_drawables))) { RedGlzDrawable *drawable = 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_display_free_glz_drawable(channel, drawable); } pthread_rwlock_unlock(&channel->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 dicitonary during the operation*/ static int red_display_free_some_independent_glz_drawables(DisplayChannel *channel) { int n = 0; if (!channel) { return 0; } RingItem *ring_link = ring_get_head(&channel->glz_drawables); while ((n < RED_RELEASE_BUNCH_SIZE) && (ring_link != NULL)) { RedGlzDrawable *glz_drawable = CONTAINEROF(ring_link, RedGlzDrawable, link); ring_link = ring_next(&channel->glz_drawables, ring_link); if (!glz_drawable->drawable) { red_display_free_glz_drawable(channel, glz_drawable); n++; } } return n; } /****************************************************** * Encoders callbacks *******************************************************/ static 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); red_printf("%s", usr_data->message_buf); longjmp(usr_data->jmp_env, 1); } static 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); red_printf("%s", usr_data->message_buf); longjmp(usr_data->jmp_env, 1); } static 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); PANIC(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 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); red_printf("%s", usr_data->message_buf); } static 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); red_printf("%s", usr_data->message_buf); } static 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); red_printf("%s", usr_data->message_buf); } static void *quic_usr_malloc(QuicUsrContext *usr, int size) { return malloc(size); } static void *lz_usr_malloc(LzUsrContext *usr, int size) { return malloc(size); } static void *glz_usr_malloc(GlzEncoderUsrContext *usr, int size) { return 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 inline int encoder_usr_more_space(EncoderData *enc_data, uint32_t **io_ptr) { RedCompressBuf *buf; if (!(buf = red_display_alloc_compress_buf(enc_data->display_channel))) { return 0; } enc_data->bufs_tail->send_next = buf; enc_data->bufs_tail = buf; buf->send_next = NULL; *io_ptr = buf->buf; return sizeof(buf->buf) >> 2; } 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, io_ptr); } 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, (uint32_t **)io_ptr) << 2); } 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, (uint32_t **)io_ptr) << 2); } static inline int encoder_usr_more_lines(EncoderData *enc_data, uint8_t **lines) { if (!enc_data->u.lines_data.next) { return 0; } QXLDataChunk *chunk = (QXLDataChunk *)(enc_data->u.lines_data.next + enc_data->u.lines_data.address_delta); if (chunk->data_size % enc_data->u.lines_data.stride) { return 0; } enc_data->u.lines_data.next = chunk->next_chunk; *lines = chunk->data; return chunk->data_size / 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 quic_usr_more_lines_revers(QuicUsrContext *usr, uint8_t **lines) { EncoderData *quic_data = &(((QuicData *)usr)->data); if (!quic_data->u.lines_data.next) { return 0; } QXLDataChunk *chunk = (QXLDataChunk *)(quic_data->u.lines_data.next + quic_data->u.lines_data.address_delta); if (chunk->data_size % quic_data->u.lines_data.stride) { return 0; } quic_data->u.lines_data.next = chunk->prev_chunk; *lines = chunk->data + chunk->data_size - quic_data->u.lines_data.stride; return chunk->data_size / quic_data->u.lines_data.stride; } static int quic_usr_more_lines_unstable(QuicUsrContext *usr, uint8_t **out_lines) { EncoderData *quic_data = &(((QuicData *)usr)->data); if (!quic_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 *out = (uint8_t *)quic_data->u.unstable_lines_data.input_bufs[ quic_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); } *out_lines = out; return lines; } static int quic_usr_no_more_lines(QuicUsrContext *usr, uint8_t **lines) { return 0; } static int lz_usr_no_more_lines(LzUsrContext *usr, uint8_t **lines) { return 0; } static int glz_usr_no_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines) { return 0; } static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image) { GlzData *lz_data = (GlzData *)usr; GlzDrawableInstanceItem *glz_drawable_instance = (GlzDrawableInstanceItem *)image; DisplayChannel *drawable_channel = glz_drawable_instance->red_glz_drawable->display_channel; DisplayChannel *this_channel = CONTAINEROF(lz_data, DisplayChannel, glz_data); if (this_channel == drawable_channel) { red_display_free_glz_drawable_instance(drawable_channel, glz_drawable_instance); } else { pthread_mutex_lock(&drawable_channel->glz_drawables_inst_to_free_lock); ring_add_before(&glz_drawable_instance->free_link, &drawable_channel->glz_drawables_inst_to_free); pthread_mutex_unlock(&drawable_channel->glz_drawables_inst_to_free_lock); } } static inline void red_init_quic(RedWorker *worker) { worker->quic_data.usr.error = quic_usr_error; worker->quic_data.usr.warn = quic_usr_warn; worker->quic_data.usr.info = quic_usr_warn; worker->quic_data.usr.malloc = quic_usr_malloc; worker->quic_data.usr.free = quic_usr_free; worker->quic_data.usr.more_space = quic_usr_more_space; worker->quic_data.usr.more_lines = quic_usr_more_lines; worker->quic = quic_create(&worker->quic_data.usr); if (!worker->quic) { PANIC("create quic failed"); } } static inline void red_init_lz(RedWorker *worker) { worker->lz_data.usr.error = lz_usr_error; worker->lz_data.usr.warn = lz_usr_warn; worker->lz_data.usr.info = lz_usr_warn; worker->lz_data.usr.malloc = lz_usr_malloc; worker->lz_data.usr.free = lz_usr_free; worker->lz_data.usr.more_space = lz_usr_more_space; worker->lz_data.usr.more_lines = lz_usr_more_lines; worker->lz = lz_create(&worker->lz_data.usr); if (!worker->lz) { PANIC("create lz failed"); } } static inline void red_display_init_glz_data(DisplayChannel *display) { display->glz_data.usr.error = glz_usr_error; display->glz_data.usr.warn = glz_usr_warn; display->glz_data.usr.info = glz_usr_warn; display->glz_data.usr.malloc = glz_usr_malloc; display->glz_data.usr.free = glz_usr_free; display->glz_data.usr.more_space = glz_usr_more_space; display->glz_data.usr.more_lines = glz_usr_more_lines; display->glz_data.usr.free_image = glz_usr_free_image; } #ifdef __GNUC__ #define ATTR_PACKED __attribute__ ((__packed__)) #else #define ATTR_PACKED #pragma pack(push) #pragma pack(1) #endif typedef struct ATTR_PACKED rgb32_pixel_t { uint8_t b; uint8_t g; uint8_t r; uint8_t pad; } rgb32_pixel_t; typedef struct ATTR_PACKED rgb24_pixel_t { uint8_t b; uint8_t g; uint8_t r; } rgb24_pixel_t; typedef uint16_t rgb16_pixel_t; #ifndef __GNUC__ #pragma pack(pop) #endif #undef ATTR_PACKED #define RED_BITMAP_UTILS_RGB16 #include "red_bitmap_utils.h" #define RED_BITMAP_UTILS_RGB24 #include "red_bitmap_utils.h" #define RED_BITMAP_UTILS_RGB32 #include "red_bitmap_utils.h" #define GRADUAL_SCORE_RGB24_TH -0.03 #define GRADUAL_SCORE_RGB16_TH 0 // assumes that stride doesn't overflow static int _bitmap_is_gradual(RedWorker *worker, Bitmap *bitmap) { long address_delta = worker->dev_info.phys_delta; double score = 0.0; int num_samples = 0; if ((bitmap->flags & QXL_BITMAP_DIRECT)) { uint8_t *lines = (uint8_t*)(bitmap->data + address_delta); switch (bitmap->format) { case BITMAP_FMT_16BIT: compute_lines_gradual_score_rgb16((rgb16_pixel_t*)lines, bitmap->x, bitmap->y, &score, &num_samples); break; case BITMAP_FMT_24BIT: compute_lines_gradual_score_rgb24((rgb24_pixel_t*)lines, bitmap->x, bitmap->y, &score, &num_samples); break; case BITMAP_FMT_32BIT: case BITMAP_FMT_RGBA: compute_lines_gradual_score_rgb32((rgb32_pixel_t*)lines, bitmap->x, bitmap->y, &score, &num_samples); break; default: red_error("invalid bitmap format (not RGB) %u", bitmap->format); } } else { QXLDataChunk *chunk = NULL; int num_lines; double chunk_score = 0.0; int chunk_num_samples = 0; ADDRESS relative_address = bitmap->data; while (relative_address) { chunk = (QXLDataChunk *)(relative_address + address_delta); num_lines = chunk->data_size / bitmap->stride; switch (bitmap->format) { case BITMAP_FMT_16BIT: compute_lines_gradual_score_rgb16((rgb16_pixel_t*)chunk->data, bitmap->x, num_lines, &chunk_score, &chunk_num_samples); break; case BITMAP_FMT_24BIT: compute_lines_gradual_score_rgb24((rgb24_pixel_t*)chunk->data, bitmap->x, num_lines, &chunk_score, &chunk_num_samples); break; case BITMAP_FMT_32BIT: case BITMAP_FMT_RGBA: compute_lines_gradual_score_rgb32((rgb32_pixel_t*)chunk->data, bitmap->x, num_lines, &chunk_score, &chunk_num_samples); break; default: red_error("invalid bitmap format (not RGB) %u", bitmap->format); } score += chunk_score; num_samples += chunk_num_samples; relative_address = chunk->next_chunk; } } ASSERT(num_samples); score /= num_samples; if (bitmap->format == BITMAP_FMT_16BIT) { return (score < GRADUAL_SCORE_RGB16_TH); } else { return (score < GRADUAL_SCORE_RGB24_TH); } } static inline int _stride_is_extra(Bitmap *bitmap) { ASSERT(bitmap); if (BITMAP_FMT_IS_RGB[bitmap->format]) { return ((bitmap->x * BITMAP_FMP_BYTES_PER_PIXEL[bitmap->format]) < bitmap->stride); } else { switch (bitmap->format) { case BITMAP_FMT_8BIT: return (bitmap->x < bitmap->stride); case BITMAP_FMT_4BIT_BE: case BITMAP_FMT_4BIT_LE: { int bytes_width = ALIGN(bitmap->x, 2) >> 1; return bytes_width < bitmap->stride; } case BITMAP_FMT_1BIT_BE: case BITMAP_FMT_1BIT_LE: { int bytes_width = ALIGN(bitmap->x, 8) >> 3; return bytes_width < bitmap->stride; } default: red_error("invalid image type %u", bitmap->format); } } } static const LzImageType MAP_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 }; typedef struct compress_send_data_t { uint32_t raw_size; void* comp_buf; uint32_t comp_buf_size; ADDRESS *plt_ptr; UINT8 *flags_ptr; } compress_send_data_t; static inline int red_glz_compress_image(DisplayChannel *display_channel, RedImage *dest, Bitmap *src, Drawable *drawable, compress_send_data_t* o_comp_data) { #ifdef COMPRESS_STAT stat_time_t start_time = stat_now(); #endif ASSERT(BITMAP_FMT_IS_RGB[src->format]); GlzData *glz_data = &display_channel->glz_data; LzImageType type = MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[src->format]; RedGlzDrawable *glz_drawable; GlzDrawableInstanceItem *glz_drawable_instance; uint8_t *lines; unsigned int num_lines; int size; long address_delta; glz_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel); glz_data->data.bufs_head = glz_data->data.bufs_tail; if (!glz_data->data.bufs_head) { return FALSE; } glz_data->data.bufs_head->send_next = NULL; glz_data->data.display_channel = display_channel; address_delta = display_channel->base.worker->dev_info.phys_delta; glz_drawable = red_display_get_glz_drawable(display_channel, drawable); glz_drawable_instance = red_display_add_glz_drawable_instance(glz_drawable); if ((src->flags & QXL_BITMAP_DIRECT)) { glz_data->usr.more_lines = glz_usr_no_more_lines; lines = (uint8_t*)(src->data + address_delta); num_lines = src->y; } else { glz_data->data.u.lines_data.address_delta = address_delta; glz_data->data.u.lines_data.stride = src->stride; glz_data->data.u.lines_data.next = src->data; glz_data->usr.more_lines = glz_usr_more_lines; lines = NULL; num_lines = 0; } size = glz_encode(display_channel->glz, type, src->x, src->y, (src->flags & QXL_BITMAP_TOP_DOWN), lines, num_lines, src->stride, (uint8_t*)glz_data->data.bufs_head->buf, sizeof(glz_data->data.bufs_head->buf), glz_drawable_instance, &glz_drawable_instance->glz_instance); dest->descriptor.type = IMAGE_TYPE_GLZ_RGB; dest->lz_rgb.data_size = size; o_comp_data->raw_size = sizeof(LZ_RGBImage); o_comp_data->comp_buf = glz_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->glz_stat, start_time, src->stride * src->y, o_comp_data->comp_buf_size); return TRUE; } static inline int red_lz_compress_image(DisplayChannel *display_channel, RedImage *dest, Bitmap *src, compress_send_data_t* o_comp_data) { RedWorker *worker = display_channel->base.worker; LzData *lz_data = &worker->lz_data; LzContext *lz = worker->lz; LzImageType type = MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[src->format]; long address_delta; int size; // size of the compressed data #ifdef COMPRESS_STAT stat_time_t start_time = stat_now(); #endif lz_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel); lz_data->data.bufs_head = lz_data->data.bufs_tail; if (!lz_data->data.bufs_head) { return FALSE; } lz_data->data.bufs_head->send_next = NULL; lz_data->data.display_channel = display_channel; address_delta = worker->dev_info.phys_delta; if (setjmp(lz_data->data.jmp_env)) { while (lz_data->data.bufs_head) { RedCompressBuf *buf = lz_data->data.bufs_head; lz_data->data.bufs_head = buf->send_next; red_display_free_compress_buf(display_channel, buf); } return FALSE; } if ((src->flags & QXL_BITMAP_DIRECT)) { lz_data->usr.more_lines = lz_usr_no_more_lines; size = lz_encode(lz, type, src->x, src->y, (src->flags & QXL_BITMAP_TOP_DOWN), (uint8_t*)(src->data + address_delta), src->y, src->stride, (uint8_t*)lz_data->data.bufs_head->buf, sizeof(lz_data->data.bufs_head->buf)); } else { lz_data->data.u.lines_data.address_delta = address_delta; lz_data->data.u.lines_data.stride = src->stride; lz_data->data.u.lines_data.next = src->data; lz_data->usr.more_lines = lz_usr_more_lines; size = lz_encode(lz, type, src->x, src->y, (src->flags & QXL_BITMAP_TOP_DOWN), NULL, 0, src->stride, (uint8_t*)lz_data->data.bufs_head->buf, 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 = IMAGE_TYPE_LZ_RGB; dest->lz_rgb.data_size = size; o_comp_data->raw_size = sizeof(LZ_RGBImage); o_comp_data->comp_buf = lz_data->data.bufs_head; o_comp_data->comp_buf_size = size; o_comp_data->plt_ptr = NULL; o_comp_data->flags_ptr = NULL; } else { dest->descriptor.type = IMAGE_TYPE_LZ_PLT; dest->lz_plt.data_size = size; dest->lz_plt.flags = src->flags & BITMAP_TOP_DOWN; dest->lz_plt.palette = src->palette; o_comp_data->raw_size = sizeof(LZ_PLTImage); o_comp_data->comp_buf = lz_data->data.bufs_head; o_comp_data->comp_buf_size = size; o_comp_data->plt_ptr = &(dest->lz_plt.palette); o_comp_data->flags_ptr = &(dest->lz_plt.flags); } stat_compress_add(&display_channel->lz_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, Bitmap *src, compress_send_data_t* o_comp_data) { RedWorker *worker = display_channel->base.worker; QuicData *quic_data = &worker->quic_data; QuicContext *quic = worker->quic; QuicImageType type; long address_delta; int size; #ifdef COMPRESS_STAT stat_time_t start_time = stat_now(); #endif switch (src->format) { case BITMAP_FMT_32BIT: type = QUIC_IMAGE_TYPE_RGB32; break; case BITMAP_FMT_RGBA: type = QUIC_IMAGE_TYPE_RGBA; break; case BITMAP_FMT_16BIT: type = QUIC_IMAGE_TYPE_RGB16; break; case BITMAP_FMT_24BIT: type = QUIC_IMAGE_TYPE_RGB24; break; default: return FALSE; } quic_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel); quic_data->data.bufs_head = quic_data->data.bufs_tail; if (!quic_data->data.bufs_head) { return FALSE; } quic_data->data.bufs_head->send_next = NULL; quic_data->data.display_channel = display_channel; address_delta = worker->dev_info.phys_delta; if (setjmp(quic_data->data.jmp_env)) { while (quic_data->data.bufs_head) { RedCompressBuf *buf = quic_data->data.bufs_head; quic_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*)(src->data + address_delta) + src->stride * (src->y - 1); stride = -src->stride; } else { data = (uint8_t*)(src->data + address_delta); stride = src->stride; } if ((src->flags & QXL_BITMAP_UNSTABLE)) { quic_data->data.u.unstable_lines_data.next = data; quic_data->data.u.unstable_lines_data.src_stride = stride; quic_data->data.u.unstable_lines_data.dest_stride = src->stride; quic_data->data.u.unstable_lines_data.lines = src->y; quic_data->data.u.unstable_lines_data.input_bufs_pos = 0; if (!(quic_data->data.u.unstable_lines_data.input_bufs[0] = red_display_alloc_compress_buf(display_channel)) || !(quic_data->data.u.unstable_lines_data.input_bufs[1] = red_display_alloc_compress_buf(display_channel))) { return FALSE; } quic_data->data.u.unstable_lines_data.max_lines_bunch = sizeof(quic_data->data.u.unstable_lines_data.input_bufs[0]->buf) / quic_data->data.u.unstable_lines_data.dest_stride; quic_data->usr.more_lines = quic_usr_more_lines_unstable; size = quic_encode(quic, type, src->x, src->y, NULL, 0, src->stride, quic_data->data.bufs_head->buf, sizeof(quic_data->data.bufs_head->buf) >> 2); } else { quic_data->usr.more_lines = quic_usr_no_more_lines; size = quic_encode(quic, type, src->x, src->y, data, src->y, stride, quic_data->data.bufs_head->buf, sizeof(quic_data->data.bufs_head->buf) >> 2); } } else { int stride; if ((src->flags & QXL_BITMAP_UNSTABLE)) { red_printf_once("unexpected unstable bitmap"); return FALSE; } quic_data->data.u.lines_data.address_delta = address_delta; quic_data->data.u.lines_data.stride = src->stride; if ((src->flags & QXL_BITMAP_TOP_DOWN)) { quic_data->data.u.lines_data.next = src->data; quic_data->usr.more_lines = quic_usr_more_lines; stride = src->stride; } else { QXLDataChunk *chunk = (QXLDataChunk *)(src->data + address_delta); while (chunk->next_chunk) { chunk = (QXLDataChunk *)(chunk->next_chunk + address_delta); ASSERT(chunk->prev_chunk); } quic_data->data.u.lines_data.next = (ADDRESS)chunk - address_delta; quic_data->usr.more_lines = quic_usr_more_lines_revers; stride = -src->stride; } size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride, quic_data->data.bufs_head->buf, sizeof(quic_data->data.bufs_head->buf) >> 2); } // 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 = IMAGE_TYPE_QUIC; dest->quic.data_size = size << 2; o_comp_data->raw_size = sizeof(QUICImage); o_comp_data->comp_buf = quic_data->data.bufs_head; o_comp_data->comp_buf_size = size << 2; o_comp_data->plt_ptr = NULL; o_comp_data->flags_ptr = NULL; stat_compress_add(&display_channel->quic_stat, start_time, src->stride * src->y, o_comp_data->comp_buf_size); return TRUE; } #define MIN_SIZE_TO_COMPRESS 54 #define MIN_DIMENSION_TO_QUIC 3 static inline int red_compress_image(DisplayChannel *display_channel, RedImage *dest, Bitmap *src, Drawable *drawable, compress_send_data_t* o_comp_data) { spice_image_compression_t image_compression = display_channel->base.worker->image_compression; int quic_compress = FALSE; if ((image_compression == SPICE_IMAGE_COMPRESS_OFF) || ((src->y * src->stride) < MIN_SIZE_TO_COMPRESS)) { // TODO: change the size cond return FALSE; } else if (image_compression == SPICE_IMAGE_COMPRESS_QUIC) { if (BITMAP_FMT_IS_PLT[src->format]) { return FALSE; } else { quic_compress = TRUE; } } else { /* lz doesn't handle (1) bitmaps with strides that are larger than the width of the image in bytes (2) unstable bitmaps */ if (_stride_is_extra(src) || (src->flags & QXL_BITMAP_UNSTABLE)) { if ((image_compression == SPICE_IMAGE_COMPRESS_LZ) || (image_compression == SPICE_IMAGE_COMPRESS_GLZ) || BITMAP_FMT_IS_PLT[src->format]) { return FALSE; } else { quic_compress = TRUE; } } else { if ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) || (image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ)) { if ((src->x < MIN_DIMENSION_TO_QUIC) || (src->y < MIN_DIMENSION_TO_QUIC)) { quic_compress = FALSE; } else { if (drawable->copy_bitmap_graduality == BITMAP_GRADUAL_INVALID) { quic_compress = BITMAP_FMT_IS_RGB[src->format] && _bitmap_is_gradual(display_channel->base.worker, src); } else { quic_compress = (drawable->copy_bitmap_graduality == BITMAP_GRADUAL_TRUE); } } } else { quic_compress = FALSE; } } } if (quic_compress) { #ifdef COMPRESS_DEBUG red_printf("QUIC compress"); #endif return red_quic_compress_image(display_channel, dest, src, o_comp_data); } else { int glz; int ret; if ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ) || (image_compression == SPICE_IMAGE_COMPRESS_GLZ)) { glz = BITMAP_FMT_IS_RGB[src->format] && ( (src->x * src->y) < glz_enc_dictionary_get_size( display_channel->glz_dict->dict)); } else if ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) || (image_compression == SPICE_IMAGE_COMPRESS_LZ)) { glz = FALSE; } else { red_error("invalid image compression type %u", image_compression); } if (glz) { /* using the global dictionary only if it is not freezed */ pthread_rwlock_rdlock(&display_channel->glz_dict->encode_lock); if (!display_channel->glz_dict->migrate_freeze) { ret = red_glz_compress_image( display_channel, dest, src, drawable, o_comp_data); } else { glz = FALSE; } pthread_rwlock_unlock(&display_channel->glz_dict->encode_lock); } if (!glz) { ret = red_lz_compress_image(display_channel, dest, src, o_comp_data); #ifdef COMPRESS_DEBUG red_printf("LZ LOCAL compress"); #endif } #ifdef COMPRESS_DEBUG else { red_printf("LZ global compress fmt=%d", src->format); } #endif return ret; } } static inline void red_display_add_image_to_pixmap_cache(DisplayChannel *display_channel, QXLImage *qxl_image, RedImage *io_image) { if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) { ASSERT(qxl_image->descriptor.width * qxl_image->descriptor.height > 0); if (pixmap_cache_add(display_channel->pixmap_cache, qxl_image->descriptor.id, qxl_image->descriptor.width * qxl_image->descriptor.height, display_channel)) { io_image->descriptor.flags |= IMAGE_CACHE_ME; stat_inc_counter(display_channel->add_to_cache_counter, 1); } } if (!(io_image->descriptor.flags & IMAGE_CACHE_ME)) { stat_inc_counter(display_channel->non_cache_counter, 1); } } /* if the number of times fill_bits can be called per one qxl_drawable increases - MAX_LZ_DRAWABLE_INSTANCES must be increased as well */ static void fill_bits(DisplayChannel *display_channel, PHYSICAL *in_bitmap, Drawable *drawable) { RedChannel *channel = &display_channel->base; RedImage *image; QXLImage *qxl_image; uint8_t *data; compress_send_data_t comp_send_data; ASSERT(*in_bitmap); image = alloc_image(display_channel); qxl_image = (QXLImage *)(*in_bitmap + channel->worker->dev_info.phys_delta); image->descriptor.id = qxl_image->descriptor.id; image->descriptor.type = qxl_image->descriptor.type; image->descriptor.flags = 0; image->descriptor.width = qxl_image->descriptor.width; image->descriptor.height = qxl_image->descriptor.height; *in_bitmap = channel->send_data.header.size; if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) { if (pixmap_cache_hit(display_channel->pixmap_cache, image->descriptor.id, display_channel)) { image->descriptor.type = IMAGE_TYPE_FROM_CACHE; add_buf(channel, BUF_TYPE_RAW, image, sizeof(ImageDescriptor)); stat_inc_counter(display_channel->cache_hits_counter, 1); return; } } switch (qxl_image->descriptor.type) { case IMAGE_TYPE_BITMAP: #ifdef DUMP_BITMAP dump_bitmap(display_channel->base.worker, &qxl_image->bitmap); #endif /* Images must be added to the cache only after they are compressed in order to prevent starvation in the client between pixmap_cache and global dictionary (in cases of multiple monitors) */ if (!red_compress_image(display_channel, image, &qxl_image->bitmap, drawable, &comp_send_data)) { red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image); image->bitmap = qxl_image->bitmap; image->bitmap.flags = image->bitmap.flags & BITMAP_TOP_DOWN; add_buf(channel, BUF_TYPE_RAW, image, sizeof(BitmapImage)); fill_palette(display_channel, &(image->bitmap.palette), &(image->bitmap.flags)); data = (uint8_t *)(image->bitmap.data + channel->worker->dev_info.phys_delta); image->bitmap.data = channel->send_data.header.size; add_buf(channel, (qxl_image->bitmap.flags & QXL_BITMAP_DIRECT) ? BUF_TYPE_RAW : BUF_TYPE_CHUNK, data, image->bitmap.y * image->bitmap.stride); } else { red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image); add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, image, comp_send_data.raw_size); add_buf((RedChannel *)display_channel, BUF_TYPE_COMPRESS_BUF, comp_send_data.comp_buf, comp_send_data.comp_buf_size); if (comp_send_data.plt_ptr != NULL) { fill_palette(display_channel, comp_send_data.plt_ptr, comp_send_data.flags_ptr); } } break; case IMAGE_TYPE_QUIC: red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image); image->quic = qxl_image->quic; add_buf(channel, BUF_TYPE_RAW, image, sizeof(QUICImage)); add_buf(channel, BUF_TYPE_CHUNK, qxl_image->quic.data, qxl_image->quic.data_size); break; default: red_error("invalid image type %u", image->descriptor.type); } } static void fill_brush(DisplayChannel *display_channel, Brush *brush, Drawable *drawable) { if (brush->type == BRUSH_TYPE_PATTERN) { fill_bits(display_channel, &brush->u.pattern.pat, drawable); } } static void fill_mask(DisplayChannel *display_channel, QMask *mask, Drawable *drawable) { if (mask->bitmap) { if (display_channel->base.worker->image_compression != SPICE_IMAGE_COMPRESS_OFF) { spice_image_compression_t save_img_comp = display_channel->base.worker->image_compression; display_channel->base.worker->image_compression = SPICE_IMAGE_COMPRESS_OFF; fill_bits(display_channel, &mask->bitmap, drawable); display_channel->base.worker->image_compression = save_img_comp; } else { fill_bits(display_channel, &mask->bitmap, drawable); } } } static void fill_attr(DisplayChannel *display_channel, LineAttr *attr) { if (attr->style_nseg) { RedChannel *channel = &display_channel->base; uint8_t *buf = (uint8_t *)(attr->style + channel->worker->dev_info.phys_delta); ASSERT(attr->style); attr->style = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, buf, attr->style_nseg * sizeof(uint32_t)); } } static void fill_cursor(CursorChannel *cursor_channel, RedCursor *red_cursor, CursorItem *cursor) { RedChannel *channel = &cursor_channel->base; if (!cursor) { red_cursor->flags = RED_CURSOR_NONE; return; } if (cursor->type == CURSOR_TYPE_DEV) { QXLCursorCmd *cursor_cmd; QXLCursor *qxl_cursor; cursor_cmd = CONTAINEROF(cursor, QXLCursorCmd, device_data); qxl_cursor = (QXLCursor *)(cursor_cmd->u.set.shape + channel->worker->dev_info.phys_delta); red_cursor->flags = 0; red_cursor->header = qxl_cursor->header; if (red_cursor->header.unique) { if (red_cursor_cache_find(cursor_channel, red_cursor->header.unique)) { red_cursor->flags |= RED_CURSOR_FROM_CACHE; return; } if (red_cursor_cache_add(cursor_channel, red_cursor->header.unique, 1)) { red_cursor->flags |= RED_CURSOR_CACHE_ME; } } if (qxl_cursor->data_size) { add_buf(channel, BUF_TYPE_CHUNK, &qxl_cursor->chunk, qxl_cursor->data_size); } } else { LocalCursor *local_cursor; ASSERT(cursor->type == CURSOR_TYPE_LOCAL); local_cursor = (LocalCursor *)cursor; *red_cursor = local_cursor->red_cursor; add_buf(channel, BUF_TYPE_RAW, local_cursor->red_cursor.data, local_cursor->data_size); } } static inline void red_channel_reset_send_data(RedChannel *channel) { channel->send_data.pos = 0; channel->send_data.n_bufs = 1; channel->send_data.header.size = 0; channel->send_data.header.sub_list = 0; ++channel->send_data.header.serial; channel->send_data.bufs[0].type = BUF_TYPE_RAW; channel->send_data.bufs[0].size = sizeof(RedDataHeader); channel->send_data.bufs[0].data = (void *)&channel->send_data.header; } static inline void red_display_reset_send_data(DisplayChannel *channel) { red_channel_reset_send_data((RedChannel *)channel); channel->send_data.bitmap_pos = 0; red_display_reset_compress_buf(channel); channel->send_data.free_list.res->count = 0; memset(channel->send_data.free_list.sync, 0, sizeof(channel->send_data.free_list.sync)); } static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *display_channel, Drawable *item) { QXLDrawable *drawable = item->qxl_drawable; RedChannel *channel = &display_channel->base; switch (drawable->type) { case QXL_DRAW_FILL: channel->send_data.header.type = RED_DISPLAY_DRAW_FILL; fill_base(display_channel, &display_channel->send_data.u.fill.base, drawable, sizeof(RedFill)); display_channel->send_data.u.fill.data = drawable->u.fill; fill_brush(display_channel, &display_channel->send_data.u.fill.data.brush, item); fill_mask(display_channel, &display_channel->send_data.u.fill.data.mask, item); break; case QXL_DRAW_OPAQUE: channel->send_data.header.type = RED_DISPLAY_DRAW_OPAQUE; fill_base(display_channel, &display_channel->send_data.u.opaque.base, drawable, sizeof(RedOpaque)); display_channel->send_data.u.opaque.data = drawable->u.opaque; fill_bits(display_channel, &display_channel->send_data.u.opaque.data.src_bitmap, item); fill_brush(display_channel, &display_channel->send_data.u.opaque.data.brush, item); fill_mask(display_channel, &display_channel->send_data.u.opaque.data.mask, item); break; case QXL_DRAW_COPY: channel->send_data.header.type = RED_DISPLAY_DRAW_COPY; fill_base(display_channel, &display_channel->send_data.u.copy.base, drawable, sizeof(RedCopy)); display_channel->send_data.u.copy.data = drawable->u.copy; fill_bits(display_channel, &display_channel->send_data.u.copy.data.src_bitmap, item); fill_mask(display_channel, &display_channel->send_data.u.copy.data.mask, item); break; case QXL_DRAW_TRANSPARENT: channel->send_data.header.type = RED_DISPLAY_DRAW_TRANSPARENT; fill_base(display_channel, &display_channel->send_data.u.transparent.base, drawable, sizeof(RedTransparent)); display_channel->send_data.u.transparent.data = drawable->u.transparent; fill_bits(display_channel, &display_channel->send_data.u.transparent.data.src_bitmap, item); break; case QXL_DRAW_ALPHA_BLEND: channel->send_data.header.type = RED_DISPLAY_DRAW_ALPHA_BLEND; fill_base(display_channel, &display_channel->send_data.u.alpha_blend.base, drawable, sizeof(RedAlphaBlend)); display_channel->send_data.u.alpha_blend.data = drawable->u.alpha_blend; fill_bits(display_channel, &display_channel->send_data.u.alpha_blend.data.src_bitmap, item); break; case QXL_COPY_BITS: channel->send_data.header.type = RED_DISPLAY_COPY_BITS; fill_base(display_channel, &display_channel->send_data.u.copy_bits.base, drawable, sizeof(RedCopyBits)); display_channel->send_data.u.copy_bits.src_pos = drawable->u.copy_bits.src_pos; break; case QXL_DRAW_BLEND: channel->send_data.header.type = RED_DISPLAY_DRAW_BLEND; fill_base(display_channel, &display_channel->send_data.u.blend.base, drawable, sizeof(RedBlend)); display_channel->send_data.u.blend.data = drawable->u.blend; fill_bits(display_channel, &display_channel->send_data.u.blend.data.src_bitmap, item); fill_mask(display_channel, &display_channel->send_data.u.blend.data.mask, item); break; case QXL_DRAW_BLACKNESS: channel->send_data.header.type = RED_DISPLAY_DRAW_BLACKNESS; fill_base(display_channel, &display_channel->send_data.u.blackness.base, drawable, sizeof(RedBlackness)); display_channel->send_data.u.blackness.data = drawable->u.blackness; fill_mask(display_channel, &display_channel->send_data.u.blackness.data.mask, item); break; case QXL_DRAW_WHITENESS: channel->send_data.header.type = RED_DISPLAY_DRAW_WHITENESS; fill_base(display_channel, &display_channel->send_data.u.whiteness.base, drawable, sizeof(RedWhiteness)); display_channel->send_data.u.whiteness.data = drawable->u.whiteness; fill_mask(display_channel, &display_channel->send_data.u.whiteness.data.mask, item); break; case QXL_DRAW_INVERS: channel->send_data.header.type = RED_DISPLAY_DRAW_INVERS; fill_base(display_channel, &display_channel->send_data.u.invers.base, drawable, sizeof(RedInvers)); display_channel->send_data.u.invers.data = drawable->u.invers; fill_mask(display_channel, &display_channel->send_data.u.invers.data.mask, item); break; case QXL_DRAW_ROP3: channel->send_data.header.type = RED_DISPLAY_DRAW_ROP3; fill_base(display_channel, &display_channel->send_data.u.rop3.base, drawable, sizeof(RedRop3)); display_channel->send_data.u.rop3.data = drawable->u.rop3; fill_bits(display_channel, &display_channel->send_data.u.rop3.data.src_bitmap, item); fill_brush(display_channel, &display_channel->send_data.u.rop3.data.brush, item); fill_mask(display_channel, &display_channel->send_data.u.rop3.data.mask, item); break; case QXL_DRAW_STROKE: channel->send_data.header.type = RED_DISPLAY_DRAW_STROKE; fill_base(display_channel, &display_channel->send_data.u.stroke.base, drawable, sizeof(RedStroke)); display_channel->send_data.u.stroke.data = drawable->u.stroke; fill_path(display_channel, &display_channel->send_data.u.stroke.data.path); fill_attr(display_channel, &display_channel->send_data.u.stroke.data.attr); fill_brush(display_channel, &display_channel->send_data.u.stroke.data.brush, item); break; case QXL_DRAW_TEXT: channel->send_data.header.type = RED_DISPLAY_DRAW_TEXT; fill_base(display_channel, &display_channel->send_data.u.text.base, drawable, sizeof(RedText)); display_channel->send_data.u.text.data = drawable->u.text; fill_brush(display_channel, &display_channel->send_data.u.text.data.fore_brush, item); fill_brush(display_channel, &display_channel->send_data.u.text.data.back_brush, item); fill_str(display_channel, &display_channel->send_data.u.text.data.str); break; default: red_error("invalid type"); } display_begin_send_massage(display_channel, &item->pipe_item); } #define MAX_SEND_VEC 100 static inline BufDescriptor *find_buf(RedChannel *channel, int buf_pos, int *buf_offset) { BufDescriptor *buf; int pos = 0; for (buf = channel->send_data.bufs; buf_pos >= pos + buf->size; buf++) { pos += buf->size; ASSERT(buf != &channel->send_data.bufs[channel->send_data.n_bufs - 1]); } *buf_offset = buf_pos - pos; return buf; } static inline uint32_t __fill_iovec(BufDescriptor *buf, int skip, struct iovec *vec, int *vec_index, long phys_delta) { uint32_t size = 0; switch (buf->type) { case BUF_TYPE_RAW: vec[*vec_index].iov_base = buf->data + skip; vec[*vec_index].iov_len = size = buf->size - skip; (*vec_index)++; break; case BUF_TYPE_COMPRESS_BUF: { RedCompressBuf *comp_buf = (RedCompressBuf *)buf->data; int max = buf->size - skip; int now; do { ASSERT(comp_buf); if (skip >= sizeof(comp_buf->buf)) { skip -= sizeof(comp_buf->buf); comp_buf = comp_buf->send_next; continue; } now = MIN(sizeof(comp_buf->buf) - skip, max); max -= now; size += now; vec[*vec_index].iov_base = (uint8_t*)comp_buf->buf + skip; vec[*vec_index].iov_len = now; skip = 0; comp_buf = comp_buf->send_next; (*vec_index)++; } while (max && *vec_index < MAX_SEND_VEC); break; } case BUF_TYPE_CHUNK: { QXLDataChunk *chunk = (QXLDataChunk *)buf->data; do { int data_size = chunk->data_size; int skip_now = MIN(data_size, skip); skip -= skip_now; data_size -= skip_now; if (data_size) { size += data_size; vec[*vec_index].iov_base = chunk->data + skip_now; vec[*vec_index].iov_len = data_size; (*vec_index)++; } chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + phys_delta) : NULL; } while (chunk && *vec_index < MAX_SEND_VEC); break; } default: red_error("invalid type"); } return size; } static inline void fill_iovec(RedChannel *channel, struct iovec *vec, int *vec_size) { int vec_index = 0; uint32_t pos = channel->send_data.pos; ASSERT(channel->send_data.size != pos && channel->send_data.size > pos); do { BufDescriptor *buf; int buf_offset; buf = find_buf(channel, pos, &buf_offset); ASSERT(buf); pos += __fill_iovec(buf, buf_offset, vec, &vec_index, channel->worker->dev_info.phys_delta); } while (vec_index < MAX_SEND_VEC && pos != channel->send_data.size); *vec_size = vec_index; } static void inline channel_release_res(RedChannel *channel) { if (!channel->send_data.item) { return; } channel->release_item(channel, channel->send_data.item); channel->send_data.item = NULL; } static void red_send_data(RedChannel *channel, void *item) { for (;;) { uint32_t n = channel->send_data.size - channel->send_data.pos; struct iovec vec[MAX_SEND_VEC]; int vec_size; if (!n) { channel->send_data.blocked = FALSE; if (channel->send_data.item) { channel->release_item(channel, channel->send_data.item); channel->send_data.item = NULL; } break; } fill_iovec(channel, vec, &vec_size); ASSERT(channel->peer); if ((n = channel->peer->cb_writev(channel->peer->ctx, vec, vec_size)) == -1) { switch (errno) { case EAGAIN: channel->send_data.blocked = TRUE; if (item) { channel->hold_item(item); channel->send_data.item = item; } return; case EINTR: break; case EPIPE: channel->disconnect(channel); return; default: red_printf("%s", strerror(errno)); channel->disconnect(channel); return; } } else { channel->send_data.pos += n; stat_inc_counter(channel->out_bytes_counter, n); } } } static void display_channel_push_release(DisplayChannel *channel, uint8_t type, uint64_t id, uint64_t* sync_data) { FreeList *free_list = &channel->send_data.free_list; int i; for (i = 0; i < MAX_CACHE_CLIENTS; i++) { free_list->sync[i] = MAX(free_list->sync[i], sync_data[i]); } if (free_list->res->count == free_list->res_size) { RedResorceList *new_list; new_list = malloc(sizeof(*new_list) + free_list->res_size * sizeof(RedResorceID) * 2); if (!new_list) { PANIC("malloc failed"); } new_list->count = free_list->res->count; memcpy(new_list->resorces, free_list->res->resorces, new_list->count * sizeof(RedResorceID)); free(free_list->res); free_list->res = new_list; free_list->res_size *= 2; } free_list->res->resorces[free_list->res->count].type = type; free_list->res->resorces[free_list->res->count++].id = id; } static inline void red_begin_send_massage(RedChannel *channel, void *item) { channel->send_data.size = channel->send_data.header.size + sizeof(RedDataHeader); channel->messages_window++; red_send_data(channel, item); } static inline void display_begin_send_massage(DisplayChannel *channel, void *item) { FreeList *free_list = &channel->send_data.free_list; if (free_list->res->count) { int sync_count = 0; int sub_index; int i; channel->base.send_data.header.sub_list = channel->base.send_data.header.size; for (i = 0; i < MAX_CACHE_CLIENTS; i++) { if (i != channel->base.id && free_list->sync[i] != 0) { free_list->wait.header.wait_list[sync_count].channel_type = RED_CHANNEL_DISPLAY; free_list->wait.header.wait_list[sync_count].channel_id = i; free_list->wait.header.wait_list[sync_count++].message_serial = free_list->sync[i]; } } RedSubMessageList *sub_list = &channel->send_data.sub_list.sub_list; RedSubMessage *sub_header = channel->send_data.sub_header; if (sync_count) { sub_list->size = 2; add_buf((RedChannel*)channel, BUF_TYPE_RAW, sub_list, sizeof(*sub_list) + 2 * sizeof(sub_list->sub_messages[0])); sub_list->sub_messages[0] = channel->base.send_data.header.size; sub_header[0].type = RED_WAIT_FOR_CHANNELS; sub_header[0].size = sizeof(free_list->wait.header) + sync_count * sizeof(free_list->wait.buf[0]); add_buf((RedChannel*)channel, BUF_TYPE_RAW, sub_header, sizeof(*sub_header)); free_list->wait.header.wait_count = sync_count; add_buf((RedChannel*)channel, BUF_TYPE_RAW, &free_list->wait.header, sub_header[0].size); sub_list->sub_messages[1] = channel->base.send_data.header.size; sub_index = 1; } else { sub_list->size = 1; add_buf((RedChannel*)channel, BUF_TYPE_RAW, sub_list, sizeof(*sub_list) + sizeof(sub_list->sub_messages[0])); sub_list->sub_messages[0] = channel->base.send_data.header.size; sub_index = 0; } sub_header[sub_index].type = RED_DISPLAY_INVAL_LIST; sub_header[sub_index].size = sizeof(*free_list->res) + free_list->res->count * sizeof(free_list->res->resorces[0]); add_buf((RedChannel*)channel, BUF_TYPE_RAW, &sub_header[sub_index], sizeof(*sub_header)); add_buf((RedChannel*)channel, BUF_TYPE_RAW, free_list->res, sub_header[sub_index].size); } red_begin_send_massage((RedChannel *)channel, item); } static inline RedChannel *red_ref_channel(RedChannel *channel) { if (!channel) { return NULL; } ++channel->listener.refs; return channel; } static inline void red_unref_channel(RedChannel *channel) { ASSERT(channel); if (!--channel->listener.refs) { free(channel); } } static inline uint8_t *red_get_image_line(QXLDataChunk **chunk, int *offset, int stride, long phys_delta) { uint8_t *ret; if ((*chunk)->data_size == *offset) { if ((*chunk)->next_chunk == 0) { return NULL; } *offset = 0; *chunk = (QXLDataChunk *)((*chunk)->next_chunk + phys_delta); } if ((*chunk)->data_size - *offset < stride) { red_printf("bad chunk aligment"); return NULL; } ret = (*chunk)->data + *offset; *offset += stride; return ret; } static void red_display_share_stream_buf(DisplayChannel *display_channel) { } static void red_display_unshare_stream_buf(DisplayChannel *display_channel) { } #define YUV32 #include "red_yuv.h" #undef YUV32 #define YUV24 #include "red_yuv.h" #undef YUV24 #define YUV16 #include "red_yuv.h" #undef YUV16 static inline int red_send_stream_data(DisplayChannel *display_channel, Drawable *drawable) { Stream *stream = drawable->stream; QXLImage *qxl_image; RedChannel *channel; RedWorker* worker; int n; ASSERT(stream); ASSERT(drawable->qxl_drawable->type == QXL_DRAW_COPY); channel = &display_channel->base; worker = channel->worker; qxl_image = (QXLImage *)(drawable->qxl_drawable->u.copy.src_bitmap + worker->dev_info.phys_delta); if (qxl_image->descriptor.type != IMAGE_TYPE_BITMAP || (qxl_image->bitmap.flags & QXL_BITMAP_DIRECT)) { return FALSE; } StreamAgent *agent = &display_channel->stream_agents[stream - worker->streams_buf]; uint64_t time_now = red_now(); if (time_now - agent->lats_send_time < (1000 * 1000 * 1000) / agent->fps) { agent->frames--; return TRUE; } switch (qxl_image->bitmap.format) { case BITMAP_FMT_32BIT: if (!red_rgb_to_yuv420_32bpp(&drawable->qxl_drawable->u.copy.src_area, &qxl_image->bitmap, stream->av_frame, worker->dev_info.phys_delta, stream - worker->streams_buf, stream)) { return FALSE; } break; case BITMAP_FMT_16BIT: if (!red_rgb_to_yuv420_16bpp(&drawable->qxl_drawable->u.copy.src_area, &qxl_image->bitmap, stream->av_frame, worker->dev_info.phys_delta, stream - worker->streams_buf, stream)) { return FALSE; } break; case BITMAP_FMT_24BIT: if (!red_rgb_to_yuv420_24bpp(&drawable->qxl_drawable->u.copy.src_area, &qxl_image->bitmap, stream->av_frame, worker->dev_info.phys_delta, stream - worker->streams_buf, stream)) { return FALSE; } break; default: red_printf_some(1000, "unsupported format %d", qxl_image->bitmap.format); return FALSE; } #if 1 uint32_t min_buf_size = stream->av_ctx->width * stream->av_ctx->height * sizeof(uint32_t) / 2; min_buf_size += FF_MIN_BUFFER_SIZE; if (display_channel->send_data.stream_outbuf_size < min_buf_size) { uint8_t *new_buf; if (!(new_buf = malloc(min_buf_size + FF_INPUT_BUFFER_PADDING_SIZE))) { return FALSE; } red_display_unshare_stream_buf(display_channel); free(display_channel->send_data.stream_outbuf); display_channel->send_data.stream_outbuf = new_buf; display_channel->send_data.stream_outbuf_size = min_buf_size; red_display_share_stream_buf(display_channel); } n = avcodec_encode_video(stream->av_ctx, display_channel->send_data.stream_outbuf, display_channel->send_data.stream_outbuf_size, stream->av_frame); if (n <= 0) { red_printf("avcodec_encode_video failed"); return FALSE; } if (n > display_channel->send_data.stream_outbuf_size) { PANIC("buf error"); } #else for (;;) { n = avcodec_encode_video(stream->av_ctx, display_channel->send_data.stream_outbuf, display_channel->send_data.stream_outbuf_size, stream->av_frame); if (n < 0) { uint8_t *new_buf; size_t max_size; size_t new_size; max_size = stream->av_ctx->width * stream->av_ctx->height * sizeof(uint32_t); max_size += MAX(1024, max_size / 10); if (display_channel->send_data.stream_outbuf_size == max_size) { return FALSE; } new_size = display_channel->send_data.stream_outbuf_size * 2; new_size = MIN(new_size, max_size); if (!(new_buf = malloc(new_size + FF_INPUT_BUFFER_PADDING_SIZE))) { return FALSE; } red_printf("new streaming video buf size %u", new_size); red_display_unshare_stream_buf(display_channel); free(display_channel->send_data.stream_outbuf); display_channel->send_data.stream_outbuf = new_buf; display_channel->send_data.stream_outbuf_size = new_size; red_display_share_stream_buf(display_channel); continue; } ASSERT(n <= display_channel->send_data.stream_outbuf_size); break; } #endif channel->send_data.header.type = RED_DISPLAY_STREAM_DATA; RedStreamData* stream_data = &display_channel->send_data.u.stream_data; add_buf(channel, BUF_TYPE_RAW, stream_data, sizeof(RedStreamData)); add_buf(channel, BUF_TYPE_RAW, display_channel->send_data.stream_outbuf, n + FF_INPUT_BUFFER_PADDING_SIZE); stream_data->id = stream - worker->streams_buf; stream_data->multi_media_time = drawable->qxl_drawable->mm_time; stream_data->data_size = n; stream_data->ped_size = FF_INPUT_BUFFER_PADDING_SIZE; display_begin_send_massage(display_channel, NULL); agent->lats_send_time = time_now; return TRUE; } static inline void send_qxl_drawable(DisplayChannel *display_channel, Drawable *item) { RedChannel *channel; ASSERT(display_channel); channel = &display_channel->base; if (item->stream && red_send_stream_data(display_channel, item)) { return; } red_send_qxl_drawable(display_channel->base.worker, display_channel, item); } static void red_send_mode(DisplayChannel *display_channel) { RedChannel *channel; RedWorker *worker; ASSERT(display_channel); worker = display_channel->base.worker; channel = &display_channel->base; channel->send_data.header.type = RED_DISPLAY_MODE; display_channel->send_data.u.mode.x_res = worker->dev_info.x_res; display_channel->send_data.u.mode.y_res = worker->dev_info.y_res; display_channel->send_data.u.mode.bits = worker->dev_info.bits; add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.mode, sizeof(RedMode)); display_begin_send_massage(display_channel, NULL); } static void red_send_set_ack(RedChannel *channel) { ASSERT(channel); channel->send_data.header.type = RED_SET_ACK; channel->send_data.u.ack.generation = ++channel->ack_generation; channel->send_data.u.ack.window = channel->client_ack_window; channel->messages_window = 0; add_buf(channel, BUF_TYPE_RAW, &channel->send_data.u.ack, sizeof(RedSetAck)); red_begin_send_massage(channel, NULL); } static inline void red_send_verb(RedChannel *channel, uint16_t verb) { ASSERT(channel); channel->send_data.header.type = verb; red_begin_send_massage(channel, NULL); } static inline void display_send_verb(DisplayChannel *channel, uint16_t verb) { ASSERT(channel); channel->base.send_data.header.type = verb; display_begin_send_massage(channel, NULL); } static inline void __red_send_inval(RedChannel *channel, CacheItem *cach_item, RedInvalOne *inval_one) { channel->send_data.header.type = cach_item->inval_type; inval_one->id = *(uint64_t *)&cach_item->id; add_buf(channel, BUF_TYPE_RAW, inval_one, sizeof(*inval_one)); } static void red_send_inval(RedChannel *channel, CacheItem *cach_item, RedInvalOne *inval_one) { __red_send_inval(channel, cach_item, inval_one); red_begin_send_massage(channel, NULL); } static void red_display_send_inval(DisplayChannel *display_channel, CacheItem *cach_item) { __red_send_inval((RedChannel *)display_channel, cach_item, &display_channel->send_data.u.inval_one); display_begin_send_massage(display_channel, NULL); } static void display_channel_send_migrate(DisplayChannel *display_channel) { display_channel->base.send_data.header.type = RED_MIGRATE; display_channel->send_data.u.migrate.flags = RED_MIGRATE_NEED_FLUSH | RED_MIGRATE_NEED_DATA_TRANSFER; add_buf((RedChannel*)display_channel, BUF_TYPE_RAW, &display_channel->send_data.u.migrate, sizeof(display_channel->send_data.u.migrate)); display_channel->expect_migrate_mark = TRUE; display_begin_send_massage(display_channel, NULL); } static void display_channel_send_migrate_data(DisplayChannel *display_channel) { DisplayChannelMigrateData* display_data; display_channel->base.send_data.header.type = RED_MIGRATE_DATA; ASSERT(display_channel->pixmap_cache); display_data = &display_channel->send_data.u.migrate_data; display_data->magic = DISPLAY_MIGRATE_DATA_MAGIC; ASSERT(MAX_CACHE_CLIENTS == 4); //MIGRATE_DATA_VERSION dependent display_data->version = DISPLAY_MIGRATE_DATA_VERSION; display_data->message_serial = channel_message_serial((RedChannel *)display_channel); display_data->pixmap_cache_freezer = pixmap_cache_freeze(display_channel->pixmap_cache); display_data->pixmap_cache_id = display_channel->pixmap_cache->id; display_data->pixmap_cache_size = display_channel->pixmap_cache->size; memcpy(display_data->pixmap_cache_clients, display_channel->pixmap_cache->sync, sizeof(display_data->pixmap_cache_clients)); ASSERT(display_channel->glz_dict); red_freeze_glz(display_channel); display_data->glz_dict_id = display_channel->glz_dict->id; glz_enc_dictionary_get_restore_data(display_channel->glz_dict->dict, &display_data->glz_dict_restore_data, &display_channel->glz_data.usr); add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, &display_channel->send_data.u.migrate_data, sizeof(display_channel->send_data.u.migrate_data)); display_begin_send_massage(display_channel, NULL); } static void display_channel_pixmap_sync(DisplayChannel *display_channel) { RedWaitForChannels *wait; PixmapCache *pixmap_cache; display_channel->base.send_data.header.type = RED_WAIT_FOR_CHANNELS; wait = &display_channel->send_data.u.wait.header; pixmap_cache = display_channel->pixmap_cache; pthread_mutex_lock(&pixmap_cache->lock); wait->wait_count = 1; wait->wait_list[0].channel_type = RED_CHANNEL_DISPLAY; wait->wait_list[0].channel_id = pixmap_cache->generation_initiator.client; wait->wait_list[0].message_serial = pixmap_cache->generation_initiator.message; display_channel->pixmap_cache_generation = pixmap_cache->generation; display_channel->pending_pixmaps_sync = FALSE; pthread_mutex_unlock(&pixmap_cache->lock); add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, wait, sizeof(*wait) + sizeof(wait->wait_list[0])); display_begin_send_massage(display_channel, NULL); } static void display_channel_reset_cache(DisplayChannel *display_channel) { RedWaitForChannels *wait = &display_channel->send_data.u.wait.header; display_channel->base.send_data.header.type = RED_DISPLAY_INVAL_ALL_PIXMAPS; pixmap_cache_reset(display_channel->pixmap_cache, display_channel, wait); add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, wait, sizeof(*wait) + wait->wait_count * sizeof(wait->wait_list[0])); display_begin_send_massage(display_channel, NULL); } static void red_send_image(DisplayChannel *display_channel, ImageItem *item) { RedChannel *channel; RedImage *red_image; RedWorker *worker; Bitmap bitmap; ASSERT(display_channel && item); channel = &display_channel->base; worker = channel->worker; red_image = alloc_image(display_channel); ASSERT(red_image); QXL_SET_IMAGE_ID(red_image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique); red_image->descriptor.type = IMAGE_TYPE_BITMAP; red_image->descriptor.flags = 0; red_image->descriptor.width = item->width; red_image->descriptor.height = item->height; bitmap.format = BITMAP_FMT_32BIT; bitmap.flags = QXL_BITMAP_DIRECT; bitmap.flags |= item->top_down ? QXL_BITMAP_TOP_DOWN : 0; bitmap.x = item->width; bitmap.y = item->height; bitmap.stride = item->stride; bitmap.palette = 0; bitmap.data = (ADDRESS)(item->data) - worker->dev_info.phys_delta; channel->send_data.header.type = RED_DISPLAY_DRAW_COPY; add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.copy, sizeof(RedCopy)); display_channel->send_data.u.copy.base.box.left = item->pos.x; display_channel->send_data.u.copy.base.box.top = item->pos.y; display_channel->send_data.u.copy.base.box.right = item->pos.x + bitmap.x; display_channel->send_data.u.copy.base.box.bottom = item->pos.y + bitmap.y; display_channel->send_data.u.copy.base.clip.type = CLIP_TYPE_NONE; display_channel->send_data.u.copy.base.clip.data = 0; display_channel->send_data.u.copy.data.rop_decriptor = ROPD_OP_PUT; display_channel->send_data.u.copy.data.src_area.left = 0; display_channel->send_data.u.copy.data.src_area.top = 0; display_channel->send_data.u.copy.data.src_area.right = bitmap.x; display_channel->send_data.u.copy.data.src_area.bottom = bitmap.y; display_channel->send_data.u.copy.data.scale_mode = 0; display_channel->send_data.u.copy.data.src_bitmap = channel->send_data.header.size; display_channel->send_data.u.copy.data.mask.bitmap = 0; compress_send_data_t comp_send_data; if (red_quic_compress_image(display_channel, red_image, &bitmap, &comp_send_data)) { add_buf(channel, BUF_TYPE_RAW, red_image, comp_send_data.raw_size); add_buf(channel, BUF_TYPE_COMPRESS_BUF, comp_send_data.comp_buf, comp_send_data.comp_buf_size); } else { red_image->descriptor.type = IMAGE_TYPE_BITMAP; red_image->bitmap = bitmap; red_image->bitmap.flags &= ~QXL_BITMAP_DIRECT; add_buf(channel, BUF_TYPE_RAW, red_image, sizeof(BitmapImage)); red_image->bitmap.data = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, item->data, bitmap.y * bitmap.stride); } display_begin_send_massage(display_channel, &item->link); } static void red_display_send_upgrade(DisplayChannel *display_channel, UpgradeItem *item) { RedChannel *channel; QXLDrawable *qxl_drawable; RedCopy *copy = &display_channel->send_data.u.copy; ASSERT(display_channel && item && item->drawable); channel = &display_channel->base; channel->send_data.header.type = RED_DISPLAY_DRAW_COPY; qxl_drawable = item->drawable->qxl_drawable; ASSERT(qxl_drawable->type == QXL_DRAW_COPY); ASSERT(qxl_drawable->u.copy.rop_decriptor == ROPD_OP_PUT); ASSERT(qxl_drawable->u.copy.mask.bitmap == 0); add_buf(channel, BUF_TYPE_RAW, copy, sizeof(RedCopy)); copy->base.box = qxl_drawable->bbox; copy->base.clip.type = CLIP_TYPE_RECTS; copy->base.clip.data = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, &item->region.num_rects, sizeof(uint32_t)); add_buf(channel, BUF_TYPE_RAW, item->region.rects, sizeof(Rect) * item->region.num_rects); copy->data = qxl_drawable->u.copy; fill_bits(display_channel, ©->data.src_bitmap, item->drawable); display_begin_send_massage(display_channel, &item->base); } static void red_display_send_stream_start(DisplayChannel *display_channel, StreamAgent *agent) { RedChannel *channel = &display_channel->base; Stream *stream = agent->stream; agent->lats_send_time = 0; ASSERT(stream); channel->send_data.header.type = RED_DISPLAY_STREAM_CREATE; RedStreamCreate *stream_create = &display_channel->send_data.u.stream_create.message; stream_create->id = agent - display_channel->stream_agents; stream_create->flags = stream->top_down ? STREAM_TOP_DOWN : 0; stream_create->codec_type = RED_VIDEO_CODEC_TYPE_MJPEG; stream_create->src_width = stream->width; stream_create->src_height = stream->height; stream_create->stream_width = ALIGN(stream_create->src_width, 2); stream_create->stream_height = ALIGN(stream_create->src_height, 2); stream_create->dest = stream->dest_area; add_buf(channel, BUF_TYPE_RAW, stream_create, sizeof(*stream_create)); if (stream->current) { QXLDrawable *qxl_drawable = stream->current->qxl_drawable; stream_create->clip = qxl_drawable->clip; if (qxl_drawable->clip.type == CLIP_TYPE_RECTS) { fill_rects_clip(channel, &stream_create->clip.data); } else { ASSERT(qxl_drawable->clip.type == CLIP_TYPE_NONE); } display_begin_send_massage(display_channel, &stream->current->pipe_item); } else { stream_create->clip.type = CLIP_TYPE_RECTS; stream_create->clip.data = channel->send_data.header.size; display_channel->send_data.u.stream_create.num_rects = 0; add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.stream_create.num_rects, sizeof(uint32_t)); display_begin_send_massage(display_channel, NULL); } } static void red_display_send_stream_clip(DisplayChannel *display_channel, StreamClipItem *item) { RedChannel *channel = &display_channel->base; StreamAgent *agent = item->stream_agent; Stream *stream = agent->stream; ASSERT(stream); channel->send_data.header.type = RED_DISPLAY_STREAM_CLIP; RedStreamClip *stream_clip = &display_channel->send_data.u.stream_clip; add_buf(channel, BUF_TYPE_RAW, stream_clip, sizeof(*stream_clip)); stream_clip->id = agent - display_channel->stream_agents; if ((stream_clip->clip.type = item->clip_type) == CLIP_TYPE_NONE) { stream_clip->clip.data = 0; } else { ASSERT(stream_clip->clip.type == CLIP_TYPE_RECTS); stream_clip->clip.data = channel->send_data.header.size; add_buf(channel, BUF_TYPE_RAW, &item->region.num_rects, sizeof(uint32_t)); add_buf(channel, BUF_TYPE_RAW, item->region.rects, item->region.num_rects * sizeof(Rect)); } display_begin_send_massage(display_channel, item); } static void red_display_send_stream_end(DisplayChannel *display_channel, StreamAgent* agent) { RedChannel *channel = &display_channel->base; channel->send_data.header.type = RED_DISPLAY_STREAM_DESTROY; display_channel->send_data.u.stream_destroy.id = agent - display_channel->stream_agents; add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.stream_destroy, sizeof(RedStreamDestroy)); display_begin_send_massage(display_channel, NULL); } static void red_cursor_send_inval(CursorChannel *channel, CacheItem *cach_item) { ASSERT(channel); red_send_inval((RedChannel *)channel, cach_item, &channel->send_data.u.inval_one); } static void red_send_cursor_init(CursorChannel *channel) { RedWorker *worker; ASSERT(channel); worker = channel->base.worker; channel->base.send_data.header.type = RED_CURSOR_INIT; channel->send_data.u.cursor_init.visible = worker->cursor_visible; channel->send_data.u.cursor_init.position = worker->cursor_position; channel->send_data.u.cursor_init.trail_length = worker->cursor_trail_length; channel->send_data.u.cursor_init.trail_frequency = worker->cursor_trail_frequency; add_buf(&channel->base, BUF_TYPE_RAW, &channel->send_data.u.cursor_init, sizeof(RedCursorInit)); fill_cursor(channel, &channel->send_data.u.cursor_init.cursor, worker->cursor); red_begin_send_massage(&channel->base, worker->cursor); } static void red_send_local_cursor(CursorChannel *cursor_channel, LocalCursor *cursor) { RedChannel *channel; ASSERT(cursor_channel); channel = &cursor_channel->base; channel->send_data.header.type = RED_CURSOR_SET; cursor_channel->send_data.u.cursor_set.postition = cursor->position; cursor_channel->send_data.u.cursor_set.visible = channel->worker->cursor_visible; add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_set, sizeof(RedCursorSet)); fill_cursor(cursor_channel, &cursor_channel->send_data.u.cursor_set.cursor, &cursor->base); red_begin_send_massage(channel, cursor); red_release_cursor(channel->worker, (CursorItem *)cursor); } static void cursor_channel_send_migrate(CursorChannel *cursor_channel) { cursor_channel->base.send_data.header.type = RED_MIGRATE; cursor_channel->send_data.u.migrate.flags = 0; add_buf((RedChannel*)cursor_channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.migrate, sizeof(cursor_channel->send_data.u.migrate)); red_begin_send_massage((RedChannel*)cursor_channel, NULL); } static void red_send_cursor(CursorChannel *cursor_channel, CursorItem *cursor) { RedChannel *channel; QXLCursorCmd *cmd; ASSERT(cursor_channel); channel = &cursor_channel->base; cmd = CONTAINEROF(cursor, QXLCursorCmd, device_data); switch (cmd->type) { case QXL_CURSOR_MOVE: channel->send_data.header.type = RED_CURSOR_MOVE; cursor_channel->send_data.u.cursor_move.postition = cmd->u.position; add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_move, sizeof(RedCursorMove)); break; case QXL_CURSOR_SET: channel->send_data.header.type = RED_CURSOR_SET; cursor_channel->send_data.u.cursor_set.postition = cmd->u.set.position; cursor_channel->send_data.u.cursor_set.visible = channel->worker->cursor_visible; add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_set, sizeof(RedCursorSet)); fill_cursor(cursor_channel, &cursor_channel->send_data.u.cursor_set.cursor, cursor); break; case QXL_CURSOR_HIDE: channel->send_data.header.type = RED_CURSOR_HIDE; break; case QXL_CURSOR_TRAIL: channel->send_data.header.type = RED_CURSOR_TRAIL; cursor_channel->send_data.u.cursor_trail.length = cmd->u.trail.length; cursor_channel->send_data.u.cursor_trail.frequency = cmd->u.trail.frequency; add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_trail, sizeof(RedCursorTrail)); break; default: red_error("bad cursor command %d", cmd->type); } red_begin_send_massage(channel, cursor); red_release_cursor(channel->worker, cursor); } static inline PipeItem *red_pipe_get(RedChannel *channel) { PipeItem *item; if (!channel || channel->send_data.blocked || !(item = (PipeItem *)ring_get_tail(&channel->pipe))) { return NULL; } if (channel->messages_window > channel->client_ack_window * 2) { channel->send_data.blocked = TRUE; return NULL; } --channel->pipe_size; ring_remove(&item->link); return item; } static void display_channel_push(RedWorker *worker) { PipeItem *pipe_item; while ((pipe_item = red_pipe_get((RedChannel *)worker->display_channel))) { DisplayChannel *display_channel; display_channel = (DisplayChannel *)red_ref_channel((RedChannel *)worker->display_channel); red_display_reset_send_data(display_channel); switch (pipe_item->type) { case PIPE_ITEM_TYPE_DRAW: { Drawable *drawable = CONTAINEROF(pipe_item, Drawable, pipe_item); send_qxl_drawable(display_channel, drawable); release_drawable(worker, drawable); break; } case PIPE_ITEM_TYPE_INVAL_ONE: red_display_send_inval(display_channel, (CacheItem *)pipe_item); free(pipe_item); break; case PIPE_ITEM_TYPE_STREAM_CREATE: { StreamAgent *agent = CONTAINEROF(pipe_item, StreamAgent, create_item); red_display_send_stream_start(display_channel, agent); red_display_release_stream(display_channel, agent); break; } case PIPE_ITEM_TYPE_STREAM_CLIP: { StreamClipItem* clip_item = (StreamClipItem *)pipe_item; red_display_send_stream_clip(display_channel, clip_item); red_display_release_stream_clip(display_channel, clip_item); break; } case PIPE_ITEM_TYPE_STREAM_DESTROY: { StreamAgent *agent = CONTAINEROF(pipe_item, StreamAgent, destroy_item); red_display_send_stream_end(display_channel, agent); red_display_release_stream(display_channel, agent); break; } case PIPE_ITEM_TYPE_UPGRADE: red_display_send_upgrade(display_channel, (UpgradeItem *)pipe_item); release_upgrade_item(worker, (UpgradeItem *)pipe_item); break; case PIPE_ITEM_TYPE_VERB: display_send_verb(display_channel, ((VerbItem*)pipe_item)->verb); free(pipe_item); break; case PIPE_ITEM_TYPE_MODE: red_send_mode(display_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_MIGRATE: red_printf("PIPE_ITEM_TYPE_MIGRATE"); display_channel_send_migrate(display_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_MIGRATE_DATA: display_channel_send_migrate_data(display_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_SET_ACK: red_send_set_ack((RedChannel *)display_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_IMAGE: red_send_image(display_channel, (ImageItem *)pipe_item); release_image_item((ImageItem *)pipe_item); break; case PIPE_ITEM_TYPE_PIXMAP_SYNC: display_channel_pixmap_sync(display_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_PIXMAP_RESET: display_channel_reset_cache(display_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE: red_reset_palette_cache(display_channel); red_send_verb((RedChannel *)display_channel, RED_DISPLAY_INVAL_ALL_PALETTES); free(pipe_item); break; default: red_error("invalid pipe item type"); } red_unref_channel((RedChannel *)display_channel); } } static void cursor_channel_push(RedWorker *worker) { PipeItem *pipe_item; while ((pipe_item = red_pipe_get((RedChannel *)worker->cursor_channel))) { CursorChannel *cursor_channel; cursor_channel = (CursorChannel *)red_ref_channel((RedChannel *)worker->cursor_channel); red_channel_reset_send_data((RedChannel*)cursor_channel); switch (pipe_item->type) { case PIPE_ITEM_TYPE_CURSOR: red_send_cursor(cursor_channel, (CursorItem *)pipe_item); break; case PIPE_ITEM_TYPE_LOCAL_CURSOR: red_send_local_cursor(cursor_channel, (LocalCursor *)pipe_item); break; case PIPE_ITEM_TYPE_INVAL_ONE: red_cursor_send_inval(cursor_channel, (CacheItem *)pipe_item); free(pipe_item); break; case PIPE_ITEM_TYPE_VERB: red_send_verb((RedChannel *)cursor_channel, ((VerbItem*)pipe_item)->verb); free(pipe_item); break; case PIPE_ITEM_TYPE_MIGRATE: red_printf("PIPE_ITEM_TYPE_MIGRATE"); cursor_channel_send_migrate(cursor_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_SET_ACK: red_send_set_ack((RedChannel *)cursor_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_CURSOR_INIT: red_send_cursor_init(cursor_channel); free(pipe_item); break; case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE: red_reset_cursor_cache(cursor_channel); red_send_verb((RedChannel *)cursor_channel, RED_CURSOR_INVAL_ALL); free(pipe_item); break; default: red_error("invalid pipe item type"); } red_unref_channel((RedChannel *)cursor_channel); } } static inline void red_push(RedWorker *worker) { cursor_channel_push(worker); display_channel_push(worker); } typedef struct ShowTreeData { RedWorker *worker; int level; Container *container; } ShowTreeData; static void __show_tree_call(TreeItem *item, void *data) { ShowTreeData *tree_data = data; const char *item_prefix = "|--"; int i; while (tree_data->container != item->container) { ASSERT(tree_data->container); tree_data->level--; tree_data->container = tree_data->container->base.container; } switch (item->type) { case TREE_ITEM_TYPE_DRAWABLE: { Drawable *drawable = CONTAINEROF(item, Drawable, tree_item); const int max_indent = 200; char indent_str[max_indent + 1]; int indent_str_len; for (i = 0; i < tree_data->level; i++) { printf(" "); } printf(item_prefix, 0); show_qxl_drawable(tree_data->worker, drawable->qxl_drawable, NULL); for (i = 0; i < tree_data->level; i++) { printf(" "); } printf("| "); show_draw_item(tree_data->worker, &drawable->tree_item, NULL); indent_str_len = MIN(max_indent, strlen(item_prefix) + tree_data->level * 2); memset(indent_str, ' ', indent_str_len); indent_str[indent_str_len] = 0; region_dump(&item->rgn, indent_str); printf("\n"); break; } case TREE_ITEM_TYPE_CONTAINER: tree_data->level++; tree_data->container = (Container *)item; break; case TREE_ITEM_TYPE_SHADOW: break; } } void red_show_tree(RedWorker *worker) { ShowTreeData show_tree_data; show_tree_data.worker = worker; show_tree_data.level = 0; show_tree_data.container = NULL; current_tree_for_each(worker, __show_tree_call, &show_tree_data); } static inline int channel_is_connected(RedChannel *channel) { return !!channel->peer; } static void red_disconnect_channel(RedChannel *channel) { channel_release_res(channel); red_pipe_clear(channel); channel->peer->cb_free(channel->peer); channel->peer = NULL; channel->send_data.blocked = FALSE; channel->send_data.size = channel->send_data.pos = 0; red_unref_channel(channel); } static void red_disconnect_display(RedChannel *channel) { DisplayChannel *display_channel; if (!channel || !channel->peer) { return; } display_channel = (DisplayChannel *)channel; ASSERT(display_channel == channel->worker->display_channel); #ifdef COMPRESS_STAT print_compress_stats(display_channel); #endif channel->worker->display_channel = NULL; red_display_unshare_stream_buf(display_channel); red_release_pixmap_cache(display_channel); red_release_glz(display_channel); red_reset_palette_cache(display_channel); free(display_channel->send_data.stream_outbuf); red_display_reset_compress_buf(display_channel); while (display_channel->send_data.free_compress_bufs) { RedCompressBuf *buf = display_channel->send_data.free_compress_bufs; display_channel->send_data.free_compress_bufs = buf->next; free(buf); } free(display_channel->send_data.free_list.res); red_display_destroy_streams(display_channel); red_disconnect_channel(channel); } static void red_migrate_display(RedWorker *worker) { if (worker->display_channel) { red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_STREAM_DESTROY_ALL); red_pipe_add_type(&worker->display_channel->base, PIPE_ITEM_TYPE_MIGRATE); } } static void destroy_cairo_canvas(CairoCanvas *canvas) { cairo_t *cairo; if (!canvas) { return; } cairo = canvas_get_cairo(canvas); canvas_destroy(canvas); cairo_destroy(cairo); } static void validate_area_nop(void *canvas, const DrawArea *draw_area, const Rect *area) { } static void init_cairo_draw_context(RedWorker *worker, CairoCanvas *canvas) { worker->draw_context.canvas = canvas; worker->draw_context.top_down = TRUE; worker->draw_context.set_access_params = (set_access_params_t)canvas_set_access_params; worker->draw_context.draw_fill = (draw_fill_t)canvas_draw_fill; worker->draw_context.draw_copy = (draw_copy_t)canvas_draw_copy; worker->draw_context.draw_opaque = (draw_opaque_t)canvas_draw_opaque; worker->draw_context.copy_bits = (copy_bits_t)canvas_copy_bits; worker->draw_context.draw_text = (draw_text_t)canvas_draw_text; worker->draw_context.draw_stroke = (draw_stroke_t)canvas_draw_stroke; worker->draw_context.draw_rop3 = (draw_rop3_t)canvas_draw_rop3; worker->draw_context.draw_blend = (draw_blend_t)canvas_draw_blend; worker->draw_context.draw_blackness = (draw_blackness_t)canvas_draw_blackness; worker->draw_context.draw_whiteness = (draw_whiteness_t)canvas_draw_whiteness; worker->draw_context.draw_invers = (draw_invers_t)canvas_draw_invers; worker->draw_context.draw_transparent = (draw_transparent_t)canvas_draw_transparent; worker->draw_context.draw_alpha_blend = (draw_alpha_blend_t)canvas_draw_alpha_blend; worker->draw_context.read_pixels = (read_pixels_t)canvas_read_bits; worker->draw_context.set_top_mask = (set_top_mask_t)canvas_group_start; worker->draw_context.clear_top_mask = (clear_top_mask_t)canvas_group_end; worker->draw_context.validate_area = validate_area_nop; worker->draw_context.destroy = (destroy_t)destroy_cairo_canvas; } static int create_cairo_context(RedWorker *worker) { cairo_surface_t *cairo_surface; CairoCanvas *canvas; cairo_t *cairo; cairo_surface = cairo_image_surface_create_for_data(worker->dev_info.draw_area.line_0, CAIRO_FORMAT_RGB24, worker->dev_info.draw_area.width, worker->dev_info.draw_area.heigth, worker->dev_info.draw_area.stride); if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) { red_error("create cairo surface failed, %s", cairo_status_to_string(cairo_surface_status(cairo_surface))); } cairo = cairo_create(cairo_surface); cairo_surface_destroy(cairo_surface); if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) { red_error("create cairo failed, %s", cairo_status_to_string(cairo_status(cairo))); } if (!(canvas = canvas_create(cairo, worker->dev_info.bits, &worker->image_cache, image_cache_put, image_cache_get))) { red_error("create cairo canvas failed"); } init_cairo_draw_context(worker, canvas); red_printf("using cairo canvas"); return TRUE; } static void destroy_gl_canvas(GLCanvas *canvas) { OGLCtx *ctx; if (!canvas) { return; } ctx = gl_canvas_get_usr_data(canvas); ASSERT(ctx); gl_canvas_destroy(canvas, 0); oglctx_destroy(ctx); } static void gl_validate_area(GLCanvas *canvas, DrawArea *draw_area, const Rect *area) { int h; if (!(h = area->bottom - area->top)) { return; } ASSERT(draw_area->stride < 0); uint8_t *dest = draw_area->line_0 + (area->top * draw_area->stride) + area->left * sizeof(uint32_t); dest += (h - 1) * draw_area->stride; gl_canvas_read_pixels(canvas, dest, -draw_area->stride, area); } static void init_ogl_draw_context(RedWorker *worker, GLCanvas *canvas) { worker->draw_context.canvas = canvas; worker->draw_context.top_down = FALSE; worker->draw_context.set_access_params = (set_access_params_t)gl_canvas_set_access_params; worker->draw_context.draw_fill = (draw_fill_t)gl_canvas_draw_fill; worker->draw_context.draw_copy = (draw_copy_t)gl_canvas_draw_copy; worker->draw_context.draw_opaque = (draw_opaque_t)gl_canvas_draw_opaque; worker->draw_context.copy_bits = (copy_bits_t)gl_canvas_copy_pixels; worker->draw_context.draw_text = (draw_text_t)gl_canvas_draw_text; worker->draw_context.draw_stroke = (draw_stroke_t)gl_canvas_draw_stroke; worker->draw_context.draw_rop3 = (draw_rop3_t)gl_canvas_draw_rop3; worker->draw_context.draw_blend = (draw_blend_t)gl_canvas_draw_blend; worker->draw_context.draw_blackness = (draw_blackness_t)gl_canvas_draw_blackness; worker->draw_context.draw_whiteness = (draw_whiteness_t)gl_canvas_draw_whiteness; worker->draw_context.draw_invers = (draw_invers_t)gl_canvas_draw_invers; worker->draw_context.draw_transparent = (draw_transparent_t)gl_canvas_draw_transparent; worker->draw_context.draw_alpha_blend = (draw_alpha_blend_t)gl_canvas_draw_alpha_blend; worker->draw_context.read_pixels = (read_pixels_t)gl_canvas_read_pixels; worker->draw_context.set_top_mask = (set_top_mask_t)gl_canvas_set_top_mask; worker->draw_context.clear_top_mask = (clear_top_mask_t)gl_canvas_clear_top_mask; worker->draw_context.validate_area = (validate_area_t)gl_validate_area; worker->draw_context.destroy = (destroy_t)destroy_gl_canvas; } static int create_ogl_context_common(RedWorker *worker, OGLCtx *ctx) { GLCanvas *canvas; int width; int height; width = worker->dev_info.x_res; height = worker->dev_info.y_res; oglctx_make_current(ctx); if (!(canvas = gl_canvas_create(ctx, width, height, worker->dev_info.bits, &worker->image_cache, image_cache_put, image_cache_get))) { return FALSE; } init_ogl_draw_context(worker, canvas); gl_canvas_clear(canvas); red_printf("using ogl %s canvas", oglctx_type_str(ctx)); return TRUE; } static int create_ogl_pbuf_context(RedWorker *worker) { OGLCtx *ctx; if (!(ctx = pbuf_create(worker->dev_info.x_res, worker->dev_info.y_res))) { return FALSE; } if (!create_ogl_context_common(worker, ctx)) { oglctx_destroy(ctx); return FALSE; } return TRUE; } static int create_ogl_pixmap_context(RedWorker *worker) { OGLCtx *ctx; if (!(ctx = pixmap_create(worker->dev_info.x_res, worker->dev_info.y_res))) { return FALSE; } if (!create_ogl_context_common(worker, ctx)) { oglctx_destroy(ctx); return FALSE; } return TRUE; } static void red_create_draw_context(RedWorker *worker) { int i; worker->draw_context.destroy(worker->draw_context.canvas); worker->draw_context.canvas = NULL; for (i = 0; i < worker->num_renderers; i++) { switch (worker->renderers[i]) { case RED_RENDERER_CAIRO: if (create_cairo_context(worker)) { return; } break; case RED_RENDERER_OGL_PBUF: if (create_ogl_pbuf_context(worker)) { return; } break; case RED_RENDERER_OGL_PIXMAP: if (create_ogl_pixmap_context(worker)) { return; } break; default: red_error("invalid renderer type"); } } red_error("create renderer failed"); memset(worker->dev_info.draw_area.buf, 0, worker->dev_info.draw_area.size); } static void push_new_mode(RedWorker *worker) { DisplayChannel *display_channel; if ((display_channel = worker->display_channel)) { red_pipe_add_type(&display_channel->base, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE); if (!display_channel->base.migrate) { red_pipe_add_type(&display_channel->base, PIPE_ITEM_TYPE_MODE); } display_channel_push(worker); } } static int display_channel_wait_for_init(DisplayChannel *display_channel) { display_channel->expect_init = TRUE; uint64_t end_time = red_now() + DISPLAY_CLIENT_TIMEOUT; for (;;) { red_receive((RedChannel *)display_channel); if (!display_channel->base.peer) { break; } if (display_channel->pixmap_cache && display_channel->glz_dict) { display_channel->pixmap_cache_generation = display_channel->pixmap_cache->generation; display_channel->glz = glz_encoder_create(display_channel->base.id, display_channel->glz_dict->dict, &display_channel->glz_data.usr); if (!display_channel->glz) { PANIC("create global lz failed"); } return TRUE; } if (red_now() > end_time) { red_printf("timeout"); red_disconnect_display((RedChannel *)display_channel); break; } usleep(DISPLAY_CLIENT_RETRY_INTERVAL); } return FALSE; } static void on_new_display_channel(RedWorker *worker) { DisplayChannel *display_channel = worker->display_channel; ASSERT(display_channel); red_pipe_add_type(&display_channel->base, PIPE_ITEM_TYPE_SET_ACK); if (display_channel->base.migrate) { display_channel->expect_migrate_data = TRUE; return; } if (!display_channel_wait_for_init(display_channel)) { return; } display_channel->base.messages_window = 0; if (worker->attached) { red_current_flush(worker); push_new_mode(worker); red_add_screen_image(worker); if (channel_is_connected(&display_channel->base)) { red_pipe_add_verb(&display_channel->base, RED_DISPLAY_MARK); red_disply_start_streams(display_channel); } } } static int channel_handle_message(RedChannel *channel, RedDataHeader *message) { switch (message->type) { case REDC_ACK_SYNC: if (message->size != sizeof(uint32_t)) { red_printf("bad message size"); return FALSE; } channel->client_ack_generation = *(uint32_t *)(message + 1); break; case REDC_ACK: if (channel->client_ack_generation == channel->ack_generation) { channel->messages_window -= channel->client_ack_window; } break; case REDC_DISCONNECTING: break; default: red_printf("invalid message type %u", message->type); return FALSE; } return TRUE; } static GlzSharedDictionary *_red_find_glz_dictionary(uint8_t dict_id) { RingItem *now; GlzSharedDictionary *ret = NULL; now = &glz_dictionary_list; while ((now = ring_next(&glz_dictionary_list, now))) { if (((GlzSharedDictionary *)now)->id == dict_id) { ret = (GlzSharedDictionary *)now; break; } } return ret; } static GlzSharedDictionary *_red_create_glz_dictionary(DisplayChannel *display, uint8_t id, GlzEncDictContext *opaque_dict) { GlzSharedDictionary *shared_dict = malloc(sizeof(*shared_dict)); memset(shared_dict, 0, sizeof(*shared_dict)); // nullify the ring base if (!shared_dict) { return NULL; } shared_dict->dict = opaque_dict; shared_dict->id = id; shared_dict->refs = 1; shared_dict->migrate_freeze = FALSE; ring_item_init(&shared_dict->base); pthread_rwlock_init(&shared_dict->encode_lock, NULL); return shared_dict; } static GlzSharedDictionary *red_create_glz_dictionary(DisplayChannel *display, uint8_t id, int window_size) { GlzEncDictContext *glz_dict = glz_enc_dictionary_create(window_size, MAX_LZ_ENCODERS, &display->glz_data.usr); #ifdef COMPRESS_DEBUG red_printf("Lz Window %d Size=%d", id, window_size); #endif if (!glz_dict) { PANIC("failed creating lz dictionary"); return NULL; } return _red_create_glz_dictionary(display, id, glz_dict); } static GlzSharedDictionary *red_create_restored_glz_dictionary(DisplayChannel *display, uint8_t id, GlzEncDictRestoreData *restore_data) { GlzEncDictContext *glz_dict = glz_enc_dictionary_restore(restore_data, &display->glz_data.usr); if (!glz_dict) { PANIC("failed creating lz dictionary"); return NULL; } return _red_create_glz_dictionary(display, id, glz_dict); } static GlzSharedDictionary *red_get_glz_dictionary(DisplayChannel *display, uint8_t id, int window_size) { GlzSharedDictionary *shared_dict = NULL; pthread_mutex_lock(&glz_dictionary_list_lock); shared_dict = _red_find_glz_dictionary(id); if (!shared_dict) { shared_dict = red_create_glz_dictionary(display, id, window_size); ring_add(&glz_dictionary_list, &shared_dict->base); } else { shared_dict->refs++; } pthread_mutex_unlock(&glz_dictionary_list_lock); return shared_dict; } static GlzSharedDictionary *red_restore_glz_dictionary(DisplayChannel *display, uint8_t id, GlzEncDictRestoreData *restore_data) { GlzSharedDictionary *shared_dict = NULL; pthread_mutex_lock(&glz_dictionary_list_lock); shared_dict = _red_find_glz_dictionary(id); if (!shared_dict) { shared_dict = red_create_restored_glz_dictionary(display, id, restore_data); ring_add(&glz_dictionary_list, &shared_dict->base); } else { shared_dict->refs++; } pthread_mutex_unlock(&glz_dictionary_list_lock); return shared_dict; } static void red_freeze_glz(DisplayChannel *channel) { pthread_rwlock_wrlock(&channel->glz_dict->encode_lock); if (!channel->glz_dict->migrate_freeze) { channel->glz_dict->migrate_freeze = TRUE; } pthread_rwlock_unlock(&channel->glz_dict->encode_lock); } /* destroy encoder, and dictionary if no one uses it*/ static void red_release_glz(DisplayChannel *channel) { GlzSharedDictionary *shared_dict; red_display_clear_glz_drawables(channel); glz_encoder_destroy(channel->glz); channel->glz = NULL; if (!(shared_dict = channel->glz_dict)) { return; } channel->glz_dict = NULL; pthread_mutex_lock(&cache_lock); if (--shared_dict->refs) { pthread_mutex_unlock(&cache_lock); return; } ring_remove(&shared_dict->base); pthread_mutex_unlock(&cache_lock); glz_enc_dictionary_destroy(shared_dict->dict, &channel->glz_data.usr); free(shared_dict); } static PixmapCache *red_create_pixmap_cache(uint8_t id, int64_t size) { PixmapCache *cache = malloc(sizeof(*cache)); if (!cache) { return NULL; } memset(cache, 0, sizeof(*cache)); ring_item_init(&cache->base); pthread_mutex_init(&cache->lock, NULL); cache->id = id; cache->refs = 1; ring_init(&cache->lru); cache->available = size; cache->size = size; return cache; } static PixmapCache *red_get_pixmap_cache(uint8_t id, int64_t size) { PixmapCache *cache = NULL; RingItem *now; pthread_mutex_lock(&cache_lock); now = &pixmap_cache_list; while ((now = ring_next(&pixmap_cache_list, now))) { if (((PixmapCache *)now)->id == id) { cache = (PixmapCache *)now; cache->refs++; break; } } if (!cache) { cache = red_create_pixmap_cache(id, size); ring_add(&pixmap_cache_list, &cache->base); } pthread_mutex_unlock(&cache_lock); return cache; } static void red_release_pixmap_cache(DisplayChannel *channel) { PixmapCache *cache; if (!(cache = channel->pixmap_cache)) { return; } channel->pixmap_cache = NULL; pthread_mutex_lock(&cache_lock); if (--cache->refs) { pthread_mutex_unlock(&cache_lock); return; } ring_remove(&cache->base); pthread_mutex_unlock(&cache_lock); pixmap_cache_destroy(cache); free(cache); } static int display_channel_init_cache(DisplayChannel *channel, RedcDisplayInit *init_info) { ASSERT(!channel->pixmap_cache); return !!(channel->pixmap_cache = red_get_pixmap_cache(init_info->pixmap_cache_id, init_info->pixmap_cache_size)); } static int display_channel_init_glz_dictionary(DisplayChannel *channel, RedcDisplayInit *init_info) { ASSERT(!channel->glz_dict); ring_init(&channel->glz_drawables); ring_init(&channel->glz_drawables_inst_to_free); pthread_mutex_init(&channel->glz_drawables_inst_to_free_lock, NULL); return !!(channel->glz_dict = red_get_glz_dictionary(channel, init_info->glz_dictionary_id, init_info->glz_dictionary_window_size)); } static int display_channel_init(DisplayChannel *channel, RedcDisplayInit *init_info) { return (display_channel_init_cache(channel, init_info) && display_channel_init_glz_dictionary(channel, init_info)); } static int display_channel_handle_migrate_glz_dictionary(DisplayChannel *channel, DisplayChannelMigrateData *migrate_info) { ASSERT(!channel->glz_dict); ring_init(&channel->glz_drawables); ring_init(&channel->glz_drawables_inst_to_free); pthread_mutex_init(&channel->glz_drawables_inst_to_free_lock, NULL); return !!(channel->glz_dict = red_restore_glz_dictionary(channel, migrate_info->glz_dict_id, &migrate_info->glz_dict_restore_data)); } static int display_channel_handle_migrate_mark(DisplayChannel *channel) { if (!channel->expect_migrate_mark) { red_printf("unexpected"); return FALSE; } channel->expect_migrate_mark = FALSE; red_pipe_add_type((RedChannel *)channel, PIPE_ITEM_TYPE_MIGRATE_DATA); return TRUE; } static int display_channel_handle_migrate_data(DisplayChannel *channel, RedDataHeader *message) { DisplayChannelMigrateData *migrate_data; int i; if (!channel->expect_migrate_data) { red_printf("unexpected"); return FALSE; } channel->expect_migrate_data = FALSE; if (message->size < sizeof(*migrate_data)) { red_printf("bad message size"); return FALSE; } migrate_data = (DisplayChannelMigrateData *)(message + 1); if (migrate_data->magic != DISPLAY_MIGRATE_DATA_MAGIC || migrate_data->version != DISPLAY_MIGRATE_DATA_VERSION) { red_printf("invalid content"); return FALSE; } ASSERT(channel->base.send_data.header.serial == 0); channel->base.send_data.header.serial = migrate_data->message_serial; if (!(channel->pixmap_cache = red_get_pixmap_cache(migrate_data->pixmap_cache_id, -1))) { return FALSE; } pthread_mutex_lock(&channel->pixmap_cache->lock); for (i = 0; i < MAX_CACHE_CLIENTS; i++) { channel->pixmap_cache->sync[i] = MAX(channel->pixmap_cache->sync[i], migrate_data->pixmap_cache_clients[i]); } pthread_mutex_unlock(&channel->pixmap_cache->lock); if (migrate_data->pixmap_cache_freezer) { channel->pixmap_cache->size = migrate_data->pixmap_cache_size; red_pipe_add_type((RedChannel *)channel, PIPE_ITEM_TYPE_PIXMAP_RESET); } if (display_channel_handle_migrate_glz_dictionary(channel, migrate_data)) { channel->glz = glz_encoder_create(channel->base.id, channel->glz_dict->dict, &channel->glz_data.usr); if (!channel->glz) { PANIC("create global lz failed"); } } else { PANIC("restoring global lz dictionary failed"); } channel->base.messages_window = 0; return TRUE; } static int display_channel_handle_message(RedChannel *channel, RedDataHeader *message) { switch (message->type) { case REDC_DISPLAY_INIT: if (message->size != sizeof(RedcDisplayInit)) { red_printf("bad message size"); return FALSE; } if (!((DisplayChannel *)channel)->expect_init) { red_printf("unexpected REDC_DISPLAY_INIT"); return FALSE; } ((DisplayChannel *)channel)->expect_init = FALSE; return display_channel_init((DisplayChannel *)channel, (RedcDisplayInit *)(message + 1)); case REDC_MIGRATE_FLUSH_MARK: return display_channel_handle_migrate_mark((DisplayChannel *)channel); case REDC_MIGRATE_DATA: return display_channel_handle_migrate_data((DisplayChannel *)channel, message); default: return channel_handle_message(channel, message); } } static void red_receive(RedChannel *channel) { for (;;) { ssize_t n; n = channel->recive_data.end - channel->recive_data.now; ASSERT(n); ASSERT(channel->peer); if ((n = channel->peer->cb_read(channel->peer->ctx, channel->recive_data.now, n)) <= 0) { if (n == 0) { channel->disconnect(channel); return; } ASSERT(n == -1); switch (errno) { case EAGAIN: return; case EINTR: break; case EPIPE: channel->disconnect(channel); return; default: red_printf("%s", strerror(errno)); channel->disconnect(channel); return; } } else { channel->recive_data.now += n; for (;;) { RedDataHeader *message = channel->recive_data.message; n = channel->recive_data.now - (uint8_t *)message; if (n < sizeof(RedDataHeader) || n < sizeof(RedDataHeader) + message->size) { break; } if (!channel->handle_message(channel, message)) { channel->disconnect(channel); return; } channel->recive_data.message = (RedDataHeader *)((uint8_t *)message + sizeof(RedDataHeader) + message->size); } if (channel->recive_data.now == (uint8_t *)channel->recive_data.message) { channel->recive_data.now = channel->recive_data.buf; channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf; } else if (channel->recive_data.now == channel->recive_data.end) { memcpy(channel->recive_data.buf, channel->recive_data.message, n); channel->recive_data.now = channel->recive_data.buf + n; channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf; } } } } static RedChannel *__new_channel(RedWorker *worker, int size, RedsStreamContext *peer, int migrate, event_listener_action_proc handler, disconnect_channel_proc disconnect, hold_item_proc hold_item, release_item_proc release_item, handle_message_proc handle_message) { struct epoll_event event; RedChannel *channel; int flags; int delay_val; if ((flags = fcntl(peer->socket, F_GETFL)) == -1) { red_printf("accept failed, %s", strerror(errno)); goto error1; } if (fcntl(peer->socket, F_SETFL, flags | O_NONBLOCK) == -1) { red_printf("accept failed, %s", strerror(errno)); goto error1; } delay_val = IS_LOW_BANDWIDTH() ? 0 : 1; if (setsockopt(peer->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) { red_printf("setsockopt failed, %s", strerror(errno)); } ASSERT(size >= sizeof(*channel)); if (!(channel = malloc(size))) { red_printf("malloc failed"); goto error1; } memset(channel, 0, size); channel->id = worker->id; channel->listener.refs = 1; channel->listener.action = handler; channel->disconnect = disconnect; channel->hold_item = hold_item; channel->release_item = release_item; channel->handle_message = handle_message; channel->peer = peer; channel->worker = worker; channel->messages_window = ~0; // blocks send message (maybe use send_data.blocked + // block flags) channel->client_ack_window = IS_LOW_BANDWIDTH() ? WIDE_CLIENT_ACK_WINDOW : NARROW_CLIENT_ACK_WINDOW; channel->client_ack_generation = ~0; channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf; channel->recive_data.now = channel->recive_data.buf; channel->recive_data.end = channel->recive_data.buf + sizeof(channel->recive_data.buf); ring_init(&channel->pipe); event.events = EPOLLIN | EPOLLOUT | EPOLLET; event.data.ptr = channel; if (epoll_ctl(worker->epoll, EPOLL_CTL_ADD, peer->socket, &event) == -1) { red_printf("epoll_ctl failed, %s", strerror(errno)); goto error2; } channel->migrate = migrate; return channel; error2: free(channel); error1: peer->cb_free(peer); return NULL; } static void handle_channel_events(EventListener *in_listener, uint32_t events) { RedChannel *channel = (RedChannel *)in_listener; if ((events & EPOLLIN)) { red_receive(channel); } if (channel->send_data.blocked) { red_send_data(channel, NULL); } } static void display_channel_hold_item(void *item) { ASSERT(item); switch (((PipeItem *)item)->type) { case PIPE_ITEM_TYPE_DRAW: case PIPE_ITEM_TYPE_STREAM_CREATE: CONTAINEROF(item, Drawable, pipe_item)->refs++; break; case PIPE_ITEM_TYPE_STREAM_CLIP: ((StreamClipItem *)item)->refs++; break; case PIPE_ITEM_TYPE_UPGRADE: ((UpgradeItem *)item)->refs++; break; case PIPE_ITEM_TYPE_IMAGE: ((ImageItem *)item)->refs++; break; default: PANIC("invalid item type"); } } static void display_channel_release_item(RedChannel *channel, void *item) { ASSERT(item); switch (((PipeItem *)item)->type) { case PIPE_ITEM_TYPE_DRAW: case PIPE_ITEM_TYPE_STREAM_CREATE: release_drawable(channel->worker, CONTAINEROF(item, Drawable, pipe_item)); break; case PIPE_ITEM_TYPE_STREAM_CLIP: red_display_release_stream_clip((DisplayChannel *)channel, (StreamClipItem *)item); break; case PIPE_ITEM_TYPE_UPGRADE: release_upgrade_item(channel->worker, (UpgradeItem *)item); break; case PIPE_ITEM_TYPE_IMAGE: release_image_item((ImageItem *)item); break; default: PANIC("invalid item type"); } } static void handle_new_display_channel(RedWorker *worker, RedsStreamContext *peer, int migrate) { DisplayChannel *display_channel; size_t stream_buf_size; red_disconnect_display((RedChannel *)worker->display_channel); if (!(display_channel = (DisplayChannel *)__new_channel(worker, sizeof(*display_channel), peer, migrate, handle_channel_events, red_disconnect_display, display_channel_hold_item, display_channel_release_item, display_channel_handle_message))) { return; } #ifdef RED_STATISTICS display_channel->stat = stat_add_node(worker->stat, "display_channel", TRUE); display_channel->base.out_bytes_counter = stat_add_counter(display_channel->stat, "out_bytes", TRUE); display_channel->cache_hits_counter = stat_add_counter(display_channel->stat, "cache_hits", TRUE); display_channel->add_to_cache_counter = stat_add_counter(display_channel->stat, "add_to_cache", TRUE); display_channel->non_cache_counter = stat_add_counter(display_channel->stat, "non_cache", TRUE); #endif ring_init(&display_channel->palette_cache_lru); display_channel->palette_cache_available = CLIENT_PALETTE_CACHE_SIZE; red_display_init_streams(display_channel); stream_buf_size = FF_MIN_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE; if (!(display_channel->send_data.stream_outbuf = malloc(stream_buf_size))) { PANIC("alloc failed"); } display_channel->send_data.stream_outbuf_size = FF_MIN_BUFFER_SIZE; red_display_share_stream_buf(display_channel); red_display_init_glz_data(display_channel); worker->display_channel = display_channel; if (!(display_channel->send_data.free_list.res = malloc(sizeof(RedResorceList) + DISPLAY_FREE_LIST_DEFAULT_SIZE * sizeof(RedResorceID)))) { PANIC("free list alloc failed"); } display_channel->send_data.free_list.res_size = DISPLAY_FREE_LIST_DEFAULT_SIZE; red_ref_channel((RedChannel*)display_channel); on_new_display_channel(worker); red_unref_channel((RedChannel*)display_channel); 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); } static void red_disconnect_cursor(RedChannel *channel) { if (!channel || !channel->peer) { return; } ASSERT(channel == (RedChannel *)channel->worker->cursor_channel); channel->worker->cursor_channel = NULL; red_reset_cursor_cache((CursorChannel *)channel); red_disconnect_channel(channel); } static void red_migrate_cursor(RedWorker *worker) { if (worker->cursor_channel) { red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE); red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_MIGRATE); } } static void on_new_cursor_channel(RedWorker *worker) { CursorChannel *channel = worker->cursor_channel; ASSERT(channel); channel->base.messages_window = 0; red_pipe_add_type(&channel->base, PIPE_ITEM_TYPE_SET_ACK); if (worker->attached && !channel->base.migrate) { red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_CURSOR_INIT); } } static void cursor_channel_hold_item(void *item) { ASSERT(item); ((CursorItem *)item)->refs++; } static void cursor_channel_release_item(RedChannel *channel, void *item) { ASSERT(item); red_release_cursor(channel->worker, item); } static void red_connect_cursor(RedWorker *worker, RedsStreamContext *peer, int migrate) { CursorChannel *channel; red_disconnect_cursor((RedChannel *)worker->cursor_channel); if (!(channel = (CursorChannel *)__new_channel(worker, sizeof(*channel), peer, migrate, handle_channel_events, red_disconnect_cursor, cursor_channel_hold_item, cursor_channel_release_item, channel_handle_message))) { return; } #ifdef RED_STATISTICS channel->stat = stat_add_node(worker->stat, "cursor_channel", TRUE); channel->base.out_bytes_counter = stat_add_counter(channel->stat, "out_bytes", TRUE); #endif ring_init(&channel->cursor_cache_lru); channel->cursor_cache_available = CLIENT_CURSOR_CACHE_SIZE; worker->cursor_channel = channel; on_new_cursor_channel(worker); } typedef struct __attribute__ ((__packed__)) CursorData { uint32_t visible; Point16 position; uint16_t trail_length; uint16_t trail_frequency; uint32_t data_size; RedCursor _cursor; } CursorData; static void red_save_cursor(RedWorker *worker) { CursorData *cursor_data; LocalCursor *local; int size; ASSERT(worker->cursor); ASSERT(worker->cursor->type == CURSOR_TYPE_LOCAL); local = (LocalCursor *)worker->cursor; size = sizeof(CursorData) + sizeof(RedCursor) + local->data_size; cursor_data = malloc(size); ASSERT(cursor_data); cursor_data->position = worker->cursor_position; cursor_data->visible = worker->cursor_visible; cursor_data->trail_frequency = worker->cursor_trail_frequency; cursor_data->trail_length = worker->cursor_trail_length; cursor_data->data_size = local->data_size; cursor_data->_cursor.header = local->red_cursor.header; memcpy(cursor_data->_cursor.data, local->red_cursor.data, local->data_size); worker->qxl->set_save_data(worker->qxl, cursor_data, size); } static LocalCursor *_new_local_cursor(CursorHeader *header, int data_size, Point16 position) { LocalCursor *local; local = (LocalCursor *)malloc(sizeof(LocalCursor) + data_size); if (!local) { return NULL; } red_pipe_item_init(&local->base.pipe_data, PIPE_ITEM_TYPE_LOCAL_CURSOR); local->base.refs = 1; local->base.type = CURSOR_TYPE_LOCAL; local->red_cursor.header = *header; local->red_cursor.header.unique = 0; local->red_cursor.flags = 0; local->position = position; local->data_size = data_size; return local; } static void red_cursor_flush(RedWorker *worker) { QXLCursorCmd *cursor_cmd; QXLCursor *qxl_cursor; LocalCursor *local; uint32_t data_size; QXLDataChunk *chunk; uint8_t *dest; if (!worker->cursor || worker->cursor->type == CURSOR_TYPE_LOCAL) { return; } ASSERT(worker->cursor->type == CURSOR_TYPE_DEV); cursor_cmd = CONTAINEROF(worker->cursor, QXLCursorCmd, device_data); ASSERT(cursor_cmd->type == QXL_CURSOR_SET); qxl_cursor = (QXLCursor *)(cursor_cmd->u.set.shape + worker->dev_info.phys_delta); local = _new_local_cursor(&qxl_cursor->header, qxl_cursor->data_size, worker->cursor_position); ASSERT(local); data_size = local->data_size; dest = local->red_cursor.data; chunk = &qxl_cursor->chunk; while (data_size) { ASSERT(chunk); ASSERT(chunk->data_size <= data_size); memcpy(dest, chunk->data, chunk->data_size); data_size -= chunk->data_size; dest += chunk->data_size; chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + worker->dev_info.phys_delta) : NULL; } red_set_cursor(worker, &local->base); red_release_cursor(worker, &local->base); } static void red_save(RedWorker *worker) { if (!worker->cursor) { worker->qxl->set_save_data(worker->qxl, NULL, 0); return; } red_save_cursor(worker); } static void red_cursor_load(RedWorker *worker) { CursorData *cursor_data = worker->qxl->get_save_data(worker->qxl); LocalCursor *local; if (!cursor_data) { return; } worker->cursor_position = cursor_data->position; worker->cursor_visible = cursor_data->visible; worker->cursor_trail_frequency = cursor_data->trail_frequency; worker->cursor_trail_length = cursor_data->trail_length; local = _new_local_cursor(&cursor_data->_cursor.header, cursor_data->data_size, cursor_data->position); ASSERT(local); memcpy(local->red_cursor.data, cursor_data->_cursor.data, cursor_data->data_size); red_set_cursor(worker, &local->base); red_release_cursor(worker, &local->base); } static void red_wait_outgoiong_item(RedChannel *channel) { uint64_t end_time; int blocked; if (!channel || !channel->send_data.blocked) { return; } red_ref_channel(channel); end_time = red_now() + DETACH_TIMEOUT; red_printf("blocked"); do { usleep(DETACH_SLEEP_DURATION); red_receive(channel); red_send_data(channel, NULL); } while ((blocked = channel->send_data.blocked) && red_now() < end_time); if (blocked) { red_printf("timeout"); channel->disconnect(channel); } red_unref_channel(channel); } static inline void handle_dev_update(RedWorker *worker) { RedWorkeMessage message; ASSERT(worker->attached && worker->running); for (;;) { uint64_t end_time; while (red_process_commands(worker, MAX_PIPE_SIZE)) { display_channel_push(worker); } if (!worker->qxl->has_command(worker->qxl)) { break; } end_time = red_now() + DISPLAY_CLIENT_TIMEOUT; int sleep_count = 0; for (;;) { display_channel_push(worker); if (!worker->display_channel || worker->display_channel->base.pipe_size <= MAX_PIPE_SIZE) { break; } RedChannel *channel = (RedChannel *)worker->display_channel; red_ref_channel(channel); red_receive(channel); red_send_data(channel, NULL); if (red_now() >= end_time) { red_printf("update timout"); red_disconnect_display((RedChannel *)worker->display_channel); } else { sleep_count++; usleep(DISPLAY_CLIENT_RETRY_INTERVAL); } red_unref_channel(channel); } } red_update_area(worker, worker->qxl->get_update_area(worker->qxl)); message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); } static void handle_dev_input(EventListener *listener, uint32_t events) { RedWorker *worker = CONTAINEROF(listener, RedWorker, dev_listener); RedWorkeMessage message; read_message(worker->channel, &message); switch (message) { case RED_WORKER_MESSAGE_UPDATE: handle_dev_update(worker); break; case RED_WORKER_MESSAGE_WAKEUP: clear_bit(RED_WORKER_PENDING_WAKEUP, worker->pending); stat_inc_counter(worker->wakeup_counter, 1); break; case RED_WORKER_MESSAGE_OOM: ASSERT(worker->attached && worker->running); while (red_process_commands(worker, MAX_PIPE_SIZE)) { display_channel_push(worker); } if (worker->qxl->flush_resources(worker->qxl) == 0) { red_printf("oom current %u pipe %u", worker->current_size, worker->display_channel ? worker->display_channel->base.pipe_size : 0); red_free_some(worker); } clear_bit(RED_WORKER_PENDING_OOM, worker->pending); break; case RED_WORKER_MESSAGE_ATTACH: ASSERT(!worker->attached); red_printf("attach"); receive_data(worker->channel, &worker->dev_info, sizeof(QXLDevInfo)); worker->attached = TRUE; worker->repoll_cmd_ring = worker->repoll_cursor_ring = 0; red_create_draw_context(worker); ASSERT(ring_is_empty(&worker->current)); if (worker->display_channel && !worker->display_channel->base.migrate) { red_pipe_add_type(&worker->display_channel->base, PIPE_ITEM_TYPE_MODE); red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_MARK); display_channel_push(worker); } if (worker->cursor_channel && !worker->cursor_channel->base.migrate) { red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_CURSOR_INIT); } message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); break; case RED_WORKER_MESSAGE_DETACH: red_printf("detach"); red_display_clear_glz_drawables(worker->display_channel); red_current_clear(worker); if (worker->display_channel) { red_pipe_clear_device_resources(&worker->display_channel->base); } if (worker->cursor_channel) { red_pipe_clear_device_resources(&worker->cursor_channel->base); } #ifdef STREAM_TRACE red_reset_stream_trace(worker); #endif if (worker->cursor) { red_release_cursor(worker, worker->cursor); worker->cursor = NULL; } red_wait_outgoiong_item((RedChannel *)worker->cursor_channel); if (worker->cursor_channel) { red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE); if (!worker->cursor_channel->base.migrate) { red_pipe_add_verb(&worker->cursor_channel->base, RED_CURSOR_RESET); } ASSERT(!worker->cursor_channel->base.send_data.item); } red_wait_outgoiong_item((RedChannel *)worker->display_channel); ASSERT(ring_is_empty(&worker->streams)); if (worker->display_channel) { red_pipe_add_type(&worker->display_channel->base, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE); red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_STREAM_DESTROY_ALL); if (!worker->display_channel->base.migrate) { red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_RESET); } ASSERT(!worker->display_channel->base.send_data.item); } worker->attached = FALSE; worker->cursor_visible = TRUE; worker->cursor_position.x = worker->cursor_position.y = 0; worker->cursor_trail_length = worker->cursor_trail_frequency = 0; image_cache_reset(&worker->image_cache); message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); break; case RED_WORKER_MESSAGE_DISPLAY_CONNECT: { RedsStreamContext *peer; int migrate; red_printf("connect"); receive_data(worker->channel, &peer, sizeof(RedsStreamContext *)); receive_data(worker->channel, &migrate, sizeof(int)); handle_new_display_channel(worker, peer, migrate); break; } case RED_WORKER_MESSAGE_DISPLAY_DISCONNECT: red_printf("disconnect"); red_disconnect_display((RedChannel *)worker->display_channel); break; case RED_WORKER_MESSAGE_SAVE: red_printf("save"); ASSERT(!worker->running); red_save(worker); message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); break; case RED_WORKER_MESSAGE_LOAD: red_printf("load"); ASSERT(!worker->running); red_add_screen_image(worker); red_cursor_load(worker); message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); break; case RED_WORKER_MESSAGE_STOP: { red_printf("stop"); ASSERT(worker->running); worker->running = FALSE; red_display_clear_glz_drawables(worker->display_channel); red_current_flush(worker); red_cursor_flush(worker); red_wait_outgoiong_item((RedChannel *)worker->display_channel); red_wait_outgoiong_item((RedChannel *)worker->cursor_channel); message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); break; } case RED_WORKER_MESSAGE_START: red_printf("start"); ASSERT(!worker->running); if (worker->cursor_channel) { worker->cursor_channel->base.migrate = FALSE; } if (worker->display_channel) { worker->display_channel->base.migrate = FALSE; } worker->running = TRUE; break; case RED_WORKER_MESSAGE_DISPLAY_MIGRATE: red_printf("migrate"); red_migrate_display(worker); break; case RED_WORKER_MESSAGE_CURSOR_CONNECT: { RedsStreamContext *peer; int migrate; red_printf("cursor connect"); receive_data(worker->channel, &peer, sizeof(RedsStreamContext *)); receive_data(worker->channel, &migrate, sizeof(int)); red_connect_cursor(worker, peer, migrate); break; } case RED_WORKER_MESSAGE_CURSOR_DISCONNECT: red_printf("cursor disconnect"); red_disconnect_cursor((RedChannel *)worker->cursor_channel); break; case RED_WORKER_MESSAGE_CURSOR_MIGRATE: red_printf("cursor migrate"); red_migrate_cursor(worker); break; case RED_WORKER_MESSAGE_SET_COMPRESSION: receive_data(worker->channel, &worker->image_compression, sizeof(spice_image_compression_t)); switch (worker->image_compression) { case SPICE_IMAGE_COMPRESS_AUTO_LZ: red_printf("ic auto_lz"); break; case SPICE_IMAGE_COMPRESS_AUTO_GLZ: red_printf("ic auto_glz"); break; case SPICE_IMAGE_COMPRESS_QUIC: red_printf("ic quic"); break; case SPICE_IMAGE_COMPRESS_LZ: red_printf("ic lz"); break; case SPICE_IMAGE_COMPRESS_GLZ: red_printf("ic glz"); break; case SPICE_IMAGE_COMPRESS_OFF: red_printf("ic off"); break; default: red_printf("ic invalid"); } #ifdef COMPRESS_STAT print_compress_stats(worker->display_channel); if (worker->display_channel) { stat_reset(&worker->display_channel->quic_stat); stat_reset(&worker->display_channel->lz_stat); stat_reset(&worker->display_channel->glz_stat); } #endif break; case RED_WORKER_MESSAGE_SET_STREAMING_VIDEO: receive_data(worker->channel, &worker->streaming_video, sizeof(uint32_t)); ASSERT(worker->streaming_video != STREAM_VIDEO_INVALID); switch(worker->streaming_video) { case STREAM_VIDEO_ALL: red_printf("sv all"); break; case STREAM_VIDEO_FILTER: red_printf("sv filter"); break; case STREAM_VIDEO_OFF: red_printf("sv off"); break; default: red_printf("sv invalid"); } break; case RED_WORKER_MESSAGE_SET_MOUSE_MODE: receive_data(worker->channel, &worker->mouse_mode, sizeof(uint32_t)); red_printf("mouse mode %u", worker->mouse_mode); break; default: red_error("message error"); } } static void default_draw_context_destroy(void *canvas) { } static void red_init(RedWorker *worker, WorkerInitData *init_data) { struct epoll_event event; RedWorkeMessage message; int epoll; ASSERT(sizeof(CursorItem) <= QXL_CURSUR_DEVICE_DATA_SIZE); memset(worker, 0, sizeof(RedWorker)); worker->qxl = init_data->qxl_interface; worker->id = init_data->id; worker->channel = init_data->channel; worker->pending = init_data->pending; worker->dev_listener.refs = 1; worker->dev_listener.action = handle_dev_input; worker->cursor_visible = TRUE; worker->draw_context.destroy = default_draw_context_destroy; ASSERT(init_data->num_renderers > 0); worker->num_renderers = init_data->num_renderers; memcpy(worker->renderers, init_data->renderers, sizeof(worker->renderers)); worker->mouse_mode = RED_MOUSE_MODE_SERVER; worker->image_compression = init_data->image_compression; worker->streaming_video = init_data->streaming_video; ring_init(&worker->current_list); ring_init(&worker->current); image_cache_init(&worker->image_cache); drawables_init(worker); red_init_streams(worker); stat_init(&worker->add_stat, add_stat_name); stat_init(&worker->exclude_stat, exclude_stat_name); stat_init(&worker->__exclude_stat, __exclude_stat_name); #ifdef RED_STATISTICS char worker_str[20]; sprintf(worker_str, "display[%d]", worker->id); worker->stat = stat_add_node(INVALID_STAT_REF, worker_str, TRUE); worker->wakeup_counter = stat_add_counter(worker->stat, "wakeups", TRUE); worker->command_counter = stat_add_counter(worker->stat, "commands", TRUE); #endif if ((epoll = epoll_create(MAX_EPOLL_SOURCES)) == -1) { red_error("epoll_create failed, %s", strerror(errno)); } worker->epoll = epoll; event.events = EPOLLIN; event.data.ptr = &worker->dev_listener; if (epoll_ctl(epoll, EPOLL_CTL_ADD, worker->channel, &event) == -1) { red_error("add channel failed, %s", strerror(errno)); } message = RED_WORKER_MESSAGE_READY; write_message(worker->channel, &message); } void *red_worker_main(void *arg) { RedWorker worker; red_printf("begin"); ASSERT(MAX_PIPE_SIZE > WIDE_CLIENT_ACK_WINDOW && MAX_PIPE_SIZE > NARROW_CLIENT_ACK_WINDOW); //ensure wakeup by ack message ASSERT(QXL_BITMAP_TOP_DOWN == BITMAP_TOP_DOWN); #if defined(RED_WORKER_STAT) || defined(COMPRESS_STAT) if (pthread_getcpuclockid(pthread_self(), &clock_id)) { red_error("pthread_getcpuclockid failed"); } #endif avcodec_init(); avcodec_register_all(); red_init(&worker, (WorkerInitData *)arg); red_init_quic(&worker); red_init_lz(&worker); worker.epoll_timeout = INF_EPOLL_WAIT; for (;;) { struct epoll_event events[MAX_EPOLL_SOURCES]; int num_events; struct epoll_event *event; struct epoll_event *end; worker.epoll_timeout = MIN(red_get_streams_timout(&worker), worker.epoll_timeout); num_events = epoll_wait(worker.epoll, events, MAX_EPOLL_SOURCES, worker.epoll_timeout); red_handle_streams_timout(&worker); if (worker.display_channel && worker.display_channel->glz_dict) { /* during migration, in the dest, the display channel can be initialized while the global lz data not since migrate data msg hasn't been recieved yet */ red_display_handle_glz_drawables_to_free(worker.display_channel); } worker.epoll_timeout = INF_EPOLL_WAIT; if (num_events == -1) { if (errno != EINTR) { red_error("poll_wait failed, %s", strerror(errno)); } num_events = 0; } for (event = events, end = event + num_events; event < end; event++) { EventListener *evt_listener = (EventListener *)event->data.ptr; evt_listener->refs++; } for (event = events, end = event + num_events; event < end; event++) { EventListener *evt_listener = (EventListener *)event->data.ptr; if (evt_listener->refs > 1) { evt_listener->action(evt_listener, event->events); if (--evt_listener->refs) { continue; } } free(evt_listener); } if (worker.attached && worker.running) { red_process_cursor(&worker, MAX_PIPE_SIZE); red_process_commands(&worker, MAX_PIPE_SIZE); } red_push(&worker); } red_printf("exit"); return 0; } #ifdef DUMP_BITMAP #include static void dump_palette(FILE *f, Palette* plt) { int i; for (i = 0; i < plt->num_ents; i++) { fwrite(plt->ents + i, sizeof(uint32_t), 1, f); } } static void dump_line(FILE *f, uint8_t* line, uint16_t n_pixel_bits, int width, int row_size) { int i; int copy_bytes_size = ALIGN(n_pixel_bits * width, 8) / 8; fwrite(line, 1, copy_bytes_size, f); if (row_size > copy_bytes_size) { // each line should be 4 bytes aligned for (i = copy_bytes_size; i < row_size; i++) { fprintf(f, "%c", 0); } } } #define RAM_PATH "/tmp/tmpfs" static void dump_bitmap(RedWorker *worker, Bitmap *bitmap) { static uint32_t file_id = 0; char file_str[200]; int rgb = TRUE; uint16_t n_pixel_bits; Palette *plt = NULL; uint32_t id; int row_size; uint32_t file_size; long address_delta = worker->dev_info.phys_delta; int alpha = 0; uint32_t header_size = 14 + 40; uint32_t bitmap_data_offset; uint32_t tmp_u32; int32_t tmp_32; uint16_t tmp_u16; FILE *f; switch (bitmap->format) { case BITMAP_FMT_1BIT_BE: case BITMAP_FMT_1BIT_LE: rgb = FALSE; n_pixel_bits = 1; break; case BITMAP_FMT_4BIT_BE: case BITMAP_FMT_4BIT_LE: rgb = FALSE; n_pixel_bits = 4; break; case BITMAP_FMT_8BIT: rgb = FALSE; n_pixel_bits = 8; break; case BITMAP_FMT_16BIT: n_pixel_bits = 16; break; case BITMAP_FMT_24BIT: n_pixel_bits = 24; break; case BITMAP_FMT_32BIT: n_pixel_bits = 32; break; case BITMAP_FMT_RGBA: n_pixel_bits = 32; alpha = 1; break; default: red_error("invalid bitmap format %u", bitmap->format); } if (!rgb) { if (!bitmap->palette) { return; // dont dump masks. } plt = (Palette *)(bitmap->palette + address_delta); } row_size = (((bitmap->x * n_pixel_bits) + 31) / 32) * 4; bitmap_data_offset = header_size; if (plt) { bitmap_data_offset += plt->num_ents * 4; } file_size = bitmap_data_offset + (bitmap->y * row_size); id = ++file_id; sprintf(file_str, "%s/%u.bmp", RAM_PATH, id); f = fopen(file_str, "wb"); if (!f) { red_error("Error creating bmp\n"); return; } /* writing the bmp v3 header */ fprintf(f, "BM"); fwrite(&file_size, sizeof(file_size), 1, f); tmp_u16 = alpha ? 1 : 0; fwrite(&tmp_u16, sizeof(tmp_u16), 1, f); // reserved for application tmp_u16 = 0; fwrite(&tmp_u16, sizeof(tmp_u16), 1, f); fwrite(&bitmap_data_offset, sizeof(bitmap_data_offset), 1, f); tmp_u32 = header_size - 14; fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); // sub header size tmp_32 = bitmap->x; fwrite(&tmp_32, sizeof(tmp_32), 1, f); tmp_32 = bitmap->y; fwrite(&tmp_32, sizeof(tmp_32), 1, f); tmp_u16 = 1; fwrite(&tmp_u16, sizeof(tmp_u16), 1, f); // color plane fwrite(&n_pixel_bits, sizeof(n_pixel_bits), 1, f); // pixel depth tmp_u32 = 0; fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); // compression method tmp_u32 = 0; //file_size - bitmap_data_offset; fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); // image size tmp_32 = 0; fwrite(&tmp_32, sizeof(tmp_32), 1, f); fwrite(&tmp_32, sizeof(tmp_32), 1, f); tmp_u32 = (!plt) ? 0 : plt->num_ents; // plt entries fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); tmp_u32 = 0; fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); if (plt) { dump_palette(f, plt); } /* writing the data */ if ((bitmap->flags & QXL_BITMAP_DIRECT)) { uint8_t *lines = (uint8_t*)(bitmap->data + address_delta); int i; for (i = 0; i < bitmap->y; i++) { dump_line(f, lines + (i * bitmap->stride), n_pixel_bits, bitmap->x, row_size); } } else { QXLDataChunk *chunk = NULL; int num_lines; ADDRESS relative_address = bitmap->data; while (relative_address) { int i; chunk = (QXLDataChunk *)(relative_address + address_delta); num_lines = chunk->data_size / bitmap->stride; for (i = 0; i < num_lines; i++) { dump_line(f, chunk->data + (i * bitmap->stride), n_pixel_bits, bitmap->x, row_size); } relative_address = chunk->next_chunk; } } fclose(f); } #endif