Re: Threaded animation with cairo_gl backend over a GtkWidget

"Henry (Yu) Song - SISA" <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <3955FA337689574EB32F94B12A7E6E9E43491E77@sisaex01sj>
I modified your code a little as attached.  Two changes

1. add cairo_gl_device_set_thread_aware ()
2. add GLX_SAMPLES/GLX_SAMPLE_BUFFER

1. The first add is to reduce GL context switch if you have dedicated one thread for rendering (regardless where you create your gl_surface).  If you have multiple threads that render to the same gl surface, you cannot set_thread_aware (device, FALSE)

2. If your driver (proprietary nvidia, fglrx and latest intel (mesa > 9.0) on sandbridge/ivybridge) supports MSAA, you might want to try our new cairo which uses MSAA for antialiasing. It is at http://code.google.com/p/cairogles.  It is faster than gl backend in the cairo upstream and we are in process of upstreaming it.  It is also more stable.  If you want to MSAA on window/pixmap, you need to add GLX_SAMPLES and GLX_SAMPLE_BUFFERS to your glx attrib.  If you want to experiment more complex drawing, I recommend you use our gles, it is more stable and fast (I think it is the fastest backend at this moment given your driver supports MSAA, and you don't want to have ANTIALIAS_BEST/subpixel). When you use our cairogles, please set CAIRO_GL_COMPOSITOR=msaa in your environment.

3. There is a caveat when you are using gl surface in cairo at this moment.  You need to dedicate a gl context to cairo.  You cannot interleave cairo with gl calls/or other library that uses the same gl context as cairo.  If you do that, you MUST same context/drawable/any other GL states before you call gl directly and you MUST restore these before you enter cairo drawing.

Let me how pixman compare to gl in your test case,  Don't have radeon/nouveau drivers, not sure how they turn out.

Henry
________________________________________
From: [email protected] [[email protected]] on behalf of Carlos López González [[email protected]]
Sent: Tuesday, January 15, 2013 11:39 AM
To: [email protected]
Subject: Re: [cairo] Threaded animation with cairo_gl backend over a    GtkWidget

Hi!
I've modified the mentioned Cairo animation with miltithreading example [1] to use a cairo_gl_surface, expecting to have some GPU acceleration. It works fine (doesn't crash) but when I use a cairo_gl_surface it runs much slow than when I use a image surface (!). See code here:
https://github.com/genete/my-test-area/blob/direct_draw/src/cairo_sample.c

commenting the line at 135 and 137 I can use cairo_image or cairo_gl.

Same code has been compiled and run in a iMac 2011 using XQuarz as Xserver with the same result.

Am I missing something at the cairo_gl code or simply I would never could make use of my GPU in my linux laptop or my iMac?

I appreciate any help.
Thanks!

[1] http://cairographics.org/threaded_animation_with_cairo/

2013/1/15 Carlos López González <[email protected]<mailto:[email protected]>>
I forgot to CC cairo list

---------- Forwarded message ----------
From: Carlos López González <[email protected]<mailto:[email protected]>>
Date: 2013/1/15
Subject: Re: [cairo] Threaded animation with cairo_gl backend over a GtkWidget
To: Uli Schlachter <[email protected]<mailto:[email protected]>>


Hi!
I've upgraded to Ubuntu 12.10 and as part of the upgrade Ubuntu disabled the proprietary nvidia driver.
With the  the nouveau driver enabled and simply enable multithreading, it crash.

