From 4e89a5edde64020bf26c4558893ddf18f53357ef Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Fri, 27 Jan 2012 16:33:26 -0500 Subject: [PATCH] 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(-) create mode 100644 src/shell-screen-grabber.c create mode 100644 src/shell-screen-grabber.h diff --git a/configure.ac b/configure.ac index a3424cce3..fe37e6e57 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 78ee4e5a4..fa6c4b58d 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 4bc1ecf6a..16a8a1e29 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" @@ -1989,25 +1990,38 @@ write_screenshot_thread (GSimpleAsyncResult *result, g_simple_async_result_set_op_res_gboolean (result, status == CAIRO_STATUS_SUCCESS); } +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 f75212eff..a32ad863c 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 #include @@ -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 000000000..283e23479 --- /dev/null +++ b/src/shell-screen-grabber.c @@ -0,0 +1,205 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include + +#include +#include +#include +#include +#include + +#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 000000000..f5a9de07e --- /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 + +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__ */