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
This commit is contained in:
parent
b3936ecadf
commit
4e89a5edde
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
205
src/shell-screen-grabber.c
Normal file
205
src/shell-screen-grabber.c
Normal file
@ -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;
|
||||
}
|
44
src/shell-screen-grabber.h
Normal file
44
src/shell-screen-grabber.h
Normal file
@ -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__ */
|
Loading…
Reference in New Issue
Block a user