Re: [gtk-vnc-devel] PATCH: 2/2: Render using cairo, allowing scaling



This patch adds support for rendering using Cairo. The configure.ac script
checks to see if cairo support is enabled in GTK, and if not will we carry
on with traditional non-cairo, non-scaling GDK rendering.

If cairo is available, it is used unconditionally whether the display is
actively scaled or not - we don't try and turn it on/off as we did for
OpenGL code.

The cairo rendering APIs can only render images which are stored in a client
side GdkPixbuf, or a server side GdkPixmap. We store our VNC display in a 
client side GdkImage, which isn't directly usable. So we have a slightly
different approach to rendering now. We keep a server side GdkPixmap with
to hold the VNC display. Whenever a VNC update comes in this updates the
GdkImage first, and then we render this to the server side GdkPixmap
immediately.

The expose event then simply has to copy the pixmap to the window, appling
the scaling operation. This is the key to high performance, because the
GdkPixmap data is stored in video memory, so the scaling  can be done with
hardware accelerated primitives.

If we agree that this cairo code is the right approach, then we could
look at further optimizing this by removing the full size client side
GdkImage, and just de-serializing the VNC updates into a small temporary
image sized to match the VNC update size.

 configure.ac     |   17 ++++++
 src/vncdisplay.c |  141 ++++++++++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 136 insertions(+), 22 deletions(-)

Notice how the GL impl we removed was 480 lines, and the new impl we're
adding is only 130

Daniel
-- 
|: Red Hat, Engineering, London   -o-   http://people.redhat.com/berrange/ :|
|: http://libvirt.org  -o-  http://virt-manager.org  -o-  http://ovirt.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: GnuPG: 7D3B9505  -o-  F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|
diff -r ddb450bc7331 configure.ac
--- a/configure.ac	Sun Sep 07 16:18:01 2008 +0100
+++ b/configure.ac	Sun Sep 07 17:16:01 2008 +0100
@@ -99,6 +99,17 @@
 PKG_CHECK_MODULES(GTK, gtk+-2.0 >= $GTK_REQUIRED)
 AC_SUBST(GTK_CFLAGS)
 AC_SUBST(GTK_LIBS)
