From 01dab99590012317ea12b505676380ebe9a87aea Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 13 Feb 2023 17:38:30 -0800 Subject: [PATCH] lib: add MksCairoFramebuffer This adds MksCairoFramebuffer which does tessellation to get damage regions for the framebuffer. Still some bugs involved, but good enough to get some bits on the screen which is what I'm looking for right now. --- lib/meson.build | 2 +- lib/mks-cairo-framebuffer-private.h | 47 +++ lib/mks-cairo-framebuffer.c | 498 ++++++++++++++++++++++++++++ lib/mks-framebuffer-private.h | 45 --- lib/mks-framebuffer.c | 280 ---------------- lib/mks-paintable.c | 220 ++++++++---- tools/mks.c | 2 + 7 files changed, 704 insertions(+), 390 deletions(-) create mode 100644 lib/mks-cairo-framebuffer-private.h create mode 100644 lib/mks-cairo-framebuffer.c delete mode 100644 lib/mks-framebuffer-private.h delete mode 100644 lib/mks-framebuffer.c diff --git a/lib/meson.build b/lib/meson.build index 6bbbdc7..253b3bb 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -22,7 +22,7 @@ libmks_headers = [ ] libmks_private_sources = [ - 'mks-framebuffer.c', + 'mks-cairo-framebuffer.c', 'mks-read-only-list-model.c', gnome.gdbus_codegen('mks-qemu', diff --git a/lib/mks-cairo-framebuffer-private.h b/lib/mks-cairo-framebuffer-private.h new file mode 100644 index 0000000..81dec9c --- /dev/null +++ b/lib/mks-cairo-framebuffer-private.h @@ -0,0 +1,47 @@ +/* mks-cairo-framebuffer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define MKS_TYPE_CAIRO_FRAMEBUFFER (mks_cairo_framebuffer_get_type()) + +G_DECLARE_FINAL_TYPE (MksCairoFramebuffer, mks_cairo_framebuffer, MKS, CAIRO_FRAMEBUFFER, GObject) + +MksCairoFramebuffer *mks_cairo_framebuffer_new (cairo_format_t format, + guint width, + guint height); +cairo_format_t mks_cairo_framebuffer_get_format (MksCairoFramebuffer *self); +guint mks_cairo_framebuffer_get_width (MksCairoFramebuffer *self); +guint mks_cairo_framebuffer_get_height (MksCairoFramebuffer *self); +cairo_t *mks_cairo_framebuffer_update (MksCairoFramebuffer *self, + guint x, + guint y, + guint width, + guint height); +void mks_cairo_framebuffer_copy_to (MksCairoFramebuffer *self, + MksCairoFramebuffer *dest); +void mks_cairo_framebuffer_clear (MksCairoFramebuffer *self); + +G_END_DECLS diff --git a/lib/mks-cairo-framebuffer.c b/lib/mks-cairo-framebuffer.c new file mode 100644 index 0000000..4c2a056 --- /dev/null +++ b/lib/mks-cairo-framebuffer.c @@ -0,0 +1,498 @@ +/* mks-cairo-framebuffer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include "mks-cairo-framebuffer-private.h" + +#define TILE_WIDTH 256 +#define TILE_HEIGHT 256 + +struct _MksCairoFramebuffer +{ + GObject parent_instance; + + /* The underlying surface we'll draw to */ + cairo_surface_t *surface; + + /* A GBytes that we can use to reference slices which ultimately + * references the cairo surface. This goes against the internal design + * of GdkTexture (which are supposed to be immutable) so that we can + * avoid additional copies beyond the one to the GPU. + * + * We somewhat abuse the GdkSnapshot diffing here by giving a new memory + * texture for tiles that changed even though they will point to the + * same memory. That way the renderer will upload the new contents for + * that area instead of using the previously cached texture. + */ + GBytes *content; + + /* The GdkMemoryTexture tiles we'll export using indices + * in the format [row0:col0,col1,.. to rowN:colN] + */ + GPtrArray *tiles; + + /* The format our framebuffer uses and corresponding format + * the uploaded textures will use. + */ + cairo_format_t format; + GdkMemoryFormat memory_format; + + /* The stride for the framebuffer so that the memory texture + * can skip past the rest of the framebuffer data. + */ + guint stride; + + /* Number of bytes per-pixel */ + guint bpp; + + /* The height and width of our cairo surface */ + guint height; + guint width; + + /* The number of tiles horizontally and vertically */ + guint n_columns; + guint n_rows; +}; + +enum { + PROP_0, + PROP_FORMAT, + PROP_HEIGHT, + PROP_WIDTH, + N_PROPS +}; + +static cairo_user_data_key_t invalidate_key; + +static int +mks_cairo_framebuffer_get_intrinsic_width (GdkPaintable *paintable) +{ + return mks_cairo_framebuffer_get_width (MKS_CAIRO_FRAMEBUFFER (paintable)); +} + +static int +mks_cairo_framebuffer_get_intrinsic_height (GdkPaintable *paintable) +{ + return mks_cairo_framebuffer_get_height (MKS_CAIRO_FRAMEBUFFER (paintable)); +} + +static double +mks_cairo_framebuffer_get_intrinsic_aspect_ratio (GdkPaintable *paintable) +{ + double width = gdk_paintable_get_intrinsic_width (paintable); + double height = gdk_paintable_get_intrinsic_width (paintable); + + return width / height; +} + +static void +mks_cairo_framebuffer_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + MksCairoFramebuffer *self = MKS_CAIRO_FRAMEBUFFER (paintable); + static GdkRGBA black = {0,0,0,1}; + double width_ratio = width / self->width; + double height_ratio = height / self->height; + + if (width_ratio > height_ratio) + width_ratio = height_ratio; + else + height_ratio = width_ratio; + + gtk_snapshot_save (snapshot); + gtk_snapshot_scale (snapshot, width_ratio, height_ratio); + + gtk_snapshot_append_color (snapshot, + &black, + &GRAPHENE_RECT_INIT (0, 0, self->width, self->height)); + + for (guint row = 0; row < self->n_rows; row++) + { + guint row_pos = row * self->n_columns; + + for (guint col = 0; col < self->n_columns; col++) + { + guint col_pos = row_pos + col; + + if G_UNLIKELY (self->tiles->pdata[col_pos] == NULL) + { + guint tile_y = row * TILE_HEIGHT; + guint tile_x = col * TILE_WIDTH; + + gsize byte_offset = (tile_y * self->stride) + (tile_x * self->bpp); + gsize n_bytes = (TILE_HEIGHT-1) * self->stride + (TILE_WIDTH * self->bpp); + + g_autoptr(GBytes) bytes = g_bytes_new_from_bytes (self->content, byte_offset, n_bytes); + + self->tiles->pdata[col_pos] = + gdk_memory_texture_new (TILE_WIDTH, + TILE_HEIGHT, + self->memory_format, + bytes, + self->stride); + } + + gtk_snapshot_append_texture (snapshot, + self->tiles->pdata[col_pos], + &GRAPHENE_RECT_INIT (col * TILE_WIDTH, + row * TILE_HEIGHT, + TILE_WIDTH, + TILE_HEIGHT)); + } + } + + gtk_snapshot_restore (snapshot); +} + +static void +paintable_iface_init (GdkPaintableInterface *iface) +{ + iface->get_intrinsic_width = mks_cairo_framebuffer_get_intrinsic_width; + iface->get_intrinsic_height = mks_cairo_framebuffer_get_intrinsic_height; + iface->get_intrinsic_aspect_ratio = mks_cairo_framebuffer_get_intrinsic_aspect_ratio; + iface->snapshot = mks_cairo_framebuffer_snapshot; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (MksCairoFramebuffer, mks_cairo_framebuffer, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, paintable_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static inline void +realign (guint *value, + guint alignment) +{ + guint rem = (*value) % alignment; + + if (rem != 0) + *value += (alignment - rem); +} + +static void +texture_clear (gpointer data) +{ + GdkTexture *texture = data; + + if (texture != NULL) + g_object_unref (texture); +} + +static void +mks_cairo_framebuffer_constructed (GObject *object) +{ + MksCairoFramebuffer *self = (MksCairoFramebuffer *)object; + guint real_width; + guint real_height; + + G_OBJECT_CLASS (mks_cairo_framebuffer_parent_class)->constructed (object); + + switch (self->format) + { + case CAIRO_FORMAT_ARGB32: + case CAIRO_FORMAT_RGB24: +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + self->memory_format = GDK_MEMORY_B8G8R8A8_PREMULTIPLIED; +#else + self->memory_format = GDK_MEMORY_A8R8G8B8_PREMULTIPLIED; +#endif + break; + + case CAIRO_FORMAT_A8: + case CAIRO_FORMAT_A1: + case CAIRO_FORMAT_RGB16_565: + case CAIRO_FORMAT_RGB30: + case CAIRO_FORMAT_RGB96F: + case CAIRO_FORMAT_RGBA128F: + case CAIRO_FORMAT_INVALID: + default: + g_warning ("Unsupported memory format from cairo format: 0x%x", + self->format); + return; + } + + real_width = self->width; + real_height = self->height; + + realign (&real_width, TILE_WIDTH); + realign (&real_height, TILE_HEIGHT); + + g_assert (real_width % TILE_WIDTH == 0); + g_assert (real_height % TILE_HEIGHT == 0); + + self->surface = cairo_image_surface_create (self->format, real_width, real_height); + + if (self->surface == NULL) + { + g_warning ("Cairo surface creation failed: format=0x%x width=%u height=%u", + self->format, real_width, real_height); + return; + } + + self->stride = cairo_format_stride_for_width (self->format, real_width); + self->bpp = self->stride / real_width; + + /* Currently only 4bbp are supported */ + g_assert (self->bpp == 4); + + self->content = g_bytes_new_with_free_func (cairo_image_surface_get_data (self->surface), + self->stride * real_height, + (GDestroyNotify) cairo_surface_destroy, + cairo_surface_reference (self->surface)); + + self->n_columns = real_width / TILE_WIDTH; + self->n_rows = real_height / TILE_HEIGHT; + + self->tiles = g_ptr_array_new_full (self->n_columns * self->n_rows, texture_clear); + g_ptr_array_set_size (self->tiles, self->n_columns * self->n_rows); +} + +static void +mks_cairo_framebuffer_dispose (GObject *object) +{ + MksCairoFramebuffer *self = (MksCairoFramebuffer *)object; + + g_clear_pointer (&self->content, g_bytes_unref); + g_clear_pointer (&self->surface, cairo_surface_destroy); + g_clear_pointer (&self->tiles, g_ptr_array_unref); + + G_OBJECT_CLASS (mks_cairo_framebuffer_parent_class)->dispose (object); +} + +static void +mks_cairo_framebuffer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MksCairoFramebuffer *self = MKS_CAIRO_FRAMEBUFFER (object); + + switch (prop_id) + { + case PROP_FORMAT: + g_value_set_enum (value, mks_cairo_framebuffer_get_format (self)); + break; + + case PROP_HEIGHT: + g_value_set_uint (value, mks_cairo_framebuffer_get_height (self)); + break; + + case PROP_WIDTH: + g_value_set_uint (value, mks_cairo_framebuffer_get_width (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mks_cairo_framebuffer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MksCairoFramebuffer *self = MKS_CAIRO_FRAMEBUFFER (object); + + switch (prop_id) + { + case PROP_FORMAT: + self->format = g_value_get_enum (value); + break; + + case PROP_HEIGHT: + self->height = g_value_get_uint (value); + break; + + case PROP_WIDTH: + self->width = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +mks_cairo_framebuffer_class_init (MksCairoFramebufferClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = mks_cairo_framebuffer_constructed; + object_class->dispose = mks_cairo_framebuffer_dispose; + object_class->get_property = mks_cairo_framebuffer_get_property; + object_class->set_property = mks_cairo_framebuffer_set_property; + + properties[PROP_FORMAT] = + g_param_spec_enum ("format", NULL, NULL, + CAIRO_GOBJECT_TYPE_FORMAT, + CAIRO_FORMAT_RGB24, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_HEIGHT] = + g_param_spec_uint ("height", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_WIDTH] = + g_param_spec_uint ("width", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +mks_cairo_framebuffer_init (MksCairoFramebuffer *self) +{ + self->format = CAIRO_FORMAT_RGB24; +} + +MksCairoFramebuffer * +mks_cairo_framebuffer_new (cairo_format_t format, + guint width, + guint height) +{ + g_return_val_if_fail (width > 0, NULL); + g_return_val_if_fail (height > 0, NULL); + + return g_object_new (MKS_TYPE_CAIRO_FRAMEBUFFER, + "format", format, + "height", height, + "width", width, + NULL); +} + +static void +invalidate_on_destroy (gpointer data) +{ + g_autoptr(MksCairoFramebuffer) self = data; + + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); +} + +cairo_t * +mks_cairo_framebuffer_update (MksCairoFramebuffer *self, + guint x, + guint y, + guint width, + guint height) +{ + cairo_t *cr; + guint col1, col2; + guint row1, row2; + + g_return_val_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (self), NULL); + g_return_val_if_fail (self->surface != NULL, NULL); + + col1 = MIN (x / TILE_WIDTH, self->n_columns-1); + col2 = MIN ((x + width) / TILE_WIDTH, self->n_columns-1); + + row1 = MIN (y / TILE_HEIGHT, self->n_rows-1); + row2 = MIN ((y + height) / TILE_HEIGHT, self->n_rows-1); + + for (guint row = row1; row <= row2; row++) + { + guint row_pos = row * self->n_rows; + + for (guint col = col1; col <= col2; col++) + { + guint col_pos = row_pos + col; + + g_assert (col_pos < self->tiles->len); + + g_clear_object (&self->tiles->pdata[col_pos]); + } + } + + cr = cairo_create (self->surface); + cairo_rectangle (cr, x, y, width, height); + cairo_clip (cr); + + cairo_set_user_data (cr, + &invalidate_key, + g_object_ref (self), + invalidate_on_destroy); + + return cr; +} + +void +mks_cairo_framebuffer_clear (MksCairoFramebuffer *self) +{ + cairo_t *cr; + + g_return_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (self)); + + cr = cairo_create (self->surface); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle (cr, 0, 0, + self->n_columns * TILE_WIDTH, + self->n_rows * TILE_HEIGHT); + cairo_set_source_rgba (cr, 0, 0, 0, 1); + cairo_fill (cr); + cairo_destroy (cr); +} + +cairo_format_t +mks_cairo_framebuffer_get_format (MksCairoFramebuffer *self) +{ + g_return_val_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (self), 0); + + return self->format; +} + +guint +mks_cairo_framebuffer_get_height (MksCairoFramebuffer *self) +{ + g_return_val_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (self), 0); + + return self->height; +} + +guint +mks_cairo_framebuffer_get_width (MksCairoFramebuffer *self) +{ + g_return_val_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (self), 0); + + return self->width; +} + +void +mks_cairo_framebuffer_copy_to (MksCairoFramebuffer *self, + MksCairoFramebuffer *dest) +{ + cairo_t *cr; + + g_return_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (self)); + g_return_if_fail (MKS_IS_CAIRO_FRAMEBUFFER (dest)); + + cr = cairo_create (dest->surface); + cairo_set_source_surface (cr, self->surface, 0, 0); + cairo_rectangle (cr, 0, 0, self->width, self->height); + //cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_fill (cr); + cairo_destroy (cr); +} diff --git a/lib/mks-framebuffer-private.h b/lib/mks-framebuffer-private.h deleted file mode 100644 index c151c71..0000000 --- a/lib/mks-framebuffer-private.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * mks-framebuffer-private.h - * - * Copyright 2023 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define MKS_TYPE_FRAMEBUFFER (mks_framebuffer_get_type()) - -G_DECLARE_FINAL_TYPE (MksFramebuffer, mks_framebuffer, MKS, FRAMEBUFFER, GObject) - -MksFramebuffer *mks_framebuffer_new (guint width, - guint height, - cairo_format_t format); -void mks_framebuffer_update (MksFramebuffer *self, - guint x, - guint y, - guint width, - guint height, - guint stride, - cairo_format_t format, - const guint8 *data, - gsize data_len); - -G_END_DECLS diff --git a/lib/mks-framebuffer.c b/lib/mks-framebuffer.c deleted file mode 100644 index 8e7b4d7..0000000 --- a/lib/mks-framebuffer.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * mks-framebuffer.c - * - * Copyright 2023 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include - -#include "mks-framebuffer-private.h" - -/* The surface we're drawing to. The framebuffer isn't our fast path, - * (that is DMA-BUF) but we should still try to make it reasonably - * fast for the situations we may need to support. - * - * We use a single surface and update it as new content comes in, which - * would race against the upload to the GPU except that we're on the - * same thread and therefore the GPU upload has already happened for the - * last frame if we're here already. - * - * The @damage region is updated as content changes so that we can - * calculate how many pixels were damaged since the last snapshot. If it's - * beyond our threshold ratio, then we snapshot with one big texture to - * update (the whole surface) rather than the whole surface + damage - * rectangles. - * - * The reason for this is that the GL renderer will likely already have - * our "full framebuffer" (minus recent damages) in VRAM so we can reuse - * it and then draw small damage rectangles after that (which get uploaded - * on every frame). - * - * But again, what we really are hoping for us the DMA-BUF paintable to - * get used instead. - */ - -/* The percentage of the framebuffer that must be damaged before a new - * scanout is performed instead of using damage rectangles. - */ -#define THRESHOLD_RATIO (.5) - -struct _MksFramebuffer -{ - GObject parent_instance; - cairo_surface_t *surface; - cairo_region_t *damage; - GdkPaintable *base_texture; - guint width; - guint height; - guint threshold; - cairo_format_t format; -}; - -static int -mks_framebuffer_get_intrinsic_width (GdkPaintable *paintable) -{ - return MKS_FRAMEBUFFER (paintable)->width; -} - -static int -mks_framebuffer_get_intrinsic_height (GdkPaintable *paintable) -{ - return MKS_FRAMEBUFFER (paintable)->height; -} - -static double -mks_framebuffer_get_intrinsic_aspect_ratio (GdkPaintable *paintable) -{ - double width = MKS_FRAMEBUFFER (paintable)->width; - double height = MKS_FRAMEBUFFER (paintable)->height; - - return width / height; -} - -static inline gboolean -mks_framebuffer_damage_over_threshold (MksFramebuffer *self) -{ - guint area = 0; - guint n_rects; - - g_assert (MKS_IS_FRAMEBUFFER (self)); - - n_rects = cairo_region_num_rectangles (self->damage); - - for (guint i = 0; i < n_rects; i++) - { - cairo_rectangle_int_t rect; - cairo_region_get_rectangle (self->damage, i, &rect); - area += rect.width * rect.height; - } - - return area > self->threshold; -} - -static void -mks_framebuffer_snapshot (GdkPaintable *paintable, - GdkSnapshot *snapshot, - double width, - double height) -{ - MksFramebuffer *self = MKS_FRAMEBUFFER (paintable); - guint n_rects; - - g_assert (GDK_IS_PAINTABLE (paintable)); - g_assert (GDK_IS_SNAPSHOT (snapshot)); - - if G_UNLIKELY (mks_framebuffer_damage_over_threshold (self)) - { - cairo_region_destroy (self->damage); - self->damage = cairo_region_create (); - g_clear_object (&self->base_texture); - } - - if G_UNLIKELY (self->base_texture == NULL) - { - GtkSnapshot *texture_snapshot = gtk_snapshot_new (); - cairo_t *cr; - - cr = gtk_snapshot_append_cairo (texture_snapshot, - &GRAPHENE_RECT_INIT (0, 0, self->width, self->height)); - cairo_set_source_surface (cr, self->surface, 0, 0); - cairo_rectangle (cr, 0, 0, self->width, self->height); - cairo_fill (cr); - cairo_destroy (cr); - - self->base_texture = gtk_snapshot_free_to_paintable (texture_snapshot, - &GRAPHENE_SIZE_INIT (self->width, self->height)); - } - - /* Always draw our "base texture" even though it's going to be - * composited over on the GPU. It saves us a large GPU upload since - * the GL renderer will cache the texture in VRAM in many cases. - */ - gdk_paintable_snapshot (GDK_PAINTABLE (self->base_texture), snapshot, width, height); - - /* Now draw our damage rectangles which are going to require an upload - * since we can't reuse them between frames without a lot of tracking. - * You could do that though, if you reset the damage each snapshot and - * then go and and hash/index them for re-use. - */ - n_rects = cairo_region_num_rectangles (self->damage); - for (guint i = 0; i < n_rects; i++) - { - cairo_rectangle_int_t rect; - cairo_t *cr; - - cairo_region_get_rectangle (self->damage, i, &rect); - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (rect.x, rect.y, rect.width, rect.height)); - cairo_set_source_surface (cr, self->surface, -rect.x, -rect.y); - cairo_rectangle (cr, 0, 0, rect.width, rect.height); - cairo_destroy (cr); - } -} - -static void -paintable_iface_init (GdkPaintableInterface *iface) -{ - iface->get_intrinsic_width = mks_framebuffer_get_intrinsic_width; - iface->get_intrinsic_height = mks_framebuffer_get_intrinsic_height; - iface->get_intrinsic_aspect_ratio = mks_framebuffer_get_intrinsic_aspect_ratio; - iface->snapshot = mks_framebuffer_snapshot; -} - -G_DEFINE_FINAL_TYPE_WITH_CODE (MksFramebuffer, mks_framebuffer, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, paintable_iface_init)) - -MksFramebuffer * -mks_framebuffer_new (guint width, - guint height, - cairo_format_t format) -{ - g_autoptr(MksFramebuffer) self = NULL; - cairo_t *cr; - - g_return_val_if_fail (width > 0, NULL); - g_return_val_if_fail (height > 0, NULL); - g_return_val_if_fail (format != 0, NULL); - - self = g_object_new (MKS_TYPE_FRAMEBUFFER, NULL); - self->width = width; - self->height = height; - self->format = format; - self->damage = cairo_region_create (); - self->threshold = (width * height) * THRESHOLD_RATIO; - - if (!(self->surface = cairo_image_surface_create (format, width, height))) - return NULL; - - cr = cairo_create (self->surface); - cairo_rectangle (cr, 0, 0, width, height); - cairo_set_source_rgb (cr, 0, 0, 0); - cairo_fill (cr); - cairo_destroy (cr); - - return g_steal_pointer (&self); -} - -void -mks_framebuffer_update (MksFramebuffer *self, - guint x, - guint y, - guint width, - guint height, - guint stride, - cairo_format_t format, - const guint8 *data, - gsize data_len) -{ - cairo_surface_t *surface; - cairo_t *cr; - - g_return_if_fail (MKS_IS_FRAMEBUFFER (self)); - g_return_if_fail (data != NULL || data_len == 0); - - if G_UNLIKELY (data == NULL || data_len == 0) - return; - - if (stride < width || - stride < cairo_format_stride_for_width (format, width) || - ((guint64)stride * (guint64)height) > data_len) - return; - - surface = cairo_image_surface_create_for_data ((guint8 *)data, format, width, height, stride); - cr = cairo_create (self->surface); - cairo_set_source_surface (cr, surface, x, y); - cairo_rectangle (cr, x, y, width, height); - cairo_surface_destroy (surface); - cairo_destroy (cr); - - if (x == 0 && y == 0 && width == self->width && height == self->height) - { - g_clear_pointer (&self->damage, cairo_region_destroy); - self->damage = cairo_region_create (); - } - else - { - cairo_region_union_rectangle (self->damage, - &(cairo_rectangle_int_t) { x, y, width, height }); - } -} - -static void -mks_framebuffer_finalize (GObject *object) -{ - MksFramebuffer *self = (MksFramebuffer *)object; - - g_clear_pointer (&self->surface, cairo_surface_destroy); - g_clear_pointer (&self->damage, cairo_region_destroy); - - G_OBJECT_CLASS (mks_framebuffer_parent_class)->finalize (object); -} - -static void -mks_framebuffer_class_init (MksFramebufferClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = mks_framebuffer_finalize; -} - -static void -mks_framebuffer_init (MksFramebuffer *self) -{ -} diff --git a/lib/mks-paintable.c b/lib/mks-paintable.c index db608d9..8053d5d 100644 --- a/lib/mks-paintable.c +++ b/lib/mks-paintable.c @@ -27,22 +27,19 @@ #include #include -#include "mks-framebuffer-private.h" +#include "mks-cairo-framebuffer-private.h" #include "mks-paintable-private.h" #include "mks-qemu.h" struct _MksPaintable { - GObject parent_instance; + GObject parent_instance; - MksQemuListener *listener; - GDBusConnection *connection; - MksFramebuffer *framebuffer; + MksQemuListener *listener; + GDBusConnection *connection; + MksCairoFramebuffer *framebuffer; - guint width; - guint height; - - guint mode : 2; + guint mode : 2; }; enum { @@ -80,13 +77,23 @@ _pixman_format_to_cairo_format (guint pixman_format) static int mks_paintable_get_intrinsic_height (GdkPaintable *paintable) { - return MKS_PAINTABLE (paintable)->height; + MksPaintable *self = MKS_PAINTABLE (paintable); + + if (self->mode == MODE_FRAMEBUFFER) + return gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (self->framebuffer)); + + return 0; } static int mks_paintable_get_intrinsic_width (GdkPaintable *paintable) { - return MKS_PAINTABLE (paintable)->width; + MksPaintable *self = MKS_PAINTABLE (paintable); + + if (self->mode == MODE_FRAMEBUFFER) + return gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (self->framebuffer)); + + return 0; } static double @@ -94,10 +101,10 @@ mks_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { MksPaintable *self = MKS_PAINTABLE (paintable); - if (self->width == 0 || self->height == 0) - return 1.; + if (self->mode == MODE_FRAMEBUFFER) + return gdk_paintable_get_intrinsic_aspect_ratio (GDK_PAINTABLE (self->framebuffer)); - return (double)self->width / (double)self->height; + return .0; } static void @@ -112,8 +119,7 @@ mks_paintable_snapshot (GdkPaintable *paintable, g_assert (GDK_IS_SNAPSHOT (snapshot)); if (self->mode == MODE_FRAMEBUFFER) - gdk_paintable_snapshot (GDK_PAINTABLE (self->framebuffer), - snapshot, width, height); + gdk_paintable_snapshot (GDK_PAINTABLE (self->framebuffer), snapshot, width, height); } static void @@ -135,6 +141,7 @@ mks_paintable_dispose (GObject *object) g_clear_object (&self->connection); g_clear_object (&self->listener); + g_clear_object (&self->framebuffer); G_OBJECT_CLASS (mks_paintable_parent_class)->dispose (object); } @@ -152,6 +159,60 @@ mks_paintable_init (MksPaintable *self) { } +static void +mks_paintable_set_framebuffer (MksPaintable *self, + MksCairoFramebuffer *framebuffer) +{ + gboolean size_changed; + + g_assert (MKS_IS_PAINTABLE (self)); + g_assert (!framebuffer || MKS_IS_CAIRO_FRAMEBUFFER (framebuffer)); + + if (self->framebuffer == framebuffer) + return; + + size_changed = self->framebuffer == NULL || + framebuffer == NULL || + mks_cairo_framebuffer_get_width (self->framebuffer) != mks_cairo_framebuffer_get_width (framebuffer) || + mks_cairo_framebuffer_get_height (self->framebuffer) != mks_cairo_framebuffer_get_height (framebuffer); + + if (self->framebuffer != NULL) + { + g_signal_handlers_disconnect_by_func (self->framebuffer, + G_CALLBACK (gdk_paintable_invalidate_size), + self); + g_signal_handlers_disconnect_by_func (self->framebuffer, + G_CALLBACK (gdk_paintable_invalidate_contents), + self); + g_clear_object (&self->framebuffer); + self->mode = MODE_INITIAL; + } + + if (framebuffer != NULL) + { + g_print ("Framebuffer set to %ux%u\n", + mks_cairo_framebuffer_get_width (framebuffer), + mks_cairo_framebuffer_get_height (framebuffer)); + self->framebuffer = g_object_ref (framebuffer); + g_signal_connect_object (self->framebuffer, + "invalidate-size", + G_CALLBACK (gdk_paintable_invalidate_size), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->framebuffer, + "invalidate-contents", + G_CALLBACK (gdk_paintable_invalidate_contents), + self, + G_CONNECT_SWAPPED); + self->mode = MODE_FRAMEBUFFER; + } + + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + + if (size_changed) + gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); +} + static gboolean mks_paintable_listener_update_dmabuf (MksPaintable *self, GDBusMethodInvocation *invocation, @@ -165,8 +226,6 @@ mks_paintable_listener_update_dmabuf (MksPaintable *self, g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); g_assert (MKS_QEMU_IS_LISTENER (listener)); - g_print ("Update dmabuf\n"); - mks_qemu_listener_complete_update_dmabuf (listener, invocation); return TRUE; @@ -185,21 +244,10 @@ mks_paintable_listener_scanout_dmabuf (MksPaintable *self, gboolean y0_top, MksQemuListener *listener) { - gboolean size_changed; - g_assert (MKS_IS_PAINTABLE (self)); g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); g_assert (MKS_QEMU_IS_LISTENER (listener)); - g_print ("Scanout dmabuf\n"); - - size_changed = width != self->width || height != self->height; - - if (size_changed) - gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); - else - gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); - mks_qemu_listener_complete_scanout_dmabuf (listener, invocation, NULL); return TRUE; @@ -217,34 +265,70 @@ mks_paintable_listener_update (MksPaintable *self, GVariant *bytes, MksQemuListener *listener) { + cairo_surface_t *source; + cairo_t *cr; + guint8 *data; cairo_format_t format; + gsize data_len; g_assert (MKS_IS_PAINTABLE (self)); g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); g_assert (MKS_QEMU_IS_LISTENER (listener)); - if (!(format = _pixman_format_to_cairo_format (pixman_format))) + if (self->mode != MODE_FRAMEBUFFER || + !(format = _pixman_format_to_cairo_format (pixman_format))) { g_dbus_method_invocation_return_error_literal (invocation, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "Pixman format not supported"); + "Invalid operation"); return TRUE; } - if (self->mode == MODE_FRAMEBUFFER) + data = (guint8 *)g_variant_get_bytestring (bytes); + data_len = g_variant_n_children (bytes); + + if (data_len < cairo_format_stride_for_width (format, width) * height) { - const guint8 *data; - gsize data_len; - - data_len = g_variant_n_children (bytes); - data = (const guint8 *)g_variant_get_bytestring (bytes); - - mks_framebuffer_update (self->framebuffer, x, y, width, height, stride, format, data, data_len); - - gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + g_dbus_method_invocation_return_error_literal (invocation, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Stride invalid for size"); + return TRUE; } + /* We can get in a protocol race condition here in that we will get updates + * for framebuffer content _BEFORE_ we'll get notified of property changes + * about the MksQemuConsole's size. + * + * To overcome that, if we detect something larger than our current + * framebuffer, we'll resize it and draw over the old contents in a + * new framebuffer. + * + * When shrinking, we can do this as well and then handle it when the + * console size notification arrives. + * + * Generally this is seen at startup during EFI/BIOS. + */ + if (x + width > mks_cairo_framebuffer_get_width (self->framebuffer) || + y + height > mks_cairo_framebuffer_get_height (self->framebuffer)) + { + guint max_width = MAX (mks_cairo_framebuffer_get_width (self->framebuffer), x + width); + guint max_height = MAX (mks_cairo_framebuffer_get_height (self->framebuffer), y + height); + g_autoptr(MksCairoFramebuffer) framebuffer = mks_cairo_framebuffer_new (format, max_width, max_height); + + mks_cairo_framebuffer_copy_to (self->framebuffer, framebuffer); + mks_paintable_set_framebuffer (self, framebuffer); + } + + source = cairo_image_surface_create_for_data (data, format, width, height, stride); + cr = mks_cairo_framebuffer_update (self->framebuffer, x, y, width, height); + cairo_set_source_surface (cr, source, 0, 0); + cairo_rectangle (cr, x, y, width, height); + cairo_paint (cr); + cairo_destroy (cr); + cairo_surface_destroy (source); + mks_qemu_listener_complete_update (listener, invocation); return TRUE; @@ -260,9 +344,10 @@ mks_paintable_listener_scanout (MksPaintable *self, GVariant *bytes, MksQemuListener *listener) { + cairo_surface_t *source; + cairo_t *cr; + guint8 *data; cairo_format_t format; - gboolean size_changed; - const guint8 *data; gsize data_len; g_assert (MKS_IS_PAINTABLE (self)); @@ -279,27 +364,34 @@ mks_paintable_listener_scanout (MksPaintable *self, return TRUE; } - size_changed = width != self->width || height != self->height; + data = (guint8 *)g_variant_get_bytestring (bytes); + data_len = g_variant_n_children (bytes); - if (size_changed || self->framebuffer == NULL) + if (data_len < cairo_format_stride_for_width (format, width) * height) { - g_clear_object (&self->framebuffer); - self->framebuffer = mks_framebuffer_new (width, height, format); + g_dbus_method_invocation_return_error_literal (invocation, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Stride invalid for size"); + return TRUE; } - data_len = g_variant_n_children (bytes); - data = (const guint8 *)g_variant_get_bytestring (bytes); - mks_framebuffer_update (self->framebuffer, - 0, 0, width, height, - stride, format, - data, data_len); + if (self->framebuffer == NULL || + width != mks_cairo_framebuffer_get_width (self->framebuffer) || + height != mks_cairo_framebuffer_get_height (self->framebuffer)) + { + g_autoptr(MksCairoFramebuffer) framebuffer = mks_cairo_framebuffer_new (format, width, height); - self->mode = MODE_FRAMEBUFFER; + mks_paintable_set_framebuffer (self, framebuffer); + } - if (size_changed) - gdk_paintable_invalidate_size (GDK_PAINTABLE (self)); - else - gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + source = cairo_image_surface_create_for_data (data, format, width, height, stride); + cr = mks_cairo_framebuffer_update (self->framebuffer, 0, 0, width, height); + cairo_set_source_surface (cr, source, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + cairo_paint (cr); + cairo_destroy (cr); + cairo_surface_destroy (source); mks_qemu_listener_complete_scanout (listener, invocation); @@ -320,8 +412,6 @@ mks_paintable_listener_cursor_define (MksPaintable *self, g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); g_assert (MKS_QEMU_IS_LISTENER (listener)); - g_print ("Cursor Define\n"); - mks_qemu_listener_complete_cursor_define (listener, invocation); return TRUE; @@ -339,8 +429,6 @@ mks_paintable_listener_mouse_set (MksPaintable *self, g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); g_assert (MKS_QEMU_IS_LISTENER (listener)); - g_print ("Mouse Set\n"); - mks_qemu_listener_complete_mouse_set (listener, invocation); return TRUE; @@ -355,9 +443,13 @@ mks_paintable_listener_disable (MksPaintable *self, g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); g_assert (MKS_QEMU_IS_LISTENER (listener)); - gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); + if (self->mode == MODE_FRAMEBUFFER) + { + if (self->framebuffer != NULL) + mks_cairo_framebuffer_clear (self->framebuffer); + } - g_print ("Disable\n"); + gdk_paintable_invalidate_contents (GDK_PAINTABLE (self)); mks_qemu_listener_complete_disable (listener, invocation); diff --git a/tools/mks.c b/tools/mks.c index f830e1e..5c8c123 100644 --- a/tools/mks.c +++ b/tools/mks.c @@ -66,6 +66,8 @@ main (int argc, "title", "Mouse, Keyboard, Screen", NULL); picture = g_object_new (GTK_TYPE_PICTURE, + "halign", GTK_ALIGN_CENTER, + "valign", GTK_ALIGN_CENTER, NULL); gtk_window_set_child (window, GTK_WIDGET (picture)); g_signal_connect_swapped (window,