1、spice通过spice_server_add_interface接口提供给qemu相应的接口,包括qxl显示接口,迁移接口,键盘接口,鼠标接口等。
SPICE_GNUC_VISIBLE int spice_server_add_interface(SpiceServer *reds,
SpiceBaseInstance *sin)
{
...
} else if (strcmp(base_interface->type, SPICE_INTERFACE_QXL) == 0) {
QXLInstance *qxl;
spice_debug("SPICE_INTERFACE_QXL");
if (base_interface->major_version != SPICE_INTERFACE_QXL_MAJOR ||
base_interface->minor_version > SPICE_INTERFACE_QXL_MINOR) {
spice_warning("unsupported qxl interface");
return -1;
}
qxl = SPICE_UPCAST(QXLInstance, sin);
if (qxl->id < 0) {
spice_warning("invalid QXL ID");
return -1;
}
if (reds_find_channel(reds, SPICE_CHANNEL_DISPLAY, qxl->id)) {
spice_warning("QXL ID already allocated");
return -1;
}
red_qxl_init(reds, qxl);
reds->qxl_instances.push_front(qxl);
/* this function has to be called after the qxl is on the list
* as QXLInstance clients expect the qxl to be on the list when
* this callback is called. This as clients assume they can start the
* qxl_instances. Also note that this should be the first callback to
* be called. */
red_qxl_attach_worker(qxl);
red_qxl_set_compression_level(qxl, calc_compression_level(reds));
...
}
2、当显示提供显示接口时,会初始spice的qxl模块,创建RedWorker处理类。
void red_qxl_init(RedsState *reds, QXLInstance *qxl)
{
QXLState *qxl_state;
spice_return_if_fail(qxl != nullptr);
qxl_state = new QXLState();
qxl_state->reds = reds;
qxl_state->qxl = qxl;
pthread_mutex_init(&qxl_state->scanout_mutex, nullptr);
qxl_state->scanout.drm_dma_buf_fd = -1;
qxl_state->gl_draw_cookie = GL_DRAW_COOKIE_INVALID;
qxl_state->dispatcher = red::make_shared<Dispatcher>(RED_WORKER_MESSAGE_COUNT);
qxl_state->max_monitors = UINT_MAX;
qxl->st = qxl_state;
qxl_state->worker = red_worker_new(qxl);
red_worker_run(qxl_state->worker);
}
3、RedWorker会实现调度器dispatcher并注册各种处理的回调函数,并在此时创建显示通道。
RedWorker* red_worker_new(QXLInstance *qxl)
{
QXLDevInitInfo init_info;
RedWorker *worker;
Dispatcher *dispatcher;
RedsState *reds = red_qxl_get_server(qxl->st);
RedChannel *channel;
red_qxl_get_init_info(qxl, &init_info);
worker = g_new0(RedWorker, 1);
worker->core = event_loop_core;
worker->core.main_context = g_main_context_new();
worker->record = reds_get_record(reds);
dispatcher = red_qxl_get_dispatcher(qxl);
dispatcher->set_opaque(worker);
worker->qxl = qxl;
register_callbacks(dispatcher);
if (worker->record) {
dispatcher->register_universal_handler(worker_dispatcher_record);
}
worker->driver_cap_monitors_config = false;
char worker_str[SPICE_STAT_NODE_NAME_MAX];
snprintf(worker_str, sizeof(worker_str), "display[%d]", worker->qxl->id & 0xff);
stat_init_node(&worker->stat, reds, nullptr, worker_str, TRUE);
stat_init_counter(&worker->wakeup_counter, reds, &worker->stat, "wakeups", TRUE);
stat_init_counter(&worker->command_counter, reds, &worker->stat, "commands", TRUE);
stat_init_counter(&worker->full_loop_counter, reds, &worker->stat, "full_loops", TRUE);
stat_init_counter(&worker->total_loop_counter, reds, &worker->stat, "total_loops", TRUE);
worker->dispatch_watch = dispatcher->create_watch(&worker->core);
spice_assert(worker->dispatch_watch != nullptr);
GSource *source = g_source_new(&worker_source_funcs, sizeof(RedWorkerSource));
SPICE_CONTAINEROF(source, RedWorkerSource, source)->worker = worker;
g_source_attach(source, worker->core.main_context);
g_source_unref(source);
memslot_info_init(&worker->mem_slots,init_info.num_memslots_groups,init_info.num_memslots,
init_info.memslot_gen_bits,init_info.memslot_id_bits,init_info.internal_groupslot_id);
worker->event_timeout = INF_EVENT_WAIT;
worker->cursor_channel = cursor_channel_new(reds, qxl->id,&worker->core, dispatcher).get(); // XXX
channel = worker->cursor_channel;
channel->init_stat_node(&worker->stat, "cursor_channel");
// TODO: handle seamless migration. Temp, setting migrate to FALSE
worker->display_channel = display_channel_new(reds, qxl, &worker->core, dispatcher,FALSE,reds_get_streaming_video(reds),
eds_get_video_codecs(reds),init_info.n_surfaces).get(); // XXX
channel = worker->display_channel;
channel->init_stat_node(&worker->stat, "display_channel");
display_channel_set_image_compression(worker->display_channel,spice_server_get_image_compression(reds));
return worker;
}
3、RedWorker主要处理qemu发过来的命令并找到调用回调函数处理,比如刷新显存图像
handle_dev_flush_surfaces_async。
static void register_callbacks(Dispatcher *dispatcher)
{
/* TODO: register cursor & display specific msg in respective channel files */
register_handler(dispatcher,
handle_dev_update,
true);
register_handler(dispatcher,
handle_dev_update_async,
false);
register_handler(dispatcher,
handle_dev_add_memslot,
true);
register_handler(dispatcher,
handle_dev_add_memslot_async,
false);
register_handler(dispatcher,
handle_dev_del_memslot,
false);
register_handler(dispatcher,
handle_dev_destroy_surfaces,
true);
register_handler(dispatcher,
handle_dev_destroy_surfaces_async,
false);
register_handler(dispatcher,
handle_dev_destroy_primary_surface,
true);
register_handler(dispatcher,
handle_dev_destroy_primary_surface_async,
false);
register_handler(dispatcher,
handle_dev_create_primary_surface_async,
false);
register_handler(dispatcher,
handle_dev_create_primary_surface,
true);
register_handler(dispatcher,
handle_dev_reset_image_cache,
true);
register_handler(dispatcher,
handle_dev_reset_cursor,
true);
register_handler(dispatcher,
handle_dev_wakeup,
false);
register_handler(dispatcher,
handle_dev_oom,
false);
register_handler(dispatcher,
handle_dev_start,
false);
register_handler(dispatcher,
handle_dev_flush_surfaces_async,
false);
register_handler(dispatcher,
handle_dev_stop,
true);
register_handler(dispatcher,
handle_dev_loadvm_commands,
true);
register_handler(dispatcher,
handle_dev_set_compression,
false);
register_handler(dispatcher,
handle_dev_set_streaming_video,
false);
register_handler(dispatcher,
handle_dev_set_video_codecs,
false);
register_handler(dispatcher,
handle_dev_set_mouse_mode,
false);
register_handler(dispatcher,
handle_dev_destroy_surface_wait,
true);
register_handler(dispatcher,
handle_dev_destroy_surface_wait_async,
false);
register_handler(dispatcher,
handle_dev_reset_memslots,
false);
register_handler(dispatcher,
handle_dev_monitors_config_async,
false);
register_handler(dispatcher,
handle_dev_driver_unload,
false);
register_handler(dispatcher,
handle_dev_gl_scanout,
false);
register_handler(dispatcher,
handle_dev_gl_draw_async,
false);
register_handler(dispatcher,
handle_dev_close,
false);
}
4、handle_dev_update_async会处理显示命令。
static void
handle_dev_update_async(RedWorker* worker, RedWorkerMessageUpdateAsync* msg)
{
QXLRect *qxl_dirty_rects = nullptr;
uint32_t num_dirty_rects = 0;
spice_return_if_fail(red_qxl_is_running(worker->qxl));
spice_return_if_fail(qxl_get_interface(worker->qxl)->update_area_complete);
flush_display_commands(worker);
display_channel_update(worker->display_channel,msg->surface_id, &msg->qxl_area, msg->clear_dirty_region,&qxl_dirty_rects, &num_dirty_rects);
red_qxl_update_area_complete(worker->qxl, msg->surface_id,qxl_dirty_rects, num_dirty_rects);
g_free(qxl_dirty_rects);
red_qxl_async_complete(worker->qxl, msg->base.cookie);
}
static void flush_display_commands(RedWorker *worker)
{
flush_commands(worker, worker->display_channel, red_process_display);
}
5、将命令发送给各个显示通道客户端。
static void flush_commands(RedWorker *worker, RedChannel *red_channel, red_process_t process)
{
for (;;) {
uint64_t end_time;
int ring_is_empty;
process(worker, &ring_is_empty); //red_process_display先处理完成
if (ring_is_empty) {
break;
}
while (process(worker, &ring_is_empty)) {
red_channel->push();
}
if (ring_is_empty) {
break;
}
end_time = spice_get_monotonic_time_ns() + COMMON_CLIENT_TIMEOUT;
for (;;) {
red_channel->push();
if (red_channel->max_pipe_size() <= MAX_PIPE_SIZE) {
break;
}
red_channel->receive();
red_channel->send();
// TODO: MC: the whole timeout will break since it takes lowest timeout, should do it client by client.
if (spice_get_monotonic_time_ns() >= end_time) {
// 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.
spice_warning("flush timeout");
red_channel->disconnect();
} else {
usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
}
}
}
}
其中显示命令处理
int red_qxl_get_command(QXLInstance *qxl, struct QXLCommandExt *cmd)
{
QXLInterface *qxl_interface = qxl_get_interface(qxl);
return qxl_interface->get_command(qxl, cmd);
}
static int red_process_display(RedWorker *worker, int *ring_is_empty)
{
QXLCommandExt ext_cmd;
int n = 0;
uint64_t start = spice_get_monotonic_time_ns();
if (!red_qxl_is_running(worker->qxl)) {
*ring_is_empty = TRUE;
return n;
}
stat_inc_counter(worker->total_loop_counter, 1);
worker->process_display_generation++;
*ring_is_empty = FALSE;
while (worker->display_channel->max_pipe_size() <= MAX_PIPE_SIZE) {
if (!red_qxl_get_command(worker->qxl, &ext_cmd)) {
*ring_is_empty = TRUE;
if (worker->display_poll_tries < CMD_RING_POLL_RETRIES) {
worker->event_timeout = MIN(worker->event_timeout, CMD_RING_POLL_TIMEOUT);
} else if (worker->display_poll_tries == CMD_RING_POLL_RETRIES && !red_qxl_req_cmd_notification(worker->qxl)) {
continue;
}
worker->display_poll_tries++;
return n;
}
if (worker->record) {
red_record_qxl_command(worker->record, &worker->mem_slots, ext_cmd);
}
stat_inc_counter(worker->command_counter, 1);
worker->display_poll_tries = 0;
switch (ext_cmd.cmd.type) {
case QXL_CMD_DRAW: {
auto red_drawable = red_drawable_new(worker->qxl, &worker->mem_slots, ext_cmd.group_id, ext_cmd.cmd.data,ext_cmd.flags); // returns with 1 ref
if (red_drawable) {
display_channel_process_draw(worker->display_channel, std::move(red_drawable),worker->process_display_generation);
}
break;
}
case QXL_CMD_UPDATE: {
auto update = red_update_cmd_new(worker->qxl, &worker->mem_slots,ext_cmd.group_id, ext_cmd.cmd.data);
if (!update) {
break;
}
if (!display_channel_validate_surface(worker->display_channel, update->surface_id)) {
spice_warning("Invalid surface in QXL_CMD_UPDATE");
} else {
display_channel_draw(worker->display_channel, &update->area, update->surface_id);
red_qxl_notify_update(worker->qxl, update->update_id);
}
break;
}
case QXL_CMD_MESSAGE: {
auto message = red_message_new(worker->qxl, &worker->mem_slots,ext_cmd.group_id, ext_cmd.cmd.data);
if (!message) {
break;
}
#ifdef DEBUG
spice_warning("MESSAGE: %.*s", message->len, message->data);
#endif
break;
}
case QXL_CMD_SURFACE:
red_process_surface_cmd(worker, &ext_cmd, FALSE);
break;
default:
spice_error("bad command type");
}
n++;
if (worker->display_channel->all_blocked() || spice_get_monotonic_time_ns() - start > NSEC_PER_SEC / 100) {
worker->event_timeout = 0;
return n;
}
}
worker->was_blocked = TRUE;
stat_inc_counter(worker->full_loop_counter, 1);
return n;
}
通过qemu中spice-display.c的dpy_interface获取数据
static const QXLInterface dpy_interface = {
.base.type = SPICE_INTERFACE_QXL,
.base.description = "qemu simple display",
.base.major_version = SPICE_INTERFACE_QXL_MAJOR,
.base.minor_version = SPICE_INTERFACE_QXL_MINOR,
.attache_worker = interface_attach_worker,
.set_compression_level = interface_set_compression_level,
#if SPICE_NEEDS_SET_MM_TIME
.set_mm_time = interface_set_mm_time,
#endif
.get_init_info = interface_get_init_info,
/* the callbacks below are called from spice server thread context */
.get_command = interface_get_command,
.req_cmd_notification = interface_req_cmd_notification,
.release_resource = interface_release_resource,
.get_cursor_command = interface_get_cursor_command,
.req_cursor_notification = interface_req_cursor_notification,
.notify_update = interface_notify_update,
.flush_resources = interface_flush_resources,
.async_complete = interface_async_complete,
.update_area_complete = interface_update_area_complete,
.set_client_capabilities = interface_set_client_capabilities,
.client_monitors_config = interface_client_monitors_config,
};
static int interface_get_command(QXLInstance *sin, QXLCommandExt *ext)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
SimpleSpiceUpdate *update;
int ret = false;
qemu_mutex_lock(&ssd->lock);
update = QTAILQ_FIRST(&ssd->updates);
if (update != NULL) {
QTAILQ_REMOVE(&ssd->updates, update, next);
*ext = update->ext;
ret = true;
}
qemu_mutex_unlock(&ssd->lock);
return ret;
}
//qemu中的qxl.c
static void display_refresh(DisplayChangeListener *dcl)
{
PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
if (qxl->mode == QXL_MODE_VGA) {
qemu_spice_display_refresh(&qxl->ssd);
}
}
static const QXLInterface qxl_interface = {
.base.type = SPICE_INTERFACE_QXL,
.base.description = "qxl gpu",
.base.major_version = SPICE_INTERFACE_QXL_MAJOR,
.base.minor_version = SPICE_INTERFACE_QXL_MINOR,
.attache_worker = interface_attach_worker,
.set_compression_level = interface_set_compression_level,
#if SPICE_NEEDS_SET_MM_TIME
.set_mm_time = interface_set_mm_time,
#endif
.get_init_info = interface_get_init_info,
/* the callbacks below are called from spice server thread context */
.get_command = interface_get_command,
.req_cmd_notification = interface_req_cmd_notification,
.release_resource = interface_release_resource,
.get_cursor_command = interface_get_cursor_command,
.req_cursor_notification = interface_req_cursor_notification,
.notify_update = interface_notify_update,
.flush_resources = interface_flush_resources,
.async_complete = interface_async_complete,
.update_area_complete = interface_update_area_complete,
.set_client_capabilities = interface_set_client_capabilities,
.client_monitors_config = interface_client_monitors_config,
};
/* called from spice server thread context only */
static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
{
PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
SimpleSpiceUpdate *update;
QXLCommandRing *ring;
QXLCommand *cmd;
int notify, ret;
trace_qxl_ring_command_check(qxl->id, qxl_mode_to_string(qxl->mode));
switch (qxl->mode) {
case QXL_MODE_VGA:
ret = false;
qemu_mutex_lock(&qxl->ssd.lock);
update = QTAILQ_FIRST(&qxl->ssd.updates);
if (update != NULL) {
QTAILQ_REMOVE(&qxl->ssd.updates, update, next);
*ext = update->ext;
ret = true;
}
qemu_mutex_unlock(&qxl->ssd.lock);
if (ret) {
trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode));
qxl_log_command(qxl, "vga", ext);
}
return ret;
case QXL_MODE_COMPAT:
case QXL_MODE_NATIVE:
case QXL_MODE_UNDEFINED:
ring = &qxl->ram->cmd_ring;
if (qxl->guest_bug || SPICE_RING_IS_EMPTY(ring)) {
return false;
}
SPICE_RING_CONS_ITEM(qxl, ring, cmd);
if (!cmd) {
return false;
}
ext->cmd = *cmd;
ext->group_id = MEMSLOT_GROUP_GUEST;
ext->flags = qxl->cmdflags;
SPICE_RING_POP(ring, notify);
qxl_ring_set_dirty(qxl);
if (notify) {
qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
}
qxl->guest_primary.commands++;
qxl_track_command(qxl, ext);
qxl_log_command(qxl, "cmd", ext);
trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode));
return true;
default:
return false;
}
}
6、遍历显示通道的TCP客户端并将处理后的数据发送
void RedChannel::push()
{
red_channel_foreach_client(this, &RedChannelClient::push);
}
void RedChannelClient::push()
{
if (priv->during_send) {
return;
}
priv->during_send = TRUE;
red::shared_ptr<RedChannelClient> hold_rcc(this);
if (is_blocked()) {
send();
}
if (!no_item_being_sent() && !is_blocked()) {
priv->set_blocked();
red_channel_warning(get_channel(),"ERROR: an item waiting to be sent and not blocked");
}
while (auto pipe_item = priv->pipe_item_get()) {
send_any_item(pipe_item.get());
}
/* prepare_pipe_add() will reenable WRITE events when the priv->pipe is empty
* ack_zero_messages_window() will reenable WRITE events
* if we were waiting for acks to be received
* If we don't remove WRITE if we are waiting for ack we will be keep
* notified that we can write and we then exit (see pipe_item_get) as we
* are waiting for the ack consuming CPU in a tight loop
*/
if ((no_item_being_sent() && priv->pipe.empty()) ||
priv->waiting_for_ack()) {
priv->watch_update_mask(SPICE_WATCH_EVENT_READ);
/* channel has no pending data to send so now we can flush data in
* order to avoid data stall into buffers in case of manual
* flushing
* We need to flush also in case of ack as it is possible
* that for a long train of small messages the message that would
* cause the client to send the ack is still in the queue
*/
red_stream_flush(priv->stream);
}
priv->during_send = FALSE;
}
7、先判断是不是父类RedChannelClient的命令类型。
void RedChannelClient::send_any_item(RedPipeItem *item)
{
spice_assert(no_item_being_sent());
priv->reset_send_data();
switch (item->type) {
case RED_PIPE_ITEM_TYPE_SET_ACK:
send_set_ack();
break;
case RED_PIPE_ITEM_TYPE_MIGRATE:
send_migrate();
break;
case RED_PIPE_ITEM_TYPE_EMPTY_MSG:
send_empty_msg(item);
break;
case RED_PIPE_ITEM_TYPE_PING:
send_ping();
break;
case RED_PIPE_ITEM_TYPE_MARKER:
static_cast<MarkerPipeItem*>(item)->item_sent = true;
break;
default:
send_item(item);
break;
}
}
8、处理子类(如显示通道)的命令。
void DisplayChannelClient::send_item(RedPipeItem *pipe_item)
{
DisplayChannelClient *dcc = this;
SpiceMarshaller *m = get_marshaller();
::reset_send_data(dcc);
switch (pipe_item->type) {
case RED_PIPE_ITEM_TYPE_DRAW: {
auto dpi = static_cast<RedDrawablePipeItem*>(pipe_item);
marshall_qxl_drawable(this, m, dpi);
break;
}
case RED_PIPE_ITEM_TYPE_INVAL_ONE:
marshall_inval_palette(this, m, static_cast<RedCachePipeItem*>(pipe_item));
break;
case RED_PIPE_ITEM_TYPE_STREAM_CREATE: {
auto item = static_cast<StreamCreateDestroyItem*>(pipe_item);
marshall_stream_start(this, m, item->agent);
break;
}
case RED_PIPE_ITEM_TYPE_STREAM_CLIP:
marshall_stream_clip(this, m, static_cast<VideoStreamClipItem*>(pipe_item));
break;
case RED_PIPE_ITEM_TYPE_STREAM_DESTROY: {
auto item = static_cast<StreamCreateDestroyItem*>(pipe_item);
marshall_stream_end(this, m, item->agent);
break;
}
case RED_PIPE_ITEM_TYPE_UPGRADE:
marshall_upgrade(this, m, static_cast<RedUpgradeItem*>(pipe_item));
break;
case RED_PIPE_ITEM_TYPE_MIGRATE_DATA:
display_channel_marshall_migrate_data(this, m);
break;
case RED_PIPE_ITEM_TYPE_IMAGE:
red_marshall_image(this, m, static_cast<RedImageItem*>(pipe_item));
break;
case RED_PIPE_ITEM_TYPE_PIXMAP_SYNC:
display_channel_marshall_pixmap_sync(this, m);
break;
case RED_PIPE_ITEM_TYPE_PIXMAP_RESET:
display_channel_marshall_reset_cache(this, m);
break;
case RED_PIPE_ITEM_TYPE_INVAL_PALETTE_CACHE:
dcc_palette_cache_reset(dcc);
init_send_data(SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES);
break;
case RED_PIPE_ITEM_TYPE_CREATE_SURFACE: {
auto surface_create = static_cast<RedSurfaceCreateItem*>(pipe_item);
marshall_surface_create(this, m, &surface_create->surface_create);
break;
}
case RED_PIPE_ITEM_TYPE_DESTROY_SURFACE: {
auto surface_destroy = static_cast<RedSurfaceDestroyItem*>(pipe_item);
marshall_surface_destroy(this, m, surface_destroy->surface_destroy.surface_id);
break;
}
case RED_PIPE_ITEM_TYPE_MONITORS_CONFIG: {
auto monconf_item = static_cast<RedMonitorsConfigItem*>(pipe_item);
marshall_monitors_config(this, m, monconf_item->monitors_config);
break;
}
case RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT: {
auto report_item =
static_cast<RedStreamActivateReportItem*>(pipe_item);
marshall_stream_activate_report(this, m, report_item);
break;
}
case RED_PIPE_ITEM_TYPE_GL_SCANOUT:
marshall_gl_scanout(this, m, pipe_item);
break;
case RED_PIPE_ITEM_TYPE_GL_DRAW:
marshall_gl_draw(this, m, pipe_item);
break;
default:
spice_warn_if_reached();
}
// a message is pending
if (send_message_pending()) {
::begin_send_message(this);
}
}
9、序列化命令,如以draw命令为例。
static void marshall_qxl_drawable(DisplayChannelClient *dcc,
SpiceMarshaller *m,
RedDrawablePipeItem *dpi)
{
spice_return_if_fail(dcc);
Drawable *item = dpi->drawable;
DisplayChannel *display = DCC_TO_DC(dcc);
spice_return_if_fail(display);
/* 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 && red_marshall_stream_data(dcc, m, item)) {
return;
}
if (display->priv->enable_jpeg)
marshall_lossy_qxl_drawable(dcc, m, dpi);
else
marshall_lossless_qxl_drawable(dcc, m, dpi);
}
static void marshall_lossy_qxl_drawable(DisplayChannelClient *dcc,
SpiceMarshaller *base_marshaller,
RedDrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
switch (item->red_drawable->type) {
case QXL_DRAW_FILL:
red_lossy_marshall_qxl_draw_fill(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_OPAQUE:
red_lossy_marshall_qxl_draw_opaque(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_COPY:
red_lossy_marshall_qxl_draw_copy(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_TRANSPARENT:
red_lossy_marshall_qxl_draw_transparent(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_ALPHA_BLEND:
red_lossy_marshall_qxl_draw_alpha_blend(dcc, base_marshaller, dpi);
break;
case QXL_COPY_BITS:
red_lossy_marshall_qxl_copy_bits(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_BLEND:
red_lossy_marshall_qxl_draw_blend(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_BLACKNESS:
red_lossy_marshall_qxl_draw_blackness(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_WHITENESS:
red_lossy_marshall_qxl_draw_whiteness(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_INVERS:
red_lossy_marshall_qxl_draw_inverse(dcc, base_marshaller, item);
break;
case QXL_DRAW_ROP3:
red_lossy_marshall_qxl_draw_rop3(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_COMPOSITE:
red_lossy_marshall_qxl_draw_composite(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_STROKE:
red_lossy_marshall_qxl_draw_stroke(dcc, base_marshaller, dpi);
break;
case QXL_DRAW_TEXT:
red_lossy_marshall_qxl_draw_text(dcc, base_marshaller, dpi);
break;
default:
spice_warn_if_reached();
}
}
static void red_lossy_marshall_qxl_draw_copy(DisplayChannelClient *dcc,
SpiceMarshaller *base_marshaller,
RedDrawablePipeItem *dpi)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable.get();
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(dcc, item, drawable->u.copy.src_bitmap,&drawable->u.copy.src_area, &src_bitmap_data);
src_send_type = red_marshall_qxl_draw_copy(dcc, 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(dcc, item, has_mask,src_is_lossy);
}
static FillBitsType red_marshall_qxl_draw_copy(DisplayChannelClient *dcc,
SpiceMarshaller *base_marshaller,
RedDrawablePipeItem *dpi,
int src_allowed_lossy)
{
Drawable *item = dpi->drawable;
RedDrawable *drawable = item->red_drawable.get();
SpiceMarshaller *src_bitmap_out;
SpiceMarshaller *mask_bitmap_out;
SpiceCopy copy;
FillBitsType src_send_type;
dcc->init_send_data(SPICE_MSG_DISPLAY_DRAW_COPY);
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(dcc, mask_bitmap_out, copy.mask.bitmap, item);
return src_send_type;
}