carlos@laptop:~/my-test-area/build$ LANG=C ./cairo-sample
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
cairo-sample: ../../src/xcb_io.c:178: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.
Abortado (`core' generado)

But when add XIniThreads()  as shown in the previous post, it doesn't crash but it runs *extremely* slow (?). I'm using a GeForce 8400M GS graphic card (glxgears runs smoothly).

If I enable the private nvidia-current driver it crashes in any case with the same message than I had before. The suggested breakpoint or the --sync option doesn't help much.

So which are my alternatives?

If I create the cairo_gl_surface but not using cairo_gl_surface_create_for_window but the cairo_gl_create_surface, would it have GPU acceleration as well?

I just want to create a minimal example of multithreading drawing over a gtkwidget using cairo_gl to later translate to a bigger application, but it seems that it is impossible.

Is there any *real* application that uses Cairo with GPU acceleration in production?

Thanks!


2013/1/14 Uli Schlachter <[email protected]<mailto:[email protected]>>
Hi,

On 14.01.2013 21:24, Carlos López González wrote:
[...]
> The program 'cairo-sample' received an X Window System error.
> This probably reflects a bug in the program.
> The error was 'BadMatch (invalid parameter attributes)'.
>   (Details: serial 230 error_code 8 request_code 135 minor_code 5)

minor_code 5 likely is DRI2's GetBuffer request, but could also be a
RANDRGetScreenInfo, RenderChangePicture, SHAPEQueryExtents, SHMCreatePixmap or
SyncQueryCounter. Of course, this doesn't really help us (does it?).

[...]
>   (Note to programmers: normally, X errors are reported asynchronously;
>    that is, you will receive the error a while after causing it.
>    To debug your program, run it with the --sync command line
>    option to change this behavior. You can then get a meaningful
>    backtrace from your debugger if you break on the gdk_x_error() function.)

do "break gdk_x_error" before you "run" the program.

> [Thread 0xb4738b40 (LWP 3625) exited]
> [Thread 0xb54ffb40 (LWP 3624) exited]
> [Thread 0xb6224880 (LWP 3620) exited]
> [Inferior 1 (process 3620) exited with code 01]
> (gdb) where
> No stack.
> (gdb)
>
>
> How can I add the --sync option?

If the program supports this option: "run --sync".

Uli
--
"Why make things difficult, when it is possible to make them cryptic
and totally illogical, with just a little bit more effort?" -- A. P. J.
--
cairo mailing list
[email protected]<mailto:[email protected]>
http://lists.cairographics.org/mailman/listinfo/cairo



--
Carlos
http://synfig.org



--
Carlos
http://synfig.org



--
Carlos
http://synfig.org

-- 
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
sample.c (text/x-csrc, 7 KB)
#include <stdio.h>
#include <stdlib.h>
#include <cairo.h>
#include <cairo-gl.h>
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <gdk/gdkx.h>

#include <unistd.h>
#include <pthread.h>

//the global pixmap that will serve as our buffer
static GdkPixmap *pixmap = NULL;


struct closure {
    Display *dpy;
    GLXContext ctx;
};

static void
cleanup (void *data)
{
    struct closure *arg = data;

    glXDestroyContext (arg->dpy, arg->ctx);
    XCloseDisplay (arg->dpy);

    free(arg);
}


static cairo_surface_t*
create_source_surface_for_size(int width, int height)
{

    int rgba_attribs[] = {
		GLX_RGBA,
		GLX_RED_SIZE, 1,
		GLX_GREEN_SIZE, 1,
		GLX_BLUE_SIZE, 1,
		GLX_ALPHA_SIZE, 1,
		GLX_SAMPLES, 4,
		GLX_SAMPLE_BUFFERS, 1,
		GLX_STENCIL_SIZE, 1,
		GLX_DOUBLEBUFFER,
		None };

    XVisualInfo *visinfo;
    GLXContext ctx;
    struct closure *arg;
    cairo_device_t *device;
    cairo_surface_t *surface;
    Display *dpy;

	dpy = XOpenDisplay (NULL);

    if (dpy == NULL)
		return NULL;
    visinfo = glXChooseVisual (dpy, DefaultScreen (dpy), rgba_attribs);
    if (visinfo == NULL) {
		XCloseDisplay (dpy);
		return NULL;
    }
    ctx = glXCreateContext (dpy, visinfo, NULL, True);
    XFree (visinfo);

    if (ctx == NULL) {
		XCloseDisplay (dpy);
		return NULL;
    }
    arg = (struct closure*)(malloc (sizeof (struct closure)));
    arg->dpy = dpy;
    arg->ctx = ctx;
    device = cairo_glx_device_create (dpy, ctx);
    if (cairo_device_set_user_data (device, (cairo_user_data_key_t *) cleanup, arg, cleanup))
    {
		cleanup (arg);
		return NULL;
    }

    surface = cairo_gl_surface_create (device, CAIRO_CONTENT_COLOR_ALPHA, width, height);
    cairo_device_destroy (device);

    return surface;

}


gboolean on_window_configure_event(GtkWidget * da, GdkEventConfigure * event, gpointer user_data){
    static int oldw = 0;
    static int oldh = 0;
    //make our selves a properly sized pixmap if our window has been resized
    if (oldw != event->width || oldh != event->height){
        //create our new pixmap with the correct size.
        GdkPixmap *tmppixmap = gdk_pixmap_new(da->window, event->width,  event->height, -1);
        //copy the contents of the old pixmap to the new pixmap.  This keeps ugly uninitialized
        //pixmaps from being painted upon resize
        int minw = oldw, minh = oldh;
        if( event->width < minw ){ minw =  event->width; }
        if( event->height < minh ){ minh =  event->height; }
        gdk_draw_drawable(tmppixmap, da->style->fg_gc[GTK_WIDGET_STATE(da)], pixmap, 0, 0, 0, 0, minw, minh);
        //we're done with our old pixmap, so we can get rid of it and replace it with our properly-sized one.
        g_object_unref(pixmap);
        pixmap = tmppixmap;
    }
    oldw = event->width;
    oldh = event->height;
    return TRUE;
}

gboolean on_window_expose_event(GtkWidget * da, GdkEventExpose * event, gpointer user_data){
    gdk_draw_drawable(da->window,
					  da->style->fg_gc[GTK_WIDGET_STATE(da)], pixmap,
					  // Only copy the area that was exposed.
					  event->area.x, event->area.y,
					  event->area.x, event->area.y,
					  event->area.width, event->area.height);
    return TRUE;
}


static int currently_drawing = 0;
//do_draw will be executed in a separate thread whenever we would like to update
//our animation
void *do_draw(void *ptr){

    currently_drawing = 1;

    int width, height;
    gdk_threads_enter();
    gdk_drawable_get_size(pixmap, &width, &height);
    gdk_threads_leave();

    //create a gtk-independant surface to draw on

	//cairo_surface_t *cst = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);

    cairo_surface_t *cst = create_source_surface_for_size(width, height);
    cairo_t *cr = cairo_create(cst);
    // set thread aware to FALSE to reduce context switch
    cairo_gl_device_set_thread_aware (cairo_surface_get_device (cst), FALSE);

    //do some time-consuming drawing
    static int i = 0;
    ++i; i = i % 300;   //give a little movement to our animation
    cairo_set_source_rgb (cr, .9, .9, .9);
    cairo_paint(cr);
    int j,k;
    for(k=0; k<100; ++k){   //lets just redraw lots of times to use a lot of proc power
        for(j=0; j < 1000; ++j){
            cairo_set_source_rgb (cr, (double)j/1000.0, (double)j/1000.0, 1.0 - (double)j/1000.0);
            cairo_move_to(cr, i,j/2);
            cairo_line_to(cr, i+100,j/2);
            cairo_stroke(cr);
        }
    }
    cairo_destroy(cr);


    //When dealing with gdkPixmap's, we need to make sure not to
    //access them from outside gtk_main().
    gdk_threads_enter();

    cairo_t *cr_pixmap = gdk_cairo_create(pixmap);
    cairo_set_source_surface (cr_pixmap, cst, 0, 0);
    cairo_paint(cr_pixmap);
    cairo_destroy(cr_pixmap);

    // set thread aware to true to allow context switch before we exit
    cairo_gl_device_set_thread_aware (cairo_surface_get_device (cst), TRUE);

    gdk_threads_leave();

    cairo_surface_destroy(cst);

    currently_drawing = 0;

    return NULL;
}

gboolean timer_exe(GtkWidget * window){

    static gboolean first_execution = TRUE;

    //use a safe function to get the value of currently_drawing so
    //we don't run into the usual multithreading issues
    int drawing_status = g_atomic_int_get(&currently_drawing);

    //if we are not currently drawing anything, launch a thread to
    //update our pixmap
    if(drawing_status == 0){
        static pthread_t thread_info;
        int  iret;
        if(first_execution != TRUE){
            pthread_join(thread_info, NULL);
        }
        iret = pthread_create( &thread_info, NULL, do_draw, NULL);
    }

    //tell our window it is time to draw our animation.
    int width, height;
    gdk_drawable_get_size(pixmap, &width, &height);
    gtk_widget_queue_draw_area(window, 0, 0, width, height);

    first_execution = FALSE;

    return TRUE;

}


int main (int argc, char *argv[]){


    //we need to initialize all these functions so that gtk knows
    //to be thread-aware
    //if (!g_thread_supported ()){ g_thread_init(NULL); }
    gdk_threads_init();
    gdk_threads_enter();

    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(G_OBJECT(window), "expose_event", G_CALLBACK(on_window_expose_event), NULL);
    g_signal_connect(G_OBJECT(window), "configure_event", G_CALLBACK(on_window_configure_event), NULL);

    //this must be done before we define our pixmap so that it can reference
    //the colour depth and such
    gtk_widget_show_all(window);

    //set up our pixmap so it is ready for drawing
    pixmap = gdk_pixmap_new(window->window,500,500,-1);
    //because we will be painting our pixmap manually during expose events
    //we can turn off gtk's automatic painting and double buffering routines.
    gtk_widget_set_app_paintable(window, TRUE);
    gtk_widget_set_double_buffered(window, FALSE);

    (void)g_timeout_add(120, (GSourceFunc)timer_exe, window);


    gtk_main();
    gdk_threads_leave();

    return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.