[gnome-shell] ShellScreenGrabber: grab the screen using pixel buffers



commit 4e89a5edde64020bf26c4558893ddf18f53357ef
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Fri Jan 27 16:33:26 2012 -0500

    ShellScreenGrabber: grab the screen using pixel buffers
    
    For the Intel drivers, using glReadPixels() to read into client-memory
    directly from the frame buffer is much slower than creating a pixel
    buffer, copying into that, and then mapping that for reading. On other
    drivers, the two approaches are likely to be similar in speed. Create
    a ShellScreenGrabber abstraction that uses pixel buffers if available.
    Use that for screenshots and screen recording.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=669065

 configure.ac               |    2 +-
 src/Makefile.am            |    4 +
 src/shell-global.c         |   50 ++++++-----
 src/shell-recorder.c       |   16 ++--
 src/shell-screen-grabber.c |  205 ++++++++++++++++++++++++++++++++++++++++++++
 src/shell-screen-grabber.h |   44 ++++++++++
 6 files changed, 292 insertions(+), 29 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a3424cc..fe37e6e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -53,7 +53,7 @@ if $PKG_CONFIG --exists gstreamer-0.10 '>=' $GSTREAMER_MIN_VERSION ; then
    AC_MSG_RESULT(yes)
    build_recorder=true
    recorder_modules="gstreamer-0.10 gstreamer-base-0.10 x11"
-   PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes)
+   PKG_CHECK_MODULES(TEST_SHELL_RECORDER, $recorder_modules clutter-1.0 xfixes gl)
 else
    AC_MSG_RESULT(no)
 fi
diff --git a/src/Makefile.am b/src/Makefile.am
index 78ee4e5..fa6c4b5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -108,6 +108,7 @@ shell_public_headers_h =		\
 	shell-mount-operation.h		\
 	shell-network-agent.h		\
 	shell-perf-log.h		\
+	shell-screen-grabber.h		\
 	shell-slicer.h			\
 	shell-stack.h			\
 	shell-tp-client.h		\
@@ -155,6 +156,7 @@ libgnome_shell_la_SOURCES =		\
 	shell-perf-log.c		\
 	shell-polkit-authentication-agent.h	\
 	shell-polkit-authentication-agent.c	\
+	shell-screen-grabber.c		\
 	shell-slicer.c			\
 	shell-stack.c			\
 	shell-tp-client.c			\
@@ -203,6 +205,8 @@ test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
 
 test_recorder_SOURCES =     \
 	$(shell_recorder_sources) $(shell_recorder_private_sources) \
+	shell-screen-grabber.c	\
+	shell-screen-grabber.h	\
 	test-recorder.c
 endif BUILD_RECORDER
 
diff --git a/src/shell-global.c b/src/shell-global.c
index 4bc1ecf..16a8a1e 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -35,6 +35,7 @@
 #include "shell-global-private.h"
 #include "shell-jsapi-compat-private.h"
 #include "shell-perf-log.h"
+#include "shell-screen-grabber.h"
 #include "shell-window-tracker.h"
 #include "shell-wm.h"
 #include "st.h"
@@ -1990,24 +1991,37 @@ write_screenshot_thread (GSimpleAsyncResult *result,
 }
 
 static void
