[gnome-shell] ShellScreenGrabber: grab the screen using pixel buffers
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] ShellScreenGrabber: grab the screen using pixel buffers
- Date: Tue, 31 Jan 2012 15:48:18 +0000 (UTC)
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]