XForms: Some news about threads and Xforms and OpenGL...

From: Nicolas Castagne (Nicolas.Castagne@imag.fr)
Date: Wed Mar 12 2003 - 16:08:52 EST

  • Next message: Steve Lamont: "Re: XForms: Some news about threads and Xforms and OpenGL..."

            Hi all,

            Thx for your advices concerning my last post "Some help about
    threads and Xforms and OpenGL...". Our program is now almost ready - with
    various threads and forked processes, and espacially threads used to
    draw in glcanvas.
            
            However, I still have some problems in managing the Expose event
    on canvas.
            
            When a handler is added to handle expose events, the Xlib
    sometimes generates GLX bad access errors ("attempt to acces private
    ressource denied" - request X_GLXMakeCurrent) - and the program aborts.
    It seems that handler for expose event cannot be added to a canvas
    "possessed" by a thread.

            Given this, I tried to superpose a FL_NO_BOX free object on the
    canvas and check for expose event on it... no success, since the free
    object does not receive Expose events...

            I then added a prehandler on the canvas and check for FL_DRAW
    events. This is, actually, my best -but still bugged - solution at that
    time.

    __________

            You will find attached a source file with a very simple program
    (should be easy to compile) which includes :
            - a form
            - a GLcanvas
            - a thread which draws a moving cube in the canvas.

            The main process creates the form and shows it ;
            The thread creates the canvas and wait for drawing.
            Drawing should occure in two cases : when the "go" button is
    pushed (the cube is then moving) and when the canvas is Exposed.

            As you will see if you run the program, everything works almost
    properly. However, in some cases, the posthandler does not receive a
    FL_DRAW event whereas it should.

            Follow, for exemple, these steps :
            1) stop moving the cube
            2) bring a SMALL window above the canvas. Make sure that the
    windows does not cross the border of the canvas.
            3) move the window a bit.

            At this step, nothing happens... The prehandler does not receive
    any FL_DRAW event, the thread does not redraw the canvas, and the canvas
    windows remains black where the window was...

            (Note that when the window crosses the canvas border, everything
    works properly... The problem only exists when the window is inside the
    canvas).

            I have tried several things to fix the problem. No success at all.
            This is of course not very important. However, it seems to be
    quite an interesting "bug" - and you may have some advices... Especially,
    I don't really see why a GLXMakeCurrent should be called before an
    ExposeEvent is managed within a canvas handler.

            Thx,

            NC

    -------------

    PS : I did not installed the version 1 yet, so I was unable to have a look
    in the XForms sources.

    PPS : it may be nice to add to the demos a program with a thread
    drawing in a canvas. In case of any advice, I may fix the bug in my
    "moving cube" program and send it back.

    --------------------------------------------------------------------
    Nicolas Castagne

    Laboratoire Informatique et Creation Artistique
    Association pour la Creation et la Recherche
                    sur les Outils d'Expression
    INPG, 46 av Felix Viallet
    38 000 Grenoble, France

    tel : (33) 4 76 57 46 60
    --------------------------------------------------------------------

    - Certains hommes parlent durant leur sommeil.
    - Il n'y a guere que les conferenciers pour parler
    - pendant le sommeil des autres.
    Alfred Camus



    /*
    This program implements a posix thread that draws into a glcanvas.


    Compiled on SGI octane aith Xforms release 0.89
    with :
    cc ThreadGL.c -o ThreadGL -O2 -g -xansi -n32 -L/usr/lib32 -B static -L$HOME/XFORMS_089_N32/FORMS -lforms -B dynamic -L/usr/X11/lib -lpthread -lGL -lGLU -lXext -lX11 -lm -I$HOME/XFORMS_089_N32/FORMS -I.

    */

    #include <stdio.h>

    #include "forms.h"
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <GL/glx.h>

    #include <pthread.h>



    /* the form */
    typedef struct {
      FL_FORM *Form;

      FL_OBJECT *GOBtn;

      /* I tried to superpose a free object on the canvas
         and check for expose event on it... no success...*/
      /* FL_OBJECT *FreeObj; */
    } TESTFORM_T;

    TESTFORM_T *TestForm;



    /* the canvas (created and added by the thread) */
    FL_OBJECT *Canvas;


    /* The threads create the canvas and perform drawings */
    pthread_t DrawThread;


    /* DrawOnMutex is used to stop drawing */
    pthread_mutex_t DrawOnMutex = PTHREAD_MUTEX_INITIALIZER;



    int ExposeEvent = 0 ;
    /* ExposeEvent is used to communicate Expose events catched (or generated) by
       the main process to the thread */
    /* ExposeEvent = 0 => no expose event currently handled
                 (value 0 is set by the main process when expose is complet
       ExposeEvent = 1 => expose requested
                 (value 1 is set by the main process)
       ExposeEvent = 2 => the threads has performed the expose
                 (value 2 is set by the thread)
    */

    /* The use of such a flag is quite a bad choice,
       since both processes spin awaiting complection
       of the expose event... Should be replaced with
       an array of mutexes or semaphores */
       




    /***************************************************************/
    /* procedures executed only within the thread */
    /***************************************************************/
    void Cube(float t);
    void DrawCanvas(int RotCube);
    void AddCanvas();
    int ExposeCanvas(int w, int h);
    void * TheThread(void *data);


    /***************************************************************/
    /* procedures executed only within the main process */
    /***************************************************************/
    int Atclose(FL_FORM *Form, void *data);
    /* callback for the button */
    void GOBtn_cb(FL_OBJECT *ob, long data);
    int CanvasPostHandler(FL_OBJECT *ob, int event,
                          FL_Coord mx, FL_Coord my, int key, void *raw_event);
    /* int handle_free(FL_OBJECT *obj, int event, FL_Coord mx, FL_Coord my, */
    /* int key, void *xev) */
    TESTFORM_T *create_form_Form(void);
    /* procedure called in case of an expose event */
    /* if no thread is used : calls ExposeCancas
       if a thread is used, ask the threads to call ExposeCanvas */
    void RqstExpose(void);



    /***************************************************************/
    /* procedures executed only within the thread */
    /***************************************************************/


    /* draw a cube of size t */
    void Cube(float t){
      /* Front */
      glBegin(GL_QUADS);
        glNormal3f( 0, 0, 1);
        glVertex3f(-t/2, t/2, t/2); glVertex3f( t/2, t/2, t/2);
        glVertex3f( t/2,-t/2, t/2); glVertex3f(-t/2,-t/2, t/2);
      glEnd();
      /* Top */
      glBegin(GL_QUADS);
        glNormal3f( 0, 1, 0);
        glVertex3f(-t/2, t/2, t/2); glVertex3f( t/2, t/2, t/2);
        glVertex3f( t/2, t/2,-t/2); glVertex3f(-t/2, t/2,-t/2);
      glEnd();
      /* Right */
      glBegin(GL_QUADS);
        glNormal3f( 1, 0, 0);
        glVertex3f( t/2, t/2, t/2); glVertex3f( t/2, t/2,-t/2);
        glVertex3f( t/2,-t/2,-t/2); glVertex3f( t/2,-t/2, t/2);
      glEnd();
      /* Bottom */
      glBegin(GL_QUADS);
        glNormal3f( 0, -1, 0);
        glVertex3f( t/2,-t/2, t/2); glVertex3f( t/2,-t/2,-t/2);
        glVertex3f(-t/2,-t/2,-t/2); glVertex3f(-t/2,-t/2, t/2);
      glEnd();
      /* Left */
      glBegin(GL_QUADS);
        glNormal3f( -1, 0, 0);
        glVertex3f(-t/2,-t/2,-t/2); glVertex3f(-t/2,-t/2, t/2);
        glVertex3f(-t/2, t/2, t/2); glVertex3f(-t/2, t/2,-t/2);
      glEnd();
      /* Rear */
      glBegin(GL_QUADS);
        glNormal3f( 0, 0, -1);
        glVertex3f(-t/2, t/2,-t/2); glVertex3f( t/2, t/2,-t/2);
        glVertex3f( t/2,-t/2,-t/2); glVertex3f(-t/2,-t/2,-t/2);
      glEnd();
      
    }


    /* draw the canvas*/
    /* if RotCube == 1 : rotate the cube a bit */
    /* if RotCube == 0 : redraw without rotation */
    void DrawCanvas(int RotCube){

      static GLfloat xRot = 0.0;
      static GLfloat yRot = 0.0;
      static GLfloat zRot = 0.0;

      if(RotCube){
        xRot+=1.0; yRot+=1.3; zRot+=2.0;
      }


      glClearColor(0.1, 0.1, 0.5, 0.);
      glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
      glLoadIdentity();

      glRotatef(xRot, 1,0,0);
      glRotatef(yRot, 0,1,0);
      glRotatef(zRot, 0,0,1);

      Cube(2);
      

      glXSwapBuffers(fl_display, fl_get_canvas_id(Canvas));
    }



    /* called by the thread */
    /* performs expose, given the size of the canvas */
    int ExposeCanvas(int w, int h){
      glViewport(0,0,w,h);

      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      gluPerspective(60.0, ((GLfloat)w)/(GLfloat)h, 1.0, 100.0);
      gluLookAt(0,0,10,0,0,0,0,1,0);


      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
      return(1);
    }


    /* called by the thread */
    /* adds a glcanvas to the form TestForm->Form*/
    void AddCanvas(){
      
      FL_OBJECT *obj ;

      glEnable(GL_LIGHTING);
      glEnable(GL_LIGHT0);
      glEnable(GL_DEPTH_TEST);

      fl_addto_form(TestForm->Form);
        Canvas = obj = fl_add_glcanvas(FL_NORMAL_CANVAS, 100, 5, 395, 390 ,"");
        fl_set_object_gravity(obj, FL_NorthWest, FL_SouthEast);
      

        /* fl_add_canvas_handler(obj, Expose, ExposeHandler, NULL);*/
        /* when an handler for ExposeEvent is used */
        /* the program sometimes generates Xlib sync. errors */
        /* and aborts */

        /* For this reason, we add a post handler on the canvas */
        /* and check for FL_DRAW events */
        fl_set_object_posthandler(obj, CanvasPostHandler);
      fl_end_form();
    }



    /* the thread itself*/
    void * TheThread(void *data)
    {
      /* The canvas must created by the thread
       since some GL variables (suc as contexts)
       are not shared between threads*/
      AddCanvas();
      
      while(1) {
        /* should the thread draw ?*/
        /* Check the mutex !*/
        pthread_mutex_lock(&DrawOnMutex) ;
        pthread_mutex_unlock(&DrawOnMutex);
        
        /* in case of an Expose Event, ExposeEvent is non zero*/
        if(ExposeEvent){
          if(ExposeEvent == 1){
            /* indicate that the ExposeEvent is gonna be performed */
            ExposeEvent = 2 ;
            /* perform the expose */
            ExposeCanvas(Canvas->w, Canvas->h);
            /* draw without moving the cube*/
            DrawCanvas(0);
          }
        }
        else {
          /* move the cube and draw */
          DrawCanvas(1);
        }
      }
    }



    /***************************************************************/
    /* procedures executed only within the main process */
    /***************************************************************/

    int Atclose(FL_FORM *Form, void *data){ exit(0); return 1 ;}



    TESTFORM_T *create_form_Form(void){
     
      FL_OBJECT *obj;
      TESTFORM_T *fdui = (TESTFORM_T *) fl_calloc(1, sizeof(*fdui));
      fdui->Form = fl_bgn_form(FL_FLAT_BOX, 500, 400);

      obj = fl_add_box(FL_FLAT_BOX,0,0,500,400 ,"");
      fl_set_object_gravity(obj, FL_NorthWest, FL_SouthEast);
      
      fdui->GOBtn = obj = fl_add_button(FL_PUSH_BUTTON,10,5,80,50,"Go !");
        fl_set_object_lalign(obj, FL_ALIGN_CENTER);
        fl_set_object_gravity(obj, FL_NorthWest, FL_NorthWest);
        fl_set_object_callback(obj,GOBtn_cb,0);

    /* fdui->FreeObj= obj = fl_add_free(FL_INACTIVE_FREE , 100, 5, 395, 390 , "", handle_free); */
    /* fl_set_object_gravity(obj, FL_NorthWest, FL_SouthEast); */
    /* fl_set_object_boxtype(obj, FL_NO_BOX); */
      fl_end_form();

      return fdui;
    }




    /* callback for the button */
    void GOBtn_cb(FL_OBJECT *ob, long data){
      if(fl_get_button(ob)){
        fl_set_object_label(ob, "Stop !");
        pthread_mutex_unlock(&DrawOnMutex);
      }
      else{
        fl_set_object_label(ob, "Go !");
        pthread_mutex_lock(&DrawOnMutex);
      }
    }


    /* post handler of the canvas */
    /* to check FL_DRAW event */
    int CanvasPostHandler(FL_OBJECT *ob, int event, FL_Coord mx, FL_Coord my, int key, void *raw_event){
      if(event == FL_DRAW)
        {
          printf("PostHandler Draw !\n");
          RqstExpose();
        }
      return 0 ;
    }


    /* int handle_free(FL_OBJECT *obj, int event, FL_Coord mx, FL_Coord my, */
    /* int key, void *xev){ */
    /* printf("handle_free\n"); */
    /* return !FL_PREEMPT ; */
    /* } */






    /* procedure called in case of an expose event */
    /* asks the threads to call ExposeCanvas
     and waits till completion */
    void RqstExpose(void) {
      ExposeEvent = 1 ;

      /* allow drawing */
      pthread_mutex_unlock(&DrawOnMutex);

      while(ExposeEvent != 2) {
        /* wait a bit to avoid wasting CPU time */
        struct timeval sleepTime;
        sleepTime.tv_sec = 0;
        sleepTime.tv_usec = 10000;
        
        select(0, NULL, NULL, NULL, &sleepTime);
      }
      if(! fl_get_button(TestForm->GOBtn)){
        pthread_mutex_lock(&DrawOnMutex);
      }
      ExposeEvent = 0 ;
    }






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


      if(XInitThreads() == 0){
        printf("************ ERROR ****************\n");
        printf("XInitThreads() is not supported on this machine.\n");
        printf("Abort\n");
        exit(1);
      }

      /* create the form */
      fl_initialize(&argc, argv, "Thread", 0, 0);
      
      TestForm = create_form_Form();
      fl_set_form_atclose(TestForm->Form , Atclose, 0);
      fl_set_form_minsize(TestForm->Form, 200, 100);

      /* show the form */
      fl_show_form(TestForm->Form,FL_PLACE_POSITION,FL_FULLBORDER,argv[0]);

      /* make sure the form is showned */
      while(fl_check_forms());
      
      {
        pthread_attr_t t_Attr;
        pthread_attr_init(&t_Attr);

        /* stop drawing */
        pthread_mutex_lock(&DrawOnMutex);
        /* create the thread.
           The thread will create the canvas, add it to the form
           and wait for drawing */
        pthread_create(&DrawThread, &t_Attr, TheThread, NULL);
        
        pthread_attr_destroy(&t_Attr);
      }


      /* make sure the thread created and added the canvas */
      {
        struct timeval sleepTime;
        sleepTime.tv_sec = 0;
        sleepTime.tv_usec = 10000;
        
        select(0, NULL, NULL, NULL, &sleepTime);
      }

      /* emulate an expose event */
      RqstExpose();


      while(1) fl_check_forms();
    }


    _________________________________________________
    To unsubscribe, send the message "unsubscribe" to
    xforms-request@bob.usuhs.mil or see
    http://bob.usuhs.mil/mailserv/xforms.html
    XForms Home Page: http://world.std.com/~xforms
    List Archive: http://bob.usuhs.mil/mailserv/list-archives/



    This archive was generated by hypermail 2b29 : Wed Mar 12 2003 - 16:06:43 EST