+do_grab_screenshot (_screenshot_data *screenshot_data,
+                    int               x,
+                    int               y,
+                    int               width,
+                    int               height)
+{
+  ShellScreenGrabber *grabber;
+  static const cairo_user_data_key_t key;
+  guchar *data;
+
+  grabber = shell_screen_grabber_new ();
+  data = shell_screen_grabber_grab (grabber, x, y, width, height);
+  g_object_unref (grabber);
+
+  screenshot_data->image = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24,
+                                                               width, height, width * 4);
+  cairo_surface_set_user_data (screenshot_data->image, &key,
+                               data, (cairo_destroy_func_t)g_free);
+}
+
+static void
 grab_screenshot (ClutterActor *stage,
                  _screenshot_data *screenshot_data)
 {
   MetaScreen *screen = shell_global_get_screen (screenshot_data->global);
-  guchar *data;
   int width, height;
-
   GSimpleAsyncResult *result;
 
   meta_plugin_query_screen_size (screenshot_data->global->plugin, &width, &height);
-  screenshot_data->image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
-  data = cairo_image_surface_get_data (screenshot_data->image);
-
-  cogl_flush();
-
-  cogl_read_pixels (0, 0, width, height, COGL_READ_PIXELS_COLOR_BUFFER, CLUTTER_CAIRO_FORMAT_ARGB32, data);
 
-  cairo_surface_mark_dirty (screenshot_data->image);
+  do_grab_screenshot (screenshot_data, 0, 0, width, height);
 
   if (meta_screen_get_n_monitors (screen) > 1)
     {
@@ -2064,20 +2078,12 @@ grab_area_screenshot (ClutterActor *stage,
                       _screenshot_data *screenshot_data)
 {
   GSimpleAsyncResult *result;
-  guchar *data;
-
-  screenshot_data->image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 
-                                                       screenshot_data->screenshot_area.width, 
-                                                       screenshot_data->screenshot_area.height);
-  data = cairo_image_surface_get_data (screenshot_data->image);
-
-  cogl_flush();
 
-  cogl_read_pixels (screenshot_data->screenshot_area.x, screenshot_data->screenshot_area.y,
-                    screenshot_data->screenshot_area.width, screenshot_data->screenshot_area.height,
-                    COGL_READ_PIXELS_COLOR_BUFFER, CLUTTER_CAIRO_FORMAT_ARGB32, data);
-
-  cairo_surface_mark_dirty (screenshot_data->image);
+  do_grab_screenshot (screenshot_data,
+                      screenshot_data->screenshot_area.x,
+                      screenshot_data->screenshot_area.y,
+                      screenshot_data->screenshot_area.width,
+                      screenshot_data->screenshot_area.height);
 
   g_signal_handlers_disconnect_by_func (stage, (void *)grab_area_screenshot, (gpointer)screenshot_data);
   result = g_simple_async_result_new (NULL, on_screenshot_written, (gpointer)screenshot_data, grab_area_screenshot);
diff --git a/src/shell-recorder.c b/src/shell-recorder.c
index f75212e..a32ad86 100644
--- a/src/shell-recorder.c
+++ b/src/shell-recorder.c
@@ -12,6 +12,7 @@
 
 #include "shell-recorder-src.h"
 #include "shell-recorder.h"
+#include "shell-screen-grabber.h"
 
 #include <clutter/x11/clutter-x11.h>
 #include <X11/extensions/Xfixes.h>
@@ -47,6 +48,8 @@ struct _ShellRecorder {
   int stage_width;
   int stage_height;
 
+  ShellScreenGrabber *grabber;
+
   gboolean have_pointer;
   int pointer_x;
   int pointer_y;
@@ -265,6 +268,8 @@ shell_recorder_init (ShellRecorder *recorder)
   recorder->recording_icon = create_recording_icon ();
   recorder->memory_target = get_memory_target();
 
+  recorder->grabber = shell_screen_grabber_new ();
+
   recorder->state = RECORDER_STATE_CLOSED;
   recorder->framerate = DEFAULT_FRAMES_PER_SECOND;
 }
@@ -296,6 +301,8 @@ shell_recorder_finalize (GObject  *object)
   recorder_set_pipeline (recorder, NULL);
   recorder_set_filename (recorder, NULL);
 
+  g_object_unref (recorder->grabber);
+
   cogl_handle_unref (recorder->recording_icon);
 
   G_OBJECT_CLASS (shell_recorder_parent_class)->finalize (object);
@@ -520,7 +527,9 @@ recorder_record_frame (ShellRecorder *recorder)
   guint size;
 
   size = recorder->stage_width * recorder->stage_height * 4;
-  data = g_malloc (size);
+
+  data = shell_screen_grabber_grab (recorder->grabber,
+                                    0, 0, recorder->stage_width, recorder->stage_height);
 
   buffer = gst_buffer_new();
   GST_BUFFER_SIZE(buffer) = size;
@@ -528,11 +537,6 @@ recorder_record_frame (ShellRecorder *recorder)
 
   GST_BUFFER_TIMESTAMP(buffer) = get_wall_time() - recorder->start_time;
 
-  cogl_read_pixels (0, 0,
-                    recorder->stage_width, recorder->stage_height,
-                    COGL_READ_PIXELS_COLOR_BUFFER,
-                    CLUTTER_CAIRO_FORMAT_ARGB32,
-                    data);
 
   recorder_draw_cursor (recorder, buffer);
 
diff --git a/src/shell-screen-grabber.c b/src/shell-screen-grabber.c
new file mode 100644
index 0000000..283e234
--- /dev/null
+++ b/src/shell-screen-grabber.c
@@ -0,0 +1,205 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include <string.h>
+
+#include <clutter/clutter.h>
+#include <cogl/cogl.h>
+#include <GL/gl.h>
+#include <GL/glx.h>
+#include <GL/glext.h>
+
+#include "shell-screen-grabber.h"
+
+PFNGLBINDBUFFERARBPROC pf_glBindBufferARB;
+PFNGLBUFFERDATAARBPROC pf_glBufferDataARB;
+PFNGLDELETEBUFFERSARBPROC pf_glDeleteBuffersARB;
+PFNGLGENBUFFERSARBPROC pf_glGenBuffersARB;
+PFNGLMAPBUFFERARBPROC pf_glMapBufferARB;
+PFNGLUNMAPBUFFERARBPROC pf_glUnmapBufferARB;
+
+struct _ShellScreenGrabberClass
+{
+  GObjectClass parent_class;
+};
+
+struct _ShellScreenGrabber
+{
+  GObject parent_instance;
+
+  int have_pixel_buffers;
+  int have_pack_invert;
+  int width, height;
+  GLuint pixel_buffer;
+};
+
+G_DEFINE_TYPE(ShellScreenGrabber, shell_screen_grabber, G_TYPE_OBJECT);
+
+static void
+shell_screen_grabber_finalize (GObject *gobject)
+{
+  ShellScreenGrabber *grabber = SHELL_SCREEN_GRABBER (gobject);
+
+  if (grabber->pixel_buffer != 0)
+    pf_glDeleteBuffersARB (1, &grabber->pixel_buffer);
+}
+
+static void
+shell_screen_grabber_class_init (ShellScreenGrabberClass *grabber_class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (grabber_class);
+
+  gobject_class->finalize = shell_screen_grabber_finalize;
+}
+
+static void
+shell_screen_grabber_init (ShellScreenGrabber *grabber)
+{
+  grabber->have_pixel_buffers = -1;
+  grabber->width = -1;
+  grabber->height= -1;
+  grabber->pixel_buffer = 0;
+}
+
+ShellScreenGrabber *
+shell_screen_grabber_new  (void)
+{
+  return g_object_new (SHELL_TYPE_SCREEN_GRABBER, NULL);
+}
+
+/**
+ * shell_screen_grabber_grab:
+ * x: X coordinate of the rectangle to grab
+ * y: Y coordinate of the rectangle to grab
+ * width: width of the rectangle to grab
+ * height: heigth of the rectangle to grab
+ *
+ * Grabs pixel data from a portion of the screen.
+ *
+ * Return value: buffer holding the grabbed data. The data is stored as 32-bit
+ *  words with native-endian xRGB pixels (i.e., the same as CAIRO_FORMAT_RGB24)
+ *  with no padding on the rows. So, the size of the buffer is width * height * 4
+ *  bytes. Free with g_free().
+ **/
+guchar *
+shell_screen_grabber_grab (ShellScreenGrabber *grabber,
+                           int                 x,
+                           int                 y,
+                           int                 width,
+                           int                 height)
+{
+  guchar *data;
+  gsize row_bytes;
+  gsize data_size;
+
+  row_bytes = width * 4;
+  data_size = row_bytes * height;
+  data = g_malloc (data_size);
+
+  if (grabber->have_pixel_buffers == -1)
+    {
+      const GLubyte* extensions = glGetString (GL_EXTENSIONS);
+      grabber->have_pixel_buffers = strstr ((const char *)extensions, "GL_EXT_pixel_buffer_object") != NULL;
+      grabber->have_pack_invert = strstr ((const char *)extensions, "GL_MESA_pack_invert") != NULL;
+    }
+
+  if (grabber->have_pixel_buffers)
+    {
+      GLubyte *mapped_data;
+      GLint old_swap_bytes, old_lsb_first, old_row_length, old_skip_pixels, old_skip_rows, old_alignment;
+      GLint old_pack_invert = GL_FALSE;
+      guchar *src_row, *dest_row;
+      int i;
+
+      cogl_flush ();
+
+      if (pf_glBindBufferARB == NULL)
+        {
+          pf_glBindBufferARB = (PFNGLBINDBUFFERARBPROC) cogl_get_proc_address ("glBindBufferARB");
+          pf_glBufferDataARB = (PFNGLBUFFERDATAARBPROC) cogl_get_proc_address ("glBufferDataARB");
+          pf_glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) cogl_get_proc_address ("glDeleteBuffersARB");
+          pf_glGenBuffersARB = (PFNGLGENBUFFERSARBPROC) cogl_get_proc_address ("glGenBuffersARB");
+          pf_glMapBufferARB = (PFNGLMAPBUFFERARBPROC) cogl_get_proc_address ("glMapBufferARB");
+          pf_glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC) cogl_get_proc_address ("glUnmapBufferARB");
+        }
+
+      glGetIntegerv (GL_PACK_SWAP_BYTES, &old_swap_bytes);
+      glGetIntegerv (GL_PACK_LSB_FIRST, &old_lsb_first);
+      glGetIntegerv (GL_PACK_ROW_LENGTH, &old_row_length);
+      glGetIntegerv (GL_PACK_SKIP_PIXELS, &old_skip_pixels);
+      glGetIntegerv (GL_PACK_SKIP_ROWS, &old_skip_rows);
+      glGetIntegerv (GL_PACK_ALIGNMENT, &old_alignment);
+
+      glPixelStorei (GL_PACK_SWAP_BYTES, GL_FALSE);
+      glPixelStorei (GL_PACK_LSB_FIRST, GL_FALSE);
+      glPixelStorei (GL_PACK_ROW_LENGTH, 0);
+      glPixelStorei (GL_PACK_SKIP_PIXELS, 0);
+      glPixelStorei (GL_PACK_SKIP_ROWS, 0);
+      glPixelStorei (GL_PACK_ALIGNMENT, 1);
+
+      if (grabber->have_pack_invert)
+        {
+          glGetIntegerv (GL_PACK_INVERT_MESA, &old_pack_invert);
+          glPixelStorei (GL_PACK_INVERT_MESA, GL_FALSE);
+        }
+
+      if (grabber->pixel_buffer != 0 &&
+          (grabber->width != width ||
+           grabber->height != height))
+        {
+          pf_glDeleteBuffersARB (1, &grabber->pixel_buffer);
+          grabber->pixel_buffer = 0;
+        }
+
+      if (grabber->pixel_buffer == 0)
+        {
+          pf_glGenBuffersARB (1, &grabber->pixel_buffer);
+
+          pf_glBindBufferARB (GL_PIXEL_PACK_BUFFER_ARB, grabber->pixel_buffer);
+          pf_glBufferDataARB (GL_PIXEL_PACK_BUFFER_ARB, data_size, 0, GL_STREAM_READ_ARB);
+
+          grabber->width = width;
+          grabber->height = height;
+        }
+      else
+        {
+          pf_glBindBufferARB (GL_PIXEL_PACK_BUFFER_ARB, grabber->pixel_buffer);
+        }
+
+      glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, 0);
+
+      mapped_data = pf_glMapBufferARB (GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB);
+
+      src_row = mapped_data + (height - 1) * row_bytes;
+      dest_row = data;
+
+      for (i = 0; i < height; i++)
+        {
+          memcpy (dest_row, src_row, row_bytes);
+          src_row -= row_bytes;
+          dest_row += row_bytes;
+        }
+
+      pf_glUnmapBufferARB (GL_PIXEL_PACK_BUFFER_ARB);
+      pf_glBindBufferARB (GL_PIXEL_PACK_BUFFER_ARB, 0);
+
+      glPixelStorei (GL_PACK_SWAP_BYTES, old_swap_bytes);
+      glPixelStorei (GL_PACK_LSB_FIRST, old_lsb_first);
+      glPixelStorei (GL_PACK_ROW_LENGTH, old_row_length);
+      glPixelStorei (GL_PACK_SKIP_PIXELS, old_skip_pixels);
+      glPixelStorei (GL_PACK_SKIP_ROWS, old_skip_rows);
+      glPixelStorei (GL_PACK_ALIGNMENT, old_alignment);
+
+      if (grabber->have_pack_invert)
+        glPixelStorei (GL_PACK_INVERT_MESA, old_pack_invert);
+    }
+  else
+    {
+      cogl_read_pixels (x, y,
+                        width, height,
+                        COGL_READ_PIXELS_COLOR_BUFFER,
+                        CLUTTER_CAIRO_FORMAT_ARGB32,
+                        data);
+    }
+
+  return data;
+}
diff --git a/src/shell-screen-grabber.h b/src/shell-screen-grabber.h
new file mode 100644
index 0000000..f5a9de0
--- /dev/null
+++ b/src/shell-screen-grabber.h
@@ -0,0 +1,44 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_SCREEN_GRABBER_H__
+#define __SHELL_SCREEN_GRABBER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/**
+ * SECTION:shell-screen-grabber
+ * @short_description: Grab pixel data from the screen
+ *
+ * The #ShellScreenGrabber object is used to download previous drawn
+ * content to the screen. It internally uses pixel-buffer objects if
+ * available, otherwise falls back to cogl_read_pixels().
+ *
+ * If you are repeatedly grabbing images of the same size from the
+ * screen, it makes sense to create one #ShellScreenGrabber and keep
+ * it around. Otherwise, it's fine to simply create one as needed and
+ * then get rid of it.
+ */
+
+typedef struct _ShellScreenGrabber      ShellScreenGrabber;
+typedef struct _ShellScreenGrabberClass ShellScreenGrabberClass;
+
+#define SHELL_TYPE_SCREEN_GRABBER              (shell_screen_grabber_get_type ())
+#define SHELL_SCREEN_GRABBER(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_SCREEN_GRABBER, ShellScreenGrabber))
+#define SHELL_SCREEN_GRABBER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_SCREEN_GRABBER, ShellScreenGrabberClass))
+#define SHELL_IS_SCREEN_GRABBER(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_SCREEN_GRABBER))
+#define SHELL_IS_SCREEN_GRABBER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_SCREEN_GRABBER))
+#define SHELL_SCREEN_GRABBER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_SCREEN_GRABBER, ShellScreenGrabberClass))
+
+GType shell_screen_grabber_get_type (void) G_GNUC_CONST;
+
+ShellScreenGrabber *shell_screen_grabber_new  (void);
+guchar *            shell_screen_grabber_grab (ShellScreenGrabber *grabber,
+                                               int                 x,
+                                               int                 y,
+                                               int                 width,
+                                               int                 height);
+
+G_END_DECLS
+
+#endif /* __SHELL_SCREEN_GRABBER_H__ */



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