+
+save_CFLAGS="$CFLAGS"
+save_LDFLAGS="$LDFLAGS"
+CFLAGS="$CFLAGS $GTK_CFLAGS"
+LDFLAGS="$LDFLAGS $GTK_LIBS"
+AC_CHECK_LIB([gdk], [gdk_cairo_create], [WITH_GTK_CAIRO=1], [WITH_GTK_CAIRO=0])
+CFLAGS="$save_CFLAGS"
+LDFLAGS="$save_LDFLAGS"
+
+AC_DEFINE_UNQUOTED(WITH_GTK_CAIRO, [$WITH_GTK_CAIRO], [Whether to use cairo for GTK rendering])
+
 
 AC_ARG_WITH(libview,
 [  --with-libview          enable libview support in gvncviewer],
@@ -219,6 +230,12 @@
 
 AC_OUTPUT
 
+if [ test "$WITH_GTK_CAIRO" = 1 ]; then
+  with_scaling=yes
+else
+  with_scaling=no
+fi
+
 echo "
 Configure summary:
 
diff -r ddb450bc7331 src/vncdisplay.c
--- a/src/vncdisplay.c	Sun Sep 07 16:18:01 2008 +0100
+++ b/src/vncdisplay.c	Sun Sep 07 17:16:01 2008 +0100
@@ -36,6 +36,7 @@
 	char *port;
 	GdkGC *gc;
 	GdkImage *image;
+	GdkPixmap *pixmap;
 	GdkCursor *null_cursor;
 	GdkCursor *remote_cursor;
 
@@ -262,11 +263,15 @@
 {
 	VncDisplay *obj = VNC_DISPLAY(widget);
 	VncDisplayPrivate *priv = obj->priv;
+	int ww, wh;
+#if WITH_GTK_CAIRO
+	cairo_t *cr;
+#else
 	int x, y, w, h;
 	GdkRectangle drawn;
 	GdkRegion *clear, *copy;
 	int mx = 0, my = 0;
-	int ww, wh;
+#endif
 
 	GVNC_DEBUG("Expose %dx%d @ %d,%d\n",
 		   expose->area.x,
@@ -274,17 +279,48 @@
 		   expose->area.width,
 		   expose->area.height);
 
-	if (priv->image == NULL) {
-		GdkGC *gc = gdk_gc_new(widget->window);
-		gdk_draw_rectangle(widget->window, gc, TRUE,
-				   expose->area.x, expose->area.y,
-				   expose->area.width,
-				   expose->area.height);
-		g_object_unref(gc);
-		return TRUE;
+	gdk_drawable_get_size(widget->window, &ww, &wh);
+
+#if WITH_GTK_CAIRO
+	cr = gdk_cairo_create(GTK_WIDGET(obj)->window);
+	cairo_rectangle(cr,
+			expose->area.x,
+			expose->area.y,
+			expose->area.width + 1,
+			expose->area.height + 1);
+	cairo_clip(cr);
+
+	/* Fill with background color */
+	cairo_rectangle(cr, 0, 0, ww, wh);
+	cairo_fill(cr);
+
+	/* Draw the VNC display */
+	if (priv->pixmap) {
+		if (priv->allow_scaling) {
+			double sx, sy;
+			/* Scale to fill window */
+			sx = (double)ww / (double)priv->fb.width;
+			sy = (double)wh / (double)priv->fb.height;
+			cairo_scale(cr, sx, sy);
+			gdk_cairo_set_source_pixmap(cr,
+						    priv->pixmap,
+						    0, 0);
+		} else {
+			int mx = 0, my = 0;
+			/* Apply an offset to fixed size */
+			if (ww > priv->fb.width)
+				mx = (ww - priv->fb.width) / 2;
+			if (wh > priv->fb.height)
+				my = (wh - priv->fb.height) / 2;
+			gdk_cairo_set_source_pixmap(cr,
+						    priv->pixmap,
+						    mx, my);
+		}
+		cairo_paint(cr);
 	}
 
-	gdk_drawable_get_size(widget->window, &ww, &wh);
+	cairo_destroy(cr);
+#else
 	if (ww > priv->fb.width)
 		mx = (ww - priv->fb.width) / 2;
 	if (wh > priv->fb.height)
@@ -311,16 +347,21 @@
 	copy = gdk_region_rectangle(&drawn);
 	gdk_region_subtract(clear, copy);
 
-	gdk_gc_set_clip_region(priv->gc, copy);
-	gdk_draw_image(widget->window, priv->gc, priv->image,
-		       x, y, x + mx, y + my, w, h);
+	if (priv->pixmap != NULL) {
+		gdk_gc_set_clip_region(priv->gc, copy);
+		gdk_draw_drawable(widget->window, priv->gc, priv->pixmap,
+				  x, y, x + mx, y + my, w, h);
+	}
 
 	gdk_gc_set_clip_region(priv->gc, clear);
-	gdk_draw_rectangle(widget->window, priv->gc, TRUE, expose->area.x, expose->area.y,
+	gdk_draw_rectangle(widget->window, priv->gc, TRUE,
+			   expose->area.x, expose->area.y,
 			   expose->area.width, expose->area.height);
 
 	gdk_region_destroy(clear);
 	gdk_region_destroy(copy);
+#endif
+
 
 	return TRUE;
 }
@@ -486,6 +527,7 @@
 {
 	VncDisplayPrivate *priv = VNC_DISPLAY(widget)->priv;
 	int dx, dy;
+	int ww, wh;
 
 	if (priv->gvnc == NULL || !gvnc_is_initialized(priv->gvnc))
 		return FALSE;
@@ -496,11 +538,18 @@
 	if (priv->read_only)
 		return FALSE;
 
-	{
-		int ww, wh;
+	gdk_drawable_get_size(widget->window, &ww, &wh);
+
+	if (priv->allow_scaling) {
+		double sx, sy;
+		sx = (double)ww / (double)priv->fb.width;
+		sy = (double)wh / (double)priv->fb.height;
+
+		motion->x *= sx;
+		motion->y *= sy;
+	} else {
 		int mw = 0, mh = 0;
 
-		gdk_drawable_get_size(widget->window, &ww, &wh);
 		if (ww > priv->fb.width)
 			mw = (ww - priv->fb.width) / 2;
 		if (wh > priv->fb.height)
@@ -724,12 +773,32 @@
 	GtkWidget *widget = GTK_WIDGET(opaque);
 	VncDisplay *obj = VNC_DISPLAY(widget);
 	VncDisplayPrivate *priv = obj->priv;
+	int ww, wh;
+	GdkRectangle r = { x, y, w, h };
 
-	{
-		int ww, wh;
+	/* Copy pixbuf to pixmap */
+	gdk_gc_set_clip_rectangle(priv->gc, &r);
+	gdk_draw_image(priv->pixmap, priv->gc, priv->image,
+		       x, y, x, y, w, h);
+
+	gdk_drawable_get_size(widget->window, &ww, &wh);
+
+	if (priv->allow_scaling) {
+		double sx, sy;
+
+		/* Scale the VNC region to produce expose region */
+
+		sx = (double)ww / (double)priv->fb.width;
+		sy = (double)wh / (double)priv->fb.height;
+		x *= sx;
+		y *= sy;
+		w *= sx;
+		h *= sy;
+	} else {
 		int mw = 0, mh = 0;
 
-		gdk_drawable_get_size(widget->window, &ww, &wh);
+		/* Offset the VNC region to produce expose region */
+
 		if (ww > priv->fb.width)
 			mw = (ww - priv->fb.width) / 2;
 		if (wh > priv->fb.height)
@@ -739,7 +808,7 @@
 		y += mh;
 	}
 
-	gtk_widget_queue_draw_area(widget, x, y, w, h);
+	gtk_widget_queue_draw_area(widget, x, y, w + 1, h + 1);
 
 	return TRUE;
 }
@@ -752,6 +821,8 @@
 	visual = gdk_drawable_get_visual(GTK_WIDGET(obj)->window);
 
 	priv->image = gdk_image_new(GDK_IMAGE_FASTEST, visual, width, height);
+	priv->pixmap = gdk_pixmap_new(GTK_WIDGET(obj)->window, width, height, -1);
+
 	GVNC_DEBUG("Visual mask: %3d %3d %3d\n      shift: %3d %3d %3d\n",
 		   visual->red_mask,
 		   visual->green_mask,
@@ -852,6 +923,10 @@
 	if (priv->image) {
 		g_object_unref(priv->image);
 		priv->image = NULL;
+	}
+	if (priv->pixmap) {
+		g_object_unref(priv->pixmap);
+		priv->pixmap = NULL;
 	}
 
 	if (priv->gc == NULL) {
@@ -1165,6 +1240,10 @@
 		g_object_unref(obj->priv->image);
 		obj->priv->image = NULL;
 	}
+	if (obj->priv->pixmap) {
+		g_object_unref(obj->priv->pixmap);
+		obj->priv->pixmap = NULL;
+	}
 
 	g_object_unref(G_OBJECT(data));
 	return FALSE;
@@ -1986,11 +2065,29 @@
 	obj->priv->shared_flag = shared;
 }
 
+#if WITH_GTK_CAIRO
+gboolean vnc_display_set_scaling(VncDisplay *obj,
+				 gboolean enable)
+{
+	int ww, wh;
+
+	obj->priv->allow_scaling = enable;
+
+	if (obj->priv->pixmap != NULL) {
+		gdk_drawable_get_size(GTK_WIDGET(obj)->window, &ww, &wh);
+		gtk_widget_queue_draw_area(GTK_WIDGET(obj), 0, 0, ww, wh);
+	}
+
+	return TRUE;
+}
+#else
 gboolean vnc_display_set_scaling(VncDisplay *obj G_GNUC_UNUSED,
-	gboolean enable G_GNUC_UNUSED)
+				 gboolean enable G_GNUC_UNUSED)
 {
 	return FALSE;
 }
+#endif
+
 
 void vnc_display_set_force_size(VncDisplay *obj, gboolean enabled)
 {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]