/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2009 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#define SPICE_LOG_DOMAIN "SpiceWorker"
/* Common variable abberiviations:
*
* rcc - RedChannelClient
* ccc - CursorChannelClient (not to be confused with common_cc)
* common_cc - CommonChannelClient
* dcc - DisplayChannelClient
* cursor_red_channel - downcast of CursorChannel to RedChannel
* display_red_channel - downcast of DisplayChannel to RedChannel
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "common/lz.h"
#include "common/marshaller.h"
#include "common/quic.h"
#include "common/rect.h"
#include "common/region.h"
#include "common/ring.h"
#include "common/generated_server_marshallers.h"
#ifdef USE_OPENGL
#include "common/ogl_ctx.h"
#include "reds_gl_canvas.h"
#endif /* USE_OPENGL */
#include "spice.h"
#include "red_worker.h"
#include "reds_sw_canvas.h"
#include "glz_encoder_dictionary.h"
#include "glz_encoder.h"
#include "stat.h"
#include "reds.h"
#include "mjpeg_encoder.h"
#include "red_memslots.h"
#include "red_parse_qxl.h"
#include "jpeg_encoder.h"
#include "demarshallers.h"
#include "zlib_encoder.h"
#include "red_channel.h"
#include "red_dispatcher.h"
#include "dispatcher.h"
#include "main_channel.h"
//#define COMPRESS_STAT
//#define DUMP_BITMAP
//#define PIPE_DEBUG
//#define RED_WORKER_STAT
//#define DRAW_ALL
//#define COMPRESS_DEBUG
//#define ACYCLIC_SURFACE_DEBUG
//#define DEBUG_CURSORS
//#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 CHANNEL_PUSH_TIMEOUT 30000000000ULL //nano
#define CHANNEL_PUSH_SLEEP_DURATION 10000 //micro
#define DISPLAY_CLIENT_TIMEOUT 15000000000ULL //nano
#define DISPLAY_CLIENT_RETRY_INTERVAL 10000 //micro
#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 WORST_BIT_RATE_PER_PIXEL 4
#define RED_COMPRESS_BUF_SIZE (1024 * 64)
#define ZLIB_DEFAULT_COMPRESSION_LEVEL 3
#define MIN_GLZ_SIZE_FOR_ZLIB 100
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;
static stat_time_t stat_now(void)
{
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 const char *jpeg_stat_name = "jpeg";
static const char *zlib_stat_name = "zlib_glz";
static const char *jpeg_alpha_stat_name = "jpeg_alpha";
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_EVENT_SOURCES 20
#define INF_EVENT_WAIT ~0
struct SpiceWatch {
struct RedWorker *worker;
SpiceWatchFunc watch_func;
void *watch_func_opaque;
};
enum {
BUF_TYPE_RAW = 1,
};
enum {
PIPE_ITEM_TYPE_DRAW = PIPE_ITEM_TYPE_CHANNEL_BASE,
PIPE_ITEM_TYPE_INVAL_ONE,
PIPE_ITEM_TYPE_CURSOR,
PIPE_ITEM_TYPE_MIGRATE,
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,
PIPE_ITEM_TYPE_CREATE_SURFACE,
PIPE_ITEM_TYPE_DESTROY_SURFACE,
};
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;
int lossy;
};
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;
};
typedef struct SurfaceCreateItem {
SpiceMsgSurfaceCreate surface_create;
PipeItem pipe_item;
} SurfaceCreateItem;
typedef struct SurfaceDestroyItem {
SpiceMsgSurfaceDestroy surface_destroy;
PipeItem pipe_item;
} SurfaceDestroyItem;
typedef struct CursorItem {
uint32_t group_id;
int refs;
RedCursorCmd *red_cursor;
} CursorItem;
typedef struct CursorPipeItem {
PipeItem base;
CursorItem *cursor_item;
int refs;
} CursorPipeItem;
typedef struct LocalCursor {
CursorItem base;
SpicePoint16 position;
uint32_t data_size;
SpiceCursor red_cursor;
} LocalCursor;
#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 ImageItem {
PipeItem link;
int refs;
SpicePoint pos;
int width;
int height;
int stride;
int top_down;
int surface_id;
int image_format;
uint32_t image_flags;
int can_lossy;
uint8_t data[0];
} ImageItem;
typedef struct Drawable Drawable;
enum {
STREAM_FRAME_NONE,
STREAM_FRAME_NATIVE,
STREAM_FRAME_CONTAINER,
};
typedef struct Stream Stream;
struct Stream {
uint8_t refs;
Drawable *current;
red_time_t last_time;
int width;
int height;
SpiceRect dest_area;
MJpegEncoder *mjpeg_encoder;
int top_down;
Stream *next;
RingItem link;
int bit_rate;
};
typedef struct StreamAgent {
QRegion vis_region; /* the part of the surface area that is currently occupied by video
fragments */
QRegion clip; /* the current video clipping. It can be different from vis_region:
for example, let c1 be the clip area at time t1, and c2
be the clip area at time t2, where t1 < t2. If c1 contains c2, and
at least part of c1/c2, hasn't been covered by a non-video images,
vis_region will contain c2 and also the part of c1/c2 that still
displays fragments of the video */
PipeItem create_item;
PipeItem destroy_item;
Stream *stream;
uint64_t last_send_time;
int frames;
int drops;
int fps;
} StreamAgent;
typedef struct StreamClipItem {
PipeItem base;
int refs;
StreamAgent *stream_agent;
int clip_type;
SpiceClipRects *rects;
} 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};
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]; // here CLIENTS refer to different channel
// clients of the same client
RedClient *client;
};
#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 {
SpiceMsgWaitForChannels header;
SpiceWaitForChannel buf[MAX_CACHE_CLIENTS];
} WaitForChannels;
typedef struct FreeList {
int res_size;
SpiceResourceList *res;
uint64_t sync[MAX_CACHE_CLIENTS];
WaitForChannels wait;
} FreeList;
typedef struct DisplayChannel DisplayChannel;
typedef struct DisplayChannelClient DisplayChannelClient;
typedef struct {
DisplayChannelClient *dcc;
RedCompressBuf *bufs_head;
RedCompressBuf *bufs_tail;
jmp_buf jmp_env;
union {
struct {
SpiceChunks *chunks;
int next;
int stride;
int reverse;
} lines_data;
struct {
RedCompressBuf* next;
int size_left;
} compressed_data; // for encoding data that was already compressed by another method
} u;
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;
typedef struct {
JpegEncoderUsrContext usr;
EncoderData data;
} JpegData;
typedef struct {
ZlibEncoderUsrContext usr;
EncoderData data;
} ZlibData;
/**********************************/
/* LZ dictionary related entities */
/**********************************/
#define MAX_GLZ_DRAWABLE_INSTANCES 2
typedef struct RedGlzDrawable RedGlzDrawable;
/* for each qxl drawable, there may be several instances of lz drawables */
/* TODO - reuse this stuff for the top level. I just added a second level of multiplicity
* at the Drawable by keeping a ring, so:
* Drawable -> (ring of) RedGlzDrawable -> (up to 2) GlzDrawableInstanceItem
* and it should probably (but need to be sure...) be
* Drawable -> ring of GlzDrawableInstanceItem.
*/
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
RingItem drawable_link;
RedDrawable *red_drawable;
Drawable *drawable;
uint32_t group_id;
SpiceImage *self_bitmap;
GlzDrawableInstanceItem instances_pool[MAX_GLZ_DRAWABLE_INSTANCES];
Ring instances;
uint8_t instances_count;
DisplayChannelClient *dcc;
};
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;
RedClient *client; // channel clients of the same client share the dict
} GlzSharedDictionary;
#define NUM_SURFACES 10000
typedef struct CommonChannel {
RedChannel base; // Must be the first thing
struct RedWorker *worker;
uint8_t recv_buf[RECIVE_BUF_SIZE];
uint32_t id_alloc; // bitfield. TODO - use this instead of shift scheme.
} CommonChannel;
typedef struct CommonChannelClient {
RedChannelClient base;
uint32_t id;
struct RedWorker *worker;
} CommonChannelClient;
/* Each drawable can refer to at most 3 images: src, brush and mask */
#define MAX_DRAWABLE_PIXMAP_CACHE_ITEMS 3
struct DisplayChannelClient {
CommonChannelClient common;
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;
struct {
uint32_t stream_outbuf_size;
uint8_t *stream_outbuf; // caution stream buffer is also used as compress bufs!!!
RedCompressBuf *used_compress_bufs;
FreeList free_list;
uint64_t pixmap_cache_items[MAX_DRAWABLE_PIXMAP_CACHE_ITEMS];
int num_pixmap_cache_items;
} send_data;
/* 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;
uint8_t surface_client_created[NUM_SURFACES];
QRegion surface_client_lossy_region[NUM_SURFACES];
StreamAgent stream_agents[NUM_STREAMS];
};
struct DisplayChannel {
CommonChannel common; // Must be the first thing
// only required for one client, can be the first (or choose it by speed
// and keep a pointer to it here?)
int expect_migrate_mark;
int expect_migrate_data;
int enable_jpeg;
int jpeg_quality;
int enable_zlib_glz_wrap;
int zlib_level;
RedCompressBuf *free_compress_bufs;
#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;
stat_info_t jpeg_stat;
stat_info_t zlib_glz_stat;
stat_info_t jpeg_alpha_stat;
#endif
};
typedef struct CursorChannelClient {
CommonChannelClient common;
CacheItem *cursor_cache[CURSOR_CACHE_HASH_SIZE];
Ring cursor_cache_lru;
long cursor_cache_available;
uint32_t cursor_cache_items;
} CursorChannelClient;
typedef struct CursorChannel {
CommonChannel common; // Must be the first thing
#ifdef RED_STATISTICS
StatNodeRef stat;
#endif
} CursorChannel;
typedef struct ImageCacheItem {
RingItem lru_link;
uint64_t id;
#ifdef IMAGE_CACHE_AGE
uint32_t age;
#endif
struct ImageCacheItem *next;
pixman_image_t *image;
} ImageCacheItem;
#define IMAGE_CACHE_HASH_SIZE 1024
typedef struct ImageCache {
SpiceImageCache base;
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_LOW,
BITMAP_GRADUAL_MEDIUM,
BITMAP_GRADUAL_HIGH,
} BitmapGradualType;
typedef struct DependItem {
Drawable *drawable;
RingItem ring_item;
} DependItem;
typedef struct DrawablePipeItem {
RingItem base; /* link for a list of pipe items held by Drawable */
PipeItem dpi_pipe_item; /* link for the client's pipe itself */
Drawable *drawable;
DisplayChannelClient *dcc;
uint8_t refs;
} DrawablePipeItem;
struct Drawable {
uint8_t refs;
RingItem surface_list_link;
RingItem list_link;
DrawItem tree_item;
Ring pipes;
PipeItem *pipe_item_rest;
uint32_t size_pipe_item_rest;
#ifdef UPDATE_AREA_BY_TREE
RingItem collect_link;
#endif
RedDrawable *red_drawable;
Ring glz_ring;
red_time_t creation_time;
int frames_count;
int gradual_frames_count;
int last_gradual_frame;
Stream *stream;
Stream *sized_stream;
int streamable;
BitmapGradualType copy_bitmap_graduality;
uint32_t group_id;
SpiceImage *self_bitmap;
DependItem depend_items[3];
uint8_t *backed_surface_data;
DependItem pipe_depend_items[3];
int surface_id;
int surfaces_dest[3];
};
typedef struct _Drawable _Drawable;
struct _Drawable {
union {
Drawable drawable;
_Drawable *next;
} u;
};
typedef struct _CursorItem _CursorItem;
struct _CursorItem {
union {
CursorItem cursor_item;
_CursorItem *next;
} u;
};
typedef struct UpgradeItem {
PipeItem base;
int refs;
Drawable *drawable;
SpiceClipRects *rects;
} UpgradeItem;
typedef struct DrawContext {
SpiceCanvas *canvas;
int canvas_draws_on_surface;
int top_down;
uint32_t width;
uint32_t height;
int32_t stride;
uint32_t format;
void *line_0;
} DrawContext;
typedef struct RedSurface {
uint32_t refs;
Ring current;
Ring current_list;
#ifdef ACYCLIC_SURFACE_DEBUG
int current_gn;
#endif
DrawContext context;
Ring depend_on_me;
QRegion draw_dirty_region;
//fix me - better handling here
QXLReleaseInfoExt create, destroy;
} RedSurface;
typedef struct ItemTrace {
red_time_t time;
int frames_count;
int gradual_frames_count;
int last_gradual_frame;
int width;
int height;
SpiceRect dest_area;
} ItemTrace;
#define TRACE_ITEMS_SHIFT 3
#define NUM_TRACE_ITEMS (1 << TRACE_ITEMS_SHIFT)
#define ITEMS_TRACE_MASK (NUM_TRACE_ITEMS - 1)
#define NUM_DRAWABLES 1000
#define NUM_CURSORS 100
typedef struct RedWorker {
DisplayChannel *display_channel;
CursorChannel *cursor_channel;
QXLInstance *qxl;
RedDispatcher *red_dispatcher;
int channel;
int id;
int running;
uint32_t *pending;
struct pollfd poll_fds[MAX_EVENT_SOURCES];
struct SpiceWatch watches[MAX_EVENT_SOURCES];
unsigned int event_timeout;
uint32_t repoll_cmd_ring;
uint32_t repoll_cursor_ring;
uint32_t num_renderers;
uint32_t renderers[RED_MAX_RENDERERS];
uint32_t renderer;
RedSurface surfaces[NUM_SURFACES];
uint32_t n_surfaces;
SpiceImageSurfaces image_surfaces;
Ring current_list;
uint32_t current_size;
uint32_t drawable_count;
uint32_t red_drawable_count;
uint32_t glz_drawable_count;
uint32_t transparent_count;
uint32_t shadows_count;
uint32_t containers_count;
uint32_t stream_count;
uint32_t bits_unique;
CursorItem *cursor;
int cursor_visible;
SpicePoint16 cursor_position;
uint16_t cursor_trail_length;
uint16_t cursor_trail_frequency;
_Drawable drawables[NUM_DRAWABLES];
_Drawable *free_drawables;
_CursorItem cursor_items[NUM_CURSORS];
_CursorItem *free_cursor_items;
RedMemSlotInfo mem_slots;
uint32_t preload_group_id;
ImageCache image_cache;
spice_image_compression_t image_compression;
spice_wan_compression_t jpeg_state;
spice_wan_compression_t zlib_glz_state;
uint32_t mouse_mode;
uint32_t streaming_video;
Stream streams_buf[NUM_STREAMS];
Stream *free_streams;
Ring streams;
ItemTrace items_trace[NUM_TRACE_ITEMS];
uint32_t next_item_trace;
QuicData quic_data;
QuicContext *quic;
LzData lz_data;
LzContext *lz;
JpegData jpeg_data;
JpegEncoderContext *jpeg;
ZlibData zlib_data;
ZlibEncoder *zlib;
#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;
typedef enum {
BITMAP_DATA_TYPE_INVALID,
BITMAP_DATA_TYPE_CACHE,
BITMAP_DATA_TYPE_SURFACE,
BITMAP_DATA_TYPE_BITMAP,
BITMAP_DATA_TYPE_BITMAP_TO_CACHE,
} BitmapDataType;
typedef struct BitmapData {
BitmapDataType type;
uint64_t id; // surface id or cache item id
SpiceRect lossy_rect;
} BitmapData;
static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable);
static void red_current_flush(RedWorker *worker, int surface_id);
#ifdef DRAW_ALL
#define red_update_area(worker, rect, surface_id)
#define red_draw_drawable(worker, item)
#else
static void red_draw_drawable(RedWorker *worker, Drawable *item);
static void red_update_area(RedWorker *worker, const SpiceRect *area, int surface_id);
#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(RedWorker *worker, StreamAgent *agent);
static inline void red_detach_stream(RedWorker *worker, Stream *stream, int detach_sized);
static void red_stop_stream(RedWorker *worker, Stream *stream);
static inline void red_stream_maintenance(RedWorker *worker, Drawable *candidate, Drawable *sect);
static inline void display_begin_send_message(RedChannelClient *rcc);
static void red_release_pixmap_cache(DisplayChannelClient *dcc);
static void red_release_glz(DisplayChannelClient *dcc);
static void red_freeze_glz(DisplayChannelClient *dcc);
static void display_channel_push_release(DisplayChannelClient *dcc, uint8_t type, uint64_t id,
uint64_t* sync_data);
static void red_display_release_stream_clip(RedWorker *worker, StreamClipItem *item);
static int red_display_free_some_independent_glz_drawables(DisplayChannelClient *dcc);
static void red_display_free_glz_drawable(DisplayChannelClient *dcc, RedGlzDrawable *drawable);
static ImageItem *red_add_surface_area_image(DisplayChannelClient *dcc, int surface_id,
SpiceRect *area, PipeItem *pos, int can_lossy);
static void reset_rate(DisplayChannelClient *dcc, StreamAgent *stream_agent);
static BitmapGradualType _get_bitmap_graduality_level(RedWorker *worker, SpiceBitmap *bitmap,
uint32_t group_id);
static inline int _stride_is_extra(SpiceBitmap *bitmap);
static void red_disconnect_cursor(RedChannel *channel);
static void display_channel_client_release_item_before_push(DisplayChannelClient *dcc,
PipeItem *item);
static void display_channel_client_release_item_after_push(DisplayChannelClient *dcc,
PipeItem *item);
static void cursor_channel_client_release_item_before_push(CursorChannelClient *ccc,
PipeItem *item);
static void cursor_channel_client_release_item_after_push(CursorChannelClient *ccc,
PipeItem *item);
static void red_wait_pipe_item_sent(RedChannelClient *rcc, PipeItem *item);
#ifdef DUMP_BITMAP
static void dump_bitmap(RedWorker *worker, SpiceBitmap *bitmap, uint32_t group_id);
#endif
/*
* Macros to make iterating over stuff easier
* The two collections we iterate over:
* given a channel, iterate over it's clients
*/
#define RCC_FOREACH(link, rcc, channel) \
for (link = ring_get_head(&(channel)->clients),\
rcc = SPICE_CONTAINEROF(link, RedChannelClient, channel_link);\
(link); \
(link) = ring_next(&(channel)->clients, link),\
rcc = SPICE_CONTAINEROF(link, RedChannelClient, channel_link))
#define RCC_FOREACH_SAFE(link, next, rcc, channel) \
for (link = ring_get_head(&(channel)->clients), \
rcc = SPICE_CONTAINEROF(link, RedChannelClient, channel_link), \
(next) = (link) ? ring_next(&(channel)->clients, (link)) : NULL; \
(link); \
(link) = (next), \
(next) = (link) ? ring_next(&(channel)->clients, (link)) : NULL, \
rcc = SPICE_CONTAINEROF(link, RedChannelClient, channel_link))
#define DCC_FOREACH(link, dcc, channel) \
for (link = channel ? ring_get_head(&(channel)->clients) : NULL,\
dcc = link ? SPICE_CONTAINEROF((link), DisplayChannelClient,\
common.base.channel_link) : NULL;\
(link); \
(link) = ring_next(&(channel)->clients, link),\
dcc = SPICE_CONTAINEROF((link), DisplayChannelClient, common.base.channel_link))
#define WORKER_FOREACH_DCC(worker, link, dcc) \
for (link = ((worker) && (worker)->display_channel) ?\
ring_get_head(&(worker)->display_channel->common.base.clients) : NULL,\
dcc = link ? SPICE_CONTAINEROF((link), DisplayChannelClient,\
common.base.channel_link) : NULL;\
(link); \
(link) = ring_next(&(worker)->display_channel->common.base.clients, link),\
dcc = SPICE_CONTAINEROF((link), DisplayChannelClient, common.base.channel_link))
#define DRAWABLE_FOREACH_DPI(drawable, link, dpi) \
for (link = (drawable) ? ring_get_head(&(drawable)->pipes) : NULL,\
dpi = (link) ? SPICE_CONTAINEROF((link), DrawablePipeItem, base) : NULL; \
(link);\
(link) = ring_next(&(drawable)->pipes, (link)),\
dpi = (link) ? SPICE_CONTAINEROF((link), DrawablePipeItem, base) : NULL)
#define DRAWABLE_FOREACH_GLZ(drawable, link, glz) \
for (link = (drawable) ? ring_get_head(&drawable->glz_ring) : NULL,\
glz = (link) ? SPICE_CONTAINEROF((link), RedGlzDrawable, drawable_link) : NULL;\
(link);\
(link) = ring_next(&drawable->glz_ring, (link)),\
glz = (link) ? SPICE_CONTAINEROF((link), RedGlzDrawable, drawable_link) : NULL)
#define DRAWABLE_FOREACH_GLZ_SAFE(drawable, link, next, glz) \
for (link = (drawable) ? ring_get_head(&drawable->glz_ring) : NULL,\
next = (link) ? ring_next(&drawable->glz_ring, link) : NULL,\
glz = (link) ? SPICE_CONTAINEROF((link), RedGlzDrawable, drawable_link) : NULL;\
(link);\
(link) = (next),\
(next) = (link) ? ring_next(&drawable->glz_ring, (link)) : NULL,\
glz = (link) ? SPICE_CONTAINEROF((link), RedGlzDrawable, drawable_link) : NULL)
#define CCC_FOREACH(link, ccc, channel) \
for (link = ring_get_head(&(channel)->clients),\
ccc = SPICE_CONTAINEROF(link, CommonChannelClient, base.channel_link);\
(link); \
(link) = ring_next(&(channel)->clients, link),\
ccc = SPICE_CONTAINEROF(link, CommonChannelClient, base.channel_link))
#define DCC_TO_WORKER(dcc) \
(SPICE_CONTAINEROF((dcc)->common.base.channel, CommonChannel, base)->worker)
// TODO: replace with DCC_FOREACH when it is introduced
#define WORKER_TO_DCC(worker) \
(worker->display_channel ? SPICE_CONTAINEROF(worker->display_channel->common.base.rcc,\
DisplayChannelClient, common.base) : NULL)
#define DCC_TO_DC(dcc) SPICE_CONTAINEROF((dcc)->common.base.channel,\
DisplayChannel, common.base)
#define RCC_TO_DCC(rcc) SPICE_CONTAINEROF((rcc), DisplayChannelClient, common.base)
#define RCC_TO_CCC(rcc) SPICE_CONTAINEROF((rcc), CursorChannelClient, common.base)
#ifdef COMPRESS_STAT
static void print_compress_stats(DisplayChannel *display_channel)
{
uint64_t glz_enc_size;
if (!display_channel) {
return;
}
glz_enc_size = display_channel->enable_zlib_glz_wrap ?
display_channel->zlib_glz_stat.comp_size :
display_channel->glz_stat.comp_size;
spice_printerr("==> Compression stats for display %u", display_channel->common.id);
spice_printerr("Method \t count \torig_size(MB)\tenc_size(MB)\tenc_time(s)");
spice_printerr("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)
);
spice_printerr("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)
);
spice_printerr("ZLIB GLZ \t%8d\t%13.2f\t%12.2f\t%12.2f",
display_channel->zlib_glz_stat.count,
stat_byte_to_mega(display_channel->zlib_glz_stat.orig_size),
stat_byte_to_mega(display_channel->zlib_glz_stat.comp_size),
stat_cpu_time_to_sec(display_channel->zlib_glz_stat.total)
);
spice_printerr("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)
);
spice_printerr("JPEG \t%8d\t%13.2f\t%12.2f\t%12.2f",
display_channel->jpeg_stat.count,
stat_byte_to_mega(display_channel->jpeg_stat.orig_size),
stat_byte_to_mega(display_channel->jpeg_stat.comp_size),
stat_cpu_time_to_sec(display_channel->jpeg_stat.total)
);
spice_printerr("JPEG-RGBA\t%8d\t%13.2f\t%12.2f\t%12.2f",
display_channel->jpeg_alpha_stat.count,
stat_byte_to_mega(display_channel->jpeg_alpha_stat.orig_size),
stat_byte_to_mega(display_channel->jpeg_alpha_stat.comp_size),
stat_cpu_time_to_sec(display_channel->jpeg_alpha_stat.total)
);
spice_printerr("-------------------------------------------------------------------");
spice_printerr("Total \t%8d\t%13.2f\t%12.2f\t%12.2f",
display_channel->lz_stat.count + display_channel->glz_stat.count +
display_channel->quic_stat.count +
display_channel->jpeg_stat.count +
display_channel->jpeg_alpha_stat.count,
stat_byte_to_mega(display_channel->lz_stat.orig_size +
display_channel->glz_stat.orig_size +
display_channel->quic_stat.orig_size +
display_channel->jpeg_stat.orig_size +
display_channel->jpeg_alpha_stat.orig_size),
stat_byte_to_mega(display_channel->lz_stat.comp_size +
glz_enc_size +
display_channel->quic_stat.comp_size +
display_channel->jpeg_stat.comp_size +
display_channel->jpeg_alpha_stat.comp_size),
stat_cpu_time_to_sec(display_channel->lz_stat.total +
display_channel->glz_stat.total +
display_channel->zlib_glz_stat.total +
display_channel->quic_stat.total +
display_channel->jpeg_stat.total +
display_channel->jpeg_alpha_stat.total)
);
}
#endif
static inline int is_primary_surface(RedWorker *worker, uint32_t surface_id)
{
if (surface_id == 0) {
return TRUE;
}
return FALSE;
}
static inline void __validate_surface(RedWorker *worker, uint32_t surface_id)
{
spice_warn_if(surface_id >= worker->n_surfaces);
}
static inline void validate_surface(RedWorker *worker, uint32_t surface_id)
{
spice_warn_if(surface_id >= worker->n_surfaces);
if (!worker->surfaces[surface_id].context.canvas) {
spice_printerr("failed on %d", surface_id);
spice_warn_if(!worker->surfaces[surface_id].context.canvas);
}
}
static const char *draw_type_to_str(uint8_t 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_red_drawable(RedWorker *worker, RedDrawable *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:
spice_error("bad drawable 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.extents.x1,
draw_item->base.rgn.extents.y1,
draw_item->base.rgn.extents.x2,
draw_item->base.rgn.extents.y2);
}
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 void red_pipe_add_verb(RedChannelClient* rcc, uint16_t verb)
{
VerbItem *item = spice_new(VerbItem, 1);
red_channel_pipe_item_init(rcc->channel, &item->base, PIPE_ITEM_TYPE_VERB);
item->verb = verb;
red_channel_client_pipe_add(rcc, &item->base);
}
static inline void red_create_surface_item(DisplayChannelClient *dcc, int surface_id);
static void red_push_surface_image(DisplayChannelClient *dcc, int surface_id);
static void red_pipes_add_verb(RedChannel *channel, uint16_t verb)
{
RedChannelClient *rcc;
RingItem *link;
RCC_FOREACH(link, rcc, channel) {
red_pipe_add_verb(rcc, verb);
}
}
static inline void red_handle_drawable_surfaces_client_synced(
DisplayChannelClient *dcc, Drawable *drawable)
{
RedWorker *worker = DCC_TO_WORKER(dcc);
int x;
for (x = 0; x < 3; ++x) {
int surface_id;
surface_id = drawable->surfaces_dest[x];
if (surface_id != -1) {
if (dcc->surface_client_created[surface_id] == TRUE) {
continue;
}
red_create_surface_item(dcc, surface_id);
red_current_flush(worker, surface_id);
red_push_surface_image(dcc, surface_id);
}
}
if (dcc->surface_client_created[drawable->surface_id] == TRUE) {
return;
}
red_create_surface_item(dcc, drawable->surface_id);
red_current_flush(worker, drawable->surface_id);
red_push_surface_image(dcc, drawable->surface_id);
}
static int display_is_connected(RedWorker *worker)
{
return (worker->display_channel && red_channel_is_connected(
&worker->display_channel->common.base));
}
static int cursor_is_connected(RedWorker *worker)
{
return (worker->cursor_channel && red_channel_is_connected(
&worker->cursor_channel->common.base));
}
static void put_drawable_pipe_item(DrawablePipeItem *dpi)
{
RedWorker *worker = DCC_TO_WORKER(dpi->dcc);
if (--dpi->refs) {
return;
}
spice_assert(!ring_item_is_linked(&dpi->dpi_pipe_item.link));
spice_assert(!ring_item_is_linked(&dpi->base));
release_drawable(worker, dpi->drawable);
free(dpi);
}
static inline DrawablePipeItem *get_drawable_pipe_item(DisplayChannelClient *dcc,
Drawable *drawable)
{
DrawablePipeItem *dpi;
dpi = spice_malloc0(sizeof(*dpi));
dpi->drawable = drawable;
dpi->dcc = dcc;
ring_item_init(&dpi->base);
ring_add(&drawable->pipes, &dpi->base);
red_channel_pipe_item_init(dcc->common.base.channel, &dpi->dpi_pipe_item, PIPE_ITEM_TYPE_DRAW);
dpi->refs++;
drawable->refs++;
return dpi;
}
static inline DrawablePipeItem *ref_drawable_pipe_item(DrawablePipeItem *dpi)
{
spice_assert(dpi->drawable);
dpi->refs++;
return dpi;
}
static inline void red_pipe_add_drawable(DisplayChannelClient *dcc, Drawable *drawable)
{
DrawablePipeItem *dpi;
red_handle_drawable_surfaces_client_synced(dcc, drawable);
dpi = get_drawable_pipe_item(dcc, drawable);
red_channel_client_pipe_add(&dcc->common.base, &dpi->dpi_pipe_item);
}
static inline void red_pipes_add_drawable(RedWorker *worker, Drawable *drawable)
{
DisplayChannelClient *dcc;
RingItem *dcc_ring_item;
spice_warn_if(!ring_is_empty(&drawable->pipes));
WORKER_FOREACH_DCC(worker, dcc_ring_item, dcc) {
red_pipe_add_drawable(dcc, drawable);
}
}
static inline void red_pipe_add_drawable_to_tail(DisplayChannelClient *dcc, Drawable *drawable)
{
DrawablePipeItem *dpi;
if (!dcc) {
return;
}
red_handle_drawable_surfaces_client_synced(dcc, drawable);
dpi = get_drawable_pipe_item(dcc, drawable);
red_channel_client_pipe_add_tail(&dcc->common.base, &dpi->dpi_pipe_item);
}
static inline void red_pipes_add_drawable_after(RedWorker *worker,
Drawable *drawable, Drawable *pos_after)
{
DrawablePipeItem *dpi, *dpi_pos_after;
RingItem *dpi_link;
DisplayChannelClient *dcc;
int num_other_linked = 0;
DRAWABLE_FOREACH_DPI(pos_after, dpi_link, dpi_pos_after) {
num_other_linked++;
dcc = dpi_pos_after->dcc;
red_handle_drawable_surfaces_client_synced(dcc, drawable);
dpi = get_drawable_pipe_item(dcc, drawable);
red_channel_client_pipe_add_after(&dcc->common.base, &dpi->dpi_pipe_item,
&dpi_pos_after->dpi_pipe_item);
}
if (num_other_linked == 0) {
red_pipes_add_drawable(worker, drawable);
return;
}
if (num_other_linked != worker->display_channel->common.base.clients_num) {
RingItem *worker_item;
spice_printerr("TODO: not O(n^2)");
WORKER_FOREACH_DCC(worker, worker_item, dcc) {
int sent = 0;
DRAWABLE_FOREACH_DPI(pos_after, dpi_link, dpi_pos_after) {
if (dpi_pos_after->dcc == dcc) {
sent = 1;
break;
}
}
if (!sent) {
red_pipe_add_drawable(dcc, drawable);
}
}
}
}
static inline PipeItem *red_pipe_get_tail(DisplayChannelClient *dcc)
{
if (!dcc) {
return NULL;
}
return (PipeItem*)ring_get_tail(&dcc->common.base.pipe);
}
static inline void red_destroy_surface(RedWorker *worker, uint32_t surface_id);
static inline void red_pipes_remove_drawable(Drawable *drawable)
{
DrawablePipeItem *dpi;
RingItem *item, *next;
RING_FOREACH_SAFE(item, next, &drawable->pipes) {
dpi = SPICE_CONTAINEROF(item, DrawablePipeItem, base);
if (pipe_item_is_linked(&dpi->dpi_pipe_item)) {
red_channel_client_pipe_remove_and_release(&dpi->dcc->common.base,
&dpi->dpi_pipe_item);
}
}
}
static inline void red_pipe_add_image_item(DisplayChannelClient *dcc, ImageItem *item)
{
if (!dcc) {
return;
}
item->refs++;
red_channel_client_pipe_add(&dcc->common.base, &item->link);
}
static inline void red_pipe_add_image_item_after(DisplayChannelClient *dcc, ImageItem *item,
PipeItem *pos)
{
if (!dcc) {
return;
}
item->refs++;
red_channel_client_pipe_add_after(&dcc->common.base, &item->link, pos);
}
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);
free(item->rects);
free(item);
}
}
static uint8_t *common_alloc_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size)
{
CommonChannel *common = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base);
return common->recv_buf;
}
static void common_release_recv_buf(RedChannelClient *rcc, uint16_t type, uint32_t size,
uint8_t* msg)
{
}
#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(DisplayChannelClient *dcc)
{
red_palette_cache_reset(dcc, CLIENT_PALETTE_CACHE_SIZE);
}
static void red_reset_cursor_cache(RedChannelClient *rcc)
{
red_cursor_cache_reset(RCC_TO_CCC(rcc), 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 void red_reset_stream_trace(RedWorker *worker);
static SurfaceDestroyItem *get_surface_destroy_item(RedChannel *channel,
uint32_t surface_id)
{
SurfaceDestroyItem *destroy;
destroy = (SurfaceDestroyItem *)malloc(sizeof(SurfaceDestroyItem));
spice_warn_if(!destroy);
destroy->surface_destroy.surface_id = surface_id;
red_channel_pipe_item_init(channel,
&destroy->pipe_item, PIPE_ITEM_TYPE_DESTROY_SURFACE);
return destroy;
}
static inline void red_destroy_surface_item(RedWorker *worker,
DisplayChannelClient *dcc, uint32_t surface_id)
{
SurfaceDestroyItem *destroy;
RedChannel *channel;
if (!dcc || !dcc->surface_client_created[surface_id]) {
return;
}
dcc->surface_client_created[surface_id] = FALSE;
channel = &worker->display_channel->common.base;
destroy = get_surface_destroy_item(channel, surface_id);
red_channel_client_pipe_add(&dcc->common.base, &destroy->pipe_item);
}
static inline void red_destroy_surface(RedWorker *worker, uint32_t surface_id)
{
RedSurface *surface = &worker->surfaces[surface_id];
DisplayChannelClient *dcc;
RingItem *link;
if (!--surface->refs) {
// only primary surface streams are supported
if (is_primary_surface(worker, surface_id)) {
red_reset_stream_trace(worker);
}
spice_assert(surface->context.canvas);
surface->context.canvas->ops->destroy(surface->context.canvas);
if (surface->create.info) {
worker->qxl->st->qif->release_resource(worker->qxl, surface->create);
}
if (surface->destroy.info) {
worker->qxl->st->qif->release_resource(worker->qxl, surface->destroy);
}
region_destroy(&surface->draw_dirty_region);
surface->context.canvas = NULL;
WORKER_FOREACH_DCC(worker, link, dcc) {
red_destroy_surface_item(worker, dcc, surface_id);
}
spice_warn_if(!ring_is_empty(&surface->depend_on_me));
}
}
static inline void set_surface_release_info(RedWorker *worker, uint32_t surface_id, int is_create,
QXLReleaseInfo *release_info, uint32_t group_id)
{
RedSurface *surface;
surface = &worker->surfaces[surface_id];
if (is_create) {
surface->create.info = release_info;
surface->create.group_id = group_id;
} else {
surface->destroy.info = release_info;
surface->destroy.group_id = group_id;
}
}
static RedDrawable *ref_red_drawable(RedDrawable *drawable)
{
drawable->refs++;
return drawable;
}
static inline void put_red_drawable(RedWorker *worker, RedDrawable *drawable, uint32_t group_id,
SpiceImage *self_bitmap)
{
QXLReleaseInfoExt release_info_ext;
if (self_bitmap) {
red_put_image(self_bitmap);
}
if (--drawable->refs) {
return;
}
worker->red_drawable_count--;
release_info_ext.group_id = group_id;
release_info_ext.info = drawable->release_info;
worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext);
red_put_drawable(drawable);
free(drawable);
}
static void remove_depended_item(DependItem *item)
{
spice_assert(item->drawable);
spice_assert(ring_item_is_linked(&item->ring_item));
item->drawable = NULL;
ring_remove(&item->ring_item);
}
static inline void red_dec_surfaces_drawable_dependencies(RedWorker *worker, Drawable *drawable)
{
int x;
int surface_id;
for (x = 0; x < 3; ++x) {
surface_id = drawable->surfaces_dest[x];
if (surface_id == -1) {
continue;
}
red_destroy_surface(worker, surface_id);
}
}
static void remove_drawable_dependencies(RedWorker *worker, Drawable *drawable)
{
int x;
int surface_id;
for (x = 0; x < 3; ++x) {
surface_id = drawable->surfaces_dest[x];
if (surface_id != -1 && drawable->depend_items[x].drawable) {
remove_depended_item(&drawable->depend_items[x]);
}
}
}
static inline void release_drawable(RedWorker *worker, Drawable *drawable)
{
RingItem *item, *next;
if (!--drawable->refs) {
spice_assert(!drawable->tree_item.shadow);
spice_assert(ring_is_empty(&drawable->pipes));
if (drawable->stream) {
red_detach_stream(worker, drawable->stream, TRUE);
}
region_destroy(&drawable->tree_item.base.rgn);
remove_drawable_dependencies(worker, drawable);
red_dec_surfaces_drawable_dependencies(worker, drawable);
red_destroy_surface(worker, drawable->surface_id);
RING_FOREACH_SAFE(item, next, &drawable->glz_ring) {
SPICE_CONTAINEROF(item, RedGlzDrawable, drawable_link)->drawable = NULL;
ring_remove(item);
}
put_red_drawable(worker, drawable->red_drawable,
drawable->group_id, drawable->self_bitmap);
free_drawable(worker, drawable);
worker->drawable_count--;
}
}
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)
{
spice_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);
spice_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;
}
}
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;
SpiceRect* src_area = &item->red_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->red_drawable->bbox;
}
static void surface_flush(RedWorker *worker, int surface_id, SpiceRect *rect)
{
red_update_area(worker, rect, surface_id);
}
static void red_flush_source_surfaces(RedWorker *worker, Drawable *drawable)
{
int x;
int surface_id;
for (x = 0; x < 3; ++x) {
surface_id = drawable->surfaces_dest[x];
if (surface_id != -1 && drawable->depend_items[x].drawable) {
remove_depended_item(&drawable->depend_items[x]);
surface_flush(worker, surface_id, &drawable->red_drawable->surfaces_rects[x]);
}
}
}
static inline void current_remove_drawable(RedWorker *worker, Drawable *item)
{
if (item->tree_item.effect != QXL_EFFECT_OPAQUE) {
worker->transparent_count--;
}
if (!item->stream) {
red_add_item_trace(worker, item);
}
remove_shadow(worker, &item->tree_item);
ring_remove(&item->tree_item.base.siblings_link);
ring_remove(&item->list_link);
ring_remove(&item->surface_list_link);
release_drawable(worker, item);
worker->current_size--;
}
static void remove_drawable(RedWorker *worker, Drawable *drawable)
{
red_pipes_remove_drawable(drawable);
current_remove_drawable(worker, drawable);
}
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, SPICE_CONTAINEROF(now, Drawable, tree_item));
} else {
Container *container = (Container *)now;
spice_assert(now->type == TREE_ITEM_TYPE_CONTAINER);
if ((ring_item = ring_get_head(&container->items))) {
now = SPICE_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 = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link);
} else {
now = (TreeItem *)container;
}
}
}
static void current_tree_for_each(Ring *ring, void (*f)(TreeItem *, void *), void * data)
{
RingItem *ring_item;
Ring *top_ring;
if (!(ring_item = ring_get_head(ring))) {
return;
}
top_ring = ring;
for (;;) {
TreeItem *now = SPICE_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, int surface_id)
{
RingItem *ring_item;
while ((ring_item = ring_get_head(&worker->surfaces[surface_id].current))) {
TreeItem *now = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link);
current_remove(worker, now);
}
}
static void red_clear_surface_drawables_from_pipe(DisplayChannelClient *dcc, int surface_id,
int force)
{
Ring *ring;
PipeItem *item;
int x;
RedChannelClient *rcc;
if (!dcc) {
return;
}
/* removing the newest drawables that their destination is surface_id and
no other drawable depends on them */
rcc = &dcc->common.base;
ring = &dcc->common.base.pipe;
item = (PipeItem *) ring;
while ((item = (PipeItem *)ring_next(ring, (RingItem *)item))) {
Drawable *drawable;
DrawablePipeItem *dpi = NULL;
int depend_found = FALSE;
if (item->type == PIPE_ITEM_TYPE_DRAW) {
dpi = SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item);
drawable = dpi->drawable;
} else if (item->type == PIPE_ITEM_TYPE_UPGRADE) {
drawable = ((UpgradeItem *)item)->drawable;
} else {
continue;
}
if (drawable->surface_id == surface_id) {
PipeItem *tmp_item = item;
item = (PipeItem *)ring_prev(ring, (RingItem *)item);
red_channel_client_pipe_remove_and_release(rcc, tmp_item);
if (!item) {
item = (PipeItem *)ring;
}
continue;
}
for (x = 0; x < 3; ++x) {
if (drawable->surfaces_dest[x] == surface_id) {
depend_found = TRUE;
break;
}
}
if (depend_found) {
if (force) {
break;
} else {
return;
}
}
}
if (item) {
red_wait_pipe_item_sent(&dcc->common.base, item);
}
}
static void red_wait_outgoing_item(RedChannelClient *rcc);
static void red_wait_outgoing_items(RedChannel *channel);
static void red_clear_surface_drawables_from_pipes(RedWorker *worker, int surface_id,
int force, int wait_for_outgoing_item)
{
RingItem *item;
DisplayChannelClient *dcc;
WORKER_FOREACH_DCC(worker, item, dcc) {
red_clear_surface_drawables_from_pipe(dcc, surface_id, force);
if (wait_for_outgoing_item) {
// in case that the pipe didn't contain any item that is dependent on the surface, but
// there is one during sending.
red_wait_outgoing_item(&dcc->common.base);
}
}
}
#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 = SPICE_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:
spice_error("invalid type %u", base->type);
}
}
void __show_current(TreeItem *item, void *data)
{
print_base_item("TREE", item);
}
static void show_current(RedWorker *worker, Ring *ring)
{
if (ring_is_empty(ring)) {
spice_printerr("TEST: TREE: EMPTY");
return;
}
current_tree_for_each(ring, __show_current, NULL);
}
#else
#define print_rgn(a, b)
#define print_draw_private(a, b)
#define show_current(a, r)
#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, Ring *ring, TreeItem *item)
{
return (item->container) ? &item->container->items : ring;
}
static inline int __contained_by(RedWorker *worker, TreeItem *item, Ring *ring)
{
spice_assert(item && ring);
do {
Ring *now = ring_of(worker, ring, item);
if (now == ring) {
return TRUE;
}
} while ((item = (TreeItem *)item->container));
return FALSE;
}
static inline void __exclude_region(RedWorker *worker, Ring *ring, 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.extents.x1;
int32_t y = item->rgn.extents.y1;
region_exclude(&draw->base.rgn, &and_rgn);
shadow = draw->shadow;
region_offset(&and_rgn, shadow->base.rgn.extents.x1 - x,
shadow->base.rgn.extents.y1 - 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, ring, (TreeItem*)shadow);
}
}
} else {
if (frame_candidate) {
Drawable *drawable = SPICE_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, ring, (TreeItem*)shadow);
}
}
}
} else {
Shadow *shadow;
spice_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);
}
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 = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link);
Container *container = now->container;
spice_assert(!region_is_empty(&now->rgn));
if (region_intersects(rgn, &now->rgn)) {
print_base_item("EXCLUDE2", now);
__exclude_region(worker, ring, now, rgn, &top_ring, frame_candidate);
print_base_item("EXCLUDE3", now);
if (region_is_empty(&now->rgn)) {
spice_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;
spice_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;
}
}
}
static inline Container *__new_container(RedWorker *worker, DrawItem *item)
{
Container *container = spice_new(Container, 1);
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)
{
RedSurface *surface;
uint32_t surface_id = drawable->surface_id;
surface = &worker->surfaces[surface_id];
ring_add_after(&drawable->tree_item.base.siblings_link, pos);
ring_add(&worker->current_list, &drawable->list_link);
ring_add(&surface->current_list, &drawable->surface_list_link);
worker->current_size++;
drawable->refs++;
}
static int is_equal_path(RedWorker *worker, SpicePath *path1, SpicePath *path2)
{
SpicePathSeg *seg1, *seg2;
int i, j;
if (path1->num_segments != path2->num_segments)
return FALSE;
for (i = 0; i < path1->num_segments; i++) {
seg1 = path1->segments[i];
seg2 = path2->segments[i];
if (seg1->flags != seg2->flags ||
seg1->count != seg2->count) {
return FALSE;
}
for (j = 0; j < seg1->count; j++) {
if (seg1->points[j].x != seg2->points[j].x ||
seg1->points[j].y != seg2->points[j].y) {
return FALSE;
}
}
}
return TRUE;
}
// partial imp
static int is_equal_brush(SpiceBrush *b1, SpiceBrush *b2)
{
return b1->type == b2->type &&
b1->type == SPICE_BRUSH_TYPE_SOLID &&
b1->u.color == b2->u.color;
}
// partial imp
static int is_equal_line_attr(SpiceLineAttr *a1, SpiceLineAttr *a2)
{
return a1->flags == a2->flags &&
a1->style_nseg == a2->style_nseg &&
a1->style_nseg == 0;
}
// partial imp
static int is_same_geometry(RedWorker *worker, Drawable *d1, Drawable *d2)
{
if (d1->red_drawable->type != d2->red_drawable->type) {
return FALSE;
}
switch (d1->red_drawable->type) {
case QXL_DRAW_STROKE:
return is_equal_line_attr(&d1->red_drawable->u.stroke.attr,
&d2->red_drawable->u.stroke.attr) &&
is_equal_path(worker, d1->red_drawable->u.stroke.path,
d2->red_drawable->u.stroke.path);
case QXL_DRAW_FILL:
return rect_is_equal(&d1->red_drawable->bbox, &d2->red_drawable->bbox);
default:
return FALSE;
}
}
static int is_same_drawable(RedWorker *worker, Drawable *d1, Drawable *d2)
{
if (!is_same_geometry(worker, d1, d2)) {
return FALSE;
}
switch (d1->red_drawable->type) {
case QXL_DRAW_STROKE:
return is_equal_brush(&d1->red_drawable->u.stroke.brush,
&d2->red_drawable->u.stroke.brush);
case QXL_DRAW_FILL:
return is_equal_brush(&d1->red_drawable->u.fill.brush,
&d2->red_drawable->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) {
spice_assert(!ring_item_is_linked(&stream->link));
if (stream->mjpeg_encoder) {
mjpeg_encoder_destroy(stream->mjpeg_encoder);
}
red_free_stream(worker, stream);
worker->stream_count--;
}
}
static inline void red_detach_stream(RedWorker *worker, Stream *stream, int detach_sized)
{
spice_assert(stream->current && stream->current->stream);
spice_assert(stream->current->stream == stream);
stream->current->stream = NULL;
if (detach_sized) {
stream->current->sized_stream = NULL;
}
stream->current = NULL;
}
static StreamClipItem *__new_stream_clip(DisplayChannelClient* dcc, StreamAgent *agent)
{
StreamClipItem *item = spice_new(StreamClipItem, 1);
red_channel_pipe_item_init(dcc->common.base.channel,
(PipeItem *)item, PIPE_ITEM_TYPE_STREAM_CLIP);
item->stream_agent = agent;
agent->stream->refs++;
item->refs = 1;
return item;
}
static void push_stream_clip(DisplayChannelClient* dcc, StreamAgent *agent)
{
StreamClipItem *item = __new_stream_clip(dcc, agent);
int n_rects;
if (!item) {
spice_critical("alloc failed");
}
item->clip_type = SPICE_CLIP_TYPE_RECTS;
n_rects = pixman_region32_n_rects(&agent->clip);
item->rects = spice_malloc_n_m(n_rects, sizeof(SpiceRect), sizeof(SpiceClipRects));
item->rects->num_rects = n_rects;
region_ret_rects(&agent->clip, item->rects->rects, n_rects);
red_channel_client_pipe_add(&dcc->common.base, (PipeItem *)item);
}
static void red_display_release_stream_clip(RedWorker *worker, StreamClipItem *item)
{
if (!--item->refs) {
red_display_release_stream(worker, item->stream_agent);
free(item->rects);
free(item);
}
}
static inline int get_stream_id(RedWorker *worker, Stream *stream)
{
return (int)(stream - worker->streams_buf);
}
static void red_attach_stream(RedWorker *worker, Drawable *drawable, Stream *stream)
{
DisplayChannelClient *dcc;
RingItem *item;
spice_assert(!drawable->stream && !stream->current);
spice_assert(drawable && stream);
stream->current = drawable;
drawable->stream = stream;
stream->last_time = drawable->creation_time;
WORKER_FOREACH_DCC(worker, item, dcc) {
StreamAgent *agent;
QRegion clip_in_draw_dest;
agent = &dcc->stream_agents[get_stream_id(worker, stream)];
region_or(&agent->vis_region, &drawable->tree_item.base.rgn);
region_init(&clip_in_draw_dest);
region_add(&clip_in_draw_dest, &drawable->red_drawable->bbox);
region_and(&clip_in_draw_dest, &agent->clip);
if (!region_is_equal(&clip_in_draw_dest, &drawable->tree_item.base.rgn)) {
region_remove(&agent->clip, &drawable->red_drawable->bbox);
region_or(&agent->clip, &drawable->tree_item.base.rgn);
push_stream_clip(dcc, agent);
}
}
}
static void red_stop_stream(RedWorker *worker, Stream *stream)
{
DisplayChannelClient *dcc;
RingItem *item;
spice_assert(ring_item_is_linked(&stream->link));
spice_assert(!stream->current);
spice_debug("stream %d", get_stream_id(worker, stream));
WORKER_FOREACH_DCC(worker, item, dcc) {
StreamAgent *stream_agent;
stream_agent = &dcc->stream_agents[get_stream_id(worker, stream)];
region_clear(&stream_agent->vis_region);
region_clear(&stream_agent->clip);
spice_assert(!pipe_item_is_linked(&stream_agent->destroy_item));
stream->refs++;
red_channel_client_pipe_add(&dcc->common.base, &stream_agent->destroy_item);
}
ring_remove(&stream->link);
red_release_stream(worker, stream);
}
static int red_display_drawable_is_in_pipe(DisplayChannelClient *dcc, Drawable *drawable)
{
DrawablePipeItem *dpi;
RingItem *dpi_link;
DRAWABLE_FOREACH_DPI(drawable, dpi_link, dpi) {
if (dpi->dcc == dcc) {
return TRUE;
}
}
return FALSE;
}
/*
* after red_display_detach_stream_gracefully is called for all the display channel clients,
* red_detach_stream should be called. See comment (1).
*/
static inline void red_display_detach_stream_gracefully(DisplayChannelClient *dcc, Stream *stream)
{
int stream_id = get_stream_id(dcc->common.worker, stream);
StreamAgent *agent = &dcc->stream_agents[stream_id];
/* stopping the client from playing older frames at once*/
region_clear(&agent->clip);
push_stream_clip(dcc, agent);
if (region_is_empty(&agent->vis_region)) {
spice_debug("stream %d: vis region empty", stream_id);
return;
}
if (stream->current &&
region_contains(&stream->current->tree_item.base.rgn, &agent->vis_region)) {
RedChannel *channel;
RedChannelClient *rcc;
UpgradeItem *upgrade_item;
int n_rects;
/* (1) The caller should detach the drawable from the stream. This will
* lead to sending the drawable losslessly, as an ordinary drawable. */
if (red_display_drawable_is_in_pipe(dcc, stream->current)) {
spice_debug("stream %d: upgrade by linked drawable. sized %d, box ==>",
stream_id, stream->current->sized_stream != NULL);
rect_debug(&stream->current->red_drawable->bbox);
return;
}
spice_debug("stream %d: upgrade by drawable. sized %d, box ==>",
stream_id, stream->current->sized_stream != NULL);
rect_debug(&stream->current->red_drawable->bbox);
rcc = &dcc->common.base;
channel = rcc->channel;
upgrade_item = spice_new(UpgradeItem, 1);
upgrade_item->refs = 1;
red_channel_pipe_item_init(channel,
&upgrade_item->base, PIPE_ITEM_TYPE_UPGRADE);
upgrade_item->drawable = stream->current;
upgrade_item->drawable->refs++;
n_rects = pixman_region32_n_rects(&upgrade_item->drawable->tree_item.base.rgn);
upgrade_item->rects = spice_malloc_n_m(n_rects, sizeof(SpiceRect), sizeof(SpiceClipRects));
upgrade_item->rects->num_rects = n_rects;
region_ret_rects(&upgrade_item->drawable->tree_item.base.rgn,
upgrade_item->rects->rects, n_rects);
red_channel_client_pipe_add(rcc, &upgrade_item->base);
} else {
SpiceRect upgrade_area;
region_extents(&agent->vis_region, &upgrade_area);
spice_debug("stream %d: upgrade by screenshot. has current %d. box ==>",
stream_id, stream->current != NULL);
rect_debug(&upgrade_area);
red_update_area(dcc->common.worker, &upgrade_area, 0);
red_add_surface_area_image(dcc, 0, &upgrade_area, NULL, FALSE);
}
}
static inline void red_detach_stream_gracefully(RedWorker *worker, Stream *stream)
{
RingItem *item;
DisplayChannelClient *dcc;
WORKER_FOREACH_DCC(worker, item, dcc) {
red_display_detach_stream_gracefully(dcc, stream);
}
if (stream->current) {
red_detach_stream(worker, stream, TRUE);
}
}
// region should be a primary surface region
static void red_detach_streams_behind(RedWorker *worker, QRegion *region)
{
Ring *ring = &worker->streams;
RingItem *item = ring_get_head(ring);
RingItem *dcc_ring_item;
DisplayChannelClient *dcc;
int has_clients = display_is_connected(worker);
while (item) {
Stream *stream = SPICE_CONTAINEROF(item, Stream, link);
int detach_stream = 0;
item = ring_next(ring, item);
WORKER_FOREACH_DCC(worker, dcc_ring_item, dcc) {
StreamAgent *agent = &dcc->stream_agents[get_stream_id(worker, stream)];
if (region_intersects(&agent->vis_region, region)) {
red_display_detach_stream_gracefully(dcc, stream);
detach_stream = 1;
spice_debug("stream %d", get_stream_id(worker, stream));
}
}
if (detach_stream && stream->current) {
red_detach_stream(worker, stream, TRUE);
} else if (!has_clients) {
if (stream->current &&
region_intersects(&stream->current->tree_item.base.rgn, region)) {
red_detach_stream(worker, stream, TRUE);
}
}
}
}
static void red_streams_update_visible_region(RedWorker *worker, Drawable *drawable)
{
Ring *ring;
RingItem *item;
RingItem *dcc_ring_item;
DisplayChannelClient *dcc;
if (!display_is_connected(worker)) {
return;
}
if (!is_primary_surface(worker, drawable->surface_id)) {
return;
}
ring = &worker->streams;
item = ring_get_head(ring);
while (item) {
Stream *stream = SPICE_CONTAINEROF(item, Stream, link);
StreamAgent *agent;
item = ring_next(ring, item);
if (stream->current == drawable) {
continue;
}
WORKER_FOREACH_DCC(worker, dcc_ring_item, dcc) {
agent = &dcc->stream_agents[get_stream_id(worker, stream)];
if (region_intersects(&agent->vis_region, &drawable->tree_item.base.rgn)) {
region_exclude(&agent->vis_region, &drawable->tree_item.base.rgn);
region_exclude(&agent->clip, &drawable->tree_item.base.rgn);
push_stream_clip(dcc, 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 = SPICE_CONTAINEROF(item, Stream, link);
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)));
}
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 = SPICE_CONTAINEROF(item, Stream, link);
item = ring_next(ring, item);
if (now >= (stream->last_time + RED_STREAM_TIMOUT)) {
red_detach_stream_gracefully(worker, stream);
red_stop_stream(worker, stream);
}
}
}
static void red_display_release_stream(RedWorker *worker, StreamAgent *agent)
{
spice_assert(agent->stream);
red_release_stream(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(DisplayChannelClient *dcc,
int width, int height)
{
uint64_t bit_rate = width * height * BEST_BIT_RATE_PER_PIXEL;
MainChannelClient *mcc;
int is_low_bandwidth = 0;
if (dcc) {
mcc = red_client_get_main(dcc->common.base.client);
is_low_bandwidth = main_channel_client_is_low_bandwidth(mcc);
}
if (is_low_bandwidth) {
bit_rate = MIN(main_channel_client_get_bitrate_per_sec(mcc) * 70 / 100, bit_rate);
bit_rate = MAX(bit_rate, width * height * WORST_BIT_RATE_PER_PIXEL);
}
return bit_rate;
}
static int get_minimal_bit_rate(RedWorker *worker, int width, int height)
{
RingItem *item;
DisplayChannelClient *dcc;
int ret = INT_MAX;
WORKER_FOREACH_DCC(worker, item, dcc) {
int bit_rate = get_bit_rate(dcc, width, height);
if (bit_rate < ret) {
ret = bit_rate;
}
}
return ret;
}
static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)
{
StreamAgent *agent = &dcc->stream_agents[get_stream_id(dcc->common.worker, stream)];
stream->refs++;
spice_assert(region_is_empty(&agent->vis_region));
if (stream->current) {
agent->frames = 1;
region_clone(&agent->vis_region, &stream->current->tree_item.base.rgn);
region_clone(&agent->clip, &agent->vis_region);
} else {
agent->frames = 0;
}
agent->drops = 0;
agent->fps = MAX_FPS;
reset_rate(dcc, agent);
red_channel_client_pipe_add(&dcc->common.base, &agent->create_item);
}
/* TODO: we create the stream even if dcc is NULL, i.e. no client - or
* maybe we can't reach this function in that case? question: do we want to? */
static void red_create_stream(RedWorker *worker, Drawable *drawable)
{
DisplayChannelClient *dcc;
RingItem *dcc_ring_item;
Stream *stream;
SpiceRect* src_rect;
int stream_width;
int stream_height;
spice_assert(!drawable->stream);
if (!(stream = red_alloc_stream(worker))) {
return;
}
spice_assert(drawable->red_drawable->type == QXL_DRAW_COPY);
src_rect = &drawable->red_drawable->u.copy.src_area;
stream_width = src_rect->right - src_rect->left;
stream_height = src_rect->bottom - src_rect->top;
stream->mjpeg_encoder = mjpeg_encoder_new();
ring_add(&worker->streams, &stream->link);
stream->current = drawable;
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->red_drawable->bbox;
stream->refs = 1;
stream->bit_rate = get_minimal_bit_rate(worker, stream_width, stream_height);
SpiceBitmap *bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap;
stream->top_down = !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN);
drawable->stream = stream;
WORKER_FOREACH_DCC(worker, dcc_ring_item, dcc) {
red_display_create_stream(dcc, stream);
}
worker->stream_count++;
spice_debug("stream %d %dx%d (%d, %d) (%d, %d)", (int)(stream - worker->streams_buf), stream->width,
stream->height, stream->dest_area.left, stream->dest_area.top,
stream->dest_area.right, stream->dest_area.bottom);
return;
}
static void red_disply_start_streams(DisplayChannelClient *dcc)
{
Ring *ring = &dcc->common.worker->streams;
RingItem *item = ring;
while ((item = ring_next(ring, item))) {
Stream *stream = SPICE_CONTAINEROF(item, Stream, link);
red_display_create_stream(dcc, stream);
}
}
static void red_display_client_init_streams(DisplayChannelClient *dcc)
{
int i;
RedWorker *worker = dcc->common.worker;
RedChannel *channel = dcc->common.base.channel;
for (i = 0; i < NUM_STREAMS; i++) {
StreamAgent *agent = &dcc->stream_agents[i];
agent->stream = &worker->streams_buf[i];
region_init(&agent->vis_region);
region_init(&agent->clip);
red_channel_pipe_item_init(channel, &agent->create_item, PIPE_ITEM_TYPE_STREAM_CREATE);
red_channel_pipe_item_init(channel, &agent->destroy_item, PIPE_ITEM_TYPE_STREAM_DESTROY);
}
}
static void red_display_destroy_streams(DisplayChannelClient *dcc)
{
int i;
for (i = 0; i < NUM_STREAMS; i++) {
StreamAgent *agent = &dcc->stream_agents[i];
region_destroy(&agent->vis_region);
region_destroy(&agent->clip);
}
}
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);
}
}
static inline int __red_is_next_stream_frame(RedWorker *worker,
const Drawable *candidate,
const int other_src_width,
const int other_src_height,
const SpiceRect *other_dest,
const red_time_t other_time,
const Stream *stream,
int container_candidate_allowed)
{
RedDrawable *red_drawable;
int is_frame_container = FALSE;
if (candidate->creation_time - other_time >
(stream ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) {
return STREAM_FRAME_NONE;
}
red_drawable = candidate->red_drawable;
if (!container_candidate_allowed) {
SpiceRect* candidate_src;
if (!rect_is_equal(&red_drawable->bbox, other_dest)) {
return STREAM_FRAME_NONE;
}
candidate_src = &red_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 STREAM_FRAME_NONE;
}
} else {
if (rect_contains(&red_drawable->bbox, other_dest)) {
int candidate_area = rect_get_area(&red_drawable->bbox);
int other_area = rect_get_area(other_dest);
/* do not stream drawables that are significantly
* bigger than the original frame */
if (candidate_area > 2 * other_area) {
spice_debug("too big candidate:");
spice_debug("prev box ==>");
rect_debug(other_dest);
spice_debug("new box ==>");
rect_debug(&red_drawable->bbox);
return STREAM_FRAME_NONE;
}
if (candidate_area > other_area) {
is_frame_container = TRUE;
}
} else {
return STREAM_FRAME_NONE;
}
}
if (stream) {
SpiceBitmap *bitmap = &red_drawable->u.copy.src_bitmap->u.bitmap;
if (stream->top_down != !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
return STREAM_FRAME_NONE;
}
}
if (is_frame_container) {
return STREAM_FRAME_CONTAINER;
} else {
return STREAM_FRAME_NATIVE;
}
}
static inline int red_is_next_stream_frame(RedWorker *worker, const Drawable *candidate,
const Drawable *prev)
{
if (!candidate->streamable) {
return FALSE;
}
SpiceRect* prev_src = &prev->red_drawable->u.copy.src_area;
return __red_is_next_stream_frame(worker, candidate, prev_src->right - prev_src->left,
prev_src->bottom - prev_src->top,
&prev->red_drawable->bbox, prev->creation_time,
prev->stream,
FALSE);
}
static void reset_rate(DisplayChannelClient *dcc, StreamAgent *stream_agent)
{
Stream *stream = stream_agent->stream;
int rate;
rate = get_bit_rate(dcc, stream->width, stream->height);
if (rate == stream->bit_rate) {
return;
}
/* MJpeg has no rate limiting anyway, so do nothing */
}
static int display_channel_client_is_low_bandwidth(DisplayChannelClient *dcc)
{
return main_channel_client_is_low_bandwidth(
red_client_get_main(red_channel_client_get_client(&dcc->common.base)));
}
static inline void pre_stream_item_swap(RedWorker *worker, Stream *stream)
{
DrawablePipeItem *dpi;
DisplayChannelClient *dcc;
int index;
StreamAgent *agent;
RingItem *ring_item;
spice_assert(stream->current);
if (!display_is_connected(worker)) {
return;
}
index = get_stream_id(worker, stream);
DRAWABLE_FOREACH_DPI(stream->current, ring_item, dpi) {
dcc = dpi->dcc;
if (!display_channel_client_is_low_bandwidth(dcc)) {
continue;
}
agent = &dcc->stream_agents[index];
if (pipe_item_is_linked(&dpi->dpi_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)
{
SpiceBitmap *bitmap;
spice_assert(drawable->red_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
}
bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap;
if (!BITMAP_FMT_IS_RGB[bitmap->format] || _stride_is_extra(bitmap) ||
(bitmap->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE)) {
drawable->copy_bitmap_graduality = BITMAP_GRADUAL_NOT_AVAIL;
} else {
drawable->copy_bitmap_graduality =
_get_bitmap_graduality_level(worker, bitmap,drawable->group_id);
}
}
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)));
}
// returns whether a stream was created
static int 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_LOW) {
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);
return TRUE;
}
return FALSE;
}
static inline void red_stream_maintenance(RedWorker *worker, Drawable *candidate, Drawable *prev)
{
Stream *stream;
if (candidate->stream) {
return;
}
if ((stream = prev->stream)) {
int is_next_frame = __red_is_next_stream_frame(worker,
candidate,
stream->width,
stream->height,
&stream->dest_area,
stream->last_time,
stream,
TRUE);
if (is_next_frame != STREAM_FRAME_NONE) {
pre_stream_item_swap(worker, stream);
red_detach_stream(worker, stream, FALSE);
prev->streamable = FALSE; //prevent item trace
red_attach_stream(worker, candidate, stream);
if (is_next_frame == STREAM_FRAME_CONTAINER) {
candidate->sized_stream = stream;
}
}
} else {
if (red_is_next_stream_frame(worker, candidate, prev) != STREAM_FRAME_NONE) {
red_stream_add_frame(worker, candidate,
prev->frames_count,
prev->gradual_frames_count,
prev->last_gradual_frame);
}
}
}
static inline int is_drawable_independent_from_surfaces(Drawable *drawable)
{
int x;
for (x = 0; x < 3; ++x) {
if (drawable->surfaces_dest[x] != -1) {
return FALSE;
}
}
return TRUE;
}
static inline int red_current_add_equal(RedWorker *worker, DrawItem *item, TreeItem *other)
{
DrawItem *other_draw_item;
Drawable *drawable;
Drawable *other_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 = SPICE_CONTAINEROF(item, Drawable, tree_item);
other_drawable = SPICE_CONTAINEROF(other_draw_item, Drawable, tree_item);
if (item->effect == QXL_EFFECT_OPAQUE) {
int add_after = !!other_drawable->stream &&
is_drawable_independent_from_surfaces(drawable);
red_stream_maintenance(worker, drawable, other_drawable);
__current_add_drawable(worker, drawable, &other->siblings_link);
other_drawable->refs++;
current_remove_drawable(worker, other_drawable);
if (add_after) {
red_pipes_add_drawable_after(worker, drawable, other_drawable);
} else {
red_pipes_add_drawable(worker, drawable);
}
red_pipes_remove_drawable(other_drawable);
release_drawable(worker, other_drawable);
return TRUE;
}
switch (item->effect) {
case QXL_EFFECT_REVERT_ON_DUP:
if (is_same_drawable(worker, drawable, other_drawable)) {
DisplayChannelClient *dcc;
DrawablePipeItem *dpi;
RingItem *worker_ring_item, *dpi_ring_item;
other_drawable->refs++;
current_remove_drawable(worker, other_drawable);
/* sending the drawable to clients that already received
* (or will receive) other_drawable */
worker_ring_item = ring_get_head(&worker->display_channel->common.base.clients);
dpi_ring_item = ring_get_head(&other_drawable->pipes);
/* dpi contains a sublist of dcc's, ordered the same */
while (worker_ring_item) {
dcc = SPICE_CONTAINEROF(worker_ring_item, DisplayChannelClient,
common.base.channel_link);
dpi = SPICE_CONTAINEROF(dpi_ring_item, DrawablePipeItem, base);
while (worker_ring_item && (!dpi || dcc != dpi->dcc)) {
red_pipe_add_drawable(dcc, drawable);
worker_ring_item = ring_next(&worker->display_channel->common.base.clients,
worker_ring_item);
dcc = SPICE_CONTAINEROF(worker_ring_item, DisplayChannelClient,
common.base.channel_link);
}
if (dpi_ring_item) {
dpi_ring_item = ring_next(&other_drawable->pipes, dpi_ring_item);
}
if (worker_ring_item) {
worker_ring_item = ring_next(&worker->display_channel->common.base.clients,
worker_ring_item);
}
}
/* not sending other_drawable where possible */
red_pipes_remove_drawable(other_drawable);
release_drawable(worker, other_drawable);
return TRUE;
}
break;
case QXL_EFFECT_OPAQUE_BRUSH:
if (is_same_geometry(worker, drawable, other_drawable)) {
__current_add_drawable(worker, drawable, &other->siblings_link);
remove_drawable(worker, other_drawable);
red_pipes_add_drawable(worker, drawable);
return TRUE;
}
break;
case QXL_EFFECT_NOP_ON_DUP:
if (is_same_drawable(worker, drawable, other_drawable)) {
return TRUE;
}
break;
}
return FALSE;
}
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 = SPICE_CONTAINEROF(item, Stream, link);
int is_next_frame = __red_is_next_stream_frame(worker,
drawable,
stream->width,
stream->height,
&stream->dest_area,
stream->last_time,
stream,
TRUE);
if (is_next_frame != STREAM_FRAME_NONE) {
if (stream->current) {
stream->current->streamable = FALSE; //prevent item trace
pre_stream_item_swap(worker, stream);
red_detach_stream(worker, stream, FALSE);
}
red_attach_stream(worker, drawable, stream);
if (is_next_frame == STREAM_FRAME_CONTAINER) {
drawable->sized_stream = 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(worker, drawable, trace->width, trace->height,
&trace->dest_area, trace->time, NULL, FALSE) !=
STREAM_FRAME_NONE) {
if (red_stream_add_frame(worker, drawable,
trace->frames_count,
trace->gradual_frames_count,
trace->last_gradual_frame)) {
return;
}
}
}
}
static void red_reset_stream_trace(RedWorker *worker)
{
Ring *ring = &worker->streams;
RingItem *item = ring_get_head(ring);
while (item) {
Stream *stream = SPICE_CONTAINEROF(item, Stream, link);
item = ring_next(ring, item);
if (!stream->current) {
red_stop_stream(worker, stream);
} else {
spice_printerr("attached stream");
}
}
worker->next_item_trace = 0;
memset(worker->items_trace, 0, sizeof(worker->items_trace));
}
static inline int red_current_add(RedWorker *worker, Ring *ring, Drawable *drawable)
{
DrawItem *item = &drawable->tree_item;
#ifdef RED_WORKER_STAT
stat_time_t start_time = stat_now();
#endif
RingItem *now;
QRegion exclude_rgn;
RingItem *exclude_base = NULL;
print_base_item("ADD", &item->base);
spice_assert(!region_is_empty(&item->base.rgn));
region_init(&exclude_rgn);
now = ring_next(ring, ring);
while (now) {
TreeItem *sibling = SPICE_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;
}
spice_assert(IS_DRAW_ITEM(sibling));
if (!((DrawItem *)sibling)->container_root) {
container = __new_container(worker, (DrawItem *)sibling);
if (!container) {
spice_printerr("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);
red_use_stream_trace(worker, drawable);
red_streams_update_visible_region(worker, drawable);
} else {
if (drawable->surface_id == 0) {
red_detach_streams_behind(worker, &drawable->tree_item.base.rgn);
}
}
region_destroy(&exclude_rgn);
__current_add_drawable(worker, drawable, ring);
stat_add(&worker->add_stat, start_time);
return TRUE;
}
static void add_clip_rects(QRegion *rgn, SpiceClipRects *data)
{
int i;
for (i = 0; i < data->num_rects; i++) {
region_add(rgn, data->rects + i);
}
}
static inline Shadow *__new_shadow(RedWorker *worker, Drawable *item, SpicePoint *delta)
{
if (!delta->x && !delta->y) {
return NULL;
}
Shadow *shadow = spice_new(Shadow, 1);
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, Ring *ring, Drawable *item,
SpicePoint *delta)
{
#ifdef RED_WORKER_STAT
stat_time_t start_time = stat_now();
#endif
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);
// item and his shadow must initially be placed in the same container.
// for now putting them on root.
// only primary surface streams are supported
if (is_primary_surface(worker, item->surface_id)) {
red_detach_streams_behind(worker, &shadow->base.rgn);
}
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);
exclude_region(worker, ring, &shadow->base.siblings_link, &exclude_rgn, NULL, NULL);
region_destroy(&exclude_rgn);
red_streams_update_visible_region(worker, item);
} else {
if (item->surface_id == 0) {
red_detach_streams_behind(worker, &item->tree_item.base.rgn);
}
}
stat_add(&worker->add_stat, start_time);
return TRUE;
}
static inline int has_shadow(RedDrawable *drawable)
{
return drawable->type == QXL_COPY_BITS;
}
static inline void red_update_streamable(RedWorker *worker, Drawable *drawable,
RedDrawable *red_drawable)
{
SpiceImage *image;
if (worker->streaming_video == STREAM_VIDEO_OFF) {
return;
}
if (!is_primary_surface(worker, drawable->surface_id)) {
return;
}
if (drawable->tree_item.effect != QXL_EFFECT_OPAQUE ||
red_drawable->type != QXL_DRAW_COPY ||
red_drawable->u.copy.rop_descriptor != SPICE_ROPD_OP_PUT) {
return;
}
image = red_drawable->u.copy.src_bitmap;
if (image == NULL ||
image->descriptor.type != SPICE_IMAGE_TYPE_BITMAP) {
return;
}
if (worker->streaming_video == STREAM_VIDEO_FILTER) {
SpiceRect* rect;
int size;
rect = &drawable->red_drawable->u.copy.src_area;
size = (rect->right - rect->left) * (rect->bottom - rect->top);
if (size < RED_STREAM_MIN_SIZE) {
return;
}
}
drawable->streamable = TRUE;
}
static inline int red_current_add_qxl(RedWorker *worker, Ring *ring, Drawable *drawable,
RedDrawable *red_drawable)
{
int ret;
if (has_shadow(red_drawable)) {
SpicePoint delta;
#ifdef RED_WORKER_STAT
++worker->add_with_shadow_count;
#endif
delta.x = red_drawable->u.copy_bits.src_pos.x - red_drawable->bbox.left;
delta.y = red_drawable->u.copy_bits.src_pos.y - red_drawable->bbox.top;
ret = red_current_add_with_shadow(worker, ring, drawable, &delta);
} else {
red_update_streamable(worker, drawable, red_drawable);
ret = red_current_add(worker, ring, drawable);
}
#ifdef RED_WORKER_STAT
if ((++worker->add_count % 100) == 0) {
stat_time_t total = worker->add_stat.total;
spice_printerr("add with shadow count %u",
worker->add_with_shadow_count);
worker->add_with_shadow_count = 0;
spice_printerr("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));
spice_printerr("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, int surface_id, const SpiceRect *area, uint8_t *dest,
int dest_stride, int update)
{
SpiceCanvas *canvas;
RedSurface *surface;
surface = &worker->surfaces[surface_id];
if (update) {
red_update_area(worker, area, surface_id);
}
canvas = surface->context.canvas;
canvas->ops->read_bits(canvas, dest, dest_stride, area);
}
static int surface_format_to_image_type(uint32_t surface_format)
{
switch (surface_format) {
case SPICE_SURFACE_FMT_16_555:
return SPICE_BITMAP_FMT_16BIT;
case SPICE_SURFACE_FMT_32_xRGB:
return SPICE_BITMAP_FMT_32BIT;
case SPICE_SURFACE_FMT_32_ARGB:
return SPICE_BITMAP_FMT_RGBA;
default:
spice_critical("Unsupported surface format");
}
return 0;
}
static int rgb32_data_has_alpha(int width, int height, size_t stride,
uint8_t *data, int *all_set_out)
{
uint32_t *line, *end, alpha;
int has_alpha;
has_alpha = FALSE;
while (height-- > 0) {
line = (uint32_t *)data;
end = line + width;
data += stride;
while (line != end) {
alpha = *line & 0xff000000U;
if (alpha != 0) {
has_alpha = TRUE;
if (alpha != 0xff000000U) {
*all_set_out = FALSE;
return TRUE;
}
}
line++;
}
}
*all_set_out = has_alpha;
return has_alpha;
}
static inline int red_handle_self_bitmap(RedWorker *worker, Drawable *drawable)
{
SpiceImage *image;
int32_t width;
int32_t height;
uint8_t *dest;
int dest_stride;
RedSurface *surface;
int bpp;
int all_set;
if (!drawable->red_drawable->self_bitmap) {
return TRUE;
}
surface = &worker->surfaces[drawable->surface_id];
bpp = SPICE_SURFACE_FMT_DEPTH(surface->context.format) / 8;
width = drawable->red_drawable->self_bitmap_area.right
- drawable->red_drawable->self_bitmap_area.left;
height = drawable->red_drawable->self_bitmap_area.bottom
- drawable->red_drawable->self_bitmap_area.top;
dest_stride = SPICE_ALIGN(width * bpp, 4);
image = spice_new0(SpiceImage, 1);
image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
image->descriptor.flags = 0;
QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique);
image->u.bitmap.flags = surface->context.top_down ? SPICE_BITMAP_FLAGS_TOP_DOWN : 0;
image->u.bitmap.format = surface_format_to_image_type(surface->context.format);
image->u.bitmap.stride = dest_stride;
image->descriptor.width = image->u.bitmap.x = width;
image->descriptor.height = image->u.bitmap.y = height;
image->u.bitmap.palette = NULL;
dest = (uint8_t *)spice_malloc_n(height, dest_stride);
image->u.bitmap.data = spice_chunks_new_linear(dest, height * dest_stride);
image->u.bitmap.data->flags |= SPICE_CHUNKS_FLAGS_FREE;
red_get_area(worker, drawable->surface_id,
&drawable->red_drawable->self_bitmap_area, dest, dest_stride, TRUE);
/* For 32bit non-primary surfaces we need to keep any non-zero
high bytes as the surface may be used as source to an alpha_blend */
if (!is_primary_surface(worker, drawable->surface_id) &&
image->u.bitmap.format == SPICE_BITMAP_FMT_32BIT &&
rgb32_data_has_alpha(width, height, dest_stride, dest, &all_set)) {
if (all_set) {
image->descriptor.flags |= SPICE_IMAGE_FLAGS_HIGH_BITS_SET;
} else {
image->u.bitmap.format = SPICE_BITMAP_FMT_RGBA;
}
}
drawable->self_bitmap = image;
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;
spice_assert(ring_item);
drawable = SPICE_CONTAINEROF(ring_item, Drawable, list_link);
if (force_glz_free) {
RingItem *glz_item, *next_item;
RedGlzDrawable *glz;
DRAWABLE_FOREACH_GLZ_SAFE(drawable, glz_item, next_item, glz) {
red_display_free_glz_drawable(glz->dcc, glz);
}
}
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, RedDrawable *red_drawable,
uint32_t group_id) {
Drawable *drawable;
struct timespec time;
int x;
while (!(drawable = alloc_drawable(worker))) {
free_one_drawable(worker, FALSE);
}
worker->drawable_count++;
worker->red_drawable_count++;
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);
ring_item_init(&drawable->surface_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;
drawable->red_drawable = ref_red_drawable(red_drawable);
drawable->group_id = group_id;
drawable->surface_id = red_drawable->surface_id;
validate_surface(worker, drawable->surface_id);
for (x = 0; x < 3; ++x) {
drawable->surfaces_dest[x] = red_drawable->surfaces_dest[x];
if (drawable->surfaces_dest[x] != -1) {
validate_surface(worker, drawable->surfaces_dest[x]);
}
}
ring_init(&drawable->pipes);
ring_init(&drawable->glz_ring);
return drawable;
}
static inline int red_handle_depends_on_target_surface(RedWorker *worker, uint32_t surface_id)
{
RedSurface *surface;
RingItem *ring_item;
surface = &worker->surfaces[surface_id];
while ((ring_item = ring_get_tail(&surface->depend_on_me))) {
Drawable *drawable;
DependItem *depended_item = SPICE_CONTAINEROF(ring_item, DependItem, ring_item);
drawable = depended_item->drawable;
surface_flush(worker, drawable->surface_id, &drawable->red_drawable->bbox);
}
return TRUE;
}
static inline void add_to_surface_dependency(RedWorker *worker, int depend_on_surface_id,
DependItem *depend_item, Drawable *drawable)
{
RedSurface *surface;
if (depend_on_surface_id == -1) {
depend_item->drawable = NULL;
return;
}
surface = &worker->surfaces[depend_on_surface_id];
depend_item->drawable = drawable;
ring_add(&surface->depend_on_me, &depend_item->ring_item);
}
static inline int red_handle_surfaces_dependencies(RedWorker *worker, Drawable *drawable)
{
int x;
for (x = 0; x < 3; ++x) {
// surface self dependency is handled by shadows in "current", or by
// handle_self_bitmap
if (drawable->surfaces_dest[x] != drawable->surface_id) {
add_to_surface_dependency(worker, drawable->surfaces_dest[x],
&drawable->depend_items[x], drawable);
if (drawable->surfaces_dest[x] == 0) {
QRegion depend_region;
region_init(&depend_region);
region_add(&depend_region, &drawable->red_drawable->surfaces_rects[x]);
red_detach_streams_behind(worker, &depend_region);
}
}
}
return TRUE;
}
static inline void red_inc_surfaces_drawable_dependencies(RedWorker *worker, Drawable *drawable)
{
int x;
int surface_id;
RedSurface *surface;
for (x = 0; x < 3; ++x) {
surface_id = drawable->surfaces_dest[x];
if (surface_id == -1) {
continue;
}
surface = &worker->surfaces[surface_id];
surface->refs++;
}
}
static inline void red_process_drawable(RedWorker *worker, RedDrawable *drawable,
uint32_t group_id)
{
int surface_id;
Drawable *item = get_drawable(worker, drawable->effect, drawable, group_id);
spice_assert(item);
surface_id = item->surface_id;
worker->surfaces[surface_id].refs++;
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 == SPICE_CLIP_TYPE_RECTS) {
QRegion rgn;
region_init(&rgn);
add_clip_rects(&rgn, drawable->clip.rects);
region_and(&item->tree_item.base.rgn, &rgn);
region_destroy(&rgn);
}
/*
surface->refs is affected by a drawable (that is
dependent on the surface) as long as the drawable is alive.
However, surface->depend_on_me is affected by a drawable only
as long as it is in the current tree (hasn't been rendered yet).
*/
red_inc_surfaces_drawable_dependencies(worker, item);
if (region_is_empty(&item->tree_item.base.rgn)) {
release_drawable(worker, item);
return;
}
if (!red_handle_self_bitmap(worker, item)) {
release_drawable(worker, item);
return;
}
if (!red_handle_depends_on_target_surface(worker, surface_id)) {
release_drawable(worker, item);
return;
}
if (!red_handle_surfaces_dependencies(worker, item)) {
release_drawable(worker, item);
return;
}
if (red_current_add_qxl(worker, &worker->surfaces[surface_id].current, item,
drawable)) {
if (item->tree_item.effect != QXL_EFFECT_OPAQUE) {
worker->transparent_count++;
}
red_pipes_add_drawable(worker, item);
#ifdef DRAW_ALL
red_draw_qxl_drawable(worker, item);
#endif
}
release_drawable(worker, item);
}
static inline void red_create_surface(RedWorker *worker, uint32_t surface_id,uint32_t width,
uint32_t height, int32_t stride, uint32_t format,
void *line_0, int data_is_valid, int send_client);
static inline void red_process_surface(RedWorker *worker, RedSurfaceCmd *surface,
uint32_t group_id, int loadvm)
{
int surface_id;
RedSurface *red_surface;
uint8_t *data;
surface_id = surface->surface_id;
__validate_surface(worker, surface_id);
red_surface = &worker->surfaces[surface_id];
switch (surface->type) {
case QXL_SURFACE_CMD_CREATE: {
uint32_t height = surface->u.surface_create.height;
int32_t stride = surface->u.surface_create.stride;
int reloaded_surface = loadvm || (surface->flags & QXL_SURF_FLAG_KEEP_DATA);
data = surface->u.surface_create.data;
if (stride < 0) {
data -= (int32_t)(stride * (height - 1));
}
red_create_surface(worker, surface_id, surface->u.surface_create.width,
height, stride, surface->u.surface_create.format, data,
reloaded_surface,
// reloaded surfaces will be sent on demand
!reloaded_surface);
set_surface_release_info(worker, surface_id, 1, surface->release_info, group_id);
break;
}
case QXL_SURFACE_CMD_DESTROY:
spice_warn_if(!red_surface->context.canvas);
set_surface_release_info(worker, surface_id, 0, surface->release_info, group_id);
red_handle_depends_on_target_surface(worker, surface_id);
/* note that red_handle_depends_on_target_surface must be called before red_current_clear.
otherwise "current" will hold items that other drawables may depend on, and then
red_current_clear will remove them from the pipe. */
red_current_clear(worker, surface_id);
red_clear_surface_drawables_from_pipes(worker, surface_id, FALSE, FALSE);
red_destroy_surface(worker, surface_id);
break;
default:
spice_error("unknown surface command");
};
red_put_surface_cmd(surface);
free(surface);
}
static SpiceCanvas *image_surfaces_get(SpiceImageSurfaces *surfaces,
uint32_t surface_id)
{
RedWorker *worker;
worker = SPICE_CONTAINEROF(surfaces, RedWorker, image_surfaces);
validate_surface(worker, surface_id);
return worker->surfaces[surface_id].context.canvas;
}
static void image_surface_init(RedWorker *worker)
{
static SpiceImageSurfacesOps image_surfaces_ops = {
image_surfaces_get,
};
worker->image_surfaces.ops = &image_surfaces_ops;
}
static ImageCacheItem *image_cache_find(ImageCache *cache, uint64_t 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_t 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 (;;) {
spice_assert(*now);
if (*now == item) {
*now = item->next;
break;
}
now = &(*now)->next;
}
ring_remove(&item->lru_link);
pixman_image_unref(item->image);
free(item);
#ifndef IMAGE_CACHE_AGE
cache->num_items--;
#endif
}
#define IMAGE_CACHE_MAX_ITEMS 2
static void image_cache_put(SpiceImageCache *spice_cache, uint64_t id, pixman_image_t *image)
{
ImageCache *cache = (ImageCache *)spice_cache;
ImageCacheItem *item;
#ifndef IMAGE_CACHE_AGE
if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) {
ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru);
spice_assert(tail);
image_cache_remove(cache, tail);
}
#endif
item = spice_new(ImageCacheItem, 1);
item->id = id;
#ifdef IMAGE_CACHE_AGE
item->age = cache->age;
#else
cache->num_items++;
#endif
item->image = pixman_image_ref(image);
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 pixman_image_t *image_cache_get(SpiceImageCache *spice_cache, uint64_t id)
{
ImageCache *cache = (ImageCache *)spice_cache;
ImageCacheItem *item = image_cache_find(cache, id);
if (!item) {
spice_error("not found");
}
return pixman_image_ref(item->image);
}
static void image_cache_init(ImageCache *cache)
{
static SpiceImageCacheOps image_cache_ops = {
image_cache_put,
image_cache_get,
};
cache->base.ops = &image_cache_ops;
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, SpiceImage **image_ptr, SpiceImage *image_store,
Drawable *drawable)
{
SpiceImage *image = *image_ptr;
if (image == NULL) {
spice_assert(drawable != NULL);
spice_assert(drawable->self_bitmap != NULL);
*image_ptr = drawable->self_bitmap;
return;
}
if (image_cache_hit(&worker->image_cache, image->descriptor.id)) {
image_store->descriptor = image->descriptor;
image_store->descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
image_store->descriptor.flags = 0;
*image_ptr = image_store;
return;
}
switch (image->descriptor.type) {
case SPICE_IMAGE_TYPE_QUIC: {
image_store->descriptor = image->descriptor;
image_store->u.quic = image->u.quic;
*image_ptr = image_store;
#ifdef IMAGE_CACHE_AGE
image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
#else
if (image_store->descriptor.width * image->descriptor.height >= 640 * 480) {
image_store->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
}
#endif
break;
}
case SPICE_IMAGE_TYPE_BITMAP:
case SPICE_IMAGE_TYPE_SURFACE:
/* nothing */
break;
default:
spice_error("invalid image type");
}
}
static void localize_brush(RedWorker *worker, SpiceBrush *brush, SpiceImage *image_store)
{
if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
localize_bitmap(worker, &brush->u.pattern.pat, image_store, NULL);
}
}
static void localize_mask(RedWorker *worker, SpiceQMask *mask, SpiceImage *image_store)
{
if (mask->bitmap) {
localize_bitmap(worker, &mask->bitmap, image_store, NULL);
}
}
static void red_draw_qxl_drawable(RedWorker *worker, Drawable *drawable)
{
RedSurface *surface;
SpiceCanvas *canvas;
SpiceClip clip = drawable->red_drawable->clip;
surface = &worker->surfaces[drawable->surface_id];
canvas = surface->context.canvas;
image_cache_eaging(&worker->image_cache);
worker->preload_group_id = drawable->group_id;
region_add(&surface->draw_dirty_region, &drawable->red_drawable->bbox);
switch (drawable->red_drawable->type) {
case QXL_DRAW_FILL: {
SpiceFill fill = drawable->red_drawable->u.fill;
SpiceImage img1, img2;
localize_brush(worker, &fill.brush, &img1);
localize_mask(worker, &fill.mask, &img2);
canvas->ops->draw_fill(canvas, &drawable->red_drawable->bbox,
&clip, &fill);
break;
}
case QXL_DRAW_OPAQUE: {
SpiceOpaque opaque = drawable->red_drawable->u.opaque;
SpiceImage img1, img2, img3;
localize_brush(worker, &opaque.brush, &img1);
localize_bitmap(worker, &opaque.src_bitmap, &img2, drawable);
localize_mask(worker, &opaque.mask, &img3);
canvas->ops->draw_opaque(canvas, &drawable->red_drawable->bbox, &clip, &opaque);
break;
}
case QXL_DRAW_COPY: {
SpiceCopy copy = drawable->red_drawable->u.copy;
SpiceImage img1, img2;
localize_bitmap(worker, ©.src_bitmap, &img1, drawable);
localize_mask(worker, ©.mask, &img2);
canvas->ops->draw_copy(canvas, &drawable->red_drawable->bbox,
&clip, ©);
break;
}
case QXL_DRAW_TRANSPARENT: {
SpiceTransparent transparent = drawable->red_drawable->u.transparent;
SpiceImage img1;
localize_bitmap(worker, &transparent.src_bitmap, &img1, drawable);
canvas->ops->draw_transparent(canvas,
&drawable->red_drawable->bbox, &clip, &transparent);
break;
}
case QXL_DRAW_ALPHA_BLEND: {
SpiceAlphaBlend alpha_blend = drawable->red_drawable->u.alpha_blend;
SpiceImage img1;
localize_bitmap(worker, &alpha_blend.src_bitmap, &img1, drawable);
canvas->ops->draw_alpha_blend(canvas,
&drawable->red_drawable->bbox, &clip, &alpha_blend);
break;
}
case QXL_COPY_BITS: {
canvas->ops->copy_bits(canvas, &drawable->red_drawable->bbox,
&clip, &drawable->red_drawable->u.copy_bits.src_pos);
break;
}
case QXL_DRAW_BLEND: {
SpiceBlend blend = drawable->red_drawable->u.blend;
SpiceImage img1, img2;
localize_bitmap(worker, &blend.src_bitmap, &img1, drawable);
localize_mask(worker, &blend.mask, &img2);
canvas->ops->draw_blend(canvas, &drawable->red_drawable->bbox,
&clip, &blend);
break;
}
case QXL_DRAW_BLACKNESS: {
SpiceBlackness blackness = drawable->red_drawable->u.blackness;
SpiceImage img1;
localize_mask(worker, &blackness.mask, &img1);
canvas->ops->draw_blackness(canvas,
&drawable->red_drawable->bbox, &clip, &blackness);
break;
}
case QXL_DRAW_WHITENESS: {
SpiceWhiteness whiteness = drawable->red_drawable->u.whiteness;
SpiceImage img1;
localize_mask(worker, &whiteness.mask, &img1);
canvas->ops->draw_whiteness(canvas,
&drawable->red_drawable->bbox, &clip, &whiteness);
break;
}
case QXL_DRAW_INVERS: {
SpiceInvers invers = drawable->red_drawable->u.invers;
SpiceImage img1;
localize_mask(worker, &invers.mask, &img1);
canvas->ops->draw_invers(canvas,
&drawable->red_drawable->bbox, &clip, &invers);
break;
}
case QXL_DRAW_ROP3: {
SpiceRop3 rop3 = drawable->red_drawable->u.rop3;
SpiceImage img1, img2, img3;
localize_brush(worker, &rop3.brush, &img1);
localize_bitmap(worker, &rop3.src_bitmap, &img2, drawable);
localize_mask(worker, &rop3.mask, &img3);
canvas->ops->draw_rop3(canvas, &drawable->red_drawable->bbox,
&clip, &rop3);
break;
}
case QXL_DRAW_STROKE: {
SpiceStroke stroke = drawable->red_drawable->u.stroke;
SpiceImage img1;
localize_brush(worker, &stroke.brush, &img1);
canvas->ops->draw_stroke(canvas,
&drawable->red_drawable->bbox, &clip, &stroke);
break;
}
case QXL_DRAW_TEXT: {
SpiceText text = drawable->red_drawable->u.text;
SpiceImage img1, img2;
localize_brush(worker, &text.fore_brush, &img1);
localize_brush(worker, &text.back_brush, &img2);
canvas->ops->draw_text(canvas, &drawable->red_drawable->bbox,
&clip, &text);
break;
}
default:
spice_printerr("invalid type");
}
}
#ifndef DRAW_ALL
static void red_draw_drawable(RedWorker *worker, Drawable *drawable)
{
#ifdef UPDATE_AREA_BY_TREE
SpiceCanvas *canvas;
canvas = surface->context.canvas;
//todo: add need top mask flag
canvas->ops->group_start(canvas,
&drawable->tree_item.base.rgn);
#endif
red_flush_source_surfaces(worker, drawable);
red_draw_qxl_drawable(worker, drawable);
#ifdef UPDATE_AREA_BY_TREE
canvas->ops->group_end(canvas);
#endif
}
static void validate_area(RedWorker *worker, const SpiceRect *area, uint32_t surface_id)
{
RedSurface *surface;
surface = &worker->surfaces[surface_id];
if (!surface->context.canvas_draws_on_surface) {
SpiceCanvas *canvas = surface->context.canvas;
int h;
int stride = surface->context.stride;
uint8_t *line_0 = surface->context.line_0;
if (!(h = area->bottom - area->top)) {
return;
}
spice_assert(stride < 0);
uint8_t *dest = line_0 + (area->top * stride) + area->left * sizeof(uint32_t);
dest += (h - 1) * stride;
canvas->ops->read_bits(canvas, dest, -stride, area);
}
}
#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 = SPICE_CONTAINEROF(ring_item, TreeItem, siblings_link);
Container *container = now->container;
if (region_intersects(rgn, &now->rgn)) {
if (IS_DRAW_ITEM(now)) {
Drawable *drawable = SPICE_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 = SPICE_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;
spice_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 SpiceRect *area, int surface_id)
{
RedSurface *surface;
Ring *ring;
RingItem *ring_item;
Ring items;
QRegion rgn;
surface = &worker->surfaces[surface_id];
ring = &surface->current;
if (!(ring_item = ring_get_head(ring))) {
worker->draw_context.validate_area(surface->context.canvas,
&worker->dev_info.surface0_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 = SPICE_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);
}
validate_area(worker, area, surface_id);
}
#else
/*
Renders drawables for updating the requested area, but only drawables that are older
than 'last' (exclusive).
*/
static void red_update_area_till(RedWorker *worker, const SpiceRect *area, int surface_id,
Drawable *last)
{
// TODO: if we use UPDATE_AREA_BY_TREE, a corresponding red_update_area_till
// should be implemented
RedSurface *surface;
Drawable *surface_last = NULL;
Ring *ring;
RingItem *ring_item;
Drawable *now;
QRegion rgn;
spice_assert(last);
spice_assert(ring_item_is_linked(&last->list_link));
surface = &worker->surfaces[surface_id];
if (surface_id != last->surface_id) {
// find the nearest older drawable from the appropriate surface
ring = &worker->current_list;
ring_item = &last->list_link;
while ((ring_item = ring_next(ring, ring_item))) {
now = SPICE_CONTAINEROF(ring_item, Drawable, list_link);
if (now->surface_id == surface_id) {
surface_last = now;
break;
}
}
} else {
ring_item = ring_next(&surface->current_list, &last->surface_list_link);
if (ring_item) {
surface_last = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
}
}
if (!surface_last) {
return;
}
ring = &surface->current_list;
ring_item = &surface_last->surface_list_link;
region_init(&rgn);
region_add(&rgn, area);
// find the first older drawable that intersects with the area
do {
now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
if (region_intersects(&rgn, &now->tree_item.base.rgn)) {
surface_last = now;
break;
}
} while ((ring_item = ring_next(ring, ring_item)));
region_destroy(&rgn);
if (!surface_last) {
return;
}
do {
Container *container;
ring_item = ring_get_tail(&surface->current_list);
now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
now->refs++;
container = now->tree_item.base.container;
current_remove_drawable(worker, now);
container_cleanup(worker, container);
/* red_draw_drawable may call red_update_area for the surfaces 'now' depends on. Notice,
that it is valid to call red_update_area in this case and not red_update_area_till:
It is impossible that there was newer item then 'last' in one of the surfaces
that red_update_area is called for, Otherwise, 'now' would have already been rendered.
See the call for red_handle_depends_on_target_surface in red_process_drawable */
red_draw_drawable(worker, now);
release_drawable(worker, now);
} while (now != surface_last);
validate_area(worker, area, surface_id);
}
static void red_update_area(RedWorker *worker, const SpiceRect *area, int surface_id)
{
RedSurface *surface;
Ring *ring;
RingItem *ring_item;
QRegion rgn;
Drawable *last;
Drawable *now;
#ifdef ACYCLIC_SURFACE_DEBUG
int gn;
#endif
spice_debug("surface %d: area ==>", surface_id);
rect_debug(area);
surface = &worker->surfaces[surface_id];
last = NULL;
#ifdef ACYCLIC_SURFACE_DEBUG
gn = ++surface->current_gn;
#endif
ring = &surface->current_list;
ring_item = ring;
region_init(&rgn);
region_add(&rgn, area);
while ((ring_item = ring_next(ring, ring_item))) {
now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
if (region_intersects(&rgn, &now->tree_item.base.rgn)) {
last = now;
break;
}
}
region_destroy(&rgn);
if (!last) {
validate_area(worker, area, surface_id);
return;
}
do {
Container *container;
ring_item = ring_get_tail(&surface->current_list);
now = SPICE_CONTAINEROF(ring_item, Drawable, surface_list_link);
now->refs++;
container = now->tree_item.base.container;
current_remove_drawable(worker, now);
container_cleanup(worker, container);
red_draw_drawable(worker, now);
release_drawable(worker, now);
#ifdef ACYCLIC_SURFACE_DEBUG
if (gn != surface->current_gn) {
spice_error("cyclic surface dependencies");
}
#endif
} while (now != last);
validate_area(worker, area, surface_id);
}
#endif
#endif
static inline void free_cursor_item(RedWorker *worker, CursorItem *item);
static void red_release_cursor(RedWorker *worker, CursorItem *cursor)
{
if (!--cursor->refs) {
QXLReleaseInfoExt release_info_ext;
RedCursorCmd *cursor_cmd;
cursor_cmd = cursor->red_cursor;
release_info_ext.group_id = cursor->group_id;
release_info_ext.info = cursor_cmd->release_info;
worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext);
free_cursor_item(worker, cursor);
red_put_cursor_cmd(cursor_cmd);
free(cursor_cmd);
}
}
static void red_set_cursor(RedWorker *worker, CursorItem *cursor)
{
if (worker->cursor) {
red_release_cursor(worker, worker->cursor);
}
++cursor->refs;
worker->cursor = cursor;
}
#ifdef DEBUG_CURSORS
static int _cursor_count = 0;
#endif
static inline CursorItem *alloc_cursor_item(RedWorker *worker)
{
CursorItem *cursor;
if (!worker->free_cursor_items) {
return NULL;
}
#ifdef DEBUG_CURSORS
--_cursor_count;
#endif
cursor = &worker->free_cursor_items->u.cursor_item;
worker->free_cursor_items = worker->free_cursor_items->u.next;
return cursor;
}
static inline void free_cursor_item(RedWorker *worker, CursorItem *item)
{
((_CursorItem *)item)->u.next = worker->free_cursor_items;
worker->free_cursor_items = (_CursorItem *)item;
#ifdef DEBUG_CURSORS
++_cursor_count;
spice_assert(_cursor_count <= NUM_CURSORS);
#endif
}
static void cursor_items_init(RedWorker *worker)
{
int i;
worker->free_cursor_items = NULL;
for (i = 0; i < NUM_CURSORS; i++) {
free_cursor_item(worker, &worker->cursor_items[i].u.cursor_item);
}
}
static CursorItem *get_cursor_item(RedWorker *worker, RedCursorCmd *cmd, uint32_t group_id)
{
CursorItem *cursor_item;
spice_warn_if(!(cursor_item = alloc_cursor_item(worker)));
cursor_item->refs = 1;
cursor_item->group_id = group_id;
cursor_item->red_cursor = cmd;
return cursor_item;
}
static CursorPipeItem *ref_cursor_pipe_item(CursorPipeItem *item)
{
spice_assert(item);
item->refs++;
return item;
}
static PipeItem *new_cursor_pipe_item(RedChannelClient *rcc, void *data, int num)
{
CursorPipeItem *item = spice_malloc0(sizeof(CursorPipeItem));
red_channel_pipe_item_init(rcc->channel, &item->base, PIPE_ITEM_TYPE_CURSOR);
item->refs = 1;
item->cursor_item = data;
item->cursor_item->refs++;
return &item->base;
}
static void put_cursor_pipe_item(CursorChannelClient *ccc, CursorPipeItem *pipe_item)
{
spice_assert(pipe_item);
if (--pipe_item->refs) {
return;
}
spice_assert(!pipe_item_is_linked(&pipe_item->base));
red_release_cursor(ccc->common.worker, pipe_item->cursor_item);
free(pipe_item);
}
static void qxl_process_cursor(RedWorker *worker, RedCursorCmd *cursor_cmd, uint32_t group_id)
{
CursorItem *cursor_item;
int cursor_show = FALSE;
cursor_item = get_cursor_item(worker, cursor_cmd, group_id);
switch (cursor_cmd->type) {
case QXL_CURSOR_SET:
worker->cursor_visible = cursor_cmd->u.set.visible;
red_set_cursor(worker, cursor_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:
spice_error("invalid cursor command %u", cursor_cmd->type);
}
if (cursor_is_connected(worker) && (worker->mouse_mode == SPICE_MOUSE_MODE_SERVER ||
cursor_cmd->type != QXL_CURSOR_MOVE || cursor_show)) {
red_channel_pipes_new_add(&worker->cursor_channel->common.base, new_cursor_pipe_item,
(void*)cursor_item);
}
red_release_cursor(worker, cursor_item);
}
static inline uint64_t red_now(void)
{
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec;
}
static int red_process_cursor(RedWorker *worker, uint32_t max_pipe_size, int *ring_is_empty)
{
QXLCommandExt ext_cmd;
int n = 0;
if (!worker->running) {
*ring_is_empty = TRUE;
return n;
}
*ring_is_empty = FALSE;
while (!cursor_is_connected(worker) ||
red_channel_min_pipe_size(&worker->cursor_channel->common.base) <= max_pipe_size) {
if (!worker->qxl->st->qif->get_cursor_command(worker->qxl, &ext_cmd)) {
*ring_is_empty = TRUE;
if (worker->repoll_cursor_ring < CMD_RING_POLL_RETRIES) {
worker->repoll_cursor_ring++;
worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT);
break;
}
if (worker->repoll_cursor_ring > CMD_RING_POLL_RETRIES ||
worker->qxl->st->qif->req_cursor_notification(worker->qxl)) {
worker->repoll_cursor_ring++;
break;
}
continue;
}
worker->repoll_cursor_ring = 0;
switch (ext_cmd.cmd.type) {
case QXL_CMD_CURSOR: {
RedCursorCmd *cursor = spice_new0(RedCursorCmd, 1);
if (!red_get_cursor_cmd(&worker->mem_slots, ext_cmd.group_id,
cursor, ext_cmd.cmd.data)) {
qxl_process_cursor(worker, cursor, ext_cmd.group_id);
}
break;
}
default:
spice_error("bad command type");
}
n++;
}
return n;
}
static RedDrawable *red_drawable_new(void)
{
RedDrawable * red = spice_new0(RedDrawable, 1);
red->refs = 1;
return red;
}
static int red_process_commands(RedWorker *worker, uint32_t max_pipe_size, int *ring_is_empty)
{
QXLCommandExt ext_cmd;
int n = 0;
uint64_t start = red_now();
if (!worker->running) {
*ring_is_empty = TRUE;
return n;
}
*ring_is_empty = FALSE;
while (!display_is_connected(worker) ||
// TODO: change to average pipe size?
red_channel_min_pipe_size(&worker->display_channel->common.base) <= max_pipe_size) {
if (!worker->qxl->st->qif->get_command(worker->qxl, &ext_cmd)) {
*ring_is_empty = TRUE;;
if (worker->repoll_cmd_ring < CMD_RING_POLL_RETRIES) {
worker->repoll_cmd_ring++;
worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT);
break;
}
if (worker->repoll_cmd_ring > CMD_RING_POLL_RETRIES ||
worker->qxl->st->qif->req_cmd_notification(worker->qxl)) {
worker->repoll_cmd_ring++;
break;
}
continue;
}
stat_inc_counter(worker->command_counter, 1);
worker->repoll_cmd_ring = 0;
switch (ext_cmd.cmd.type) {
case QXL_CMD_DRAW: {
RedDrawable *drawable = red_drawable_new(); // returns with 1 ref
if (red_get_drawable(&worker->mem_slots, ext_cmd.group_id,
drawable, ext_cmd.cmd.data, ext_cmd.flags)) {
break;
}
red_process_drawable(worker, drawable, ext_cmd.group_id);
// release the red_drawable
put_red_drawable(worker, drawable, ext_cmd.group_id, NULL);
break;
}
case QXL_CMD_UPDATE: {
RedUpdateCmd update;
QXLReleaseInfoExt release_info_ext;
if (red_get_update_cmd(&worker->mem_slots, ext_cmd.group_id,
&update, ext_cmd.cmd.data)) {
break;
}
validate_surface(worker, update.surface_id);
red_update_area(worker, &update.area, update.surface_id);
worker->qxl->st->qif->notify_update(worker->qxl, update.update_id);
release_info_ext.group_id = ext_cmd.group_id;
release_info_ext.info = update.release_info;
worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext);
red_put_update_cmd(&update);
break;
}
case QXL_CMD_MESSAGE: {
RedMessage message;
QXLReleaseInfoExt release_info_ext;
if (red_get_message(&worker->mem_slots, ext_cmd.group_id,
&message, ext_cmd.cmd.data)) {
break;
}
#ifdef DEBUG
/* alert: accessing message.data is insecure */
spice_printerr("MESSAGE: %s", message.data);
#endif
release_info_ext.group_id = ext_cmd.group_id;
release_info_ext.info = message.release_info;
worker->qxl->st->qif->release_resource(worker->qxl, release_info_ext);
red_put_message(&message);
break;
}
case QXL_CMD_SURFACE: {
RedSurfaceCmd *surface = spice_new0(RedSurfaceCmd, 1);
if (red_get_surface_cmd(&worker->mem_slots, ext_cmd.group_id,
surface, ext_cmd.cmd.data)) {
break;
}
red_process_surface(worker, surface, ext_cmd.group_id, FALSE);
break;
}
default:
spice_error("bad command type");
}
n++;
if ((worker->display_channel &&
red_channel_all_blocked(&worker->display_channel->common.base))
|| red_now() - start > 10 * 1000 * 1000) {
worker->event_timeout = 0;
return n;
}
}
return n;
}
#define RED_RELEASE_BUNCH_SIZE 64
static void red_free_some(RedWorker *worker)
{
int n = 0;
DisplayChannelClient *dcc;
RingItem *item;
spice_debug("#draw=%d, #red_draw=%d, #glz_draw=%d", worker->drawable_count,
worker->red_drawable_count, worker->glz_drawable_count);
WORKER_FOREACH_DCC(worker, item, dcc) {
GlzSharedDictionary *glz_dict = dcc ? dcc->glz_dict : NULL;
if (glz_dict) {
// encoding using the dictionary is prevented since the following operations might
// change the dictionary
pthread_rwlock_wrlock(&glz_dict->encode_lock);
n = red_display_free_some_independent_glz_drawables(dcc);
}
}
while (!ring_is_empty(&worker->current_list) && n++ < RED_RELEASE_BUNCH_SIZE) {
free_one_drawable(worker, TRUE);
}
WORKER_FOREACH_DCC(worker, item, dcc) {
GlzSharedDictionary *glz_dict = dcc ? dcc->glz_dict : NULL;
if (glz_dict) {
pthread_rwlock_unlock(&glz_dict->encode_lock);
}
}
}
static void red_current_flush(RedWorker *worker, int surface_id)
{
while (!ring_is_empty(&worker->surfaces[surface_id].current_list)) {
free_one_drawable(worker, FALSE);
}
red_current_clear(worker, surface_id);
}
// adding the pipe item after pos. If pos == NULL, adding to head.
static ImageItem *red_add_surface_area_image(DisplayChannelClient *dcc, int surface_id,
SpiceRect *area, PipeItem *pos, int can_lossy)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
RedChannel *channel = &display_channel->common.base;
RedSurface *surface = &worker->surfaces[surface_id];
SpiceCanvas *canvas = surface->context.canvas;
ImageItem *item;
int stride;
int width;
int height;
int bpp;
int all_set;
spice_assert(area);
width = area->right - area->left;
height = area->bottom - area->top;
bpp = SPICE_SURFACE_FMT_DEPTH(surface->context.format) / 8;
stride = SPICE_ALIGN(width * bpp, 4);
item = (ImageItem *)spice_malloc_n_m(height, stride, sizeof(ImageItem));
red_channel_pipe_item_init(channel, &item->link, PIPE_ITEM_TYPE_IMAGE);
item->refs = 1;
item->surface_id = surface_id;
item->image_format =
surface_format_to_image_type(surface->context.format);
item->image_flags = 0;
item->pos.x = area->left;
item->pos.y = area->top;
item->width = width;
item->height = height;
item->stride = stride;
item->top_down = surface->context.top_down;
item->can_lossy = can_lossy;
canvas->ops->read_bits(canvas, item->data, stride, area);
/* For 32bit non-primary surfaces we need to keep any non-zero
high bytes as the surface may be used as source to an alpha_blend */
if (!is_primary_surface(worker, surface_id) &&
item->image_format == SPICE_BITMAP_FMT_32BIT &&
rgb32_data_has_alpha(item->width, item->height, item->stride, item->data, &all_set)) {
if (all_set) {
item->image_flags |= SPICE_IMAGE_FLAGS_HIGH_BITS_SET;
} else {
item->image_format = SPICE_BITMAP_FMT_RGBA;
}
}
if (!pos) {
red_pipe_add_image_item(dcc, item);
} else {
red_pipe_add_image_item_after(dcc, item, pos);
}
release_image_item(item);
return item;
}
static void red_push_surface_image(DisplayChannelClient *dcc, int surface_id)
{
SpiceRect area;
RedSurface *surface;
RedWorker *worker;
if (!dcc) {
return;
}
worker = DCC_TO_WORKER(dcc);
surface = &worker->surfaces[surface_id];
if (!surface->context.canvas) {
return;
}
area.top = area.left = 0;
area.right = surface->context.width;
area.bottom = surface->context.height;
/* not allowing lossy compression because probably, especially if it is a primary surface,
it combines both "picture-like" areas with areas that are more "artificial"*/
red_add_surface_area_image(dcc, surface_id, &area, NULL, FALSE);
red_channel_client_push(&dcc->common.base);
}
typedef struct {
uint32_t type;
void *data;
uint32_t size;
} AddBufInfo;
static void marshaller_add_compressed(SpiceMarshaller *m,
RedCompressBuf *comp_buf, size_t size)
{
size_t max = size;
size_t now;
do {
spice_assert(comp_buf);
now = MIN(sizeof(comp_buf->buf), max);
max -= now;
spice_marshaller_add_ref(m, (uint8_t*)comp_buf->buf, now);
comp_buf = comp_buf->send_next;
} while (max);
}
static void add_buf_from_info(SpiceMarshaller *m, AddBufInfo *info)
{
if (info->data) {
switch (info->type) {
case BUF_TYPE_RAW:
spice_marshaller_add_ref(m, info->data, info->size);
break;
}
}
}
static inline void fill_rects_clip(SpiceMarshaller *m, SpiceClipRects *data)
{
int i;
spice_marshaller_add_uint32(m, data->num_rects);
for (i = 0; i < data->num_rects; i++) {
spice_marshall_Rect(m, data->rects + i);
}
}
static void fill_base(SpiceMarshaller *base_marshaller, Drawable *drawable)
{
SpiceMsgDisplayBase base;
base.surface_id = drawable->surface_id;
base.box = drawable->red_drawable->bbox;
base.clip = drawable->red_drawable->clip;
spice_marshall_DisplayBase(base_marshaller, &base);
}
static inline void fill_palette(DisplayChannelClient *dcc,
SpicePalette *palette,
uint8_t *flags)
{
if (palette == NULL) {
return;
}
if (palette->unique) {
if (red_palette_cache_find(dcc, palette->unique)) {
*flags |= SPICE_BITMAP_FLAGS_PAL_FROM_CACHE;
return;
}
if (red_palette_cache_add(dcc, palette->unique, 1)) {
*flags |= SPICE_BITMAP_FLAGS_PAL_CACHE_ME;
}
}
}
static inline RedCompressBuf *red_display_alloc_compress_buf(DisplayChannelClient *dcc)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedCompressBuf *ret;
if (display_channel->free_compress_bufs) {
ret = display_channel->free_compress_bufs;
display_channel->free_compress_bufs = ret->next;
} else {
ret = spice_new(RedCompressBuf, 1);
}
ret->next = dcc->send_data.used_compress_bufs;
dcc->send_data.used_compress_bufs = ret;
return ret;
}
static inline void __red_display_free_compress_buf(DisplayChannel *dc,
RedCompressBuf *buf)
{
buf->next = dc->free_compress_bufs;
dc->free_compress_bufs = buf;
}
static void red_display_free_compress_buf(DisplayChannelClient *dcc,
RedCompressBuf *buf)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedCompressBuf **curr_used = &dcc->send_data.used_compress_bufs;
for (;;) {
spice_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(DisplayChannelClient *dcc)
{
while (dcc->send_data.used_compress_bufs) {
RedCompressBuf *buf = dcc->send_data.used_compress_bufs;
dcc->send_data.used_compress_bufs = buf->next;
__red_display_free_compress_buf(DCC_TO_DC(dcc), buf);
}
}
static void red_display_destroy_compress_bufs(DisplayChannel *display_channel)
{
spice_assert(!red_channel_is_connected(&display_channel->common.base));
while (display_channel->free_compress_bufs) {
RedCompressBuf *buf = display_channel->free_compress_bufs;
display_channel->free_compress_bufs = buf->next;
free(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(DisplayChannelClient *dcc, Drawable *drawable)
{
RedGlzDrawable *ret;
RingItem *item;
// TODO - I don't really understand what's going on here, so doing the technical equivalent
// now that we have multiple glz_dicts, so the only way to go from dcc to drawable glz is to go
// over the glz_ring (unless adding some better data structure then a ring)
DRAWABLE_FOREACH_GLZ(drawable, item, ret) {
if (ret->dcc == dcc) {
return ret;
}
}
ret = spice_new(RedGlzDrawable, 1);
ret->dcc = dcc;
ret->red_drawable = ref_red_drawable(drawable->red_drawable);
ret->drawable = drawable;
ret->group_id = drawable->group_id;
ret->self_bitmap = drawable->self_bitmap;
ret->instances_count = 0;
ring_init(&ret->instances);
ring_item_init(&ret->link);
ring_item_init(&ret->drawable_link);
ring_add_before(&ret->link, &dcc->glz_drawables);
ring_add(&drawable->glz_ring, &ret->drawable_link);
dcc->common.worker->glz_drawable_count++;
return ret;
}
/* allocates new instance and adds it to instances in the given drawable.
NOTE - the caller should set the glz_instance returned by the encoder by itself.*/
static GlzDrawableInstanceItem *red_display_add_glz_drawable_instance(RedGlzDrawable *glz_drawable)
{
spice_assert(glz_drawable->instances_count < MAX_GLZ_DRAWABLE_INSTANCES);
// NOTE: We assume the additions are performed consecutively, without removals in the middle
GlzDrawableInstanceItem *ret = glz_drawable->instances_pool + glz_drawable->instances_count;
glz_drawable->instances_count++;
ring_item_init(&ret->free_link);
ring_item_init(&ret->glz_link);
ring_add(&glz_drawable->instances, &ret->glz_link);
ret->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 drawable too, if
it is not used by Drawable).
NOTE - 1) can be called only by the display channel that created the drawable
2) it is assumed that the instance was already removed from the dictionary*/
static void red_display_free_glz_drawable_instance(DisplayChannelClient *dcc,
GlzDrawableInstanceItem *glz_drawable_instance)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
RedGlzDrawable *glz_drawable;
spice_assert(glz_drawable_instance);
spice_assert(glz_drawable_instance->red_glz_drawable);
glz_drawable = glz_drawable_instance->red_glz_drawable;
spice_assert(glz_drawable->dcc == dcc);
spice_assert(glz_drawable->instances_count);
ring_remove(&glz_drawable_instance->glz_link);
glz_drawable->instances_count--;
// when the remove callback is performed from the channel that the
// drawable belongs to, the instance is not added to the 'to_free' list
if (ring_item_is_linked(&glz_drawable_instance->free_link)) {
ring_remove(&glz_drawable_instance->free_link);
}
if (ring_is_empty(&glz_drawable->instances)) {
spice_assert(!glz_drawable->instances_count);
Drawable *drawable = glz_drawable->drawable;
if (drawable) {
ring_remove(&glz_drawable->drawable_link);
}
put_red_drawable(worker, glz_drawable->red_drawable,
glz_drawable->group_id, glz_drawable->self_bitmap);
worker->glz_drawable_count--;
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(DisplayChannelClient* dcc)
{
RingItem *ring_link;
if (!dcc->glz_dict) {
return;
}
pthread_mutex_lock(&dcc->glz_drawables_inst_to_free_lock);
while ((ring_link = ring_get_head(&dcc->glz_drawables_inst_to_free))) {
GlzDrawableInstanceItem *drawable_instance = SPICE_CONTAINEROF(ring_link,
GlzDrawableInstanceItem,
free_link);
red_display_free_glz_drawable_instance(dcc, drawable_instance);
}
pthread_mutex_unlock(&dcc->glz_drawables_inst_to_free_lock);
}
/*
* Releases all the instances of the drawable from the dictionary and the display channel client.
* The release of the last instance will also release the drawable itself and the qxl drawable
* if possible.
* NOTE - the caller should prevent encoding using the dictionary during this operation
*/
static void red_display_free_glz_drawable(DisplayChannelClient *dcc, 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 = SPICE_CONTAINEROF(head_instance,
GlzDrawableInstanceItem,
glz_link);
if (!ring_item_is_linked(&instance->free_link)) {
// the instance didn't get out from window yet
glz_enc_dictionary_remove_image(dcc->glz_dict->dict,
instance->glz_instance,
&dcc->glz_data.usr);
}
red_display_free_glz_drawable_instance(dcc, 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 dictionary during the operation*/
static void red_display_client_clear_glz_drawables(DisplayChannelClient *dcc)
{
RingItem *ring_link;
GlzSharedDictionary *glz_dict = dcc ? dcc->glz_dict : NULL;
if (!glz_dict) {
return;
}
// assure no display channel is during global lz encoding
pthread_rwlock_wrlock(&glz_dict->encode_lock);
while ((ring_link = ring_get_head(&dcc->glz_drawables))) {
RedGlzDrawable *drawable = SPICE_CONTAINEROF(ring_link, RedGlzDrawable, link);
// no need to lock the to_free list, since we assured no other thread is encoding and
// thus not other thread access the to_free list of the channel
red_display_free_glz_drawable(dcc, drawable);
}
pthread_rwlock_unlock(&glz_dict->encode_lock);
}
static void red_display_clear_glz_drawables(DisplayChannel *display_channel)
{
RingItem *link;
DisplayChannelClient *dcc;
if (!display_channel) {
return;
}
DCC_FOREACH(link, dcc, &display_channel->common.base) {
red_display_client_clear_glz_drawables(dcc);
}
}
/*
* Remove from the global lz dictionary some glz_drawables that have no reference to
* Drawable (their qxl drawables are released too).
* NOTE - the caller should prevent encoding using the dictionary during the operation
*/
static int red_display_free_some_independent_glz_drawables(DisplayChannelClient *dcc)
{
RingItem *ring_link;
int n = 0;
if (!dcc) {
return 0;
}
ring_link = ring_get_head(&dcc->glz_drawables);
while ((n < RED_RELEASE_BUNCH_SIZE) && (ring_link != NULL)) {
RedGlzDrawable *glz_drawable = SPICE_CONTAINEROF(ring_link, RedGlzDrawable, link);
ring_link = ring_next(&dcc->glz_drawables, ring_link);
if (!glz_drawable->drawable) {
red_display_free_glz_drawable(dcc, glz_drawable);
n++;
}
}
return n;
}
/******************************************************
* Encoders callbacks
*******************************************************/
static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void
quic_usr_error(QuicUsrContext *usr, const char *fmt, ...)
{
EncoderData *usr_data = &(((QuicData *)usr)->data);
va_list ap;
va_start(ap, fmt);
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
va_end(ap);
spice_printerr("%s", usr_data->message_buf);
longjmp(usr_data->jmp_env, 1);
}
static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void
lz_usr_error(LzUsrContext *usr, const char *fmt, ...)
{
EncoderData *usr_data = &(((LzData *)usr)->data);
va_list ap;
va_start(ap, fmt);
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
va_end(ap);
spice_printerr("%s", usr_data->message_buf);
longjmp(usr_data->jmp_env, 1);
}
static SPICE_GNUC_PRINTF(2, 3) void glz_usr_error(GlzEncoderUsrContext *usr, const char *fmt, ...)
{
EncoderData *usr_data = &(((GlzData *)usr)->data);
va_list ap;
va_start(ap, fmt);
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
va_end(ap);
spice_critical("%s", usr_data->message_buf); // if global lz fails in the middle
// the consequences are not predictable since the window
// can turn to be unsynchronized between the server and
// and the client
}
static SPICE_GNUC_PRINTF(2, 3) void quic_usr_warn(QuicUsrContext *usr, const char *fmt, ...)
{
EncoderData *usr_data = &(((QuicData *)usr)->data);
va_list ap;
va_start(ap, fmt);
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
va_end(ap);
spice_printerr("%s", usr_data->message_buf);
}
static SPICE_GNUC_PRINTF(2, 3) void lz_usr_warn(LzUsrContext *usr, const char *fmt, ...)
{
EncoderData *usr_data = &(((LzData *)usr)->data);
va_list ap;
va_start(ap, fmt);
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
va_end(ap);
spice_printerr("%s", usr_data->message_buf);
}
static SPICE_GNUC_PRINTF(2, 3) void glz_usr_warn(GlzEncoderUsrContext *usr, const char *fmt, ...)
{
EncoderData *usr_data = &(((GlzData *)usr)->data);
va_list ap;
va_start(ap, fmt);
vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
va_end(ap);
spice_printerr("%s", usr_data->message_buf);
}
static void *quic_usr_malloc(QuicUsrContext *usr, int size)
{
return spice_malloc(size);
}
static void *lz_usr_malloc(LzUsrContext *usr, int size)
{
return spice_malloc(size);
}
static void *glz_usr_malloc(GlzEncoderUsrContext *usr, int size)
{
return spice_malloc(size);
}
static void quic_usr_free(QuicUsrContext *usr, void *ptr)
{
free(ptr);
}
static void lz_usr_free(LzUsrContext *usr, void *ptr)
{
free(ptr);
}
static void glz_usr_free(GlzEncoderUsrContext *usr, void *ptr)
{
free(ptr);
}
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->dcc))) {
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 int jpeg_usr_more_space(JpegEncoderUsrContext *usr, uint8_t **io_ptr)
{
EncoderData *usr_data = &(((JpegData *)usr)->data);
return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
}
static int zlib_usr_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr)
{
EncoderData *usr_data = &(((ZlibData *)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)
{
struct SpiceChunk *chunk;
if (enc_data->u.lines_data.reverse) {
if (!(enc_data->u.lines_data.next >= 0)) {
return 0;
}
} else {
if (!(enc_data->u.lines_data.next < enc_data->u.lines_data.chunks->num_chunks)) {
return 0;
}
}
chunk = &enc_data->u.lines_data.chunks->chunk[enc_data->u.lines_data.next];
if (chunk->len % enc_data->u.lines_data.stride) {
return 0;
}
if (enc_data->u.lines_data.reverse) {
enc_data->u.lines_data.next--;
*lines = chunk->data + chunk->len - enc_data->u.lines_data.stride;
} else {
enc_data->u.lines_data.next++;
*lines = chunk->data;
}
return chunk->len / enc_data->u.lines_data.stride;
}
static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines)
{
EncoderData *usr_data = &(((QuicData *)usr)->data);
return encoder_usr_more_lines(usr_data, lines);
}
static int lz_usr_more_lines(LzUsrContext *usr, uint8_t **lines)
{
EncoderData *usr_data = &(((LzData *)usr)->data);
return encoder_usr_more_lines(usr_data, lines);
}
static int glz_usr_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines)
{
EncoderData *usr_data = &(((GlzData *)usr)->data);
return encoder_usr_more_lines(usr_data, lines);
}
static int jpeg_usr_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines)
{
EncoderData *usr_data = &(((JpegData *)usr)->data);
return encoder_usr_more_lines(usr_data, lines);
}
static int zlib_usr_more_input(ZlibEncoderUsrContext *usr, uint8_t** input)
{
EncoderData *usr_data = &(((ZlibData *)usr)->data);
int buf_size;
if (!usr_data->u.compressed_data.next) {
spice_assert(usr_data->u.compressed_data.size_left == 0);
return 0;
}
*input = (uint8_t*)usr_data->u.compressed_data.next->buf;
buf_size = MIN(sizeof(usr_data->u.compressed_data.next->buf),
usr_data->u.compressed_data.size_left);
usr_data->u.compressed_data.next = usr_data->u.compressed_data.next->send_next;
usr_data->u.compressed_data.size_left -= buf_size;
return buf_size;
}
static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image)
{
GlzData *lz_data = (GlzData *)usr;
GlzDrawableInstanceItem *glz_drawable_instance = (GlzDrawableInstanceItem *)image;
DisplayChannelClient *drawable_cc = glz_drawable_instance->red_glz_drawable->dcc;
DisplayChannelClient *this_cc = SPICE_CONTAINEROF(lz_data, DisplayChannelClient, glz_data);
if (this_cc == drawable_cc) {
red_display_free_glz_drawable_instance(drawable_cc, glz_drawable_instance);
} else {
/* The glz dictionary is shared between all DisplayChannelClient
* instances that belong to the same client, and glz_usr_free_image
* can be called by the dictionary code
* (glz_dictionary_window_remove_head). Thus this function can be
* called from any DisplayChannelClient thread, hence the need for
* this check.
*/
pthread_mutex_lock(&drawable_cc->glz_drawables_inst_to_free_lock);
ring_add_before(&glz_drawable_instance->free_link,
&drawable_cc->glz_drawables_inst_to_free);
pthread_mutex_unlock(&drawable_cc->glz_drawables_inst_to_free_lock);
}
}
static 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) {
spice_critical("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) {
spice_critical("create lz failed");
}
}
/* TODO: split off to DisplayChannel? avoid just copying those cb pointers */
static inline void red_display_init_glz_data(DisplayChannelClient *dcc)
{
dcc->glz_data.usr.error = glz_usr_error;
dcc->glz_data.usr.warn = glz_usr_warn;
dcc->glz_data.usr.info = glz_usr_warn;
dcc->glz_data.usr.malloc = glz_usr_malloc;
dcc->glz_data.usr.free = glz_usr_free;
dcc->glz_data.usr.more_space = glz_usr_more_space;
dcc->glz_data.usr.more_lines = glz_usr_more_lines;
dcc->glz_data.usr.free_image = glz_usr_free_image;
}
static inline void red_init_jpeg(RedWorker *worker)
{
worker->jpeg_data.usr.more_space = jpeg_usr_more_space;
worker->jpeg_data.usr.more_lines = jpeg_usr_more_lines;
worker->jpeg = jpeg_encoder_create(&worker->jpeg_data.usr);
if (!worker->jpeg) {
spice_critical("create jpeg encoder failed");
}
}
static inline void red_init_zlib(RedWorker *worker)
{
worker->zlib_data.usr.more_space = zlib_usr_more_space;
worker->zlib_data.usr.more_input = zlib_usr_more_input;
worker->zlib = zlib_encoder_create(&worker->zlib_data.usr, ZLIB_DEFAULT_COMPRESSION_LEVEL);
if (!worker->zlib) {
spice_critical("create zlib encoder failed");
}
}
#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_HIGH_RGB24_TH -0.03
#define GRADUAL_HIGH_RGB16_TH 0
// setting a more permissive threshold for stream identification in order
// not to miss streams that were artificially scaled on the guest (e.g., full screen view
// in window media player 12). see red_stream_add_frame
#define GRADUAL_MEDIUM_SCORE_TH 0.002
// assumes that stride doesn't overflow
static BitmapGradualType _get_bitmap_graduality_level(RedWorker *worker, SpiceBitmap *bitmap,
uint32_t group_id)
{
double score = 0.0;
int num_samples = 0;
int num_lines;
double chunk_score = 0.0;
int chunk_num_samples = 0;
uint32_t x, i;
SpiceChunk *chunk;
chunk = bitmap->data->chunk;
for (i = 0; i < bitmap->data->num_chunks; i++) {
num_lines = chunk[i].len / bitmap->stride;
x = bitmap->x;
switch (bitmap->format) {
case SPICE_BITMAP_FMT_16BIT:
compute_lines_gradual_score_rgb16((rgb16_pixel_t *)chunk[i].data, x, num_lines,
&chunk_score, &chunk_num_samples);
break;
case SPICE_BITMAP_FMT_24BIT:
compute_lines_gradual_score_rgb24((rgb24_pixel_t *)chunk[i].data, x, num_lines,
&chunk_score, &chunk_num_samples);
break;
case SPICE_BITMAP_FMT_32BIT:
case SPICE_BITMAP_FMT_RGBA:
compute_lines_gradual_score_rgb32((rgb32_pixel_t *)chunk[i].data, x, num_lines,
&chunk_score, &chunk_num_samples);
break;
default:
spice_error("invalid bitmap format (not RGB) %u", bitmap->format);
}
score += chunk_score;
num_samples += chunk_num_samples;
}
spice_assert(num_samples);
score /= num_samples;
if (bitmap->format == SPICE_BITMAP_FMT_16BIT) {
if (score < GRADUAL_HIGH_RGB16_TH) {
return BITMAP_GRADUAL_HIGH;
}
} else {
if (score < GRADUAL_HIGH_RGB24_TH) {
return BITMAP_GRADUAL_HIGH;
}
}
if (score < GRADUAL_MEDIUM_SCORE_TH) {
return BITMAP_GRADUAL_MEDIUM;
} else {
return BITMAP_GRADUAL_LOW;
}
}
static inline int _stride_is_extra(SpiceBitmap *bitmap)
{
spice_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 SPICE_BITMAP_FMT_8BIT:
return (bitmap->x < bitmap->stride);
case SPICE_BITMAP_FMT_4BIT_BE:
case SPICE_BITMAP_FMT_4BIT_LE: {
int bytes_width = SPICE_ALIGN(bitmap->x, 2) >> 1;
return bytes_width < bitmap->stride;
}
case SPICE_BITMAP_FMT_1BIT_BE:
case SPICE_BITMAP_FMT_1BIT_LE: {
int bytes_width = SPICE_ALIGN(bitmap->x, 8) >> 3;
return bytes_width < bitmap->stride;
}
default:
spice_error("invalid image type %u", bitmap->format);
return 0;
}
}
return 0;
}
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 {
void* comp_buf;
uint32_t comp_buf_size;
SpicePalette *lzplt_palette;
int is_lossy;
} compress_send_data_t;
static inline int red_glz_compress_image(DisplayChannelClient *dcc,
SpiceImage *dest, SpiceBitmap *src, Drawable *drawable,
compress_send_data_t* o_comp_data)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
#ifdef COMPRESS_STAT
stat_time_t start_time = stat_now();
#endif
spice_assert(BITMAP_FMT_IS_RGB[src->format]);
GlzData *glz_data = &dcc->glz_data;
ZlibData *zlib_data;
LzImageType type = MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[src->format];
RedGlzDrawable *glz_drawable;
GlzDrawableInstanceItem *glz_drawable_instance;
int glz_size;
int zlib_size;
glz_data->data.bufs_tail = red_display_alloc_compress_buf(dcc);
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.dcc = dcc;
glz_drawable = red_display_get_glz_drawable(dcc, drawable);
glz_drawable_instance = red_display_add_glz_drawable_instance(glz_drawable);
glz_data->data.u.lines_data.chunks = src->data;
glz_data->data.u.lines_data.stride = src->stride;
glz_data->data.u.lines_data.next = 0;
glz_data->data.u.lines_data.reverse = 0;
glz_data->usr.more_lines = glz_usr_more_lines;
glz_size = glz_encode(dcc->glz, type, src->x, src->y,
(src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN), NULL, 0,
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);
stat_compress_add(&display_channel->glz_stat, start_time, src->stride * src->y, glz_size);
if (!display_channel->enable_zlib_glz_wrap || (glz_size < MIN_GLZ_SIZE_FOR_ZLIB)) {
goto glz;
}
#ifdef COMPRESS_STAT
start_time = stat_now();
#endif
zlib_data = &worker->zlib_data;
zlib_data->data.bufs_tail = red_display_alloc_compress_buf(dcc);
zlib_data->data.bufs_head = zlib_data->data.bufs_tail;
if (!zlib_data->data.bufs_head) {
spice_printerr("failed to allocate zlib compress buffer");
goto glz;
}
zlib_data->data.bufs_head->send_next = NULL;
zlib_data->data.dcc = dcc;
zlib_data->data.u.compressed_data.next = glz_data->data.bufs_head;
zlib_data->data.u.compressed_data.size_left = glz_size;
zlib_size = zlib_encode(worker->zlib, display_channel->zlib_level,
glz_size, (uint8_t*)zlib_data->data.bufs_head->buf,
sizeof(zlib_data->data.bufs_head->buf));
// the compressed buffer is bigger than the original data
if (zlib_size >= glz_size) {
while (zlib_data->data.bufs_head) {
RedCompressBuf *buf = zlib_data->data.bufs_head;
zlib_data->data.bufs_head = buf->send_next;
red_display_free_compress_buf(dcc, buf);
}
goto glz;
}
dest->descriptor.type = SPICE_IMAGE_TYPE_ZLIB_GLZ_RGB;
dest->u.zlib_glz.glz_data_size = glz_size;
dest->u.zlib_glz.data_size = zlib_size;
o_comp_data->comp_buf = zlib_data->data.bufs_head;
o_comp_data->comp_buf_size = zlib_size;
stat_compress_add(&display_channel->zlib_glz_stat, start_time, glz_size, zlib_size);
return TRUE;
glz:
dest->descriptor.type = SPICE_IMAGE_TYPE_GLZ_RGB;
dest->u.lz_rgb.data_size = glz_size;
o_comp_data->comp_buf = glz_data->data.bufs_head;
o_comp_data->comp_buf_size = glz_size;
return TRUE;
}
static inline int red_lz_compress_image(DisplayChannelClient *dcc,
SpiceImage *dest, SpiceBitmap *src,
compress_send_data_t* o_comp_data, uint32_t group_id)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
LzData *lz_data = &worker->lz_data;
LzContext *lz = worker->lz;
LzImageType type = MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[src->format];
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(dcc);
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.dcc = dcc;
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(dcc, buf);
}
return FALSE;
}
lz_data->data.u.lines_data.chunks = src->data;
lz_data->data.u.lines_data.stride = src->stride;
lz_data->data.u.lines_data.next = 0;
lz_data->data.u.lines_data.reverse = 0;
lz_data->usr.more_lines = lz_usr_more_lines;
size = lz_encode(lz, type, src->x, src->y,
!!(src->flags & SPICE_BITMAP_FLAGS_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 = SPICE_IMAGE_TYPE_LZ_RGB;
dest->u.lz_rgb.data_size = size;
o_comp_data->comp_buf = lz_data->data.bufs_head;
o_comp_data->comp_buf_size = size;
} else {
dest->descriptor.type = SPICE_IMAGE_TYPE_LZ_PLT;
dest->u.lz_plt.data_size = size;
dest->u.lz_plt.flags = src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN;
dest->u.lz_plt.palette = src->palette;
dest->u.lz_plt.palette_id = src->palette->unique;
o_comp_data->comp_buf = lz_data->data.bufs_head;
o_comp_data->comp_buf_size = size;
fill_palette(dcc, dest->u.lz_plt.palette, &(dest->u.lz_plt.flags));
o_comp_data->lzplt_palette = dest->u.lz_plt.palette;
}
stat_compress_add(&display_channel->lz_stat, start_time, src->stride * src->y,
o_comp_data->comp_buf_size);
return TRUE;
}
static int red_jpeg_compress_image(DisplayChannelClient *dcc, SpiceImage *dest,
SpiceBitmap *src, compress_send_data_t* o_comp_data,
uint32_t group_id)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
JpegData *jpeg_data = &worker->jpeg_data;
LzData *lz_data = &worker->lz_data;
JpegEncoderContext *jpeg = worker->jpeg;
LzContext *lz = worker->lz;
volatile JpegEncoderImageType jpeg_in_type;
int jpeg_size = 0;
volatile int has_alpha = FALSE;
int alpha_lz_size = 0;
int comp_head_filled;
int comp_head_left;
int stride;
uint8_t *lz_out_start_byte;
#ifdef COMPRESS_STAT
stat_time_t start_time = stat_now();
#endif
switch (src->format) {
case SPICE_BITMAP_FMT_16BIT:
jpeg_in_type = JPEG_IMAGE_TYPE_RGB16;
break;
case SPICE_BITMAP_FMT_24BIT:
jpeg_in_type = JPEG_IMAGE_TYPE_BGR24;
break;
case SPICE_BITMAP_FMT_32BIT:
jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32;
break;
case SPICE_BITMAP_FMT_RGBA:
jpeg_in_type = JPEG_IMAGE_TYPE_BGRX32;
has_alpha = TRUE;
break;
default:
return FALSE;
}
jpeg_data->data.bufs_tail = red_display_alloc_compress_buf(dcc);
jpeg_data->data.bufs_head = jpeg_data->data.bufs_tail;
if (!jpeg_data->data.bufs_head) {
spice_printerr("failed to allocate compress buffer");
return FALSE;
}
jpeg_data->data.bufs_head->send_next = NULL;
jpeg_data->data.dcc = dcc;
if (setjmp(jpeg_data->data.jmp_env)) {
while (jpeg_data->data.bufs_head) {
RedCompressBuf *buf = jpeg_data->data.bufs_head;
jpeg_data->data.bufs_head = buf->send_next;
red_display_free_compress_buf(dcc, buf);
}
return FALSE;
}
if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) {
spice_chunks_linearize(src->data);
}
jpeg_data->data.u.lines_data.chunks = src->data;
jpeg_data->data.u.lines_data.stride = src->stride;
jpeg_data->usr.more_lines = jpeg_usr_more_lines;
if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
jpeg_data->data.u.lines_data.next = 0;
jpeg_data->data.u.lines_data.reverse = 0;
stride = src->stride;
} else {
jpeg_data->data.u.lines_data.next = src->data->num_chunks - 1;
jpeg_data->data.u.lines_data.reverse = 1;
stride = -src->stride;
}
jpeg_size = jpeg_encode(jpeg, display_channel->jpeg_quality, jpeg_in_type,
src->x, src->y, NULL,
0, stride, (uint8_t*)jpeg_data->data.bufs_head->buf,
sizeof(jpeg_data->data.bufs_head->buf));
// the compressed buffer is bigger than the original data
if (jpeg_size > (src->y * src->stride)) {
longjmp(jpeg_data->data.jmp_env, 1);
}
if (!has_alpha) {
dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG;
dest->u.jpeg.data_size = jpeg_size;
o_comp_data->comp_buf = jpeg_data->data.bufs_head;
o_comp_data->comp_buf_size = jpeg_size;
o_comp_data->is_lossy = TRUE;
stat_compress_add(&display_channel->jpeg_stat, start_time, src->stride * src->y,
o_comp_data->comp_buf_size);
return TRUE;
}
lz_data->data.bufs_head = jpeg_data->data.bufs_tail;
lz_data->data.bufs_tail = lz_data->data.bufs_head;
comp_head_filled = jpeg_size % sizeof(lz_data->data.bufs_head->buf);
comp_head_left = sizeof(lz_data->data.bufs_head->buf) - comp_head_filled;
lz_out_start_byte = ((uint8_t *)lz_data->data.bufs_head->buf) + comp_head_filled;
lz_data->data.dcc = dcc;
lz_data->data.u.lines_data.chunks = src->data;
lz_data->data.u.lines_data.stride = src->stride;
lz_data->data.u.lines_data.next = 0;
lz_data->data.u.lines_data.reverse = 0;
lz_data->usr.more_lines = lz_usr_more_lines;
alpha_lz_size = lz_encode(lz, LZ_IMAGE_TYPE_XXXA, src->x, src->y,
!!(src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN),
NULL, 0, src->stride,
lz_out_start_byte,
comp_head_left);
// the compressed buffer is bigger than the original data
if ((jpeg_size + alpha_lz_size) > (src->y * src->stride)) {
longjmp(jpeg_data->data.jmp_env, 1);
}
dest->descriptor.type = SPICE_IMAGE_TYPE_JPEG_ALPHA;
dest->u.jpeg_alpha.flags = 0;
if (src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN) {
dest->u.jpeg_alpha.flags |= SPICE_JPEG_ALPHA_FLAGS_TOP_DOWN;
}
dest->u.jpeg_alpha.jpeg_size = jpeg_size;
dest->u.jpeg_alpha.data_size = jpeg_size + alpha_lz_size;
o_comp_data->comp_buf = jpeg_data->data.bufs_head;
o_comp_data->comp_buf_size = jpeg_size + alpha_lz_size;
o_comp_data->is_lossy = TRUE;
stat_compress_add(&display_channel->jpeg_alpha_stat, start_time, src->stride * src->y,
o_comp_data->comp_buf_size);
return TRUE;
}
static inline int red_quic_compress_image(DisplayChannelClient *dcc, SpiceImage *dest,
SpiceBitmap *src, compress_send_data_t* o_comp_data,
uint32_t group_id)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
QuicData *quic_data = &worker->quic_data;
QuicContext *quic = worker->quic;
volatile QuicImageType type;
int size, stride;
#ifdef COMPRESS_STAT
stat_time_t start_time = stat_now();
#endif
switch (src->format) {
case SPICE_BITMAP_FMT_32BIT:
type = QUIC_IMAGE_TYPE_RGB32;
break;
case SPICE_BITMAP_FMT_RGBA:
type = QUIC_IMAGE_TYPE_RGBA;
break;
case SPICE_BITMAP_FMT_16BIT:
type = QUIC_IMAGE_TYPE_RGB16;
break;
case SPICE_BITMAP_FMT_24BIT:
type = QUIC_IMAGE_TYPE_RGB24;
break;
default:
return FALSE;
}
quic_data->data.bufs_tail = red_display_alloc_compress_buf(dcc);
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.dcc = dcc;
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(dcc, buf);
}
return FALSE;
}
if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) {
spice_chunks_linearize(src->data);
}
quic_data->data.u.lines_data.chunks = src->data;
quic_data->data.u.lines_data.stride = src->stride;
quic_data->usr.more_lines = quic_usr_more_lines;
if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
quic_data->data.u.lines_data.next = 0;
quic_data->data.u.lines_data.reverse = 0;
stride = src->stride;
} else {
quic_data->data.u.lines_data.next = src->data->num_chunks - 1;
quic_data->data.u.lines_data.reverse = 1;
stride = -src->stride;
}
size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride,
quic_data->data.bufs_head->buf,
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 = SPICE_IMAGE_TYPE_QUIC;
dest->u.quic.data_size = size << 2;
o_comp_data->comp_buf = quic_data->data.bufs_head;
o_comp_data->comp_buf_size = size << 2;
stat_compress_add(&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(DisplayChannelClient *dcc,
SpiceImage *dest, SpiceBitmap *src, Drawable *drawable,
int can_lossy,
compress_send_data_t* o_comp_data)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
spice_image_compression_t image_compression =
display_channel->common.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->data->flags & SPICE_CHUNKS_FLAGS_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] &&
(_get_bitmap_graduality_level(display_channel->common.worker, src,
drawable->group_id) ==
BITMAP_GRADUAL_HIGH);
} else {
quic_compress = (drawable->copy_bitmap_graduality == BITMAP_GRADUAL_HIGH);
}
}
} else {
quic_compress = FALSE;
}
}
}
if (quic_compress) {
#ifdef COMPRESS_DEBUG
spice_printerr("QUIC compress");
#endif
// if bitmaps is picture-like, compress it using jpeg
if (can_lossy && display_channel->enable_jpeg &&
((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
(image_compression == SPICE_IMAGE_COMPRESS_AUTO_GLZ))) {
// if we use lz for alpha, the stride can't be extra
if (src->format != SPICE_BITMAP_FMT_RGBA || !_stride_is_extra(src)) {
return red_jpeg_compress_image(dcc, dest,
src, o_comp_data, drawable->group_id);
}
}
return red_quic_compress_image(dcc, dest,
src, o_comp_data, drawable->group_id);
} 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(
dcc->glz_dict->dict));
} else if ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
(image_compression == SPICE_IMAGE_COMPRESS_LZ)) {
glz = FALSE;
} else {
spice_error("invalid image compression type %u", image_compression);
return FALSE;
}
if (glz) {
/* using the global dictionary only if it is not frozen */
pthread_rwlock_rdlock(&dcc->glz_dict->encode_lock);
if (!dcc->glz_dict->migrate_freeze) {
ret = red_glz_compress_image(dcc,
dest, src,
drawable, o_comp_data);
} else {
glz = FALSE;
}
pthread_rwlock_unlock(&dcc->glz_dict->encode_lock);
}
if (!glz) {
ret = red_lz_compress_image(dcc, dest, src, o_comp_data,
drawable->group_id);
#ifdef COMPRESS_DEBUG
spice_printerr("LZ LOCAL compress");
#endif
}
#ifdef COMPRESS_DEBUG
else {
spice_printerr("LZ global compress fmt=%d", src->format);
}
#endif
return ret;
}
}
static inline void red_display_add_image_to_pixmap_cache(RedChannelClient *rcc,
SpiceImage *image, SpiceImage *io_image,
int is_lossy)
{
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
if ((image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) {
spice_assert(image->descriptor.width * image->descriptor.height > 0);
if (!(io_image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME)) {
if (pixmap_cache_add(dcc->pixmap_cache, image->descriptor.id,
image->descriptor.width * image->descriptor.height, is_lossy,
dcc)) {
io_image->descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_ME;
dcc->send_data.pixmap_cache_items[dcc->send_data.num_pixmap_cache_items++] =
image->descriptor.id;
stat_inc_counter(display_channel->add_to_cache_counter, 1);
}
}
}
if (!(io_image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) {
stat_inc_counter(display_channel->non_cache_counter, 1);
}
}
typedef enum {
FILL_BITS_TYPE_INVALID,
FILL_BITS_TYPE_CACHE,
FILL_BITS_TYPE_SURFACE,
FILL_BITS_TYPE_COMPRESS_LOSSLESS,
FILL_BITS_TYPE_COMPRESS_LOSSY,
FILL_BITS_TYPE_BITMAP,
} FillBitsType;
/* 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 FillBitsType fill_bits(DisplayChannelClient *dcc, SpiceMarshaller *m,
SpiceImage *simage, Drawable *drawable, int can_lossy)
{
RedChannelClient *rcc = &dcc->common.base;
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
RedWorker *worker = dcc->common.worker;
SpiceImage image;
compress_send_data_t comp_send_data = {0};
SpiceMarshaller *bitmap_palette_out, *lzplt_palette_out;
if (simage == NULL) {
spice_assert(drawable->self_bitmap);
simage = drawable->self_bitmap;
}
image.descriptor = simage->descriptor;
if ((simage->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) {
int lossy_cache_item;
if (pixmap_cache_hit(dcc->pixmap_cache, image.descriptor.id,
&lossy_cache_item, dcc)) {
dcc->send_data.pixmap_cache_items[dcc->send_data.num_pixmap_cache_items++] =
image.descriptor.id;
if (can_lossy || !lossy_cache_item) {
if (!display_channel->enable_jpeg || lossy_cache_item) {
image.descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE;
} else {
// making sure, in multiple monitor scenario, that lossy items that
// should have been replaced with lossless data by one display channel,
// will be retrieved as lossless by another display channel.
image.descriptor.type = SPICE_IMAGE_TYPE_FROM_CACHE_LOSSLESS;
}
spice_marshall_Image(m, &image,
&bitmap_palette_out, &lzplt_palette_out);
spice_assert(bitmap_palette_out == NULL);
spice_assert(lzplt_palette_out == NULL);
stat_inc_counter(display_channel->cache_hits_counter, 1);
return FILL_BITS_TYPE_CACHE;
} else {
pixmap_cache_set_lossy(dcc->pixmap_cache, simage->descriptor.id,
FALSE);
image.descriptor.flags |= SPICE_IMAGE_FLAGS_CACHE_REPLACE_ME;
}
}
}
switch (simage->descriptor.type) {
case SPICE_IMAGE_TYPE_SURFACE: {
int surface_id;
RedSurface *surface;
surface_id = simage->u.surface.surface_id;
validate_surface(worker, surface_id);
surface = &worker->surfaces[surface_id];
image.descriptor.type = SPICE_IMAGE_TYPE_SURFACE;
image.descriptor.flags = 0;
image.descriptor.width = surface->context.width;
image.descriptor.height = surface->context.height;
image.u.surface.surface_id = surface_id;
spice_marshall_Image(m, &image,
&bitmap_palette_out, &lzplt_palette_out);
spice_assert(bitmap_palette_out == NULL);
spice_assert(lzplt_palette_out == NULL);
return FILL_BITS_TYPE_SURFACE;
}
case SPICE_IMAGE_TYPE_BITMAP: {
SpiceBitmap *bitmap = &image.u.bitmap;
#ifdef DUMP_BITMAP
dump_bitmap(display_channel->common.worker, &simage->u.bitmap, drawable->group_id);
#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(dcc, &image, &simage->u.bitmap,
drawable, can_lossy, &comp_send_data)) {
SpicePalette *palette;
red_display_add_image_to_pixmap_cache(rcc, simage, &image, FALSE);
*bitmap = simage->u.bitmap;
bitmap->flags = bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN;
palette = bitmap->palette;
fill_palette(dcc, palette, &bitmap->flags);
spice_marshall_Image(m, &image,
&bitmap_palette_out, &lzplt_palette_out);
spice_assert(lzplt_palette_out == NULL);
if (bitmap_palette_out && palette) {
spice_marshall_Palette(bitmap_palette_out, palette);
}
spice_marshaller_add_ref_chunks(m, bitmap->data);
return FILL_BITS_TYPE_BITMAP;
} else {
red_display_add_image_to_pixmap_cache(rcc, simage, &image,
comp_send_data.is_lossy);
spice_marshall_Image(m, &image,
&bitmap_palette_out, &lzplt_palette_out);
spice_assert(bitmap_palette_out == NULL);
marshaller_add_compressed(m, comp_send_data.comp_buf,
comp_send_data.comp_buf_size);
if (lzplt_palette_out && comp_send_data.lzplt_palette) {
spice_marshall_Palette(lzplt_palette_out, comp_send_data.lzplt_palette);
}
spice_assert(!comp_send_data.is_lossy || can_lossy);
return (comp_send_data.is_lossy ? FILL_BITS_TYPE_COMPRESS_LOSSY :
FILL_BITS_TYPE_COMPRESS_LOSSLESS);
}
break;
}
case SPICE_IMAGE_TYPE_QUIC:
red_display_add_image_to_pixmap_cache(rcc, simage, &image, FALSE);
image.u.quic = simage->u.quic;
spice_marshall_Image(m, &image,
&bitmap_palette_out, &lzplt_palette_out);
spice_assert(bitmap_palette_out == NULL);
spice_assert(lzplt_palette_out == NULL);
spice_marshaller_add_ref_chunks(m, image.u.quic.data);
return FILL_BITS_TYPE_COMPRESS_LOSSLESS;
default:
spice_error("invalid image type %u", image.descriptor.type);
}
return 0;
}
static void fill_mask(RedChannelClient *rcc, SpiceMarshaller *m,
SpiceImage *mask_bitmap, Drawable *drawable)
{
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
if (mask_bitmap && m) {
if (display_channel->common.worker->image_compression != SPICE_IMAGE_COMPRESS_OFF) {
spice_image_compression_t save_img_comp =
display_channel->common.worker->image_compression;
display_channel->common.worker->image_compression = SPICE_IMAGE_COMPRESS_OFF;
fill_bits(dcc, m, mask_bitmap, drawable, FALSE);
display_channel->common.worker->image_compression = save_img_comp;
} else {
fill_bits(dcc, m, mask_bitmap, drawable, FALSE);
}
}
}
static void fill_attr(SpiceMarshaller *m, SpiceLineAttr *attr, uint32_t group_id)
{
int i;
if (m && attr->style_nseg) {
for (i = 0 ; i < attr->style_nseg; i++) {
spice_marshaller_add_uint32(m, attr->style[i]);
}
}
}
static void fill_cursor(CursorChannelClient *ccc, SpiceCursor *red_cursor,
CursorItem *cursor, AddBufInfo *addbuf)
{
RedCursorCmd *cursor_cmd;
addbuf->data = NULL;
if (!cursor) {
red_cursor->flags = SPICE_CURSOR_FLAGS_NONE;
return;
}
cursor_cmd = cursor->red_cursor;
*red_cursor = cursor_cmd->u.set.shape;
if (red_cursor->header.unique) {
if (red_cursor_cache_find(ccc, red_cursor->header.unique)) {
red_cursor->flags |= SPICE_CURSOR_FLAGS_FROM_CACHE;
return;
}
if (red_cursor_cache_add(ccc, red_cursor->header.unique, 1)) {
red_cursor->flags |= SPICE_CURSOR_FLAGS_CACHE_ME;
}
}
if (red_cursor->data_size) {
addbuf->type = BUF_TYPE_RAW;
addbuf->data = red_cursor->data;
addbuf->size = red_cursor->data_size;
}
}
static inline void red_display_reset_send_data(DisplayChannelClient *dcc)
{
red_display_reset_compress_buf(dcc);
dcc->send_data.free_list.res->count = 0;
dcc->send_data.num_pixmap_cache_items = 0;
memset(dcc->send_data.free_list.sync, 0, sizeof(dcc->send_data.free_list.sync));
}
/* set area=NULL for testing the whole surface */
static int is_surface_area_lossy(DisplayChannelClient *dcc, uint32_t surface_id,
const SpiceRect *area, SpiceRect *out_lossy_area)
{
RedSurface *surface;
QRegion *surface_lossy_region;
QRegion lossy_region;
RedWorker *worker = dcc->common.worker;
validate_surface(worker, surface_id);
surface = &worker->surfaces[surface_id];
surface_lossy_region = &dcc->surface_client_lossy_region[surface_id];
if (!area) {
if (region_is_empty(surface_lossy_region)) {
return FALSE;
} else {
out_lossy_area->top = 0;
out_lossy_area->left = 0;
out_lossy_area->bottom = surface->context.height;
out_lossy_area->right = surface->context.width;
return TRUE;
}
}
region_init(&lossy_region);
region_add(&lossy_region, area);
region_and(&lossy_region, surface_lossy_region);
if (!region_is_empty(&lossy_region)) {
out_lossy_area->left = lossy_region.extents.x1;
out_lossy_area->top = lossy_region.extents.y1;
out_lossy_area->right = lossy_region.extents.x2;
out_lossy_area->bottom = lossy_region.extents.y2;
region_destroy(&lossy_region);
return TRUE;
} else {
return FALSE;
}
}
/* returns if the bitmap was already sent lossy to the client. If the bitmap hasn't been sent yet
to the client, returns false. "area" is for surfaces. If area = NULL,
all the surface is considered. out_lossy_data will hold info about the bitmap, and its lossy
area in case it is lossy and part of a surface. */
static int is_bitmap_lossy(RedChannelClient *rcc, SpiceImage *image, SpiceRect *area,
Drawable *drawable, BitmapData *out_data)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
if (image == NULL) {
// self bitmap
out_data->type = BITMAP_DATA_TYPE_BITMAP;
return FALSE;
}
if ((image->descriptor.flags & SPICE_IMAGE_FLAGS_CACHE_ME)) {
int is_hit_lossy;
out_data->id = image->descriptor.id;
if (pixmap_cache_hit(dcc->pixmap_cache, image->descriptor.id,
&is_hit_lossy, dcc)) {
out_data->type = BITMAP_DATA_TYPE_CACHE;
if (is_hit_lossy) {
return TRUE;
} else {
return FALSE;
}
} else {
out_data->type = BITMAP_DATA_TYPE_BITMAP_TO_CACHE;
}
} else {
out_data->type = BITMAP_DATA_TYPE_BITMAP;
}
if (image->descriptor.type != SPICE_IMAGE_TYPE_SURFACE) {
return FALSE;
}
out_data->type = BITMAP_DATA_TYPE_SURFACE;
out_data->id = image->u.surface.surface_id;
if (is_surface_area_lossy(dcc, out_data->id,
area, &out_data->lossy_rect))
{
return TRUE;
} else {
return FALSE;
}
}
static int is_brush_lossy(RedChannelClient *rcc, SpiceBrush *brush,
Drawable *drawable, BitmapData *out_data)
{
if (brush->type == SPICE_BRUSH_TYPE_PATTERN) {
return is_bitmap_lossy(rcc, brush->u.pattern.pat, NULL,
drawable, out_data);
} else {
out_data->type = BITMAP_DATA_TYPE_INVALID;
return FALSE;
}
}
static void surface_lossy_region_update(RedWorker *worker, DisplayChannelClient *dcc,
Drawable *item, int has_mask, int lossy)
{
QRegion *surface_lossy_region;
RedDrawable *drawable;
if (has_mask && !lossy) {
return;
}
surface_lossy_region = &dcc->surface_client_lossy_region[item->surface_id];
drawable = item->red_drawable;
if (drawable->clip.type == SPICE_CLIP_TYPE_RECTS ) {
QRegion clip_rgn;
QRegion draw_region;
region_init(&clip_rgn);
region_init(&draw_region);
region_add(&draw_region, &drawable->bbox);
add_clip_rects(&clip_rgn, drawable->clip.rects);
region_and(&draw_region, &clip_rgn);
if (lossy) {
region_or(surface_lossy_region, &draw_region);
} else {
region_exclude(surface_lossy_region, &draw_region);
}
region_destroy(&clip_rgn);
region_destroy(&draw_region);
} else { /* no clip */
if (!lossy) {
region_remove(surface_lossy_region, &drawable->bbox);
} else {
region_add(surface_lossy_region, &drawable->bbox);
}
}
}
static inline int drawable_intersects_with_areas(Drawable *drawable, int surface_ids[],
SpiceRect *surface_areas[],
int num_surfaces)
{
int i;
for (i = 0; i < num_surfaces; i++) {
if (surface_ids[i] == drawable->red_drawable->surface_id) {
if (rect_intersects(surface_areas[i], &drawable->red_drawable->bbox)) {
return TRUE;
}
}
}
return FALSE;
}
static inline int drawable_depends_on_areas(Drawable *drawable,
int surface_ids[],
SpiceRect surface_areas[],
int num_surfaces)
{
int i;
RedDrawable *red_drawable;
int drawable_has_shadow;
SpiceRect shadow_rect = {0, 0, 0, 0};
red_drawable = drawable->red_drawable;
drawable_has_shadow = has_shadow(red_drawable);
if (drawable_has_shadow) {
int delta_x = red_drawable->u.copy_bits.src_pos.x - red_drawable->bbox.left;
int delta_y = red_drawable->u.copy_bits.src_pos.y - red_drawable->bbox.top;
shadow_rect.left = red_drawable->u.copy_bits.src_pos.x;
shadow_rect.top = red_drawable->u.copy_bits.src_pos.y;
shadow_rect.right = red_drawable->bbox.right + delta_x;
shadow_rect.bottom = red_drawable->bbox.bottom + delta_y;
}
for (i = 0; i < num_surfaces; i++) {
int x;
int dep_surface_id;
for (x = 0; x < 3; ++x) {
dep_surface_id = drawable->surfaces_dest[x];
if (dep_surface_id == surface_ids[i]) {
if (rect_intersects(&surface_areas[i], &red_drawable->surfaces_rects[x])) {
return TRUE;
}
}
}
if (surface_ids[i] == red_drawable->surface_id) {
if (drawable_has_shadow) {
if (rect_intersects(&surface_areas[i], &shadow_rect)) {
return TRUE;
}
}
// not dependent on dest
if (red_drawable->effect == QXL_EFFECT_OPAQUE) {
continue;
}
if (rect_intersects(&surface_areas[i], &red_drawable->bbox)) {
return TRUE;
}
}
}
return FALSE;
}
static int pipe_rendered_drawables_intersect_with_areas(RedWorker *worker,
DisplayChannelClient *dcc,
int surface_ids[],
SpiceRect *surface_areas[],
int num_surfaces)
{
PipeItem *pipe_item;
Ring *pipe;
spice_assert(num_surfaces);
pipe = &dcc->common.base.pipe;
for (pipe_item = (PipeItem *)ring_get_head(pipe);
pipe_item;
pipe_item = (PipeItem *)ring_next(pipe, &pipe_item->link))
{
Drawable *drawable;
if (pipe_item->type != PIPE_ITEM_TYPE_DRAW)
continue;
drawable = SPICE_CONTAINEROF(pipe_item, DrawablePipeItem, dpi_pipe_item)->drawable;
if (ring_item_is_linked(&drawable->list_link))
continue; // item hasn't been rendered
if (drawable_intersects_with_areas(drawable, surface_ids, surface_areas, num_surfaces)) {
return TRUE;
}
}
return FALSE;
}
static void red_pipe_replace_rendered_drawables_with_images(RedWorker *worker,
DisplayChannelClient *dcc,
int first_surface_id,
SpiceRect *first_area)
{
/* TODO: can't have those statics with multiple clients */
static int resent_surface_ids[MAX_PIPE_SIZE];
static SpiceRect resent_areas[MAX_PIPE_SIZE]; // not pointers since drawbales may be released
int num_resent;
PipeItem *pipe_item;
Ring *pipe;
resent_surface_ids[0] = first_surface_id;
resent_areas[0] = *first_area;
num_resent = 1;
pipe = &dcc->common.base.pipe;
// going from the oldest to the newest
for (pipe_item = (PipeItem *)ring_get_tail(pipe);
pipe_item;
pipe_item = (PipeItem *)ring_prev(pipe, &pipe_item->link)) {
Drawable *drawable;
DrawablePipeItem *dpi;
ImageItem *image;
if (pipe_item->type != PIPE_ITEM_TYPE_DRAW)
continue;
dpi = SPICE_CONTAINEROF(pipe_item, DrawablePipeItem, dpi_pipe_item);
drawable = dpi->drawable;
if (ring_item_is_linked(&drawable->list_link))
continue; // item hasn't been rendered
// When a drawable command, X, depends on bitmaps that were resent,
// these bitmaps state at the client might not be synchronized with X
// (i.e., the bitmaps can be more futuristic w.r.t X). Thus, X shouldn't
// be rendered at the client, and we replace it with an image as well.
if (!drawable_depends_on_areas(drawable,
resent_surface_ids,
resent_areas,
num_resent)) {
continue;
}
image = red_add_surface_area_image(dcc, drawable->red_drawable->surface_id,
&drawable->red_drawable->bbox, pipe_item, TRUE);
resent_surface_ids[num_resent] = drawable->red_drawable->surface_id;
resent_areas[num_resent] = drawable->red_drawable->bbox;
num_resent++;
spice_assert(image);
red_channel_client_pipe_remove_and_release(&dcc->common.base, &dpi->dpi_pipe_item);
pipe_item = &image->link;
}
}
static void red_add_lossless_drawable_dependencies(RedWorker *worker,
RedChannelClient *rcc,
Drawable *item,
int deps_surfaces_ids[],
SpiceRect *deps_areas[],
int num_deps)
{
RedDrawable *drawable = item->red_drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
int sync_rendered = FALSE;
int i;
if (!ring_item_is_linked(&item->list_link)) {
/* drawable was already rendered, we may not be able to retrieve the lossless data
for the lossy areas */
sync_rendered = TRUE;
// checking if the drawable itself or one of the other commands
// that were rendered, affected the areas that need to be resent
if (!drawable_intersects_with_areas(item, deps_surfaces_ids,
deps_areas, num_deps)) {
if (pipe_rendered_drawables_intersect_with_areas(worker, dcc,
deps_surfaces_ids,
deps_areas,
num_deps)) {
sync_rendered = TRUE;
}
} else {
sync_rendered = TRUE;
}
} else {
sync_rendered = FALSE;
for (i = 0; i < num_deps; i++) {
red_update_area_till(worker, deps_areas[i],
deps_surfaces_ids[i], item);
}
}
if (!sync_rendered) {
// pushing the pipe item back to the pipe
red_pipe_add_drawable_to_tail(dcc, item);
// the surfaces areas will be sent as DRAW_COPY commands, that
// will be executed before the current drawable
for (i = 0; i < num_deps; i++) {
red_add_surface_area_image(dcc, deps_surfaces_ids[i], deps_areas[i],
red_pipe_get_tail(dcc), FALSE);
}
} else {
int drawable_surface_id[1];
SpiceRect *drawable_bbox[1];
drawable_surface_id[0] = drawable->surface_id;
drawable_bbox[0] = &drawable->bbox;
// check if the other rendered images in the pipe have updated the drawable bbox
if (pipe_rendered_drawables_intersect_with_areas(worker, dcc,
drawable_surface_id,
drawable_bbox,
1)) {
red_pipe_replace_rendered_drawables_with_images(worker, dcc,
drawable->surface_id,
&drawable->bbox);
}
red_add_surface_area_image(dcc, drawable->surface_id, &drawable->bbox,
red_pipe_get_tail(dcc), TRUE);
}
}
static void red_marshall_qxl_draw_fill(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
SpiceMarshaller *brush_pat_out;
SpiceMarshaller *mask_bitmap_out;
SpiceFill fill;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_FILL, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
fill = drawable->u.fill;
spice_marshall_Fill(base_marshaller,
&fill,
&brush_pat_out,
&mask_bitmap_out);
if (brush_pat_out) {
fill_bits(dcc, brush_pat_out, fill.brush.u.pattern.pat, item, FALSE);
}
fill_mask(rcc, mask_bitmap_out, fill.mask.bitmap, item);
}
static void red_lossy_marshall_qxl_draw_fill(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *m,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int dest_allowed_lossy = FALSE;
int dest_is_lossy = FALSE;
SpiceRect dest_lossy_area;
int brush_is_lossy;
BitmapData brush_bitmap_data;
uint16_t rop;
rop = drawable->u.fill.rop_descriptor;
dest_allowed_lossy = !((rop & SPICE_ROPD_OP_OR) ||
(rop & SPICE_ROPD_OP_AND) ||
(rop & SPICE_ROPD_OP_XOR));
brush_is_lossy = is_brush_lossy(rcc, &drawable->u.fill.brush, item,
&brush_bitmap_data);
if (!dest_allowed_lossy) {
dest_is_lossy = is_surface_area_lossy(dcc, item->surface_id, &drawable->bbox,
&dest_lossy_area);
}
if (!dest_is_lossy &&
!(brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE))) {
int has_mask = !!drawable->u.fill.mask.bitmap;
red_marshall_qxl_draw_fill(worker, rcc, m, dpi);
// either the brush operation is opaque, or the dest is not lossy
surface_lossy_region_update(worker, dcc, item, has_mask, FALSE);
} else {
int resend_surface_ids[2];
SpiceRect *resend_areas[2];
int num_resend = 0;
if (dest_is_lossy) {
resend_surface_ids[num_resend] = item->surface_id;
resend_areas[num_resend] = &dest_lossy_area;
num_resend++;
}
if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = brush_bitmap_data.id;
resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
num_resend++;
}
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, num_resend);
}
}
static FillBitsType red_marshall_qxl_draw_opaque(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi, int src_allowed_lossy)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *brush_pat_out;
SpiceMarshaller *src_bitmap_out;
SpiceMarshaller *mask_bitmap_out;
SpiceOpaque opaque;
FillBitsType src_send_type;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_OPAQUE, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
opaque = drawable->u.opaque;
spice_marshall_Opaque(base_marshaller,
&opaque,
&src_bitmap_out,
&brush_pat_out,
&mask_bitmap_out);
src_send_type = fill_bits(dcc, src_bitmap_out, opaque.src_bitmap, item,
src_allowed_lossy);
if (brush_pat_out) {
fill_bits(dcc, brush_pat_out, opaque.brush.u.pattern.pat, item, FALSE);
}
fill_mask(rcc, mask_bitmap_out, opaque.mask.bitmap, item);
return src_send_type;
}
static void red_lossy_marshall_qxl_draw_opaque(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *m,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int src_allowed_lossy;
int rop;
int src_is_lossy = FALSE;
int brush_is_lossy = FALSE;
BitmapData src_bitmap_data;
BitmapData brush_bitmap_data;
rop = drawable->u.opaque.rop_descriptor;
src_allowed_lossy = !((rop & SPICE_ROPD_OP_OR) ||
(rop & SPICE_ROPD_OP_AND) ||
(rop & SPICE_ROPD_OP_XOR));
brush_is_lossy = is_brush_lossy(rcc, &drawable->u.opaque.brush, item,
&brush_bitmap_data);
if (!src_allowed_lossy) {
src_is_lossy = is_bitmap_lossy(rcc, drawable->u.opaque.src_bitmap,
&drawable->u.opaque.src_area,
item,
&src_bitmap_data);
}
if (!(brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) &&
!(src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE))) {
FillBitsType src_send_type;
int has_mask = !!drawable->u.opaque.mask.bitmap;
src_send_type = red_marshall_qxl_draw_opaque(worker, rcc, m, dpi, src_allowed_lossy);
if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) {
src_is_lossy = TRUE;
} else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) {
src_is_lossy = FALSE;
}
surface_lossy_region_update(worker, dcc, item, has_mask, src_is_lossy);
} else {
int resend_surface_ids[2];
SpiceRect *resend_areas[2];
int num_resend = 0;
if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = src_bitmap_data.id;
resend_areas[num_resend] = &src_bitmap_data.lossy_rect;
num_resend++;
}
if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = brush_bitmap_data.id;
resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
num_resend++;
}
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, num_resend);
}
}
static FillBitsType red_marshall_qxl_draw_copy(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi, int src_allowed_lossy)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
SpiceMarshaller *src_bitmap_out;
SpiceMarshaller *mask_bitmap_out;
SpiceCopy copy;
FillBitsType src_send_type;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COPY, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
copy = drawable->u.copy;
spice_marshall_Copy(base_marshaller,
©,
&src_bitmap_out,
&mask_bitmap_out);
src_send_type = fill_bits(dcc, src_bitmap_out, copy.src_bitmap, item, src_allowed_lossy);
fill_mask(rcc, mask_bitmap_out, copy.mask.bitmap, item);
return src_send_type;
}
static void red_lossy_marshall_qxl_draw_copy(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int has_mask = !!drawable->u.copy.mask.bitmap;
int src_is_lossy;
BitmapData src_bitmap_data;
FillBitsType src_send_type;
src_is_lossy = is_bitmap_lossy(rcc, drawable->u.copy.src_bitmap,
&drawable->u.copy.src_area, item, &src_bitmap_data);
src_send_type = red_marshall_qxl_draw_copy(worker, rcc, base_marshaller, dpi, TRUE);
if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) {
src_is_lossy = TRUE;
} else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) {
src_is_lossy = FALSE;
}
surface_lossy_region_update(worker, dcc, item, has_mask,
src_is_lossy);
}
static void red_marshall_qxl_draw_transparent(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *src_bitmap_out;
SpiceTransparent transparent;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_TRANSPARENT,
&dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
transparent = drawable->u.transparent;
spice_marshall_Transparent(base_marshaller,
&transparent,
&src_bitmap_out);
fill_bits(dcc, src_bitmap_out, transparent.src_bitmap, item, FALSE);
}
static void red_lossy_marshall_qxl_draw_transparent(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
int src_is_lossy;
BitmapData src_bitmap_data;
src_is_lossy = is_bitmap_lossy(rcc, drawable->u.transparent.src_bitmap,
&drawable->u.transparent.src_area, item, &src_bitmap_data);
if (!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) {
red_marshall_qxl_draw_transparent(worker, rcc, base_marshaller, dpi);
// don't update surface lossy region since transperent areas might be lossy
} else {
int resend_surface_ids[1];
SpiceRect *resend_areas[1];
resend_surface_ids[0] = src_bitmap_data.id;
resend_areas[0] = &src_bitmap_data.lossy_rect;
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, 1);
}
}
static FillBitsType red_marshall_qxl_draw_alpha_blend(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi,
int src_allowed_lossy)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *src_bitmap_out;
SpiceAlphaBlend alpha_blend;
FillBitsType src_send_type;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND,
&dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
alpha_blend = drawable->u.alpha_blend;
spice_marshall_AlphaBlend(base_marshaller,
&alpha_blend,
&src_bitmap_out);
src_send_type = fill_bits(dcc, src_bitmap_out, alpha_blend.src_bitmap, item,
src_allowed_lossy);
return src_send_type;
}
static void red_lossy_marshall_qxl_draw_alpha_blend(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int src_is_lossy;
BitmapData src_bitmap_data;
FillBitsType src_send_type;
src_is_lossy = is_bitmap_lossy(rcc, drawable->u.alpha_blend.src_bitmap,
&drawable->u.alpha_blend.src_area, item, &src_bitmap_data);
src_send_type = red_marshall_qxl_draw_alpha_blend(worker, rcc, base_marshaller, dpi, TRUE);
if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSY) {
src_is_lossy = TRUE;
} else if (src_send_type == FILL_BITS_TYPE_COMPRESS_LOSSLESS) {
src_is_lossy = FALSE;
}
if (src_is_lossy) {
surface_lossy_region_update(worker, dcc, item, FALSE, src_is_lossy);
} // else, the area stays lossy/lossless as the destination
}
static void red_marshall_qxl_copy_bits(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
SpicePoint copy_bits;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_COPY_BITS, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
copy_bits = drawable->u.copy_bits.src_pos;
spice_marshall_Point(base_marshaller,
©_bits);
}
static void red_lossy_marshall_qxl_copy_bits(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceRect src_rect;
int horz_offset;
int vert_offset;
int src_is_lossy;
SpiceRect src_lossy_area;
red_marshall_qxl_copy_bits(worker, rcc, base_marshaller, dpi);
horz_offset = drawable->u.copy_bits.src_pos.x - drawable->bbox.left;
vert_offset = drawable->u.copy_bits.src_pos.y - drawable->bbox.top;
src_rect.left = drawable->u.copy_bits.src_pos.x;
src_rect.top = drawable->u.copy_bits.src_pos.y;
src_rect.right = drawable->bbox.right + horz_offset;
src_rect.bottom = drawable->bbox.bottom + vert_offset;
src_is_lossy = is_surface_area_lossy(dcc, item->surface_id,
&src_rect, &src_lossy_area);
surface_lossy_region_update(worker, dcc, item, FALSE,
src_is_lossy);
}
static void red_marshall_qxl_draw_blend(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *src_bitmap_out;
SpiceMarshaller *mask_bitmap_out;
SpiceBlend blend;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_BLEND, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
blend = drawable->u.blend;
spice_marshall_Blend(base_marshaller,
&blend,
&src_bitmap_out,
&mask_bitmap_out);
fill_bits(dcc, src_bitmap_out, blend.src_bitmap, item, FALSE);
fill_mask(rcc, mask_bitmap_out, blend.mask.bitmap, item);
}
static void red_lossy_marshall_qxl_draw_blend(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int src_is_lossy;
BitmapData src_bitmap_data;
int dest_is_lossy;
SpiceRect dest_lossy_area;
src_is_lossy = is_bitmap_lossy(rcc, drawable->u.blend.src_bitmap,
&drawable->u.blend.src_area, item, &src_bitmap_data);
dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id,
&drawable->bbox, &dest_lossy_area);
if (!dest_is_lossy &&
(!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) {
red_marshall_qxl_draw_blend(worker, rcc, base_marshaller, dpi);
} else {
int resend_surface_ids[2];
SpiceRect *resend_areas[2];
int num_resend = 0;
if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = src_bitmap_data.id;
resend_areas[num_resend] = &src_bitmap_data.lossy_rect;
num_resend++;
}
if (dest_is_lossy) {
resend_surface_ids[num_resend] = item->surface_id;
resend_areas[num_resend] = &dest_lossy_area;
num_resend++;
}
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, num_resend);
}
}
static void red_marshall_qxl_draw_blackness(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *mask_bitmap_out;
SpiceBlackness blackness;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_BLACKNESS, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
blackness = drawable->u.blackness;
spice_marshall_Blackness(base_marshaller,
&blackness,
&mask_bitmap_out);
fill_mask(rcc, mask_bitmap_out, blackness.mask.bitmap, item);
}
static void red_lossy_marshall_qxl_draw_blackness(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int has_mask = !!drawable->u.blackness.mask.bitmap;
red_marshall_qxl_draw_blackness(worker, rcc, base_marshaller, dpi);
surface_lossy_region_update(worker, dcc, item, has_mask, FALSE);
}
static void red_marshall_qxl_draw_whiteness(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *mask_bitmap_out;
SpiceWhiteness whiteness;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_WHITENESS, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
whiteness = drawable->u.whiteness;
spice_marshall_Whiteness(base_marshaller,
&whiteness,
&mask_bitmap_out);
fill_mask(rcc, mask_bitmap_out, whiteness.mask.bitmap, item);
}
static void red_lossy_marshall_qxl_draw_whiteness(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int has_mask = !!drawable->u.whiteness.mask.bitmap;
red_marshall_qxl_draw_whiteness(worker, rcc, base_marshaller, dpi);
surface_lossy_region_update(worker, dcc, item, has_mask, FALSE);
}
static void red_marshall_qxl_draw_inverse(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
Drawable *item)
{
RedDrawable *drawable = item->red_drawable;
SpiceMarshaller *mask_bitmap_out;
SpiceInvers inverse;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_INVERS, NULL);
fill_base(base_marshaller, item);
inverse = drawable->u.invers;
spice_marshall_Invers(base_marshaller,
&inverse,
&mask_bitmap_out);
fill_mask(rcc, mask_bitmap_out, inverse.mask.bitmap, item);
}
static void red_lossy_marshall_qxl_draw_inverse(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
Drawable *item)
{
red_marshall_qxl_draw_inverse(worker, rcc, base_marshaller, item);
}
static void red_marshall_qxl_draw_rop3(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceRop3 rop3;
SpiceMarshaller *src_bitmap_out;
SpiceMarshaller *brush_pat_out;
SpiceMarshaller *mask_bitmap_out;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_ROP3, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
rop3 = drawable->u.rop3;
spice_marshall_Rop3(base_marshaller,
&rop3,
&src_bitmap_out,
&brush_pat_out,
&mask_bitmap_out);
fill_bits(dcc, src_bitmap_out, rop3.src_bitmap, item, FALSE);
if (brush_pat_out) {
fill_bits(dcc, brush_pat_out, rop3.brush.u.pattern.pat, item, FALSE);
}
fill_mask(rcc, mask_bitmap_out, rop3.mask.bitmap, item);
}
static void red_lossy_marshall_qxl_draw_rop3(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int src_is_lossy;
BitmapData src_bitmap_data;
int brush_is_lossy;
BitmapData brush_bitmap_data;
int dest_is_lossy;
SpiceRect dest_lossy_area;
src_is_lossy = is_bitmap_lossy(rcc, drawable->u.rop3.src_bitmap,
&drawable->u.rop3.src_area, item, &src_bitmap_data);
brush_is_lossy = is_brush_lossy(rcc, &drawable->u.rop3.brush, item,
&brush_bitmap_data);
dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id,
&drawable->bbox, &dest_lossy_area);
if ((!src_is_lossy || (src_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) &&
(!brush_is_lossy || (brush_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) &&
!dest_is_lossy) {
int has_mask = !!drawable->u.rop3.mask.bitmap;
red_marshall_qxl_draw_rop3(worker, rcc, base_marshaller, dpi);
surface_lossy_region_update(worker, dcc, item, has_mask, FALSE);
} else {
int resend_surface_ids[3];
SpiceRect *resend_areas[3];
int num_resend = 0;
if (src_is_lossy && (src_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = src_bitmap_data.id;
resend_areas[num_resend] = &src_bitmap_data.lossy_rect;
num_resend++;
}
if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = brush_bitmap_data.id;
resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
num_resend++;
}
if (dest_is_lossy) {
resend_surface_ids[num_resend] = item->surface_id;
resend_areas[num_resend] = &dest_lossy_area;
num_resend++;
}
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, num_resend);
}
}
static void red_marshall_qxl_draw_stroke(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceStroke stroke;
SpiceMarshaller *brush_pat_out;
SpiceMarshaller *style_out;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_STROKE, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
stroke = drawable->u.stroke;
spice_marshall_Stroke(base_marshaller,
&stroke,
&style_out,
&brush_pat_out);
fill_attr(style_out, &stroke.attr, item->group_id);
if (brush_pat_out) {
fill_bits(dcc, brush_pat_out, stroke.brush.u.pattern.pat, item, FALSE);
}
}
static void red_lossy_marshall_qxl_draw_stroke(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int brush_is_lossy;
BitmapData brush_bitmap_data;
int dest_is_lossy = FALSE;
SpiceRect dest_lossy_area;
int rop;
brush_is_lossy = is_brush_lossy(rcc, &drawable->u.stroke.brush, item,
&brush_bitmap_data);
// back_mode is not used at the client. Ignoring.
rop = drawable->u.stroke.fore_mode;
// assuming that if the brush type is solid, the destination can
// be lossy, no matter what the rop is.
if (drawable->u.stroke.brush.type != SPICE_BRUSH_TYPE_SOLID &&
((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) ||
(rop & SPICE_ROPD_OP_XOR))) {
dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id,
&drawable->bbox, &dest_lossy_area);
}
if (!dest_is_lossy &&
(!brush_is_lossy || (brush_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)))
{
red_marshall_qxl_draw_stroke(worker, rcc, base_marshaller, dpi);
} else {
int resend_surface_ids[2];
SpiceRect *resend_areas[2];
int num_resend = 0;
if (brush_is_lossy && (brush_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = brush_bitmap_data.id;
resend_areas[num_resend] = &brush_bitmap_data.lossy_rect;
num_resend++;
}
// TODO: use the path in order to resend smaller areas
if (dest_is_lossy) {
resend_surface_ids[num_resend] = drawable->surface_id;
resend_areas[num_resend] = &dest_lossy_area;
num_resend++;
}
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, num_resend);
}
}
static void red_marshall_qxl_draw_text(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
SpiceText text;
SpiceMarshaller *brush_pat_out;
SpiceMarshaller *back_brush_pat_out;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_TEXT, &dpi->dpi_pipe_item);
fill_base(base_marshaller, item);
text = drawable->u.text;
spice_marshall_Text(base_marshaller,
&text,
&brush_pat_out,
&back_brush_pat_out);
if (brush_pat_out) {
fill_bits(dcc, brush_pat_out, text.fore_brush.u.pattern.pat, item, FALSE);
}
if (back_brush_pat_out) {
fill_bits(dcc, back_brush_pat_out, text.back_brush.u.pattern.pat, item, FALSE);
}
}
static void red_lossy_marshall_qxl_draw_text(RedWorker *worker,
RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *drawable = item->red_drawable;
int fg_is_lossy;
BitmapData fg_bitmap_data;
int bg_is_lossy;
BitmapData bg_bitmap_data;
int dest_is_lossy = FALSE;
SpiceRect dest_lossy_area;
int rop = 0;
fg_is_lossy = is_brush_lossy(rcc, &drawable->u.text.fore_brush, item,
&fg_bitmap_data);
bg_is_lossy = is_brush_lossy(rcc, &drawable->u.text.back_brush, item,
&bg_bitmap_data);
// assuming that if the brush type is solid, the destination can
// be lossy, no matter what the rop is.
if (drawable->u.text.fore_brush.type != SPICE_BRUSH_TYPE_SOLID) {
rop = drawable->u.text.fore_mode;
}
if (drawable->u.text.back_brush.type != SPICE_BRUSH_TYPE_SOLID) {
rop |= drawable->u.text.back_mode;
}
if ((rop & SPICE_ROPD_OP_OR) || (rop & SPICE_ROPD_OP_AND) ||
(rop & SPICE_ROPD_OP_XOR)) {
dest_is_lossy = is_surface_area_lossy(dcc, drawable->surface_id,
&drawable->bbox, &dest_lossy_area);
}
if (!dest_is_lossy &&
(!fg_is_lossy || (fg_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE)) &&
(!bg_is_lossy || (bg_bitmap_data.type != BITMAP_DATA_TYPE_SURFACE))) {
red_marshall_qxl_draw_text(worker, rcc, base_marshaller, dpi);
} else {
int resend_surface_ids[3];
SpiceRect *resend_areas[3];
int num_resend = 0;
if (fg_is_lossy && (fg_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = fg_bitmap_data.id;
resend_areas[num_resend] = &fg_bitmap_data.lossy_rect;
num_resend++;
}
if (bg_is_lossy && (bg_bitmap_data.type == BITMAP_DATA_TYPE_SURFACE)) {
resend_surface_ids[num_resend] = bg_bitmap_data.id;
resend_areas[num_resend] = &bg_bitmap_data.lossy_rect;
num_resend++;
}
if (dest_is_lossy) {
resend_surface_ids[num_resend] = drawable->surface_id;
resend_areas[num_resend] = &dest_lossy_area;
num_resend++;
}
red_add_lossless_drawable_dependencies(worker, rcc, item,
resend_surface_ids, resend_areas, num_resend);
}
}
static void red_lossy_marshall_qxl_drawable(RedWorker *worker, RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
switch (item->red_drawable->type) {
case QXL_DRAW_FILL:
red_lossy_marshall_qxl_draw_fill(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_OPAQUE:
red_lossy_marshall_qxl_draw_opaque(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_COPY:
red_lossy_marshall_qxl_draw_copy(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_TRANSPARENT:
red_lossy_marshall_qxl_draw_transparent(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_ALPHA_BLEND:
red_lossy_marshall_qxl_draw_alpha_blend(worker, rcc, base_marshaller, dpi);
break;
case QXL_COPY_BITS:
red_lossy_marshall_qxl_copy_bits(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_BLEND:
red_lossy_marshall_qxl_draw_blend(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_BLACKNESS:
red_lossy_marshall_qxl_draw_blackness(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_WHITENESS:
red_lossy_marshall_qxl_draw_whiteness(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_INVERS:
red_lossy_marshall_qxl_draw_inverse(worker, rcc, base_marshaller, item);
break;
case QXL_DRAW_ROP3:
red_lossy_marshall_qxl_draw_rop3(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_STROKE:
red_lossy_marshall_qxl_draw_stroke(worker, rcc, base_marshaller, dpi);
break;
case QXL_DRAW_TEXT:
red_lossy_marshall_qxl_draw_text(worker, rcc, base_marshaller, dpi);
break;
default:
spice_error("invalid type");
}
}
static inline void red_marshall_qxl_drawable(RedWorker *worker, RedChannelClient *rcc,
SpiceMarshaller *m, DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable;
switch (drawable->type) {
case QXL_DRAW_FILL:
red_marshall_qxl_draw_fill(worker, rcc, m, dpi);
break;
case QXL_DRAW_OPAQUE:
red_marshall_qxl_draw_opaque(worker, rcc, m, dpi, FALSE);
break;
case QXL_DRAW_COPY:
red_marshall_qxl_draw_copy(worker, rcc, m, dpi, FALSE);
break;
case QXL_DRAW_TRANSPARENT:
red_marshall_qxl_draw_transparent(worker, rcc, m, dpi);
break;
case QXL_DRAW_ALPHA_BLEND:
red_marshall_qxl_draw_alpha_blend(worker, rcc, m, dpi, FALSE);
break;
case QXL_COPY_BITS:
red_marshall_qxl_copy_bits(worker, rcc, m, dpi);
break;
case QXL_DRAW_BLEND:
red_marshall_qxl_draw_blend(worker, rcc, m, dpi);
break;
case QXL_DRAW_BLACKNESS:
red_marshall_qxl_draw_blackness(worker, rcc, m, dpi);
break;
case QXL_DRAW_WHITENESS:
red_marshall_qxl_draw_whiteness(worker, rcc, m, dpi);
break;
case QXL_DRAW_INVERS:
red_marshall_qxl_draw_inverse(worker, rcc, m, item);
break;
case QXL_DRAW_ROP3:
red_marshall_qxl_draw_rop3(worker, rcc, m, dpi);
break;
case QXL_DRAW_STROKE:
red_marshall_qxl_draw_stroke(worker, rcc, m, dpi);
break;
case QXL_DRAW_TEXT:
red_marshall_qxl_draw_text(worker, rcc, m, dpi);
break;
default:
spice_error("invalid type");
}
}
static void display_channel_push_release(DisplayChannelClient *dcc, uint8_t type, uint64_t id,
uint64_t* sync_data)
{
FreeList *free_list = &dcc->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) {
SpiceResourceList *new_list;
new_list = spice_malloc(sizeof(*new_list) +
free_list->res_size * sizeof(SpiceResourceID) * 2);
new_list->count = free_list->res->count;
memcpy(new_list->resources, free_list->res->resources,
new_list->count * sizeof(SpiceResourceID));
free(free_list->res);
free_list->res = new_list;
free_list->res_size *= 2;
}
free_list->res->resources[free_list->res->count].type = type;
free_list->res->resources[free_list->res->count++].id = id;
}
static inline void display_marshal_sub_msg_inval_list(SpiceMarshaller *m,
FreeList *free_list)
{
/* type + size + submessage */
spice_marshaller_add_uint16(m, SPICE_MSG_DISPLAY_INVAL_LIST);
spice_marshaller_add_uint32(m, sizeof(*free_list->res) +
free_list->res->count * sizeof(free_list->res->resources[0]));
spice_marshall_msg_display_inval_list(m, free_list->res);
}
static inline void display_marshal_sub_msg_inval_list_wait(SpiceMarshaller *m,
FreeList *free_list)
{
/* type + size + submessage */
spice_marshaller_add_uint16(m, SPICE_MSG_WAIT_FOR_CHANNELS);
spice_marshaller_add_uint32(m, sizeof(free_list->wait.header) +
free_list->wait.header.wait_count * sizeof(free_list->wait.buf[0]));
spice_marshall_msg_wait_for_channels(m, &free_list->wait.header);
}
/* use legacy SpiceDataHeader (with sub_list) */
static inline void display_channel_send_free_list_legacy(RedChannelClient *rcc)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
FreeList *free_list = &dcc->send_data.free_list;
SpiceMarshaller *marshaller;
int sub_list_len = 1;
SpiceMarshaller *wait_m = NULL;
SpiceMarshaller *inval_m;
SpiceMarshaller *sub_list_m;
marshaller = red_channel_client_get_marshaller(rcc);
inval_m = spice_marshaller_get_submarshaller(marshaller);
display_marshal_sub_msg_inval_list(inval_m, free_list);
if (free_list->wait.header.wait_count) {
wait_m = spice_marshaller_get_submarshaller(marshaller);
display_marshal_sub_msg_inval_list_wait(wait_m, free_list);
sub_list_len++;
}
sub_list_m = spice_marshaller_get_submarshaller(marshaller);
spice_marshaller_add_uint16(sub_list_m, sub_list_len);
if (wait_m) {
spice_marshaller_add_uint32(sub_list_m, spice_marshaller_get_offset(wait_m));
}
spice_marshaller_add_uint32(sub_list_m, spice_marshaller_get_offset(inval_m));
red_channel_client_set_header_sub_list(rcc, spice_marshaller_get_offset(sub_list_m));
}
/* use mini header and SPICE_MSG_LIST */
static inline void display_channel_send_free_list(RedChannelClient *rcc)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
FreeList *free_list = &dcc->send_data.free_list;
int sub_list_len = 1;
SpiceMarshaller *urgent_marshaller;
SpiceMarshaller *wait_m = NULL;
SpiceMarshaller *inval_m;
uint32_t sub_arr_offset;
uint32_t wait_offset = 0;
uint32_t inval_offset = 0;
int i;
urgent_marshaller = red_channel_client_switch_to_urgent_sender(rcc);
for (i = 0; i < dcc->send_data.num_pixmap_cache_items; i++) {
int dummy;
/* When using the urgent marshaller, the serial number of the message that is
* going to be sent right after the SPICE_MSG_LIST, is increased by one.
* But all this message pixmaps cache references used its old serial.
* we use pixmap_cache_items to collect these pixmaps, and we update their serial
* by calling pixmap_cache_hit. */
pixmap_cache_hit(dcc->pixmap_cache, dcc->send_data.pixmap_cache_items[i],
&dummy, dcc);
}
if (free_list->wait.header.wait_count) {
red_channel_client_init_send_data(rcc, SPICE_MSG_LIST, NULL);
} else { /* only one message, no need for a list */
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_INVAL_LIST, NULL);
spice_marshall_msg_display_inval_list(urgent_marshaller, free_list->res);
return;
}
inval_m = spice_marshaller_get_submarshaller(urgent_marshaller);
display_marshal_sub_msg_inval_list(inval_m, free_list);
if (free_list->wait.header.wait_count) {
wait_m = spice_marshaller_get_submarshaller(urgent_marshaller);
display_marshal_sub_msg_inval_list_wait(wait_m, free_list);
sub_list_len++;
}
sub_arr_offset = sub_list_len * sizeof(uint32_t);
spice_marshaller_add_uint16(urgent_marshaller, sub_list_len);
inval_offset = spice_marshaller_get_offset(inval_m); // calc the offset before
// adding the sub list
// offsets array to the marshaller
/* adding the array of offsets */
if (wait_m) {
wait_offset = spice_marshaller_get_offset(wait_m);
spice_marshaller_add_uint32(urgent_marshaller, wait_offset + sub_arr_offset);
}
spice_marshaller_add_uint32(urgent_marshaller, inval_offset + sub_arr_offset);
}
static inline void display_begin_send_message(RedChannelClient *rcc)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
FreeList *free_list = &dcc->send_data.free_list;
if (free_list->res->count) {
int sync_count = 0;
int i;
for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
if (i != dcc->common.id && free_list->sync[i] != 0) {
free_list->wait.header.wait_list[sync_count].channel_type = SPICE_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];
}
}
free_list->wait.header.wait_count = sync_count;
if (rcc->is_mini_header) {
display_channel_send_free_list(rcc);
} else {
display_channel_send_free_list_legacy(rcc);
}
}
red_channel_client_begin_send_message(rcc);
}
static inline uint8_t *red_get_image_line(RedWorker *worker, SpiceChunks *chunks, size_t *offset,
int *chunk_nr, int stride)
{
uint8_t *ret;
SpiceChunk *chunk;
chunk = &chunks->chunk[*chunk_nr];
if (*offset == chunk->len) {
if (*chunk_nr == chunks->num_chunks - 1) {
return NULL; /* Last chunk */
}
*offset = 0;
(*chunk_nr)++;
chunk = &chunks->chunk[*chunk_nr];
}
if (chunk->len - *offset < stride) {
spice_printerr("bad chunk alignment");
return NULL;
}
ret = chunk->data + *offset;
*offset += stride;
return ret;
}
static int encode_frame (RedWorker *worker, const SpiceRect *src,
const SpiceBitmap *image, Stream *stream)
{
SpiceChunks *chunks;
uint32_t image_stride;
size_t offset;
int i, chunk;
chunks = image->data;
offset = 0;
chunk = 0;
image_stride = image->stride;
const int skip_lines = stream->top_down ? src->top : image->y - (src->bottom - 0);
for (i = 0; i < skip_lines; i++) {
red_get_image_line(worker, chunks, &offset, &chunk, image_stride);
}
const unsigned int stream_height = src->bottom - src->top;
const unsigned int stream_width = src->right - src->left;
for (i = 0; i < stream_height; i++) {
uint8_t *src_line =
(uint8_t *)red_get_image_line(worker, chunks, &offset, &chunk, image_stride);
if (!src_line) {
return FALSE;
}
src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(stream->mjpeg_encoder);
if (mjpeg_encoder_encode_scanline(stream->mjpeg_encoder, src_line, stream_width) == 0)
return FALSE;
}
return TRUE;
}
static inline int red_marshall_stream_data(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, Drawable *drawable)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
Stream *stream = drawable->stream;
SpiceImage *image;
RedWorker *worker = dcc->common.worker;
int n;
int width, height;
if (!stream) {
spice_assert(drawable->sized_stream);
stream = drawable->sized_stream;
}
spice_assert(drawable->red_drawable->type == QXL_DRAW_COPY);
worker = display_channel->common.worker;
image = drawable->red_drawable->u.copy.src_bitmap;
if (image->descriptor.type != SPICE_IMAGE_TYPE_BITMAP) {
return FALSE;
}
if (drawable->sized_stream) {
if (red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_SIZED_STREAM)) {
SpiceRect *src_rect = &drawable->red_drawable->u.copy.src_area;
width = src_rect->right - src_rect->left;
height = src_rect->bottom - src_rect->top;
} else {
return FALSE;
}
} else {
width = stream->width;
height = stream->height;
}
StreamAgent *agent = &dcc->stream_agents[get_stream_id(worker, stream)];
uint64_t time_now = red_now();
size_t outbuf_size;
if (time_now - agent->last_send_time < (1000 * 1000 * 1000) / agent->fps) {
agent->frames--;
return TRUE;
}
outbuf_size = dcc->send_data.stream_outbuf_size;
if (!mjpeg_encoder_start_frame(stream->mjpeg_encoder, image->u.bitmap.format,
width, height,
&dcc->send_data.stream_outbuf,
&outbuf_size)) {
return FALSE;
}
if (!encode_frame(worker, &drawable->red_drawable->u.copy.src_area,
&image->u.bitmap, stream)) {
return FALSE;
}
n = mjpeg_encoder_end_frame(stream->mjpeg_encoder);
dcc->send_data.stream_outbuf_size = outbuf_size;
if (!drawable->sized_stream) {
SpiceMsgDisplayStreamData stream_data;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_DATA, NULL);
stream_data.base.id = get_stream_id(worker, stream);
stream_data.base.multi_media_time = drawable->red_drawable->mm_time;
stream_data.data_size = n;
spice_marshall_msg_display_stream_data(base_marshaller, &stream_data);
} else {
SpiceMsgDisplayStreamDataSized stream_data;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_DATA_SIZED, NULL);
stream_data.base.id = get_stream_id(worker, stream);
stream_data.base.multi_media_time = drawable->red_drawable->mm_time;
stream_data.data_size = n;
stream_data.width = width;
stream_data.height = height;
stream_data.dest = drawable->red_drawable->bbox;
spice_debug("stream %d: sized frame: dest ==> ", stream_data.base.id);
rect_debug(&stream_data.dest);
spice_marshall_msg_display_stream_data_sized(base_marshaller, &stream_data);
}
spice_marshaller_add_ref(base_marshaller,
dcc->send_data.stream_outbuf, n);
agent->last_send_time = time_now;
return TRUE;
}
static inline void marshall_qxl_drawable(RedChannelClient *rcc,
SpiceMarshaller *m, DrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
spice_assert(display_channel && rcc);
/* allow sized frames to be streamed, even if they where replaced by another frame, since
* newer frames might not cover sized frames completely if they are bigger */
if ((item->stream || item->sized_stream) && red_marshall_stream_data(rcc, m, item)) {
return;
}
if (!display_channel->enable_jpeg)
red_marshall_qxl_drawable(display_channel->common.worker, rcc, m, dpi);
else
red_lossy_marshall_qxl_drawable(display_channel->common.worker, rcc, m, dpi);
}
static inline void red_marshall_verb(RedChannelClient *rcc, uint16_t verb)
{
spice_assert(rcc);
red_channel_client_init_send_data(rcc, verb, NULL);
}
static inline void red_marshall_inval(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, CacheItem *cach_item)
{
SpiceMsgDisplayInvalOne inval_one;
red_channel_client_init_send_data(rcc, cach_item->inval_type, NULL);
inval_one.id = *(uint64_t *)&cach_item->id;
spice_marshall_msg_cursor_inval_one(base_marshaller, &inval_one);
}
static void display_channel_marshall_migrate(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller)
{
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
SpiceMsgMigrate migrate;
red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE, NULL);
migrate.flags = SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER;
spice_marshall_msg_migrate(base_marshaller, &migrate);
display_channel->expect_migrate_mark = TRUE;
}
static void display_channel_marshall_migrate_data(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
DisplayChannelMigrateData display_data;
red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE_DATA, NULL);
spice_assert(dcc->pixmap_cache);
display_data.magic = DISPLAY_MIGRATE_DATA_MAGIC;
spice_assert(MAX_CACHE_CLIENTS == 4); //MIGRATE_DATA_VERSION dependent
display_data.version = DISPLAY_MIGRATE_DATA_VERSION;
display_data.message_serial = red_channel_client_get_message_serial(rcc);
display_data.pixmap_cache_freezer = pixmap_cache_freeze(dcc->pixmap_cache);
display_data.pixmap_cache_id = dcc->pixmap_cache->id;
display_data.pixmap_cache_size = dcc->pixmap_cache->size;
memcpy(display_data.pixmap_cache_clients, dcc->pixmap_cache->sync,
sizeof(display_data.pixmap_cache_clients));
spice_assert(dcc->glz_dict);
red_freeze_glz(dcc);
display_data.glz_dict_id = dcc->glz_dict->id;
glz_enc_dictionary_get_restore_data(dcc->glz_dict->dict,
&display_data.glz_dict_restore_data,
&dcc->glz_data.usr);
spice_marshaller_add_ref(base_marshaller,
(uint8_t *)&display_data, sizeof(display_data));
}
static void display_channel_marshall_pixmap_sync(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
SpiceMsgWaitForChannels wait;
PixmapCache *pixmap_cache;
red_channel_client_init_send_data(rcc, SPICE_MSG_WAIT_FOR_CHANNELS, NULL);
pixmap_cache = dcc->pixmap_cache;
pthread_mutex_lock(&pixmap_cache->lock);
wait.wait_count = 1;
wait.wait_list[0].channel_type = SPICE_CHANNEL_DISPLAY;
wait.wait_list[0].channel_id = pixmap_cache->generation_initiator.client;
wait.wait_list[0].message_serial = pixmap_cache->generation_initiator.message;
dcc->pixmap_cache_generation = pixmap_cache->generation;
dcc->pending_pixmaps_sync = FALSE;
pthread_mutex_unlock(&pixmap_cache->lock);
spice_marshall_msg_wait_for_channels(base_marshaller, &wait);
}
static void display_channel_marshall_reset_cache(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
SpiceMsgWaitForChannels wait;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS, NULL);
pixmap_cache_reset(dcc->pixmap_cache, dcc, &wait);
spice_marshall_msg_display_inval_all_pixmaps(base_marshaller,
&wait);
}
static void red_marshall_image(RedChannelClient *rcc, SpiceMarshaller *m, ImageItem *item)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
DisplayChannel *display_channel = DCC_TO_DC(dcc);
SpiceImage red_image;
RedWorker *worker;
SpiceBitmap bitmap;
SpiceChunks *chunks;
QRegion *surface_lossy_region;
int comp_succeeded;
int lossy_comp = FALSE;
int lz_comp = FALSE;
spice_image_compression_t comp_mode;
SpiceMsgDisplayDrawCopy copy;
SpiceMarshaller *src_bitmap_out, *mask_bitmap_out;
SpiceMarshaller *bitmap_palette_out, *lzplt_palette_out;
spice_assert(rcc && display_channel && item);
worker = display_channel->common.worker;
QXL_SET_IMAGE_ID(&red_image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique);
red_image.descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
red_image.descriptor.flags = item->image_flags;
red_image.descriptor.width = item->width;
red_image.descriptor.height = item->height;
bitmap.format = item->image_format;
bitmap.flags = 0;
if (item->top_down) {
bitmap.flags |= SPICE_BITMAP_FLAGS_TOP_DOWN;
}
bitmap.x = item->width;
bitmap.y = item->height;
bitmap.stride = item->stride;
bitmap.palette = 0;
bitmap.palette_id = 0;
chunks = spice_chunks_new_linear(item->data, bitmap.stride * bitmap.y);
bitmap.data = chunks;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COPY, &item->link);
copy.base.surface_id = item->surface_id;
copy.base.box.left = item->pos.x;
copy.base.box.top = item->pos.y;
copy.base.box.right = item->pos.x + bitmap.x;
copy.base.box.bottom = item->pos.y + bitmap.y;
copy.base.clip.type = SPICE_CLIP_TYPE_NONE;
copy.data.rop_descriptor = SPICE_ROPD_OP_PUT;
copy.data.src_area.left = 0;
copy.data.src_area.top = 0;
copy.data.src_area.right = bitmap.x;
copy.data.src_area.bottom = bitmap.y;
copy.data.scale_mode = 0;
copy.data.src_bitmap = 0;
copy.data.mask.flags = 0;
copy.data.mask.flags = 0;
copy.data.mask.pos.x = 0;
copy.data.mask.pos.y = 0;
copy.data.mask.bitmap = 0;
spice_marshall_msg_display_draw_copy(m, ©,
&src_bitmap_out, &mask_bitmap_out);
compress_send_data_t comp_send_data = {0};
comp_mode = display_channel->common.worker->image_compression;
if ((comp_mode == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
(comp_mode == SPICE_IMAGE_COMPRESS_AUTO_GLZ)) {
if (BITMAP_FMT_IS_RGB[item->image_format]) {
if (!_stride_is_extra(&bitmap)) {
BitmapGradualType grad_level;
grad_level = _get_bitmap_graduality_level(display_channel->common.worker,
&bitmap,
worker->mem_slots.internal_groupslot_id);
if (grad_level == BITMAP_GRADUAL_HIGH) {
// if we use lz for alpha, the stride can't be extra
lossy_comp = display_channel->enable_jpeg && item->can_lossy;
} else {
lz_comp = TRUE;
}
}
} else {
lz_comp = TRUE;
}
}
if (lossy_comp) {
comp_succeeded = red_jpeg_compress_image(dcc, &red_image,
&bitmap, &comp_send_data,
worker->mem_slots.internal_groupslot_id);
} else {
if (!lz_comp) {
comp_succeeded = red_quic_compress_image(dcc, &red_image, &bitmap,
&comp_send_data,
worker->mem_slots.internal_groupslot_id);
} else {
comp_succeeded = red_lz_compress_image(dcc, &red_image, &bitmap,
&comp_send_data,
worker->mem_slots.internal_groupslot_id);
}
}
surface_lossy_region = &dcc->surface_client_lossy_region[item->surface_id];
if (comp_succeeded) {
spice_marshall_Image(src_bitmap_out, &red_image,
&bitmap_palette_out, &lzplt_palette_out);
marshaller_add_compressed(src_bitmap_out,
comp_send_data.comp_buf, comp_send_data.comp_buf_size);
if (lzplt_palette_out && comp_send_data.lzplt_palette) {
spice_marshall_Palette(lzplt_palette_out, comp_send_data.lzplt_palette);
}
if (lossy_comp) {
region_add(surface_lossy_region, ©.base.box);
} else {
region_remove(surface_lossy_region, ©.base.box);
}
} else {
red_image.descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
red_image.u.bitmap = bitmap;
spice_marshall_Image(src_bitmap_out, &red_image,
&bitmap_palette_out, &lzplt_palette_out);
spice_marshaller_add_ref(src_bitmap_out, item->data,
bitmap.y * bitmap.stride);
region_remove(surface_lossy_region, ©.base.box);
}
spice_chunks_destroy(chunks);
}
static void red_display_marshall_upgrade(RedChannelClient *rcc, SpiceMarshaller *m,
UpgradeItem *item)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedDrawable *red_drawable;
SpiceMsgDisplayDrawCopy copy;
SpiceMarshaller *src_bitmap_out, *mask_bitmap_out;
spice_assert(rcc && rcc->channel && item && item->drawable);
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_DRAW_COPY, &item->base);
red_drawable = item->drawable->red_drawable;
spice_assert(red_drawable->type == QXL_DRAW_COPY);
spice_assert(red_drawable->u.copy.rop_descriptor == SPICE_ROPD_OP_PUT);
spice_assert(red_drawable->u.copy.mask.bitmap == 0);
copy.base.surface_id = 0;
copy.base.box = red_drawable->bbox;
copy.base.clip.type = SPICE_CLIP_TYPE_RECTS;
copy.base.clip.rects = item->rects;
copy.data = red_drawable->u.copy;
spice_marshall_msg_display_draw_copy(m, ©,
&src_bitmap_out, &mask_bitmap_out);
fill_bits(dcc, src_bitmap_out, copy.data.src_bitmap, item->drawable, FALSE);
}
static void red_display_marshall_stream_start(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, StreamAgent *agent)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
Stream *stream = agent->stream;
agent->last_send_time = 0;
spice_assert(stream);
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_CREATE, &agent->create_item);
SpiceMsgDisplayStreamCreate stream_create;
SpiceClipRects clip_rects;
stream_create.surface_id = 0;
stream_create.id = get_stream_id(dcc->common.worker, stream);
stream_create.flags = stream->top_down ? SPICE_STREAM_FLAGS_TOP_DOWN : 0;
stream_create.codec_type = SPICE_VIDEO_CODEC_TYPE_MJPEG;
stream_create.src_width = stream->width;
stream_create.src_height = stream->height;
stream_create.stream_width = stream_create.src_width;
stream_create.stream_height = stream_create.src_height;
stream_create.dest = stream->dest_area;
if (stream->current) {
RedDrawable *red_drawable = stream->current->red_drawable;
stream_create.clip = red_drawable->clip;
} else {
stream_create.clip.type = SPICE_CLIP_TYPE_RECTS;
clip_rects.num_rects = 0;
stream_create.clip.rects = &clip_rects;
}
stream_create.stamp = 0;
spice_marshall_msg_display_stream_create(base_marshaller, &stream_create);
}
static void red_display_marshall_stream_clip(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller,
StreamClipItem *item)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
StreamAgent *agent = item->stream_agent;
spice_assert(agent->stream);
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_CLIP, &item->base);
SpiceMsgDisplayStreamClip stream_clip;
stream_clip.id = get_stream_id(dcc->common.worker, agent->stream);
stream_clip.clip.type = item->clip_type;
stream_clip.clip.rects = item->rects;
spice_marshall_msg_display_stream_clip(base_marshaller, &stream_clip);
}
static void red_display_marshall_stream_end(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, StreamAgent* agent)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
SpiceMsgDisplayStreamDestroy destroy;
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_STREAM_DESTROY, NULL);
destroy.id = get_stream_id(dcc->common.worker, agent->stream);
spice_marshall_msg_display_stream_destroy(base_marshaller, &destroy);
}
static void red_cursor_marshall_inval(RedChannelClient *rcc,
SpiceMarshaller *m, CacheItem *cach_item)
{
spice_assert(rcc);
red_marshall_inval(rcc, m, cach_item);
}
static void red_marshall_cursor_init(RedChannelClient *rcc, SpiceMarshaller *base_marshaller,
PipeItem *pipe_item)
{
CursorChannel *cursor_channel;
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
RedWorker *worker;
SpiceMsgCursorInit msg;
AddBufInfo info;
spice_assert(rcc);
cursor_channel = SPICE_CONTAINEROF(rcc->channel, CursorChannel, common.base);
worker = cursor_channel->common.worker;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_INIT, NULL);
msg.visible = worker->cursor_visible;
msg.position = worker->cursor_position;
msg.trail_length = worker->cursor_trail_length;
msg.trail_frequency = worker->cursor_trail_frequency;
fill_cursor(ccc, &msg.cursor, worker->cursor, &info);
spice_marshall_msg_cursor_init(base_marshaller, &msg);
add_buf_from_info(base_marshaller, &info);
}
static void cursor_channel_marshall_migrate(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller)
{
SpiceMsgMigrate migrate;
red_channel_client_init_send_data(rcc, SPICE_MSG_MIGRATE, NULL);
migrate.flags = 0;
spice_marshall_msg_migrate(base_marshaller, &migrate);
}
static void red_marshall_cursor(RedChannelClient *rcc,
SpiceMarshaller *m, CursorPipeItem *cursor_pipe_item)
{
CursorChannel *cursor_channel = SPICE_CONTAINEROF(rcc->channel, CursorChannel, common.base);
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
CursorItem *cursor = cursor_pipe_item->cursor_item;
PipeItem *pipe_item = &cursor_pipe_item->base;
RedCursorCmd *cmd;
RedWorker *worker;
spice_assert(cursor_channel);
worker = cursor_channel->common.worker;
cmd = cursor->red_cursor;
switch (cmd->type) {
case QXL_CURSOR_MOVE:
{
SpiceMsgCursorMove cursor_move;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_MOVE, pipe_item);
cursor_move.position = cmd->u.position;
spice_marshall_msg_cursor_move(m, &cursor_move);
break;
}
case QXL_CURSOR_SET:
{
SpiceMsgCursorSet cursor_set;
AddBufInfo info;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_SET, pipe_item);
cursor_set.position = cmd->u.set.position;
cursor_set.visible = worker->cursor_visible;
fill_cursor(ccc, &cursor_set.cursor, cursor, &info);
spice_marshall_msg_cursor_set(m, &cursor_set);
add_buf_from_info(m, &info);
break;
}
case QXL_CURSOR_HIDE:
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_HIDE, pipe_item);
break;
case QXL_CURSOR_TRAIL:
{
SpiceMsgCursorTrail cursor_trail;
red_channel_client_init_send_data(rcc, SPICE_MSG_CURSOR_TRAIL, pipe_item);
cursor_trail.length = cmd->u.trail.length;
cursor_trail.frequency = cmd->u.trail.frequency;
spice_marshall_msg_cursor_trail(m, &cursor_trail);
}
break;
default:
spice_error("bad cursor command %d", cmd->type);
}
}
static void red_marshall_surface_create(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, SpiceMsgSurfaceCreate *surface_create)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
region_init(&dcc->surface_client_lossy_region[surface_create->surface_id]);
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SURFACE_CREATE, NULL);
spice_marshall_msg_display_surface_create(base_marshaller, surface_create);
}
static void red_marshall_surface_destroy(RedChannelClient *rcc,
SpiceMarshaller *base_marshaller, uint32_t surface_id)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
SpiceMsgSurfaceDestroy surface_destroy;
region_destroy(&dcc->surface_client_lossy_region[surface_id]);
red_channel_client_init_send_data(rcc, SPICE_MSG_DISPLAY_SURFACE_DESTROY, NULL);
surface_destroy.surface_id = surface_id;
spice_marshall_msg_display_surface_destroy(base_marshaller, &surface_destroy);
}
static void display_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item)
{
SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
red_display_reset_send_data(dcc);
switch (pipe_item->type) {
case PIPE_ITEM_TYPE_DRAW: {
DrawablePipeItem *dpi = SPICE_CONTAINEROF(pipe_item, DrawablePipeItem, dpi_pipe_item);
marshall_qxl_drawable(rcc, m, dpi);
break;
}
case PIPE_ITEM_TYPE_INVAL_ONE:
red_marshall_inval(rcc, m, (CacheItem *)pipe_item);
break;
case PIPE_ITEM_TYPE_STREAM_CREATE: {
StreamAgent *agent = SPICE_CONTAINEROF(pipe_item, StreamAgent, create_item);
red_display_marshall_stream_start(rcc, m, agent);
break;
}
case PIPE_ITEM_TYPE_STREAM_CLIP: {
StreamClipItem* clip_item = (StreamClipItem *)pipe_item;
red_display_marshall_stream_clip(rcc, m, clip_item);
break;
}
case PIPE_ITEM_TYPE_STREAM_DESTROY: {
StreamAgent *agent = SPICE_CONTAINEROF(pipe_item, StreamAgent, destroy_item);
red_display_marshall_stream_end(rcc, m, agent);
break;
}
case PIPE_ITEM_TYPE_UPGRADE:
red_display_marshall_upgrade(rcc, m, (UpgradeItem *)pipe_item);
break;
case PIPE_ITEM_TYPE_VERB:
red_marshall_verb(rcc, ((VerbItem*)pipe_item)->verb);
break;
case PIPE_ITEM_TYPE_MIGRATE:
spice_printerr("PIPE_ITEM_TYPE_MIGRATE");
display_channel_marshall_migrate(rcc, m);
break;
case PIPE_ITEM_TYPE_MIGRATE_DATA:
display_channel_marshall_migrate_data(rcc, m);
break;
case PIPE_ITEM_TYPE_IMAGE:
red_marshall_image(rcc, m, (ImageItem *)pipe_item);
break;
case PIPE_ITEM_TYPE_PIXMAP_SYNC:
display_channel_marshall_pixmap_sync(rcc, m);
break;
case PIPE_ITEM_TYPE_PIXMAP_RESET:
display_channel_marshall_reset_cache(rcc, m);
break;
case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE:
red_reset_palette_cache(dcc);
red_marshall_verb(rcc, SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES);
break;
case PIPE_ITEM_TYPE_CREATE_SURFACE: {
SurfaceCreateItem *surface_create = SPICE_CONTAINEROF(pipe_item, SurfaceCreateItem,
pipe_item);
red_marshall_surface_create(rcc, m, &surface_create->surface_create);
break;
}
case PIPE_ITEM_TYPE_DESTROY_SURFACE: {
SurfaceDestroyItem *surface_destroy = SPICE_CONTAINEROF(pipe_item, SurfaceDestroyItem,
pipe_item);
red_marshall_surface_destroy(rcc, m, surface_destroy->surface_destroy.surface_id);
break;
}
default:
spice_error("invalid pipe item type");
}
display_channel_client_release_item_before_push(dcc, pipe_item);
// a message is pending
if (red_channel_client_send_message_pending(rcc)) {
display_begin_send_message(rcc);
}
}
static void cursor_channel_send_item(RedChannelClient *rcc, PipeItem *pipe_item)
{
SpiceMarshaller *m = red_channel_client_get_marshaller(rcc);
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
switch (pipe_item->type) {
case PIPE_ITEM_TYPE_CURSOR:
red_marshall_cursor(rcc, m, SPICE_CONTAINEROF(pipe_item, CursorPipeItem, base));
break;
case PIPE_ITEM_TYPE_INVAL_ONE:
red_cursor_marshall_inval(rcc, m, (CacheItem *)pipe_item);
break;
case PIPE_ITEM_TYPE_VERB:
red_marshall_verb(rcc, ((VerbItem*)pipe_item)->verb);
break;
case PIPE_ITEM_TYPE_MIGRATE:
spice_printerr("PIPE_ITEM_TYPE_MIGRATE");
cursor_channel_marshall_migrate(rcc, m);
break;
case PIPE_ITEM_TYPE_CURSOR_INIT:
red_reset_cursor_cache(rcc);
red_marshall_cursor_init(rcc, m, pipe_item);
break;
case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE:
red_reset_cursor_cache(rcc);
red_marshall_verb(rcc, SPICE_MSG_CURSOR_INVAL_ALL);
break;
default:
spice_error("invalid pipe item type");
}
cursor_channel_client_release_item_before_push(ccc, pipe_item);
red_channel_client_begin_send_message(rcc);
}
static inline void red_push(RedWorker *worker)
{
if (worker->cursor_channel) {
red_channel_push(&worker->cursor_channel->common.base);
}
if (worker->display_channel) {
red_channel_push(&worker->display_channel->common.base);
}
}
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) {
spice_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 = SPICE_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_red_drawable(tree_data->worker, drawable->red_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)
{
int x;
ShowTreeData show_tree_data;
show_tree_data.worker = worker;
show_tree_data.level = 0;
show_tree_data.container = NULL;
for (x = 0; x < NUM_SURFACES; ++x) {
if (worker->surfaces[x].context.canvas) {
current_tree_for_each(&worker->surfaces[x].current, __show_tree_call,
&show_tree_data);
}
}
}
static void display_channel_client_on_disconnect(RedChannelClient *rcc)
{
DisplayChannel *display_channel;
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
CommonChannel *common;
RedWorker *worker;
if (!rcc) {
return;
}
spice_printerr("");
common = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base);
worker = common->worker;
display_channel = (DisplayChannel *)rcc->channel;
spice_assert(display_channel == worker->display_channel);
#ifdef COMPRESS_STAT
print_compress_stats(display_channel);
#endif
red_release_pixmap_cache(dcc);
red_release_glz(dcc);
red_reset_palette_cache(dcc);
free(dcc->send_data.stream_outbuf);
red_display_reset_compress_buf(dcc);
free(dcc->send_data.free_list.res);
red_display_destroy_streams(dcc);
// this was the last channel client
if (!red_channel_is_connected(rcc->channel)) {
red_display_destroy_compress_bufs(display_channel);
}
spice_debug("#draw=%d, #red_draw=%d, #glz_draw=%d",
worker->drawable_count, worker->red_drawable_count,
worker->glz_drawable_count);
}
void red_disconnect_all_display_TODO_remove_me(RedChannel *channel)
{
// TODO: we need to record the client that actually causes the timeout. So
// we need to check the locations of the various pipe heads when counting,
// and disconnect only those/that.
if (!channel) {
return;
}
red_channel_apply_clients(channel, red_channel_client_disconnect);
}
static void red_migrate_display(RedWorker *worker, RedChannelClient *rcc)
{
// TODO: replace all worker->display_channel tests with
// is_connected
if (red_channel_client_is_connected(rcc)) {
red_pipe_add_verb(rcc, PIPE_ITEM_TYPE_MIGRATE);
// red_pipes_add_verb(&worker->display_channel->common.base,
// SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL);
// red_channel_pipes_add_type(&worker->display_channel->common.base,
// PIPE_ITEM_TYPE_MIGRATE);
}
}
#ifdef USE_OPENGL
static SpiceCanvas *create_ogl_context_common(RedWorker *worker, OGLCtx *ctx, uint32_t width,
uint32_t height, int32_t stride, uint8_t depth)
{
SpiceCanvas *canvas;
oglctx_make_current(ctx);
if (!(canvas = gl_canvas_create(width, height, depth, &worker->image_cache.base,
&worker->image_surfaces, NULL, NULL, NULL))) {
return NULL;
}
spice_canvas_set_usr_data(canvas, ctx, (spice_destroy_fn_t)oglctx_destroy);
canvas->ops->clear(canvas);
return canvas;
}
static SpiceCanvas *create_ogl_pbuf_context(RedWorker *worker, uint32_t width, uint32_t height,
int32_t stride, uint8_t depth)
{
OGLCtx *ctx;
SpiceCanvas *canvas;
if (!(ctx = pbuf_create(width, height))) {
return NULL;
}
if (!(canvas = create_ogl_context_common(worker, ctx, width, height, stride, depth))) {
oglctx_destroy(ctx);
return NULL;
}
return canvas;
}
static SpiceCanvas *create_ogl_pixmap_context(RedWorker *worker, uint32_t width, uint32_t height,
int32_t stride, uint8_t depth) {
OGLCtx *ctx;
SpiceCanvas *canvas;
if (!(ctx = pixmap_create(width, height))) {
return NULL;
}
if (!(canvas = create_ogl_context_common(worker, ctx, width, height, stride, depth))) {
oglctx_destroy(ctx);
return NULL;
}
return canvas;
}
#endif
static inline void *create_canvas_for_surface(RedWorker *worker, RedSurface *surface,
uint32_t renderer, uint32_t width, uint32_t height,
int32_t stride, uint32_t format, void *line_0)
{
SpiceCanvas *canvas;
switch (renderer) {
case RED_RENDERER_SW:
canvas = canvas_create_for_data(width, height, format,
line_0, stride,
&worker->image_cache.base,
&worker->image_surfaces, NULL, NULL, NULL);
surface->context.top_down = TRUE;
surface->context.canvas_draws_on_surface = TRUE;
return canvas;
#ifdef USE_OPENGL
case RED_RENDERER_OGL_PBUF:
canvas = create_ogl_pbuf_context(worker, width, height, stride,
SPICE_SURFACE_FMT_DEPTH(format));
surface->context.top_down = FALSE;
return canvas;
case RED_RENDERER_OGL_PIXMAP:
canvas = create_ogl_pixmap_context(worker, width, height, stride,
SPICE_SURFACE_FMT_DEPTH(format));
surface->context.top_down = FALSE;
return canvas;
#endif
default:
spice_error("invalid renderer type");
};
return NULL;
}
static SurfaceCreateItem *get_surface_create_item(
RedChannel* channel,
uint32_t surface_id, uint32_t width,
uint32_t height, uint32_t format, uint32_t flags)
{
SurfaceCreateItem *create;
create = (SurfaceCreateItem *)malloc(sizeof(SurfaceCreateItem));
spice_warn_if(!create);
create->surface_create.surface_id = surface_id;
create->surface_create.width = width;
create->surface_create.height = height;
create->surface_create.flags = flags;
create->surface_create.format = format;
red_channel_pipe_item_init(channel,
&create->pipe_item, PIPE_ITEM_TYPE_CREATE_SURFACE);
return create;
}
static inline void red_create_surface_item(DisplayChannelClient *dcc, int surface_id)
{
RedSurface *surface;
SurfaceCreateItem *create;
RedWorker *worker = dcc ? dcc->common.worker : NULL;
uint32_t flags = is_primary_surface(worker, surface_id) ? SPICE_SURFACE_FLAGS_PRIMARY : 0;
/* don't send redundant create surface commands to client */
if (!dcc || dcc->surface_client_created[surface_id]) {
return;
}
surface = &worker->surfaces[surface_id];
create = get_surface_create_item(dcc->common.base.channel,
surface_id, surface->context.width, surface->context.height,
surface->context.format, flags);
dcc->surface_client_created[surface_id] = TRUE;
red_channel_client_pipe_add(&dcc->common.base, &create->pipe_item);
}
static void red_worker_create_surface_item(RedWorker *worker, int surface_id)
{
DisplayChannelClient *dcc;
RingItem *item;
WORKER_FOREACH_DCC(worker, item, dcc) {
red_create_surface_item(dcc, surface_id);
}
}
static void red_worker_push_surface_image(RedWorker *worker, int surface_id)
{
DisplayChannelClient *dcc;
RingItem *item;
WORKER_FOREACH_DCC(worker, item, dcc) {
red_push_surface_image(dcc, surface_id);
}
}
static inline void red_create_surface(RedWorker *worker, uint32_t surface_id, uint32_t width,
uint32_t height, int32_t stride, uint32_t format,
void *line_0, int data_is_valid, int send_client)
{
RedSurface *surface = &worker->surfaces[surface_id];
uint32_t i;
if (stride >= 0) {
spice_critical("Untested path stride >= 0");
}
spice_warn_if(surface->context.canvas);
surface->context.canvas_draws_on_surface = FALSE;
surface->context.width = width;
surface->context.height = height;
surface->context.format = format;
surface->context.stride = stride;
surface->context.line_0 = line_0;
if (!data_is_valid) {
memset((char *)line_0 + (int32_t)(stride * (height - 1)), 0, height*abs(stride));
}
surface->create.info = NULL;
surface->destroy.info = NULL;
ring_init(&surface->current);
ring_init(&surface->current_list);
ring_init(&surface->depend_on_me);
region_init(&surface->draw_dirty_region);
surface->refs = 1;
if (worker->renderer != RED_RENDERER_INVALID) {
surface->context.canvas = create_canvas_for_surface(worker, surface, worker->renderer,
width, height, stride,
surface->context.format, line_0);
if (!surface->context.canvas) {
spice_critical("drawing canvas creating failed - can`t create same type canvas");
}
if (send_client) {
red_worker_create_surface_item(worker, surface_id);
if (data_is_valid) {
red_worker_push_surface_image(worker, surface_id);
}
}
return;
}
for (i = 0; i < worker->num_renderers; i++) {
surface->context.canvas = create_canvas_for_surface(worker, surface, worker->renderers[i],
width, height, stride,
surface->context.format, line_0);
if (surface->context.canvas) { //no need canvas check
worker->renderer = worker->renderers[i];
if (send_client) {
red_worker_create_surface_item(worker, surface_id);
if (data_is_valid) {
red_worker_push_surface_image(worker, surface_id);
}
}
return;
}
}
spice_critical("unable to create drawing canvas");
}
static inline void flush_display_commands(RedWorker *worker)
{
RedChannel *display_red_channel = &worker->display_channel->common.base;
for (;;) {
uint64_t end_time;
int ring_is_empty;
red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty);
if (ring_is_empty) {
break;
}
while (red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty)) {
red_channel_push(&worker->display_channel->common.base);
}
if (ring_is_empty) {
break;
}
end_time = red_now() + DISPLAY_CLIENT_TIMEOUT * 10;
int sleep_count = 0;
for (;;) {
red_channel_push(&worker->display_channel->common.base);
if (!display_is_connected(worker) ||
red_channel_max_pipe_size(display_red_channel) <= MAX_PIPE_SIZE) {
break;
}
RedChannel *channel = (RedChannel *)worker->display_channel;
red_channel_receive(channel);
red_channel_send(channel);
// TODO: MC: the whole timeout will break since it takes lowest timeout, should
// do it client by client.
if (red_now() >= end_time) {
spice_printerr("update timeout");
red_disconnect_all_display_TODO_remove_me(channel);
} else {
sleep_count++;
usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
}
}
}
}
static inline void flush_cursor_commands(RedWorker *worker)
{
RedChannel *cursor_red_channel = &worker->cursor_channel->common.base;
for (;;) {
uint64_t end_time;
int ring_is_empty = FALSE;
red_process_cursor(worker, MAX_PIPE_SIZE, &ring_is_empty);
if (ring_is_empty) {
break;
}
while (red_process_cursor(worker, MAX_PIPE_SIZE, &ring_is_empty)) {
red_channel_push(&worker->cursor_channel->common.base);
}
if (ring_is_empty) {
break;
}
end_time = red_now() + DISPLAY_CLIENT_TIMEOUT * 10;
int sleep_count = 0;
for (;;) {
red_channel_push(&worker->cursor_channel->common.base);
if (!cursor_is_connected(worker)
|| red_channel_min_pipe_size(cursor_red_channel) <= MAX_PIPE_SIZE) {
break;
}
RedChannel *channel = (RedChannel *)worker->cursor_channel;
red_channel_receive(channel);
red_channel_send(channel);
if (red_now() >= end_time) {
spice_printerr("flush cursor timeout");
red_disconnect_cursor(channel);
} else {
sleep_count++;
usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
}
}
}
}
// TODO: on timeout, don't disconnect all channeld immeduiatly - try to disconnect the slowest ones
// first and maybe turn timeouts to several timeouts in order to disconnect channels gradually.
// Should use disconnect or shutdown?
static inline void flush_all_qxl_commands(RedWorker *worker)
{
flush_display_commands(worker);
flush_cursor_commands(worker);
}
static void push_new_primary_surface(DisplayChannelClient *dcc)
{
RedWorker *worker = DCC_TO_WORKER(dcc);
RedChannelClient *rcc = &dcc->common.base;
red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE);
if (!worker->display_channel->common.base.migrate) {
red_create_surface_item(dcc, 0);
}
red_channel_client_push(rcc);
}
/* TODO: this function is evil^Wsynchronous, fix */
static int display_channel_client_wait_for_init(DisplayChannelClient *dcc)
{
dcc->expect_init = TRUE;
uint64_t end_time = red_now() + DISPLAY_CLIENT_TIMEOUT;
for (;;) {
red_channel_client_receive(&dcc->common.base);
if (!red_channel_client_is_connected(&dcc->common.base)) {
break;
}
if (dcc->pixmap_cache && dcc->glz_dict) {
dcc->pixmap_cache_generation = dcc->pixmap_cache->generation;
/* TODO: move common.id? if it's used for a per client structure.. */
spice_printerr("creating encoder with id == %d", dcc->common.id);
dcc->glz = glz_encoder_create(dcc->common.id, dcc->glz_dict->dict, &dcc->glz_data.usr);
if (!dcc->glz) {
spice_critical("create global lz failed");
}
return TRUE;
}
if (red_now() > end_time) {
spice_printerr("timeout");
red_channel_client_disconnect(&dcc->common.base);
break;
}
usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
}
return FALSE;
}
static void on_new_display_channel_client(DisplayChannelClient *dcc)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
RedChannelClient *rcc = &dcc->common.base;
red_channel_push_set_ack(&display_channel->common.base);
if (display_channel->common.base.migrate) {
display_channel->expect_migrate_data = TRUE;
return;
}
if (!display_channel_client_wait_for_init(dcc)) {
return;
}
red_channel_client_ack_zero_messages_window(&dcc->common.base);
red_channel_client_push_set_ack(&dcc->common.base);
if (worker->surfaces[0].context.canvas) {
red_current_flush(worker, 0);
push_new_primary_surface(dcc);
red_push_surface_image(dcc, 0);
red_pipe_add_verb(rcc, SPICE_MSG_DISPLAY_MARK);
red_disply_start_streams(dcc);
}
}
static GlzSharedDictionary *_red_find_glz_dictionary(RedClient *client, uint8_t dict_id)
{
RingItem *now;
GlzSharedDictionary *ret = NULL;
now = &glz_dictionary_list;
while ((now = ring_next(&glz_dictionary_list, now))) {
GlzSharedDictionary *dict = (GlzSharedDictionary *)now;
if ((dict->client == client) && (dict->id == dict_id)) {
ret = dict;
break;
}
}
return ret;
}
static GlzSharedDictionary *_red_create_glz_dictionary(RedClient *client, uint8_t id,
GlzEncDictContext *opaque_dict)
{
GlzSharedDictionary *shared_dict = spice_new0(GlzSharedDictionary, 1);
shared_dict->dict = opaque_dict;
shared_dict->id = id;
shared_dict->refs = 1;
shared_dict->migrate_freeze = FALSE;
shared_dict->client = client;
ring_item_init(&shared_dict->base);
pthread_rwlock_init(&shared_dict->encode_lock, NULL);
return shared_dict;
}
static GlzSharedDictionary *red_create_glz_dictionary(DisplayChannelClient *dcc,
uint8_t id, int window_size)
{
GlzEncDictContext *glz_dict = glz_enc_dictionary_create(window_size,
MAX_LZ_ENCODERS,
&dcc->glz_data.usr);
#ifdef COMPRESS_DEBUG
spice_printerr("Lz Window %d Size=%d", id, window_size);
#endif
if (!glz_dict) {
spice_critical("failed creating lz dictionary");
return NULL;
}
return _red_create_glz_dictionary(dcc->common.base.client, id, glz_dict);
}
static GlzSharedDictionary *red_create_restored_glz_dictionary(DisplayChannelClient *dcc,
uint8_t id,
GlzEncDictRestoreData *restore_data)
{
GlzEncDictContext *glz_dict = glz_enc_dictionary_restore(restore_data,
&dcc->glz_data.usr);
if (!glz_dict) {
spice_critical("failed creating lz dictionary");
return NULL;
}
return _red_create_glz_dictionary(dcc->common.base.client, id, glz_dict);
}
static GlzSharedDictionary *red_get_glz_dictionary(DisplayChannelClient *dcc,
uint8_t id, int window_size)
{
GlzSharedDictionary *shared_dict = NULL;
pthread_mutex_lock(&glz_dictionary_list_lock);
shared_dict = _red_find_glz_dictionary(dcc->common.base.client, id);
if (!shared_dict) {
shared_dict = red_create_glz_dictionary(dcc, 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(DisplayChannelClient *dcc,
uint8_t id,
GlzEncDictRestoreData *restore_data)
{
GlzSharedDictionary *shared_dict = NULL;
pthread_mutex_lock(&glz_dictionary_list_lock);
shared_dict = _red_find_glz_dictionary(dcc->common.base.client, id);
if (!shared_dict) {
shared_dict = red_create_restored_glz_dictionary(dcc, 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(DisplayChannelClient *dcc)
{
pthread_rwlock_wrlock(&dcc->glz_dict->encode_lock);
if (!dcc->glz_dict->migrate_freeze) {
dcc->glz_dict->migrate_freeze = TRUE;
}
pthread_rwlock_unlock(&dcc->glz_dict->encode_lock);
}
/* destroy encoder, and dictionary if no one uses it*/
static void red_release_glz(DisplayChannelClient *dcc)
{
GlzSharedDictionary *shared_dict;
red_display_client_clear_glz_drawables(dcc);
glz_encoder_destroy(dcc->glz);
dcc->glz = NULL;
if (!(shared_dict = dcc->glz_dict)) {
return;
}
dcc->glz_dict = NULL;
pthread_mutex_lock(&glz_dictionary_list_lock);
if (--shared_dict->refs) {
pthread_mutex_unlock(&glz_dictionary_list_lock);
return;
}
ring_remove(&shared_dict->base);
pthread_mutex_unlock(&glz_dictionary_list_lock);
glz_enc_dictionary_destroy(shared_dict->dict, &dcc->glz_data.usr);
free(shared_dict);
}
static PixmapCache *red_create_pixmap_cache(RedClient *client, uint8_t id, int64_t size)
{
PixmapCache *cache = spice_new0(PixmapCache, 1);
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;
cache->client = client;
return cache;
}
static PixmapCache *red_get_pixmap_cache(RedClient *client, uint8_t id, int64_t size)
{
PixmapCache *ret = NULL;
RingItem *now;
pthread_mutex_lock(&cache_lock);
now = &pixmap_cache_list;
while ((now = ring_next(&pixmap_cache_list, now))) {
PixmapCache *cache = (PixmapCache *)now;
if ((cache->client == client) && (cache->id == id)) {
ret = cache;
ret->refs++;
break;
}
}
if (!ret) {
ret = red_create_pixmap_cache(client, id, size);
ring_add(&pixmap_cache_list, &ret->base);
}
pthread_mutex_unlock(&cache_lock);
return ret;
}
static void red_release_pixmap_cache(DisplayChannelClient *dcc)
{
PixmapCache *cache;
if (!(cache = dcc->pixmap_cache)) {
return;
}
dcc->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(DisplayChannelClient *dcc, SpiceMsgcDisplayInit *init_info)
{
spice_assert(!dcc->pixmap_cache);
return !!(dcc->pixmap_cache = red_get_pixmap_cache(dcc->common.base.client,
init_info->pixmap_cache_id,
init_info->pixmap_cache_size));
}
static int display_channel_init_glz_dictionary(DisplayChannelClient *dcc,
SpiceMsgcDisplayInit *init_info)
{
spice_assert(!dcc->glz_dict);
ring_init(&dcc->glz_drawables);
ring_init(&dcc->glz_drawables_inst_to_free);
pthread_mutex_init(&dcc->glz_drawables_inst_to_free_lock, NULL);
return !!(dcc->glz_dict = red_get_glz_dictionary(dcc,
init_info->glz_dictionary_id,
init_info->glz_dictionary_window_size));
}
static int display_channel_init(DisplayChannelClient *dcc, SpiceMsgcDisplayInit *init_info)
{
return (display_channel_init_cache(dcc, init_info) &&
display_channel_init_glz_dictionary(dcc, init_info));
}
static int display_channel_handle_migrate_glz_dictionary(DisplayChannelClient *dcc,
DisplayChannelMigrateData *migrate_info)
{
spice_assert(!dcc->glz_dict);
ring_init(&dcc->glz_drawables);
ring_init(&dcc->glz_drawables_inst_to_free);
pthread_mutex_init(&dcc->glz_drawables_inst_to_free_lock, NULL);
return !!(dcc->glz_dict = red_restore_glz_dictionary(dcc,
migrate_info->glz_dict_id,
&migrate_info->glz_dict_restore_data));
}
static int display_channel_handle_migrate_mark(RedChannelClient *rcc)
{
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
RedChannel *channel = &display_channel->common.base;
if (!display_channel->expect_migrate_mark) {
spice_printerr("unexpected");
return FALSE;
}
display_channel->expect_migrate_mark = FALSE;
red_channel_pipes_add_type(channel, PIPE_ITEM_TYPE_MIGRATE_DATA);
return TRUE;
}
static uint64_t display_channel_handle_migrate_data_get_serial(
RedChannelClient *rcc, uint32_t size, void *message)
{
DisplayChannelMigrateData *migrate_data = message;
if (size < sizeof(*migrate_data)) {
spice_printerr("bad message size");
return 0;
}
if (migrate_data->magic != DISPLAY_MIGRATE_DATA_MAGIC ||
migrate_data->version != DISPLAY_MIGRATE_DATA_VERSION) {
spice_printerr("invalid content");
return 0;
}
return migrate_data->message_serial;
}
static uint64_t display_channel_handle_migrate_data(RedChannelClient *rcc, uint32_t size,
void *message)
{
DisplayChannelMigrateData *migrate_data;
DisplayChannel *display_channel = SPICE_CONTAINEROF(rcc->channel, DisplayChannel, common.base);
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
RedChannel *channel = &display_channel->common.base;
int i;
if (size < sizeof(*migrate_data)) {
spice_printerr("bad message size");
return FALSE;
}
migrate_data = (DisplayChannelMigrateData *)message;
if (migrate_data->magic != DISPLAY_MIGRATE_DATA_MAGIC ||
migrate_data->version != DISPLAY_MIGRATE_DATA_VERSION) {
spice_printerr("invalid content");
return FALSE;
}
if (!display_channel->expect_migrate_data) {
spice_printerr("unexpected");
return FALSE;
}
display_channel->expect_migrate_data = FALSE;
dcc->pixmap_cache = red_get_pixmap_cache(dcc->common.base.client,
migrate_data->pixmap_cache_id, -1);
if (!dcc->pixmap_cache) {
return FALSE;
}
pthread_mutex_lock(&dcc->pixmap_cache->lock);
for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
dcc->pixmap_cache->sync[i] = MAX(dcc->pixmap_cache->sync[i],
migrate_data->pixmap_cache_clients[i]);
}
pthread_mutex_unlock(&dcc->pixmap_cache->lock);
if (migrate_data->pixmap_cache_freezer) {
dcc->pixmap_cache->size = migrate_data->pixmap_cache_size;
// TODO - should this be red_channel_client_pipe_add_type?
red_channel_pipes_add_type(channel,
PIPE_ITEM_TYPE_PIXMAP_RESET);
}
if (display_channel_handle_migrate_glz_dictionary(dcc, migrate_data)) {
dcc->glz = glz_encoder_create(dcc->common.id,
dcc->glz_dict->dict, &dcc->glz_data.usr);
if (!dcc->glz) {
spice_critical("create global lz failed");
}
} else {
spice_critical("restoring global lz dictionary failed");
}
red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE);
red_channel_client_ack_zero_messages_window(rcc);
return TRUE;
}
static int display_channel_handle_message(RedChannelClient *rcc, uint32_t size, uint16_t type,
void *message)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
switch (type) {
case SPICE_MSGC_DISPLAY_INIT:
if (!dcc->expect_init) {
spice_printerr("unexpected SPICE_MSGC_DISPLAY_INIT");
return FALSE;
}
dcc->expect_init = FALSE;
return display_channel_init(dcc, (SpiceMsgcDisplayInit *)message);
default:
return red_channel_client_handle_message(rcc, size, type, message);
}
}
static int common_channel_config_socket(RedChannelClient *rcc)
{
RedClient *client = red_channel_client_get_client(rcc);
MainChannelClient *mcc = red_client_get_main(client);
RedsStream *stream = red_channel_client_get_stream(rcc);
int flags;
int delay_val;
if ((flags = fcntl(stream->socket, F_GETFL)) == -1) {
spice_printerr("accept failed, %s", strerror(errno));
return FALSE;
}
if (fcntl(stream->socket, F_SETFL, flags | O_NONBLOCK) == -1) {
spice_printerr("accept failed, %s", strerror(errno));
return FALSE;
}
// TODO - this should be dynamic, not one time at channel creation
delay_val = main_channel_client_is_low_bandwidth(mcc) ? 0 : 1;
if (setsockopt(stream->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val,
sizeof(delay_val)) == -1) {
if (errno != ENOTSUP) {
spice_printerr("setsockopt failed, %s", strerror(errno));
}
}
return TRUE;
}
static void worker_watch_update_mask(SpiceWatch *watch, int event_mask)
{
struct RedWorker *worker;
int i;
if (!watch) {
return;
}
worker = watch->worker;
i = watch - worker->watches;
worker->poll_fds[i].events = 0;
if (event_mask & SPICE_WATCH_EVENT_READ) {
worker->poll_fds[i].events |= POLLIN;
}
if (event_mask & SPICE_WATCH_EVENT_WRITE) {
worker->poll_fds[i].events |= POLLOUT;
}
}
static SpiceWatch *worker_watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
{
/* Since we are a channel core implementation, we always get called from
red_channel_client_create(), so opaque always is our rcc */
RedChannelClient *rcc = opaque;
struct RedWorker *worker;
int i;
/* Since we are called from red_channel_client_create()
CommonChannelClient->worker has not been set yet! */
worker = SPICE_CONTAINEROF(rcc->channel, CommonChannel, base)->worker;
/* Search for a free slot in our poll_fds & watches arrays */
for (i = 0; i < MAX_EVENT_SOURCES; i++) {
if (worker->poll_fds[i].fd == -1) {
break;
}
}
if (i == MAX_EVENT_SOURCES) {
spice_printerr("ERROR could not add a watch for channel type %u id %u",
rcc->channel->type, rcc->channel->id);
return NULL;
}
worker->poll_fds[i].fd = fd;
worker->watches[i].worker = worker;
worker->watches[i].watch_func = func;
worker->watches[i].watch_func_opaque = opaque;
worker_watch_update_mask(&worker->watches[i], event_mask);
return &worker->watches[i];
}
static void worker_watch_remove(SpiceWatch *watch)
{
if (!watch) {
return;
}
/* Note we don't touch the poll_fd here, to avoid the
poll_fds/watches table entry getting re-used in the same
red_worker_main loop over the fds as it is removed.
This is done because re-using it while events were pending on
the fd previously occupying the slot would lead to incorrectly
calling the watch_func for the new fd. */
memset(watch, 0, sizeof(SpiceWatch));
}
SpiceCoreInterface worker_core = {
.watch_update_mask = worker_watch_update_mask,
.watch_add = worker_watch_add,
.watch_remove = worker_watch_remove,
};
static CommonChannelClient *common_channel_client_create(int size,
CommonChannel *common,
RedClient *client,
RedsStream *stream,
uint32_t *common_caps,
int num_common_caps,
uint32_t *caps,
int num_caps)
{
MainChannelClient *mcc = red_client_get_main(client);
RedChannelClient *rcc =
red_channel_client_create(size, &common->base, client, stream,
num_common_caps, common_caps, num_caps, caps);
CommonChannelClient *common_cc = (CommonChannelClient*)rcc;
common_cc->worker = common->worker;
common_cc->id = common->worker->id;
// TODO: move wide/narrow ack setting to red_channel.
red_channel_client_ack_set_client_window(rcc,
main_channel_client_is_low_bandwidth(mcc) ?
WIDE_CLIENT_ACK_WINDOW : NARROW_CLIENT_ACK_WINDOW);
return common_cc;
}
DisplayChannelClient *display_channel_client_create(CommonChannel *common,
RedClient *client, RedsStream *stream,
uint32_t *common_caps, int num_common_caps,
uint32_t *caps, int num_caps)
{
DisplayChannelClient *dcc =
(DisplayChannelClient*)common_channel_client_create(
sizeof(DisplayChannelClient), common, client, stream,
common_caps, num_common_caps,
caps, num_caps);
if (!dcc) {
return NULL;
}
ring_init(&dcc->palette_cache_lru);
dcc->palette_cache_available = CLIENT_PALETTE_CACHE_SIZE;
return dcc;
}
CursorChannelClient *cursor_channel_create_rcc(CommonChannel *common,
RedClient *client, RedsStream *stream,
uint32_t *common_caps, int num_common_caps,
uint32_t *caps, int num_caps)
{
CursorChannelClient *ccc =
(CursorChannelClient*)common_channel_client_create(
sizeof(CursorChannelClient), common, client, stream,
common_caps,
num_common_caps,
caps,
num_caps);
if (!ccc) {
return NULL;
}
ring_init(&ccc->cursor_cache_lru);
ccc->cursor_cache_available = CLIENT_CURSOR_CACHE_SIZE;
return ccc;
}
static RedChannel *__new_channel(RedWorker *worker, int size, uint32_t channel_type, int migrate,
channel_disconnect_proc on_disconnect,
channel_send_pipe_item_proc send_item,
channel_hold_pipe_item_proc hold_item,
channel_release_pipe_item_proc release_item,
channel_handle_parsed_proc handle_parsed,
channel_handle_migrate_flush_mark_proc handle_migrate_flush_mark,
channel_handle_migrate_data_proc handle_migrate_data,
channel_handle_migrate_data_get_serial_proc migrate_get_serial)
{
RedChannel *channel = NULL;
CommonChannel *common;
ChannelCbs channel_cbs = { NULL, };
channel_cbs.config_socket = common_channel_config_socket;
channel_cbs.on_disconnect = on_disconnect;
channel_cbs.send_item = send_item;
channel_cbs.hold_item = hold_item;
channel_cbs.release_item = release_item;
channel_cbs.alloc_recv_buf = common_alloc_recv_buf;
channel_cbs.release_recv_buf = common_release_recv_buf;
channel_cbs.handle_migrate_flush_mark = handle_migrate_flush_mark;
channel_cbs.handle_migrate_data = handle_migrate_data;
channel_cbs.handle_migrate_data_get_serial = migrate_get_serial;
channel = red_channel_create_parser(size, &worker_core,
channel_type, worker->id,
migrate,
TRUE /* handle_acks */,
spice_get_client_channel_parser(channel_type, NULL),
handle_parsed,
&channel_cbs);
common = (CommonChannel *)channel;
if (!channel) {
goto error;
}
common->worker = worker;
return channel;
error:
free(channel);
return NULL;
}
static void display_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
{
spice_assert(item);
switch (item->type) {
case PIPE_ITEM_TYPE_DRAW:
ref_drawable_pipe_item(SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item));
break;
case PIPE_ITEM_TYPE_STREAM_CREATE: {
StreamAgent *stream_agent = SPICE_CONTAINEROF(item, StreamAgent, create_item);
if (stream_agent->stream->current) {
stream_agent->stream->current->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:
spice_critical("invalid item type");
}
}
static void display_channel_client_release_item_after_push(DisplayChannelClient *dcc,
PipeItem *item)
{
RedWorker *worker = dcc->common.worker;
switch (item->type) {
case PIPE_ITEM_TYPE_DRAW:
put_drawable_pipe_item(SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item));
break;
case PIPE_ITEM_TYPE_STREAM_CREATE: {
StreamAgent *stream_agent = SPICE_CONTAINEROF(item, StreamAgent, create_item);
if (stream_agent->stream->current) {
release_drawable(worker, stream_agent->stream->current);
}
break;
}
case PIPE_ITEM_TYPE_STREAM_CLIP:
red_display_release_stream_clip(worker, (StreamClipItem *)item);
break;
case PIPE_ITEM_TYPE_UPGRADE:
release_upgrade_item(worker, (UpgradeItem *)item);
break;
case PIPE_ITEM_TYPE_IMAGE:
release_image_item((ImageItem *)item);
break;
case PIPE_ITEM_TYPE_VERB:
free(item);
break;
default:
spice_critical("invalid item type");
}
}
// TODO: share code between before/after_push since most of the items need the same
// release
static void display_channel_client_release_item_before_push(DisplayChannelClient *dcc,
PipeItem *item)
{
RedWorker *worker = dcc->common.worker;
switch (item->type) {
case PIPE_ITEM_TYPE_DRAW: {
DrawablePipeItem *dpi = SPICE_CONTAINEROF(item, DrawablePipeItem, dpi_pipe_item);
ring_remove(&dpi->base);
put_drawable_pipe_item(dpi);
break;
}
case PIPE_ITEM_TYPE_STREAM_CREATE: {
StreamAgent *agent = SPICE_CONTAINEROF(item, StreamAgent, create_item);
red_display_release_stream(worker, agent);
break;
}
case PIPE_ITEM_TYPE_STREAM_CLIP:
red_display_release_stream_clip(worker, (StreamClipItem *)item);
break;
case PIPE_ITEM_TYPE_STREAM_DESTROY: {
StreamAgent *agent = SPICE_CONTAINEROF(item, StreamAgent, destroy_item);
red_display_release_stream(worker, agent);
break;
}
case PIPE_ITEM_TYPE_UPGRADE:
release_upgrade_item(worker, (UpgradeItem *)item);
break;
case PIPE_ITEM_TYPE_IMAGE:
release_image_item((ImageItem *)item);
break;
case PIPE_ITEM_TYPE_CREATE_SURFACE: {
SurfaceCreateItem *surface_create = SPICE_CONTAINEROF(item, SurfaceCreateItem,
pipe_item);
free(surface_create);
break;
}
case PIPE_ITEM_TYPE_DESTROY_SURFACE: {
SurfaceDestroyItem *surface_destroy = SPICE_CONTAINEROF(item, SurfaceDestroyItem,
pipe_item);
free(surface_destroy);
break;
}
case PIPE_ITEM_TYPE_INVAL_ONE:
case PIPE_ITEM_TYPE_VERB:
case PIPE_ITEM_TYPE_MIGRATE:
case PIPE_ITEM_TYPE_MIGRATE_DATA:
case PIPE_ITEM_TYPE_PIXMAP_SYNC:
case PIPE_ITEM_TYPE_PIXMAP_RESET:
case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE:
free(item);
break;
default:
spice_critical("invalid item type");
}
}
static void display_channel_release_item(RedChannelClient *rcc, PipeItem *item, int item_pushed)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
spice_assert(item);
if (item_pushed) {
display_channel_client_release_item_after_push(dcc, item);
} else {
spice_debug("not pushed (%d)", item->type);
display_channel_client_release_item_before_push(dcc, item);
}
}
static void display_channel_create(RedWorker *worker, int migrate)
{
DisplayChannel *display_channel;
if (worker->display_channel) {
return;
}
spice_printerr("create display channel");
if (!(worker->display_channel = (DisplayChannel *)__new_channel(
worker, sizeof(*display_channel),
SPICE_CHANNEL_DISPLAY, migrate,
display_channel_client_on_disconnect,
display_channel_send_item,
display_channel_hold_pipe_item,
display_channel_release_item,
display_channel_handle_message,
display_channel_handle_migrate_mark,
display_channel_handle_migrate_data,
display_channel_handle_migrate_data_get_serial
))) {
spice_printerr("failed to create display channel");
return;
}
display_channel = worker->display_channel;
#ifdef RED_STATISTICS
display_channel->stat = stat_add_node(worker->stat, "display_channel", TRUE);
display_channel->common.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
stat_compress_init(&display_channel->lz_stat, lz_stat_name);
stat_compress_init(&display_channel->glz_stat, glz_stat_name);
stat_compress_init(&display_channel->quic_stat, quic_stat_name);
stat_compress_init(&display_channel->jpeg_stat, jpeg_stat_name);
stat_compress_init(&display_channel->zlib_glz_stat, zlib_stat_name);
stat_compress_init(&display_channel->jpeg_alpha_stat, jpeg_alpha_stat_name);
}
static void handle_new_display_channel(RedWorker *worker, RedClient *client, RedsStream *stream,
int migrate,
uint32_t *common_caps, int num_common_caps,
uint32_t *caps, int num_caps)
{
DisplayChannel *display_channel;
DisplayChannelClient *dcc;
size_t stream_buf_size;
int is_low_bandwidth = main_channel_client_is_low_bandwidth(red_client_get_main(client));
if (!worker->display_channel) {
spice_printerr("Warning: Display channel was not created");
return;
}
display_channel = worker->display_channel;
spice_printerr("add display channel client");
dcc = display_channel_client_create(&display_channel->common, client, stream,
common_caps, num_common_caps,
caps, num_caps);
if (!dcc) {
return;
}
spice_printerr("New display (client %p) dcc %p stream %p", client, dcc, stream);
stream_buf_size = 32*1024;
dcc->send_data.stream_outbuf = spice_malloc(stream_buf_size);
dcc->send_data.stream_outbuf_size = stream_buf_size;
red_display_init_glz_data(dcc);
dcc->send_data.free_list.res =
spice_malloc(sizeof(SpiceResourceList) +
DISPLAY_FREE_LIST_DEFAULT_SIZE * sizeof(SpiceResourceID));
dcc->send_data.free_list.res_size = DISPLAY_FREE_LIST_DEFAULT_SIZE;
if (worker->jpeg_state == SPICE_WAN_COMPRESSION_AUTO) {
display_channel->enable_jpeg = is_low_bandwidth;
} else {
display_channel->enable_jpeg = (worker->jpeg_state == SPICE_WAN_COMPRESSION_ALWAYS);
}
// todo: tune quality according to bandwidth
display_channel->jpeg_quality = 85;
if (worker->zlib_glz_state == SPICE_WAN_COMPRESSION_AUTO) {
display_channel->enable_zlib_glz_wrap = is_low_bandwidth;
} else {
display_channel->enable_zlib_glz_wrap = (worker->zlib_glz_state ==
SPICE_WAN_COMPRESSION_ALWAYS);
}
spice_printerr("jpeg %s", display_channel->enable_jpeg ? "enabled" : "disabled");
spice_printerr("zlib-over-glz %s", display_channel->enable_zlib_glz_wrap ? "enabled" : "disabled");
// todo: tune level according to bandwidth
display_channel->zlib_level = ZLIB_DEFAULT_COMPRESSION_LEVEL;
red_display_client_init_streams(dcc);
on_new_display_channel_client(dcc);
}
static void cursor_channel_client_on_disconnect(RedChannelClient *rcc)
{
if (!rcc) {
return;
}
red_reset_cursor_cache(rcc);
}
static void red_disconnect_cursor(RedChannel *channel)
{
CommonChannel *common;
if (!channel || !red_channel_is_connected(channel)) {
return;
}
common = SPICE_CONTAINEROF(channel, CommonChannel, base);
spice_assert(channel == (RedChannel *)common->worker->cursor_channel);
common->worker->cursor_channel = NULL;
red_channel_apply_clients(channel, red_reset_cursor_cache);
red_channel_disconnect(channel);
}
static void red_migrate_cursor(RedWorker *worker, RedChannelClient *rcc)
{
// if (cursor_is_connected(worker)) {
if (red_channel_client_is_connected(rcc)) {
red_channel_client_pipe_add_type(rcc,
PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE);
red_channel_client_pipe_add_type(rcc,
PIPE_ITEM_TYPE_MIGRATE);
}
}
static void on_new_cursor_channel(RedWorker *worker, RedChannelClient *rcc)
{
CursorChannel *channel = worker->cursor_channel;
spice_assert(channel);
red_channel_client_ack_zero_messages_window(rcc);
red_channel_client_push_set_ack(rcc);
// TODO: why do we check for context.canvas? defer this to after display cc is connected
// and test it's canvas? this is just a test to see if there is an active renderer?
if (worker->surfaces[0].context.canvas && !channel->common.base.migrate) {
red_channel_client_pipe_add_type(rcc, PIPE_ITEM_TYPE_CURSOR_INIT);
}
}
static void cursor_channel_hold_pipe_item(RedChannelClient *rcc, PipeItem *item)
{
CursorPipeItem *cursor_pipe_item;
spice_assert(item);
cursor_pipe_item = SPICE_CONTAINEROF(item, CursorPipeItem, base);
ref_cursor_pipe_item(cursor_pipe_item);
}
// TODO: share code between before/after_push since most of the items need the same
// release
static void cursor_channel_client_release_item_before_push(CursorChannelClient *ccc,
PipeItem *item)
{
switch (item->type) {
case PIPE_ITEM_TYPE_CURSOR: {
CursorPipeItem *cursor_pipe_item = SPICE_CONTAINEROF(item, CursorPipeItem, base);
put_cursor_pipe_item(ccc, cursor_pipe_item);
break;
}
case PIPE_ITEM_TYPE_INVAL_ONE:
case PIPE_ITEM_TYPE_VERB:
case PIPE_ITEM_TYPE_MIGRATE:
case PIPE_ITEM_TYPE_CURSOR_INIT:
case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE:
free(item);
break;
default:
spice_error("invalid pipe item type");
}
}
static void cursor_channel_client_release_item_after_push(CursorChannelClient *ccc,
PipeItem *item)
{
switch (item->type) {
case PIPE_ITEM_TYPE_CURSOR: {
CursorPipeItem *cursor_pipe_item = SPICE_CONTAINEROF(item, CursorPipeItem, base);
put_cursor_pipe_item(ccc, cursor_pipe_item);
break;
}
default:
spice_critical("invalid item type");
}
}
static void cursor_channel_release_item(RedChannelClient *rcc, PipeItem *item, int item_pushed)
{
CursorChannelClient *ccc = RCC_TO_CCC(rcc);
spice_assert(item);
if (item_pushed) {
cursor_channel_client_release_item_after_push(ccc, item);
} else {
spice_debug("not pushed (%d)", item->type);
cursor_channel_client_release_item_before_push(ccc, item);
}
}
static void cursor_channel_create(RedWorker *worker, int migrate)
{
if (worker->cursor_channel != NULL) {
return;
}
spice_printerr("create cursor channel");
worker->cursor_channel = (CursorChannel *)__new_channel(
worker, sizeof(*worker->cursor_channel),
SPICE_CHANNEL_CURSOR, migrate,
cursor_channel_client_on_disconnect,
cursor_channel_send_item,
cursor_channel_hold_pipe_item,
cursor_channel_release_item,
red_channel_client_handle_message,
NULL,
NULL,
NULL);
}
static void red_connect_cursor(RedWorker *worker, RedClient *client, RedsStream *stream,
int migrate,
uint32_t *common_caps, int num_common_caps,
uint32_t *caps, int num_caps)
{
CursorChannel *channel;
CursorChannelClient *ccc;
if (worker->cursor_channel == NULL) {
spice_printerr("Warning: cursor channel was not created");
return;
}
channel = worker->cursor_channel;
spice_printerr("add cursor channel client");
ccc = cursor_channel_create_rcc(&channel->common, client, stream,
common_caps, num_common_caps,
caps, num_caps);
if (!ccc) {
return;
}
#ifdef RED_STATISTICS
channel->stat = stat_add_node(worker->stat, "cursor_channel", TRUE);
channel->common.base.out_bytes_counter = stat_add_counter(channel->stat, "out_bytes", TRUE);
#endif
on_new_cursor_channel(worker, &ccc->common.base);
}
typedef struct __attribute__ ((__packed__)) CursorData {
uint32_t visible;
SpicePoint16 position;
uint16_t trail_length;
uint16_t trail_frequency;
uint32_t data_size;
SpiceCursor _cursor;
} CursorData;
static void red_wait_outgoing_item(RedChannelClient *rcc)
{
uint64_t end_time;
int blocked;
if (!red_channel_client_blocked(rcc)) {
return;
}
end_time = red_now() + DETACH_TIMEOUT;
spice_printerr("blocked");
do {
usleep(DETACH_SLEEP_DURATION);
red_channel_client_receive(rcc);
red_channel_client_send(rcc);
} while ((blocked = red_channel_client_blocked(rcc)) && red_now() < end_time);
if (blocked) {
spice_printerr("timeout");
// TODO - shutting down the socket but we still need to trigger
// disconnection. Right now we wait for main channel to error for that.
red_channel_client_shutdown(rcc);
} else {
spice_assert(red_channel_client_no_item_being_sent(rcc));
}
}
static void rcc_shutdown_if_blocked(RedChannelClient *rcc)
{
if (red_channel_client_blocked(rcc)) {
red_channel_client_shutdown(rcc);
} else {
spice_assert(red_channel_client_no_item_being_sent(rcc));
}
}
static void red_wait_outgoing_items(RedChannel *channel)
{
uint64_t end_time;
int blocked;
if (!red_channel_any_blocked(channel)) {
return;
}
end_time = red_now() + DETACH_TIMEOUT;
spice_printerr("blocked");
do {
usleep(DETACH_SLEEP_DURATION);
red_channel_receive(channel);
red_channel_send(channel);
} while ((blocked = red_channel_any_blocked(channel)) && red_now() < end_time);
if (blocked) {
spice_printerr("timeout");
red_channel_apply_clients(channel, rcc_shutdown_if_blocked);
} else {
spice_assert(red_channel_no_item_being_sent(channel));
}
}
/* TODO: more evil sync stuff. anything with the word wait in it's name. */
static void red_wait_pipe_item_sent(RedChannelClient *rcc, PipeItem *item)
{
RedChannel *channel = rcc->channel;
uint64_t end_time;
int item_in_pipe;
if (!red_channel_client_blocked(rcc)) {
return;
}
spice_printerr("");
channel->channel_cbs.hold_item(rcc, item);
end_time = red_now() + CHANNEL_PUSH_TIMEOUT;
if (red_channel_client_blocked(rcc)) {
red_channel_client_receive(rcc);
red_channel_client_send(rcc);
}
red_channel_client_push(rcc);
while((item_in_pipe = ring_item_is_linked(&item->link)) && (red_now() < end_time)) {
usleep(CHANNEL_PUSH_SLEEP_DURATION);
red_channel_client_receive(rcc);
red_channel_client_send(rcc);
red_channel_client_push(rcc);
}
if (item_in_pipe) {
spice_printerr("timeout");
red_channel_client_disconnect(rcc);
} else {
if (red_channel_client_item_being_sent(rcc, item)) {
red_wait_outgoing_item(rcc);
}
}
channel->channel_cbs.release_item(rcc, item, FALSE);
}
static void surface_dirty_region_to_rects(RedSurface *surface,
QXLRect *qxl_dirty_rects,
uint32_t num_dirty_rects,
int clear_dirty_region)
{
QRegion *surface_dirty_region;
SpiceRect *dirty_rects;
int i;
surface_dirty_region = &surface->draw_dirty_region;
dirty_rects = spice_new0(SpiceRect, num_dirty_rects);
region_ret_rects(surface_dirty_region, dirty_rects, num_dirty_rects);
if (clear_dirty_region) {
region_clear(surface_dirty_region);
}
for (i = 0; i < num_dirty_rects; i++) {
qxl_dirty_rects[i].top = dirty_rects[i].top;
qxl_dirty_rects[i].left = dirty_rects[i].left;
qxl_dirty_rects[i].bottom = dirty_rects[i].bottom;
qxl_dirty_rects[i].right = dirty_rects[i].right;
}
free(dirty_rects);
}
void handle_dev_update_async(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedWorkerMessageUpdateAsync *msg = payload;
SpiceRect rect;
QXLRect *qxl_dirty_rects;
uint32_t num_dirty_rects;
RedSurface *surface;
uint32_t surface_id = msg->surface_id;
QXLRect qxl_area = msg->qxl_area;
uint32_t clear_dirty_region = msg->clear_dirty_region;
red_get_rect_ptr(&rect, &qxl_area);
flush_display_commands(worker);
spice_assert(worker->running);
validate_surface(worker, surface_id);
red_update_area(worker, &rect, surface_id);
if (!worker->qxl->st->qif->update_area_complete) {
return;
}
surface = &worker->surfaces[surface_id];
num_dirty_rects = pixman_region32_n_rects(&surface->draw_dirty_region);
if (num_dirty_rects == 0) {
return;
}
qxl_dirty_rects = spice_new0(QXLRect, num_dirty_rects);
surface_dirty_region_to_rects(surface, qxl_dirty_rects, num_dirty_rects,
clear_dirty_region);
worker->qxl->st->qif->update_area_complete(worker->qxl, surface_id,
qxl_dirty_rects, num_dirty_rects);
free(qxl_dirty_rects);
}
void handle_dev_update(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedWorkerMessageUpdate *msg = payload;
SpiceRect *rect = spice_new0(SpiceRect, 1);
RedSurface *surface;
uint32_t surface_id = msg->surface_id;
const QXLRect *qxl_area = msg->qxl_area;
uint32_t num_dirty_rects = msg->num_dirty_rects;
QXLRect *qxl_dirty_rects = msg->qxl_dirty_rects;
uint32_t clear_dirty_region = msg->clear_dirty_region;
surface = &worker->surfaces[surface_id];
red_get_rect_ptr(rect, qxl_area);
flush_display_commands(worker);
spice_assert(worker->running);
validate_surface(worker, surface_id);
red_update_area(worker, rect, surface_id);
free(rect);
surface_dirty_region_to_rects(surface, qxl_dirty_rects, num_dirty_rects,
clear_dirty_region);
}
static void dev_add_memslot(RedWorker *worker, QXLDevMemSlot mem_slot)
{
red_memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id,
mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end,
mem_slot.generation);
}
void handle_dev_add_memslot(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedWorkerMessageAddMemslot *msg = payload;
QXLDevMemSlot mem_slot = msg->mem_slot;
red_memslot_info_add_slot(&worker->mem_slots, mem_slot.slot_group_id, mem_slot.slot_id,
mem_slot.addr_delta, mem_slot.virt_start, mem_slot.virt_end,
mem_slot.generation);
}
void handle_dev_del_memslot(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedWorkerMessageDelMemslot *msg = payload;
uint32_t slot_id = msg->slot_id;
uint32_t slot_group_id = msg->slot_group_id;
red_memslot_info_del_slot(&worker->mem_slots, slot_group_id, slot_id);
}
/* TODO: destroy_surface_wait, dev_destroy_surface_wait - confusing. one asserts
* surface_id == 0, maybe move the assert upward and merge the two functions? */
static inline void destroy_surface_wait(RedWorker *worker, int surface_id)
{
if (!worker->surfaces[surface_id].context.canvas) {
return;
}
red_handle_depends_on_target_surface(worker, surface_id);
/* note that red_handle_depends_on_target_surface must be called before red_current_clear.
otherwise "current" will hold items that other drawables may depend on, and then
red_current_clear will remove them from the pipe. */
red_current_clear(worker, surface_id);
red_clear_surface_drawables_from_pipes(worker, surface_id, TRUE, TRUE);
}
static void dev_destroy_surface_wait(RedWorker *worker, uint32_t surface_id)
{
spice_assert(surface_id == 0);
flush_all_qxl_commands(worker);
if (worker->surfaces[0].context.canvas) {
destroy_surface_wait(worker, 0);
}
}
void handle_dev_destroy_surface_wait(void *opaque, void *payload)
{
RedWorkerMessageDestroySurfaceWait *msg = payload;
RedWorker *worker = opaque;
dev_destroy_surface_wait(worker, msg->surface_id);
}
static inline void red_cursor_reset(RedWorker *worker)
{
if (worker->cursor) {
red_release_cursor(worker, worker->cursor);
worker->cursor = NULL;
}
worker->cursor_visible = TRUE;
worker->cursor_position.x = worker->cursor_position.y = 0;
worker->cursor_trail_length = worker->cursor_trail_frequency = 0;
if (cursor_is_connected(worker)) {
red_channel_pipes_add_type(&worker->cursor_channel->common.base,
PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE);
if (!worker->cursor_channel->common.base.migrate) {
red_pipes_add_verb(&worker->cursor_channel->common.base, SPICE_MSG_CURSOR_RESET);
}
red_wait_outgoing_items(&worker->cursor_channel->common.base);
}
}
/* called upon device reset */
/* TODO: split me*/
static inline void dev_destroy_surfaces(RedWorker *worker)
{
int i;
flush_all_qxl_commands(worker);
//to handle better
for (i = 0; i < NUM_SURFACES; ++i) {
if (worker->surfaces[i].context.canvas) {
destroy_surface_wait(worker, i);
if (worker->surfaces[i].context.canvas) {
red_destroy_surface(worker, i);
}
spice_assert(!worker->surfaces[i].context.canvas);
}
}
spice_assert(ring_is_empty(&worker->streams));
if (display_is_connected(worker)) {
red_channel_pipes_add_type(&worker->display_channel->common.base,
PIPE_ITEM_TYPE_INVAL_PALLET_CACHE);
red_pipes_add_verb(&worker->display_channel->common.base,
SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL);
}
red_display_clear_glz_drawables(worker->display_channel);
red_cursor_reset(worker);
}
void handle_dev_destroy_surfaces(void *opaque, void *payload)
{
RedWorker *worker = opaque;
dev_destroy_surfaces(worker);
}
static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id,
QXLDevSurfaceCreate surface)
{
uint8_t *line_0;
int error;
spice_warn_if(surface_id != 0);
spice_warn_if(surface.height == 0);
spice_warn_if(((uint64_t)abs(surface.stride) * (uint64_t)surface.height) !=
abs(surface.stride) * surface.height);
line_0 = (uint8_t*)get_virt(&worker->mem_slots, surface.mem,
surface.height * abs(surface.stride),
surface.group_id, &error);
if (error) {
return;
}
if (surface.stride < 0) {
line_0 -= (int32_t)(surface.stride * (surface.height -1));
}
red_create_surface(worker, 0, surface.width, surface.height, surface.stride, surface.format,
line_0, surface.flags & QXL_SURF_FLAG_KEEP_DATA, TRUE);
if (display_is_connected(worker)) {
red_pipes_add_verb(&worker->display_channel->common.base,
SPICE_MSG_DISPLAY_MARK);
red_channel_push(&worker->display_channel->common.base);
}
if (cursor_is_connected(worker)) {
red_channel_pipes_add_type(&worker->cursor_channel->common.base,
PIPE_ITEM_TYPE_CURSOR_INIT);
}
}
void handle_dev_create_primary_surface(void *opaque, void *payload)
{
RedWorkerMessageCreatePrimarySurface *msg = payload;
RedWorker *worker = opaque;
dev_create_primary_surface(worker, msg->surface_id, msg->surface);
}
static void dev_destroy_primary_surface(RedWorker *worker, uint32_t surface_id)
{
spice_warn_if(surface_id != 0);
if (!worker->surfaces[surface_id].context.canvas) {
spice_printerr("double destroy of primary surface");
return;
}
flush_all_qxl_commands(worker);
dev_destroy_surface_wait(worker, 0);
red_destroy_surface(worker, 0);
spice_assert(ring_is_empty(&worker->streams));
spice_assert(!worker->surfaces[surface_id].context.canvas);
red_cursor_reset(worker);
}
void handle_dev_destroy_primary_surface(void *opaque, void *payload)
{
RedWorkerMessageDestroyPrimarySurface *msg = payload;
RedWorker *worker = opaque;
uint32_t surface_id = msg->surface_id;
dev_destroy_primary_surface(worker, surface_id);
}
void handle_dev_destroy_primary_surface_async(void *opaque, void *payload)
{
RedWorkerMessageDestroyPrimarySurfaceAsync *msg = payload;
RedWorker *worker = opaque;
uint32_t surface_id = msg->surface_id;
dev_destroy_primary_surface(worker, surface_id);
}
static void flush_all_surfaces(RedWorker *worker)
{
int x;
for (x = 0; x < NUM_SURFACES; ++x) {
if (worker->surfaces[x].context.canvas) {
red_current_flush(worker, x);
}
}
}
static void dev_flush_surfaces(RedWorker *worker)
{
flush_all_qxl_commands(worker);
flush_all_surfaces(worker);
red_wait_outgoing_items(&worker->display_channel->common.base);
red_wait_outgoing_items(&worker->cursor_channel->common.base);
}
void handle_dev_flush_surfaces(void *opaque, void *payload)
{
RedWorker *worker = opaque;
dev_flush_surfaces(worker);
}
void handle_dev_flush_surfaces_async(void *opaque, void *payload)
{
RedWorker *worker = opaque;
dev_flush_surfaces(worker);
}
void handle_dev_stop(void *opaque, void *payload)
{
RedWorker *worker = opaque;
spice_printerr("stop");
spice_assert(worker->running);
worker->running = FALSE;
red_display_clear_glz_drawables(worker->display_channel);
flush_all_surfaces(worker);
red_wait_outgoing_items(&worker->display_channel->common.base);
red_wait_outgoing_items(&worker->cursor_channel->common.base);
}
void handle_dev_start(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedChannel *cursor_red_channel = &worker->cursor_channel->common.base;
RedChannel *display_red_channel = &worker->display_channel->common.base;
spice_assert(!worker->running);
if (worker->cursor_channel) {
cursor_red_channel->migrate = FALSE;
}
if (worker->display_channel) {
display_red_channel->migrate = FALSE;
}
worker->running = TRUE;
}
void handle_dev_wakeup(void *opaque, void *payload)
{
RedWorker *worker = opaque;
clear_bit(RED_WORKER_PENDING_WAKEUP, worker->pending);
stat_inc_counter(worker->wakeup_counter, 1);
}
void handle_dev_oom(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedChannel *display_red_channel = &worker->display_channel->common.base;
int ring_is_empty;
spice_assert(worker->running);
// streams? but without streams also leak
spice_debug("OOM1 #draw=%u, #red_draw=%u, #glz_draw=%u current %u pipes %u",
worker->drawable_count,
worker->red_drawable_count,
worker->glz_drawable_count,
worker->current_size,
worker->display_channel ?
red_channel_sum_pipes_size(display_red_channel) : 0);
while (red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty)) {
red_channel_push(&worker->display_channel->common.base);
}
if (worker->qxl->st->qif->flush_resources(worker->qxl) == 0) {
red_free_some(worker);
worker->qxl->st->qif->flush_resources(worker->qxl);
}
spice_debug("OOM2 #draw=%u, #red_draw=%u, #glz_draw=%u current %u pipes %u",
worker->drawable_count,
worker->red_drawable_count,
worker->glz_drawable_count,
worker->current_size,
worker->display_channel ?
red_channel_sum_pipes_size(display_red_channel) : 0);
clear_bit(RED_WORKER_PENDING_OOM, worker->pending);
}
void handle_dev_reset_cursor(void *opaque, void *payload)
{
red_cursor_reset((RedWorker *)opaque);
}
void handle_dev_reset_image_cache(void *opaque, void *payload)
{
image_cache_reset(&((RedWorker *)opaque)->image_cache);
}
void handle_dev_destroy_surface_wait_async(void *opaque, void *payload)
{
RedWorkerMessageDestroySurfaceWaitAsync *msg = payload;
RedWorker *worker = opaque;
dev_destroy_surface_wait(worker, msg->surface_id);
}
void handle_dev_destroy_surfaces_async(void *opaque, void *payload)
{
RedWorker *worker = opaque;
dev_destroy_surfaces(worker);
}
void handle_dev_create_primary_surface_async(void *opaque, void *payload)
{
RedWorkerMessageCreatePrimarySurfaceAsync *msg = payload;
RedWorker *worker = opaque;
dev_create_primary_surface(worker, msg->surface_id, msg->surface);
}
/* exception for Dispatcher, data going from red_worker to main thread,
* TODO: use a different dispatcher?
* TODO: leave direct usage of channel(fd)? It's only used right after the
* pthread is created, since the channel duration is the lifetime of the spice
* server. */
void handle_dev_display_channel_create(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedChannel *red_channel;
// TODO: handle seemless migration. Temp, setting migrate to FALSE
display_channel_create(worker, FALSE);
red_channel = &worker->display_channel->common.base;
send_data(worker->channel, &red_channel, sizeof(RedChannel *));
}
void handle_dev_display_connect(void *opaque, void *payload)
{
RedWorkerMessageDisplayConnect *msg = payload;
RedWorker *worker = opaque;
RedsStream *stream = msg->stream;
RedClient *client = msg->client;
int migration = msg->migration;
spice_printerr("connect");
handle_new_display_channel(worker, client, stream, migration,
msg->common_caps, msg->num_common_caps,
msg->caps, msg->num_caps);
free(msg->caps);
free(msg->common_caps);
}
void handle_dev_display_disconnect(void *opaque, void *payload)
{
RedWorkerMessageDisplayDisconnect *msg = payload;
RedChannelClient *rcc = msg->rcc;
spice_printerr("disconnect display client");
spice_assert(rcc);
red_channel_client_disconnect(rcc);
}
void handle_dev_display_migrate(void *opaque, void *payload)
{
RedWorkerMessageDisplayMigrate *msg = payload;
RedWorker *worker = opaque;
RedChannelClient *rcc = msg->rcc;
spice_printerr("migrate display client");
spice_assert(rcc);
red_migrate_display(worker, rcc);
}
/* TODO: special, perhaps use another dispatcher? */
void handle_dev_cursor_channel_create(void *opaque, void *payload)
{
RedWorker *worker = opaque;
RedChannel *red_channel;
// TODO: handle seemless migration. Temp, setting migrate to FALSE
cursor_channel_create(worker, FALSE);
red_channel = &worker->cursor_channel->common.base;
send_data(worker->channel, &red_channel, sizeof(RedChannel *));
}
void handle_dev_cursor_connect(void *opaque, void *payload)
{
RedWorkerMessageCursorConnect *msg = payload;
RedWorker *worker = opaque;
RedsStream *stream = msg->stream;
RedClient *client = msg->client;
int migration = msg->migration;
spice_printerr("cursor connect");
red_connect_cursor(worker, client, stream, migration,
msg->common_caps, msg->num_common_caps,
msg->caps, msg->num_caps);
free(msg->caps);
free(msg->common_caps);
}
void handle_dev_cursor_disconnect(void *opaque, void *payload)
{
RedWorkerMessageCursorDisconnect *msg = payload;
RedChannelClient *rcc = msg->rcc;
spice_printerr("disconnect cursor client");
spice_assert(rcc);
red_channel_client_disconnect(rcc);
}
void handle_dev_cursor_migrate(void *opaque, void *payload)
{
RedWorkerMessageCursorMigrate *msg = payload;
RedWorker *worker = opaque;
RedChannelClient *rcc = msg->rcc;
spice_printerr("migrate cursor client");
spice_assert(rcc);
red_migrate_cursor(worker, rcc);
}
void handle_dev_set_compression(void *opaque, void *payload)
{
RedWorkerMessageSetCompression *msg = payload;
RedWorker *worker = opaque;
worker->image_compression = msg->image_compression;
switch (worker->image_compression) {
case SPICE_IMAGE_COMPRESS_AUTO_LZ:
spice_printerr("ic auto_lz");
break;
case SPICE_IMAGE_COMPRESS_AUTO_GLZ:
spice_printerr("ic auto_glz");
break;
case SPICE_IMAGE_COMPRESS_QUIC:
spice_printerr("ic quic");
break;
case SPICE_IMAGE_COMPRESS_LZ:
spice_printerr("ic lz");
break;
case SPICE_IMAGE_COMPRESS_GLZ:
spice_printerr("ic glz");
break;
case SPICE_IMAGE_COMPRESS_OFF:
spice_printerr("ic off");
break;
default:
spice_printerr("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);
stat_reset(&worker->display_channel->jpeg_stat);
stat_reset(&worker->display_channel->zlib_glz_stat);
stat_reset(&worker->display_channel->jpeg_alpha_stat);
}
#endif
}
void handle_dev_set_streaming_video(void *opaque, void *payload)
{
RedWorkerMessageSetStreamingVideo *msg = payload;
RedWorker *worker = opaque;
worker->streaming_video = msg->streaming_video;
spice_assert(worker->streaming_video != STREAM_VIDEO_INVALID);
switch(worker->streaming_video) {
case STREAM_VIDEO_ALL:
spice_printerr("sv all");
break;
case STREAM_VIDEO_FILTER:
spice_printerr("sv filter");
break;
case STREAM_VIDEO_OFF:
spice_printerr("sv off");
break;
default:
spice_printerr("sv invalid");
}
}
void handle_dev_set_mouse_mode(void *opaque, void *payload)
{
RedWorkerMessageSetMouseMode *msg = payload;
RedWorker *worker = opaque;
worker->mouse_mode = msg->mode;
spice_printerr("mouse mode %u", worker->mouse_mode);
}
void handle_dev_add_memslot_async(void *opaque, void *payload)
{
RedWorkerMessageAddMemslotAsync *msg = payload;
RedWorker *worker = opaque;
dev_add_memslot(worker, msg->mem_slot);
}
void handle_dev_reset_memslots(void *opaque, void *payload)
{
RedWorker *worker = opaque;
red_memslot_info_reset(&worker->mem_slots);
}
void handle_dev_loadvm_commands(void *opaque, void *payload)
{
RedWorkerMessageLoadvmCommands *msg = payload;
RedWorker *worker = opaque;
uint32_t i;
RedCursorCmd *cursor_cmd;
RedSurfaceCmd *surface_cmd;
uint32_t count = msg->count;
QXLCommandExt *ext = msg->ext;
spice_printerr("loadvm_commands");
for (i = 0 ; i < count ; ++i) {
switch (ext[i].cmd.type) {
case QXL_CMD_CURSOR:
cursor_cmd = spice_new0(RedCursorCmd, 1);
if (red_get_cursor_cmd(&worker->mem_slots, ext[i].group_id,
cursor_cmd, ext[i].cmd.data)) {
/* XXX allow failure in loadvm? */
spice_printerr("failed loadvm command type (%d)",
ext[i].cmd.type);
continue;
}
qxl_process_cursor(worker, cursor_cmd, ext[i].group_id);
break;
case QXL_CMD_SURFACE:
surface_cmd = spice_new0(RedSurfaceCmd, 1);
red_get_surface_cmd(&worker->mem_slots, ext[i].group_id,
surface_cmd, ext[i].cmd.data);
red_process_surface(worker, surface_cmd, ext[i].group_id, TRUE);
break;
default:
spice_printerr("unhandled loadvm command type (%d)", ext[i].cmd.type);
break;
}
}
}
static void worker_handle_dispatcher_async_done(void *opaque,
uint32_t message_type,
void *payload)
{
RedWorker *worker = opaque;
RedWorkerMessageAsync *msg_async = payload;
spice_debug(NULL);
red_dispatcher_async_complete(worker->red_dispatcher, msg_async->cmd);
}
static void register_callbacks(Dispatcher *dispatcher)
{
dispatcher_register_async_done_callback(
dispatcher,
worker_handle_dispatcher_async_done);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DISPLAY_CONNECT,
handle_dev_display_connect,
sizeof(RedWorkerMessageDisplayConnect),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DISPLAY_DISCONNECT,
handle_dev_display_disconnect,
sizeof(RedWorkerMessageDisplayDisconnect),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DISPLAY_MIGRATE,
handle_dev_display_migrate,
sizeof(RedWorkerMessageDisplayMigrate),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_CURSOR_CONNECT,
handle_dev_cursor_connect,
sizeof(RedWorkerMessageCursorConnect),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_CURSOR_DISCONNECT,
handle_dev_cursor_disconnect,
sizeof(RedWorkerMessageCursorDisconnect),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_CURSOR_MIGRATE,
handle_dev_cursor_migrate,
sizeof(RedWorkerMessageCursorMigrate),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_UPDATE,
handle_dev_update,
sizeof(RedWorkerMessageUpdate),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_UPDATE_ASYNC,
handle_dev_update_async,
sizeof(RedWorkerMessageUpdateAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_ADD_MEMSLOT,
handle_dev_add_memslot,
sizeof(RedWorkerMessageAddMemslot),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_ADD_MEMSLOT_ASYNC,
handle_dev_add_memslot_async,
sizeof(RedWorkerMessageAddMemslotAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DEL_MEMSLOT,
handle_dev_del_memslot,
sizeof(RedWorkerMessageDelMemslot),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DESTROY_SURFACES,
handle_dev_destroy_surfaces,
sizeof(RedWorkerMessageDestroySurfaces),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DESTROY_SURFACES_ASYNC,
handle_dev_destroy_surfaces_async,
sizeof(RedWorkerMessageDestroySurfacesAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE,
handle_dev_destroy_primary_surface,
sizeof(RedWorkerMessageDestroyPrimarySurface),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE_ASYNC,
handle_dev_destroy_primary_surface_async,
sizeof(RedWorkerMessageDestroyPrimarySurfaceAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC,
handle_dev_create_primary_surface_async,
sizeof(RedWorkerMessageCreatePrimarySurfaceAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE,
handle_dev_create_primary_surface,
sizeof(RedWorkerMessageCreatePrimarySurface),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_RESET_IMAGE_CACHE,
handle_dev_reset_image_cache,
sizeof(RedWorkerMessageResetImageCache),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_RESET_CURSOR,
handle_dev_reset_cursor,
sizeof(RedWorkerMessageResetCursor),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_WAKEUP,
handle_dev_wakeup,
sizeof(RedWorkerMessageWakeup),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_OOM,
handle_dev_oom,
sizeof(RedWorkerMessageOom),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_START,
handle_dev_start,
sizeof(RedWorkerMessageStart),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_FLUSH_SURFACES_ASYNC,
handle_dev_flush_surfaces_async,
sizeof(RedWorkerMessageFlushSurfacesAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_STOP,
handle_dev_stop,
sizeof(RedWorkerMessageStop),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_LOADVM_COMMANDS,
handle_dev_loadvm_commands,
sizeof(RedWorkerMessageLoadvmCommands),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_SET_COMPRESSION,
handle_dev_set_compression,
sizeof(RedWorkerMessageSetCompression),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_SET_STREAMING_VIDEO,
handle_dev_set_streaming_video,
sizeof(RedWorkerMessageSetStreamingVideo),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_SET_MOUSE_MODE,
handle_dev_set_mouse_mode,
sizeof(RedWorkerMessageSetMouseMode),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DISPLAY_CHANNEL_CREATE,
handle_dev_display_channel_create,
sizeof(RedWorkerMessageDisplayChannelCreate),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_CURSOR_CHANNEL_CREATE,
handle_dev_cursor_channel_create,
sizeof(RedWorkerMessageCursorChannelCreate),
DISPATCHER_NONE);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT,
handle_dev_destroy_surface_wait,
sizeof(RedWorkerMessageDestroySurfaceWait),
DISPATCHER_ACK);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_DESTROY_SURFACE_WAIT_ASYNC,
handle_dev_destroy_surface_wait_async,
sizeof(RedWorkerMessageDestroySurfaceWaitAsync),
DISPATCHER_ASYNC);
dispatcher_register_handler(dispatcher,
RED_WORKER_MESSAGE_RESET_MEMSLOTS,
handle_dev_reset_memslots,
sizeof(RedWorkerMessageResetMemslots),
DISPATCHER_NONE);
}
static void handle_dev_input(int fd, int event, void *opaque)
{
RedWorker *worker = opaque;
dispatcher_handle_recv_read(red_dispatcher_get_dispatcher(worker->red_dispatcher));
}
static void red_init(RedWorker *worker, WorkerInitData *init_data)
{
RedWorkerMessage message;
Dispatcher *dispatcher;
int i;
spice_assert(sizeof(CursorItem) <= QXL_CURSUR_DEVICE_DATA_SIZE);
memset(worker, 0, sizeof(RedWorker));
dispatcher = red_dispatcher_get_dispatcher(init_data->red_dispatcher);
dispatcher_set_opaque(dispatcher, worker);
worker->red_dispatcher = init_data->red_dispatcher;
worker->qxl = init_data->qxl;
worker->id = init_data->id;
worker->channel = dispatcher_get_recv_fd(dispatcher);
register_callbacks(dispatcher);
worker->pending = init_data->pending;
worker->cursor_visible = TRUE;
spice_assert(init_data->num_renderers > 0);
worker->num_renderers = init_data->num_renderers;
memcpy(worker->renderers, init_data->renderers, sizeof(worker->renderers));
worker->renderer = RED_RENDERER_INVALID;
worker->mouse_mode = SPICE_MOUSE_MODE_SERVER;
worker->image_compression = init_data->image_compression;
worker->jpeg_state = init_data->jpeg_state;
worker->zlib_glz_state = init_data->zlib_glz_state;
worker->streaming_video = init_data->streaming_video;
ring_init(&worker->current_list);
image_cache_init(&worker->image_cache);
image_surface_init(worker);
drawables_init(worker);
cursor_items_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
for (i = 0; i < MAX_EVENT_SOURCES; i++) {
worker->poll_fds[i].fd = -1;
}
worker->poll_fds[0].fd = worker->channel;
worker->poll_fds[0].events = POLLIN;
worker->watches[0].worker = worker;
worker->watches[0].watch_func = handle_dev_input;
worker->watches[0].watch_func_opaque = worker;
red_memslot_info_init(&worker->mem_slots,
init_data->num_memslots_groups,
init_data->num_memslots,
init_data->memslot_gen_bits,
init_data->memslot_id_bits,
init_data->internal_groupslot_id);
spice_warn_if(init_data->n_surfaces > NUM_SURFACES);
worker->n_surfaces = init_data->n_surfaces;
message = RED_WORKER_MESSAGE_READY;
write_message(worker->channel, &message);
}
static void red_display_cc_free_glz_drawables(RedChannelClient *rcc)
{
DisplayChannelClient *dcc = RCC_TO_DCC(rcc);
red_display_handle_glz_drawables_to_free(dcc);
}
SPICE_GNUC_NORETURN void *red_worker_main(void *arg)
{
RedWorker *worker = spice_malloc(sizeof(RedWorker));
spice_printerr("begin");
spice_assert(MAX_PIPE_SIZE > WIDE_CLIENT_ACK_WINDOW &&
MAX_PIPE_SIZE > NARROW_CLIENT_ACK_WINDOW); //ensure wakeup by ack message
#if defined(RED_WORKER_STAT) || defined(COMPRESS_STAT)
if (pthread_getcpuclockid(pthread_self(), &clock_id)) {
spice_error("pthread_getcpuclockid failed");
}
#endif
red_init(worker, (WorkerInitData *)arg);
red_init_quic(worker);
red_init_lz(worker);
red_init_jpeg(worker);
red_init_zlib(worker);
worker->event_timeout = INF_EVENT_WAIT;
for (;;) {
int i, num_events;
worker->event_timeout = MIN(red_get_streams_timout(worker), worker->event_timeout);
num_events = poll(worker->poll_fds, MAX_EVENT_SOURCES, worker->event_timeout);
red_handle_streams_timout(worker);
if (worker->display_channel) {
/* during migration, in the dest, the display channel can be initialized
while the global lz data not since migrate data msg hasn't been
received yet */
red_channel_apply_clients(&worker->display_channel->common.base,
red_display_cc_free_glz_drawables);
}
worker->event_timeout = INF_EVENT_WAIT;
if (num_events == -1) {
if (errno != EINTR) {
spice_error("poll failed, %s", strerror(errno));
}
}
for (i = 0; i < MAX_EVENT_SOURCES; i++) {
/* The watch may have been removed by the watch-func from
another fd (ie a disconnect through the dispatcher),
in this case watch_func is NULL. */
if (worker->poll_fds[i].revents && worker->watches[i].watch_func) {
int events = 0;
if (worker->poll_fds[i].revents & POLLIN) {
events |= SPICE_WATCH_EVENT_READ;
}
if (worker->poll_fds[i].revents & POLLOUT) {
events |= SPICE_WATCH_EVENT_WRITE;
}
worker->watches[i].watch_func(worker->poll_fds[i].fd, events,
worker->watches[i].watch_func_opaque);
}
}
/* Clear the poll_fd for any removed watches, see the comment in
watch_remove for why we don't do this there. */
for (i = 0; i < MAX_EVENT_SOURCES; i++) {
if (!worker->watches[i].watch_func) {
worker->poll_fds[i].fd = -1;
}
}
if (worker->running) {
int ring_is_empty;
red_process_cursor(worker, MAX_PIPE_SIZE, &ring_is_empty);
red_process_commands(worker, MAX_PIPE_SIZE, &ring_is_empty);
}
red_push(worker);
}
abort();
}
#ifdef DUMP_BITMAP
#include
static void dump_palette(FILE *f, SpicePalette* 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 = SPICE_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, SpiceBitmap *bitmap, uint32_t group_id)
{
static uint32_t file_id = 0;
char file_str[200];
int rgb = TRUE;
uint16_t n_pixel_bits;
SpicePalette *plt = NULL;
uint32_t id;
int row_size;
uint32_t file_size;
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;
int i;
switch (bitmap->format) {
case SPICE_BITMAP_FMT_1BIT_BE:
case SPICE_BITMAP_FMT_1BIT_LE:
rgb = FALSE;
n_pixel_bits = 1;
break;
case SPICE_BITMAP_FMT_4BIT_BE:
case SPICE_BITMAP_FMT_4BIT_LE:
rgb = FALSE;
n_pixel_bits = 4;
break;
case SPICE_BITMAP_FMT_8BIT:
rgb = FALSE;
n_pixel_bits = 8;
break;
case SPICE_BITMAP_FMT_16BIT:
n_pixel_bits = 16;
break;
case SPICE_BITMAP_FMT_24BIT:
n_pixel_bits = 24;
break;
case SPICE_BITMAP_FMT_32BIT:
n_pixel_bits = 32;
break;
case SPICE_BITMAP_FMT_RGBA:
n_pixel_bits = 32;
alpha = 1;
break;
default:
spice_error("invalid bitmap format %u", bitmap->format);
}
if (!rgb) {
if (!bitmap->palette) {
return; // dont dump masks.
}
plt = bitmap->palette;
}
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) {
spice_error("Error creating bmp");
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 */
for (i = 0; i < bitmap->data->num_chunks; i++) {
SpiceChunk *chunk = &bitmap->data->chunk[i];
int num_lines = chunk->len / bitmap->stride;
for (i = 0; i < num_lines; i++) {
dump_line(f, chunk->data + (i * bitmap->stride), n_pixel_bits, bitmap->x, row_size);
}
}
fclose(f);
}
#endif