User avatar
ab1jx
Posts: 702
Joined: Thu Sep 26, 2013 1:54 pm
Location: Heath, MA USA
Contact: Website

So when does xlib actually do work?

Thu Sep 06, 2018 3:30 am

I wasn't entirely happy with the select trick I found before, but I also find hints that select and epoll might be the way to go. I can find this basic event loop:

Code: Select all

 XSelectInput(dpy, win1, ExposureMask | ButtonPressMask | ... );
 
  /* infinite event loop */
 while(1) {
     XEvent xev;
     XNextEvent(dpy, &xev);
     switch(xev.type) {
         case Expose:
            redraw(&xev);
            break;
        case ButtonPress:
            button(&xev);
            break;
        /* other events ... */
 }
But where does the business/user code go? I'm working on an SDR program, so far I have 3 threads for getting IQ data from the dongle, doing FFT on it, and displaying it. I have 2 queues set up, mutexes, semaphores but none of that involves any XEvents (except drawing). Do I have to create an event when I have data to draw? Or is there a place in the event loop where some actual work can get done while X is idle? Reading from the dongle is slow because there's only a certain amount of throughput, but then a semaphore tells the FFT thread that there's data for it and the dongle acquisition continues the next buffer full while the FFT is being processed. When the FFT finishes a buffer full it sends a semaphore to the display thread which does some scaling, then maybe it should generate an event. That could get X's attention to plot it.

None of the program examples I can find seem to give consideration to the idea that I want to heavily load the machine completely independently of mouse, cursor, expose events. I'm actually thinking of attaching a terminal emulator window and having the user interface be curses-based since it'll save using widgets. Just use X for the spectrum and waterfall. I'm at the point of adding the waterfall, I've been able to keep CPU usage to about 5% for both my program and Xorg, but the waterfall is a mess. I think there's some waiting/polling going on, but I'm doing a lot of XCopyArea, the load could be real.
xsdr2.png
xsdr2.png (5.86 KiB) Viewed 829 times
I saw one mention that I should really be using the X Tookit, Iintrinsics, but that seems like another hefty reading chore since I've never used it at all.

Heater
Posts: 10299
Joined: Tue Jul 17, 2012 3:02 pm

Re: So when does xlib actually do work?

Thu Sep 06, 2018 6:20 am

ab1jx,

There is much discussion and example code in this document about pthreads: https://www8.cs.umu.se/kurser/TDBC64/VT ... primer.pdf It's almost 20 years old so who knows what has changed since then. However I suspect a lot of stuff in there works on Linux today.

I do like this statement it makes about mixing pthreads and UI Threads:

It is legal to write a program that uses APIs from both libraries. It is certainly not
anything you should do voluntarily


However there are examples of communicating between GUI threads and other threads in there. You can use pipes or sockets or whatever.

Personally I think you would have a much easier time using a GUI tool kit. For example Qt. That will take care of all this and make for much nicer code. Also your finished program will than work on Linux mac and Windows.

User avatar
ab1jx
Posts: 702
Joined: Thu Sep 26, 2013 1:54 pm
Location: Heath, MA USA
Contact: Website

Re: So when does xlib actually do work?

Thu Sep 06, 2018 5:45 pm

I'm not looking for a pthreads tutorial. I have most of the program working so far, and X is a small part of it I've only recently added. But it's how to shoehorn my realworld events into the X event stream I don't get. X is event-driven, I get that, but it's so busy running in circles I'm not sure how to throw data in, and I have a lot of data. An RTL2832 dongle takes about 2 million samples/second which I process as 32K buffers full, about 60 per second. I decimate that by 8 so the buffers going to FFT are 4K. The data coming out of the FFT gets scaled and plotted on the screen, that's stable enough to run for 24 hours, no problem.

The latest stage I'm adding is called the waterfall, where peak heights in the spectrum get mapped to colors and that's drawn as a 1 pixel high line. That part of the screen is copied down 1 pixel when every new batch of data comes along, so looking at the colors in the waterfall tells the history of the spectrum. You can tell when transmissions began or ended, you can even write the time and date onto it and it should work its way down and off the screen.

This is SDR#, a popular Windows SDR program. Gqrx is similar but so inefficient it's barely usable on a Pi 3B. There isn't much for Linux that works on a Pi. Efficiency isn't a priority.
Image

What I'm trying to fit into X's schedule is copying the waterfall data down the screen, which I'm doing with XCopyArea, but it has problems so far.

My code after a moderate cleanup, minus the header and Makefile. I hear thunder out there so I'm going to upload now while I can.

Code: Select all

/*

I'm seeing a video loop because of XCopyArea, like what happens when you
point a CCTV camera at its monitor.  It's a feedback loop almost like a 
microphone squeal with audio.  Partly it's because my areas for XCopyArea
overlap.  Seems like I could make it more synchronous, don't copy until the
draw is done, have an intermediate offscreen pixmap.

video capture (for documentation):
ffmpeg -f x11grab -video_size cif -framerate 25 -i :0.0+10+10 vid1.mp4
did another at +20-+20, a slight improvement.  I'm not using a standard
aspect ratio.  omxplayer doesn't show it but smplayer does.

 // You put new data into tail (queues)
It can be done either way but you have to be consistent

*/

#include "xs6.h"  // includes and defines

struct dongle_state dongle; // librtlsdr defines this
struct rtlsdr_dev hard_dev; // actually allocates memory (not just *)
// I use classic producer-consumer queue models, with modifications
// There are 2: rfq, ftq.  3 threads, the queues pass data between.
int rfqhead = 0;
int rfqtail = 0;
int rfqempty = 1;
int rfqfull = 0;
pthread_mutex_t *rfqmut;
pthread_cond_t  *rfqNotFull;
pthread_cond_t  *rfqNotEmpty;
pthread_t rf_thread, fft_thread, dpy_thread;
uchar rfbufs[NUMRFBUFS][MAXIMUM_BUF_LENGTH];
// cloning declarations for fft queue
pthread_mutex_t *ftqmut;
pthread_cond_t  *ftqNotFull;
pthread_cond_t  *ftqNotEmpty;
fftwf_complex *fftin[NUMFFT], *fftout[NUMFFT];
fftwf_plan fftplan[NUMFFT];
int ftqhead = 0;
int ftqtail = 0;
int ftqempty = 1;
int ftqfull = 0;
int st = 1000; // sleep time (usec)
sem_t rfqnotfull,rfqnotempty,ftqnotempty,ftqnotfull, dpqnotfull,rfready, ftready;
int rfqwasempty = 0; // whether to sem_post notempty
int rfqwasfull = 0;
int ftqwasfull = 0;
int ftqwasempty = 0;
int haverf = 0; // for startup only
int spread = 66666; // delay between threads at start
// stuff for X
Display *dpy = NULL;
Window w;
int blackcolor,whitecolor;
GC copygc, ongc;
uint32_t wfc[YBINS]; // waterfall colors, from pal[]
int swidth, sheight;  // screen size
int wwidth,wheight; // plot window size
int x11_fd = 0;
int splitat = 100; // spectrum/waterfall screen split point (0 is top)
int specbot,wftop; // bottom and top of parts
int specheight = 100;  // height of top area
int all_stop = 0; // reachable by a screen button, replaces while (1)
uint16_t cbinct[YBINS]; // counts of x values in color bins

void rfinit(void); // prototype
void rfchecks(void); // check dongle parameters

// this reads the "data" block where data[2] has pll lock flag
//r82xx_read(
// this is in /tuner_r82xx.c
extern int r82xx_read(struct r82xx_priv *priv, uint8_t reg, uint8_t *val, int len);


uchar pal[][3] = {  // 16 colors for the waterfall (Learmonth Observatory):
  {0,0,0}, // 0 of the original 256 color set, using every 16th one
  {60,60,60}, //    16 so 1
  {127,127,127}, // 32 so 2
  {68,68,195}, //  48
  {131,131,250},  // 64
  {195,195,186}, // 80
  {255,246,127},  // 96
  {255,182,127}, // 112
  {242,139,127},  // 128
  {178,203,127}, // 144
  {143,238,143},  // 160
  {207,174,207},  // 176
  {234,147,255}, // 192
  {170,212,255}, // 208
  {151,255,255},  // 224
  {216,255,255},  // 240
  {255,255,255} // 256
};

void rffunc(void) {  // rf_thread  Reads the dongle
  int bread = 0;
  int toread = MAXIMUM_BUF_LENGTH - 1;
  int rslt;
  printf("Start of rffunc, toread = %i\n",toread);
  printf("Running rfinit\n");
  rfinit();
  rfchecks();
  while (all_stop == 0) { // not sure I want/need this
    rslt = rtlsdr_read_sync(mydev,(void *)&rfbufs[rfqtail],toread,&bread);
    if (rslt) {
      if (bread < toread) {
         printf("Short read\n");
         printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
         }
      else
        printf("error %i in dongle read\n",rslt);
    }
    if (rfqfull) {
       sem_wait(&rfqnotfull);
       rfqwasfull = 1;
    } 
    pthread_mutex_lock(rfqmut);  // protect head/tail calcs
    rfqtail = (rfqtail + 1) % NUMRFBUFS;
    if (rfqtail == rfqhead) { 
       rfqfull = 1;
       printf("rfq full\n");
     }
     rfqempty = 0;
     haverf = 1;
     pthread_mutex_unlock(rfqmut);
     sem_post(&rfready);  // handshake with fftfunc
  } // while not all_stop
} // /rffunc

void fftfunc(void) { // fft_thread: consumer then producer
  int i = 0;
  int j = 0;
  int lochead = 0; // local copy of head index, to free original
  // if DECIMATE is odd this won't work:
  int halfdec = (DECIMATE/2) + 1; // IQ difference for 90 degrees
//  int halfdec = 1; // both have merit, I see no signals this way
// not sure which is right
  while (all_stop == 0) {
      sem_wait(&rfready); // posted when there's new data
      pthread_mutex_lock(rfqmut); // head/tail need to be stable    
      pthread_mutex_lock(ftqmut);
      if (ftqfull) {
        sem_wait(&ftqnotfull);
        ftqwasfull = 1; 
      }
      lochead = rfqhead; // refers to RF buffers to read from
      rfqhead = (rfqhead +1) % NUMRFBUFS;
      if (rfqhead == rfqtail) 
        rfqempty = 1;
      rfqfull = 0;
      pthread_mutex_unlock(rfqmut); // done (almost) with rf data
      if (rfqwasfull) {
        sem_post(&rfqnotfull);
        rfqwasfull = 0;
      }
      ftqtail = (ftqtail + 1) % NUMRFBUFS;
      ftqempty = 0; // clear it
      if (ftqhead == ftqtail)
        ftqempty = 1;
      ftqfull = 0;
      pthread_mutex_unlock(ftqmut);
      j = 0;
      for (i=0; i<(MAXIMUM_BUF_LENGTH-halfdec); i++) {  // decimation/load
        if ((i % DECIMATE ) == 0) {
          fftin[ftqtail][j][0] = rfbufs[lochead][i];
          fftin[ftqtail][j][1] = rfbufs[lochead][i+halfdec];
          j++;
       }
      } // now really done with rf data
      fftwf_execute(fftplan[ftqtail]);    
    sem_post(&ftready);
  } // while not all_stop
} // /fftfunc

void dpyfunc(void) { // run by dpy_thread create
  float max = -99e35;
  float min = 99e35;
  int i,j;
  int lochead = 0;
  float dpybuf[FFTSIZE]; // holds demodulated values to display
  uint32_t xcaht = wheight - splitat - 2; // xcopyarea height (708)
  float yrange = 0;  // max - min
  float cinc = 0; // signal range per color
  uint16_t oldx, oldy; // each start point is last end point
  float xscale = (FFTSIZE * 1.0) / (wwidth * 1.0);
  int stop = FFTSIZE - 6; // skip ends (windowing?)
  XSegment segs[(FFTSIZE *2)+1];  // [why *2?  I forget]
  uint16_t cindex = 0;
  uint16_t cbins[YBINS][wwidth]; // data for waterfall, x locations
// This isn't well implemented here, so disabling it
//  struct timeval tv;  // used in select trick
//  fd_set in_fds;  // for select timing trick
  // this was wwidth wide when averaging:
  XPoint xpb[FFTSIZE];  // points for waterfall line
  
  printf("dpyfunc: wwidth: %i wheight: %i\n",wwidth,wheight);
  printf("dpyfunc: swidth: %i sheight: %i\n",swidth,sheight);


  while (all_stop == 0) {
// remnants of an event loop that didn't work here:
/*
    XEvent xev;
    XNextEvent(dpy, &xev);
      while (xev.type != Expose) {
        sleep(1);
      }
*/      
/*    
    switch(xev.type) {
         case Expose:
      //      redraw(&xev);
            break;
        case ButtonPress:
      //      button(&xev);
            break;
*/            
//     }  // matches while all_stop
    
    sem_wait(&ftready); // wait for data from FFT to plot
    pthread_mutex_lock(ftqmut);  // so head/tail etc are safe
    lochead = ftqhead;  // use the oldest first (FIFO)
    ftqhead = (ftqhead + 1) % NUMRFBUFS;
    if (ftqhead == ftqtail)
      ftqempty = 1;
    ftqfull = 0;
    pthread_mutex_unlock(ftqmut);
    // now the actual display stuff
    XSetForeground(dpy,ongc,0x01f5f6);  // Tektronix scope blue or close
    for (i=0; i<YBINS; i++) // zero counts each frame
      cbinct[i] = 0;
    for (i=2; i<FFTSIZE; i++) {  // sum of squares AM demod
      dpybuf[i] = sqrt(((fftout[lochead][i][0]) * (fftout[lochead][i][0])) + \
       ((fftout[lochead][i][1]) * (fftout[lochead][i][1])));
    } // end for
    max = -99e35; min = 99e35; // bogus tmp vals, reset each FFT batch
    for (i=2; i<(FFTSIZE-10); i++) {  // minmax loop
      if (dpybuf[i] > max) {
        max = dpybuf[i];
      }
      if (dpybuf[i] < min)
        min = dpybuf[i];
    } // end minmax
    yrange = max - min;
    cinc = yrange/YBINS; // color increment: signal range per color
//printf("yrange %f = %f - %f, cinc = %f\n",yrange,max,min,cinc);
    for (i=0; i<YBINS; i++) // zero counts each frame
      cbinct[i] = 0;
    oldx=0; oldy=0; i = 2; j = 0;
    while (i < stop) {  // work through values in dpybuf
      if ((j % 2) == 0) { // even segs are old for erasing (by xor)
        segs[j] = segs[j+1]; // copy for erasing
        j++;
      } else {  // these are new lines
        segs[j].x1 = oldx;  // start point is old end point
        segs[j].y1 = oldy;
        oldx = ((i * 1.0)/((FFTSIZE * 1.0) - 8.0)) * (wwidth * 1.0);
        oldy = splitat - ((((dpybuf[i]-min) * 1.0) / (wwidth * 1.0)) * splitat);
        segs[j].x2 = oldx;
        segs[j].y2 = oldy;
        cindex = oldy / cinc;  // color for waterfall pixel
        cbins[cindex][cbinct[cindex]] = oldx; // store pixel x position
        cbinct[cindex]++; // increment count
        j++;  // destination index in segs
      } // else odd
      i++;  // somewhere about here
    }  // while i
    
// line segments drawn as a batch are fast, it takes work to set them up

    XDrawSegments(dpy,w,ongc,segs,j-1);  // spectrum display at top
    
/*
  XCopyArea eats huge amounts of CPU here but it may be hitting a wait/poll 
  loop.  It's 50 times as much CPU as the whole rest of the program uses.
  
  Then again it's running much faster (frames/second) than it needs to.  
  I could live with 2 frames/second, it's running as fast as it can.
*/

// this copies the waterfall area down 1 pixel
    XCopyArea(dpy,w,w,copygc,0,splitat+1,wwidth,xcaht,0,splitat+2);
     
     
// this draws a new line at the top (which then gets copied down)    
// different x positions are different colors depending on signal strengths
// XDrawPoints is the fastest way to draw them (as a batch)
    for (cindex=0; cindex<YBINS; cindex++) {  // draw line
      if (cbinct[cindex] > 0) { // skip unused colors
        XSetForeground(dpy,ongc,wfc[cindex]);
        // need to be in XPoint format first (could do this ahead of this loop)
        for (i=0; i<cbinct[cindex]; i++) {
          xpb[i].x = cbins[cindex][i]; // x values are stored earlier
          xpb[i].y = splitat +1; //  new multicolored line to copy down
        }
        // draw the set of points that need to be in cindex color:
        XDrawPoints(dpy,w,ongc,xpb,cbinct[cindex],CoordModeOrigin);
      }
    } 

     XFlush(dpy);  // at end of drawing
  
/*  
  // this is an old ugly timing trick:
https://www.linuxquestions.org/questions/showthread.php?p=2431345#post2431345  
    FD_ZERO(&in_fds);
    FD_SET(x11_fd, &in_fds);
    tv.tv_usec = 200000;  // 1/5 second
    tv.tv_sec = 0;
    if (select(x11_fd+1, &in_fds, 0, 0, &tv))
      printf("Event Received! %u\n",(unsigned int) time(NULL));
      // some loop here?  The select blocks?
    else {
      // Handle timer here (original comment, had other contents)
      // wasteland but part of the select trick
    } // end else
*/    

  
  } // while all_stop
  
}

// I'm not doing any (FFT) windowing, forget how.  I drop the first and last 
// points which is most of it.

/*
  dpyfunc1 is an early version that's not used anymore.  It averaged FFTSIZE
  bins down into wwidth pixels.  The problem was that minmax was done on raw
  values and I was plotting averaged values so peaks were smoothed out.
*/

void dpyfunc1(void) {  // dpy_thread version 1 with averaging
  int i = 0;
  int j = 0, n;
  float accum = 0.90; //  huh?
  int count = 0;
  float yrange = 0;
  fd_set in_fds;  // for select timing trick
  struct timeval tv;
  float max = -99e35;
  float min = 99e35;
  int lochead = 0;
  int modskip = 0;
  short oldx, oldy; // previous point, not trace
  float dpybuf[FFTSIZE];
  XSegment segs[(FFTSIZE *2)+1];
  XPoint xpb[wwidth];  // points for waterfall line
  int istop, stop;
  float yval = 0;
  uint16_t cbins[YBINS][wwidth]; // data for waterfall, x locations
  uint16_t cindex = 0;
  int ci;  // cindex test
  int cbct;  // color bin count, tmp test
  float cinc = 0;
  int xcaht = wheight - splitat - 2; // xcopyarea height (708)
  printf("dpyfunc before while\n");
  printf("wwidth: %i wheight: %i\n",wwidth,wheight);
  printf("swidth: %i sheight: %i\n",swidth,sheight);
  modskip = FFTSIZE/wwidth;
  printf("modskip = %i\n",modskip);
//  XSetForeground(dpy,ongc,0x01f5f6);  // Tektronix scope blue or close [moved]
  while (all_stop == 0) {
    sem_wait(&ftready);
    pthread_mutex_lock(ftqmut);
    lochead = ftqhead;  // use the oldest first (FIFO)
    ftqhead = (ftqhead + 1) % NUMRFBUFS;
    if (ftqhead == ftqtail)
      ftqempty = 1;
    ftqfull = 0;
    pthread_mutex_unlock(ftqmut);
    // now the actual display stuff
    XSetForeground(dpy,ongc,0x01f5f6);  // Tektronix scope blue or close    
    for (i=0; i<YBINS; i++) // zero counts each frame
      cbinct[i] = 0;
    // is this the right demodulation? [for AM yes]
//    for (i=2; i<(FFTSIZE-1); i++) {
    for (i=2; i<FFTSIZE; i++) {
      dpybuf[i] = sqrt(((fftout[lochead][i][0]) * (fftout[lochead][i][0])) + \
       ((fftout[lochead][i][1]) * (fftout[lochead][i][1])));
    } // end for
    max = -99e35; min = 99e35; // bogus tmp vals    
    for (i=2; i<(FFTSIZE-10); i++) {  // minmax loop (not on averages)
      if (dpybuf[i] > max) {
        max = dpybuf[i];
      }
      if (dpybuf[i] < min)
        min = dpybuf[i];
    } // end minmax
    yrange = max - min;    
    cinc = yrange/YBINS; // signal range per color
printf("yrange %f = %f - %f, cinc = %f\n",yrange,max,min,cinc);    
/*    
// this is for doing auto screenshots
    if (yrange > 12000) {  
      snap = 1;
      now_t = time(NULL);
      snprintf(timestr,100,"/usr/bin/xwd -display ':0.0' -root -out /tmp/%u.xwd",\
       (unsigned int) now_t);
    }
*/    
    j=0; i=0; oldx=0; oldy=0; 
    stop = FFTSIZE - modskip;
    while (i < stop) {
      if ((j % 2) == 0) {
        segs[j] = segs[j+1]; // for erasing
        j++;
      } else {
        segs[j].x1 = oldx;  // start point is old end point
        segs[j].y1 = oldy;  
        // FFTSIZE bins are scaled into wwidth pixels
        oldx = ((i * 1.0)/(FFTSIZE * 1.0)) * (wwidth * 1.0); // fudged in
        segs[j].x2 = oldx;
        n = i;  
        count = 0;
        accum = 0;
        istop = i + modskip;
        // y is from an averaged pixel value
        while ((n < istop) && (n < stop)) {  // averaging stats
          accum += dpybuf[n];
          count++;
          n++;
        }
        yval = accum / (count * 1.0);  // average
/*        
 moving minmax here (can't): needs to be overall

 Need yrange to set cinc to set cindex for each point, but you don't know 
 yrange until all the points in the FFT run have been processed.        
 
 Need to do 2(or 3?) loops.  Store the yvals then do a ranking on them and that
 becomes cindex after cinc can be set.  [skipping averaging for now]
*/

        cindex = yval / cinc;
/*
   The yval is the live averaged value of ~3 samples.  cinc is the signal 
   range per color increment (max-min)/YBINS. Dividing an unknown incoming
   yval/cinc gives cindex which is the bin to put it into.  Each cbins[] holds
   x values of dots to draw later.  (cbins[cindex][x vals]) cbct is a count of
   how many are in each one.
   
   The minmax is done on the not-averaged values.  The averaging knocks down
   the peaks.  But cinc is set from not-averaged so cindex never comes close
   to 16.  Need to move the minmax.
   
*/        
        // store oldx into array (draw a dot at this x value later)
        ci = oldx;  // not atomic?  don't tie up oldx?
        cbins[cindex][cbinct[cindex]] = ci;
        // incrementing the count screws up drawing (making cbinct global fixes)
        cbct = cbinct[cindex];  // copy out
printf("yval %f ci %i cinc %f cindex %u\n",yval,ci,cinc,cindex);
        cbct++;
// when I change cbinct[cindex] it screws up        
// only if cbinct is a local var?  Looks that way
//        cbinct[cindex] = cbct;
// with globals this may work:
        cbinct[cindex]++;  // atomic?
//        cbinct[cindex] = cbinct[cindex] + 1;
//  end wf
        oldy = specheight - (((yval - min)/yrange) * specheight);  // scale
//printf("specheight: %i yval: %f min: %f oldy: %hi yranage: %f\n",specheight,yval,min,oldy,yrange);        
        segs[j].y2 = oldy;
//printf("segs[%i].x1: %hi y1: %hi x2: %hi, y2: %hi\n",j,segs[j].x1,segs[j].y1,segs[j].x2,segs[j].y2);        
        j++;
      } // end else a new segment
      i += modskip;
    }  // while i < stop

// cbins looks OK, seems odd that they're all low, may need fix
/*
printf("cbin dump:\n");
for (i=0; i<YBINS; i++) {
  printf("%i\t%hu\n",i,cbinct[i]);
}
*/
    XDrawSegments(dpy,w,ongc,segs,j-1);  // spectrum display
    
    // move down 1 pixel
/*
  From man page:
  int XCopyArea(Display *display, Drawable src, Drawable dest, GC gc, int
              src_x, int src_y, unsigned int width, unsigned height, int
              dest_x, int dest_y);
*/    

/*
    here: splitat: 100 wwidth: 640 xcaht: 708
    splitat+1 is where the colored pixel data is drawn
    splitat+2 is the top of the copied-in area
    
    XGetSubImage may work better here
*/

//printf("XCopyArea gets splitat: %i wwidth: %i xcaht: %i\n",splitat,wwidth,xcaht);

   XCopyArea(dpy,w,w,ongc,0,splitat+1,wwidth,xcaht,0,splitat+2);
   
    
    // waterfall (line)
// when I started enabling waterfall stuff I got odd stuff at left
///*    
    for (cindex=0; cindex<YBINS; cindex++) {
      if (cbinct[cindex] > 0) { // skip unused colors
        XSetForeground(dpy,ongc,wfc[cindex]);
        
        // need to be in XPoint format first (could do this ahead)
        for (i=0; i<cbinct[cindex]; i++) {
          xpb[i].x = cbins[cindex][i];
          xpb[i].y = splitat +1;
        }
        XDrawPoints(dpy,w,ongc,xpb,cbinct[cindex],CoordModeOrigin);
      }
    
    }
//*/    
    // need to reset color after loop
    
/*
  I think I can do something like bin = yval / binsize
  
  where binsize is yrange/YBINS
  
  bin needs to be an int, then store the oldx values into a collection of them
  chosen by bin as the index
  
  Afterwards each bin holds the x values of all the pixels to plot in that color,
  to feed to XDrawPoints in YBINS batches
  
  declare bins as [YBINS][wwidth] to be sure each is big enough
  changing width by dragging will probably segfault
  
*/
    
//    XFlush(dpy);  // less frantic without this
/*    
if (snap > 0) {  // screenshot
  system(timestr);
  usleep(500000);
  printf("Snapped at %u\n",(unsigned int) now_t);
  snap = 0;
}
*/

  } // /while all_stop
} // dpyfunc1 (obsolete)


void gainloop(void) {  // try gain settings in a loop, 5 seconds each
  int rslt;
  int i;
  int g = 0;
  for (i=0; i<100; i++) { // not forever
    rslt = rtlsdr_set_tuner_gain(mydev,g);
    if (rslt) {
      printf("return code %i at gain setting %i\n",rslt,g);
    }
    printf("gain is %i\n",g);
    sleep(5);
    g = (g +1) % 29;  // circular mod 29
  }
}

/*

librtlsdr doesn't expose all of my tuner's settings, I haven't worked that out 
yet.  Having a working waterfall so I could see signal history would be
helpful so I went to that.

mydev is : struct rtlsdr_dev
which contains a struct r82xx_priv r82xx_p
which is defined in tuners/tuner_r82xx.h
and has a has_lock (int) field

But that may not be populated since it's done by r82xx_set_pll in tuner_r82xx.c
Could be done elsewhere too.  That does r82xx_read() to read the data array
then looks at (data[2] & 0x40).  It sets the has_lock field based on that.

grep -r shows has_lock is in librtlsdr as well as tuner_r82xx

r82xx_read is similar.  nm shows r82xx_read.constprop.2, that's only in 
binaries by grep.  Could try something like:
 rc = r82xx_read(priv, 0x00, data, sizeof(data));
in tuner_r82xx.c there's
static int r82xx_read(struct r82xx_priv *priv, uint8_t reg, uint8_t *val, int le
n)

That gets an undefined reference error

Which, looking at it does:
rtlsdr_i2c_write_fn
rtlsdr_i2c_read_fn
then copies output 

Trying the parts, same idea, not in header, probably not library either
*/

// from r82xx_set_pll() in tuner_r82xx.c 
/*  on hold
int checklock(void) {  
  int rslt;
  int rc; // return code?  from int r82xx_read
  uint8_t data[5];
//  rc = rtlsdr_i2c_write_fn(mydev, priv->cfg->i2c_addr, priv->buf,1);
  
//  rslt = r82xx_read(&mydev->r82xx_p,0x00,data,sizeof(data));
//  rslt = r82xx_read.constprop.2(mydev->r82xx_p,0x00,data,sizeof(data));
  

//  if (!(data[2] & 0x40)) {
//    fprintf(stderr, "[R82XX] PLL not locked!\n");
    
  //  priv->has_lock = 0;
    return 0;  // change
   }
   return 0;
}
*/

void rfchecks(void) { // verify settings
  uint32_t tmp32,t32;
  int rslt;
  int gain = 0; // selected gain value
  char *rmfg = NULL; // returned mfg * (from libusb really)
  char *rprd = NULL; // returned product ID
  char *rser = NULL; // returned serial
  int locked;
  tmp32 = rtlsdr_get_center_freq(mydev);
  printf("readback ctr freq: %u\n",tmp32);
  tmp32 = rtlsdr_get_device_count();
  printf("readback device count: %u\n",tmp32);
  printf("readback device name: %s\n",rtlsdr_get_device_name(0));
  // usb strings don't work
  rslt = rtlsdr_get_device_usb_strings(0,rmfg,rprd,rser);
  printf("readback usb code %i mfg %s product %s serial %s\n",rslt,rmfg,rprd,rser);
  rslt = rtlsdr_get_direct_sampling(mydev);
  printf("readback direct sampling: %i\n",rslt);
  rslt = rtlsdr_get_freq_correction(mydev);
  printf("readback freq correction %i\n",rslt);
  rslt = rtlsdr_get_offset_tuning(mydev);
  printf("readback offset tuning %i\n",rslt);
  tmp32 = rtlsdr_get_sample_rate(mydev);
  printf("readback sample rate %u\n",tmp32);
  rtlsdr_get_xtal_freq(mydev,&tmp32,&t32);
  printf("readback rtl freq %u tuner freq %u\n",tmp32,t32);
// I think this only gets the list of values anyway, didn't bother  
//  rtlsdr_get_tuner_gains(mydev,gains);  // works(?)
//  printf("readback tuner gains: %i\n",*gains);  // crashes
  gain =  rtlsdr_get_tuner_gain(mydev);
  printf("readback gain: %i\n",gain);  // enum(?)  1st test I get 7,
  // but I can set this to 0
  // in a struct rtlsdr_dev is struct r82xx_priv r82xx_p which has has_lock
  // hard_dev.  It's complicated, need a bitmask
  locked = mydev->r82xx_p.has_lock;
  printf("PLL locked: %i\n",locked);
// this is in tuner_r82xx.c but not public/exported
//  rslt = r82xx_set_pll(mydev->r82xx_p,dongle.freq);
  // this is all the rtl_get commands I can find in librtlsd.c
}

// try async, maybe also the r820 init function
void rfinit(void) { // mostly dongle stuff
  int rslt = 0;
  int i = 0;
  uint32_t newfreq = 0;
  
  // the dongle.* don't actually do much, the sets do
  dongle.dev = NULL;  // rtlsdr_dev_t, set in rtlsdr_open()
  dongle.dev_index = 0;
  // hard-coding frequencies to monitor for now:
//  dongle.freq = 88450000;  // near WFCR
  dongle.freq = 88494000; 
//  dongle.freq = 93000000; // randomish FM band garbage
//  dongle.freq = 118252000; // aircraft band somewhere
//  dongle.freq = 162400000; // wx tx area
//  dongle.freq = 27000000; // CB band
//  dongle.freq = 626000000; // near TV channel 40
//  dongle.gain = GAIN;
// there are 30 gain values, trying the highest one:
dongle.gain = 29; // from list, quick test
  // not sure what buf16 is, doesn't fit queue scheme
  bzero(dongle.buf16,2 * MAXIMUM_BUF_LENGTH);
  dongle.buf_len = MAXIMUM_BUF_LENGTH;
  dongle.ppm_error = PPM;
  dongle.offset_tuning = 0;
  dongle.direct_sampling = 0;
  dongle.mute = 0;
  dongle.demod_target = NULL;  // struct demod_state
  mydev = &hard_dev;

  rslt = rtlsdr_open(&mydev,(uint32_t) dongle.dev_index);
  if (rslt != 0) {
    printf("Dongle open failed.\n");
    exit(1);
  }
  rslt = rtlsdr_reset_buffer(mydev);  // put first as a test
  if (rslt)
    printf("Result from rtlsdr_reset_buffer %i\n",rslt);  
  rslt = rtlsdr_set_tuner_gain_mode(mydev,0); // 1 is manual
  if (rslt)
    printf("Set gain mode result %i\n",rslt);
//#undef GAIN  // set in xs6.h, tmp override
//#define GAIN 29
  rslt = rtlsdr_set_tuner_gain(mydev,GAIN); 
  if (rslt)
    printf("Set tuner gain result %i\n",rslt);
/*
See the tuner gains note in librtlsdr.c.  In 10ths of a db for the r820 they're
 0, 9, 14, 27, 37, 77, 87, 125, 144, 157, 166, 197, 207, 229, 254, 280, 297, 
 328, 338, 364, 372, 386, 402, 421, 434, 439, 445, 480, 496.
 But in use: specify a gain or the index to the array?  Index I think.
*/    
  rslt = rtlsdr_set_tuner_if_gain(mydev,1,31);  // was 31
  if (rslt)
    printf("Set tuner IF gain result %i\n",rslt);
  rslt = rtlsdr_set_center_freq(mydev,dongle.freq);
  if (rslt)
    printf("Set center freq result %i\n",rslt);
  newfreq = rtlsdr_get_center_freq(mydev);
  printf("Current ctr freq %u\n",newfreq);
  rslt = rtlsdr_set_freq_correction(mydev,PPM);
  if (rslt)
    printf("Set PPM result %i\n",rslt);
  rslt = rtlsdr_set_sample_rate(mydev,250000);  // 250k  [why?]
// 250,000 on readback, I thought it waas 2,500,000
// see rtlsdr_set_sample_rate() in librtlsdr.c
  if (rslt)
    printf("Set sample rate result %i\n",rslt);

printf("End of rfinit dongle settings\n");
  // fft init
  for (i=0;i<NUMFFT; i++) {
    fftin[i] = (fftwf_complex *) fftwf_malloc(sizeof(fftwf_complex) *  FFTSIZE);
    if (fftin[i] == NULL) {
      printf("malloc() of space for fftin failed.\n");
      perror("malloc ");
      exit(1);
    }
    fftout[i] = (fftwf_complex *)fftwf_malloc(sizeof(fftwf_complex) * FFTSIZE);
    if (fftout[i] == NULL) {
      printf("malloc() of space for fftout failed.\n");
      perror("malloc ");
      fftwf_free(fftin);
      exit(1);
    } 
    fftplan[i] = fftwf_plan_dft_1d(FFTSIZE,fftin[i],fftout[i],FFTW_FORWARD,FFTW_ESTIMATE);
  }

}

void queuesetup(void) {
  int rslt = 0;
// rf queue  
  rfqhead = 0;
  rfqtail = 0;
  rfqempty = 1;
  rfqfull = 0;
  rfqmut = (pthread_mutex_t *) calloc(sizeof (pthread_mutex_t),1);
  rslt = pthread_mutex_init (rfqmut,NULL);
  if (rslt) printf("Error doing init on rfqmut\n");
  rfqNotFull = (pthread_cond_t *) calloc(sizeof (pthread_cond_t),1);
  pthread_cond_init (rfqNotFull, NULL);
  rfqNotEmpty = (pthread_cond_t *) calloc(sizeof (pthread_cond_t),1);
  pthread_cond_init (rfqNotEmpty, NULL);
// fft queue
  ftqhead = 0;
  ftqtail = 0;
  ftqempty = 1;
  ftqfull = 0;
  ftqmut = (pthread_mutex_t *) calloc(sizeof (pthread_mutex_t),1);
  pthread_mutex_init (ftqmut,NULL);
  ftqNotFull = (pthread_cond_t *) calloc(sizeof (pthread_cond_t),1);
  pthread_cond_init (ftqNotFull, NULL);
  ftqNotEmpty = (pthread_cond_t *) calloc(sizeof (pthread_cond_t),1);
  pthread_cond_init (ftqNotEmpty, NULL);
  // threads
  // this cast looks bizarre but it was copied from the  compiler warning
  rslt = pthread_create(&rf_thread, NULL, (void * (*)(void *)) rffunc, NULL);
  if (rslt != 0) {
    printf("Error %i creating RF acquisition thread\n",rslt);
    exit(1);
  } else {
    printf("Created RF thread\n");
  }
/*
   I start these threads offset by spread because otherwise the later stages
   are starved for input and hang.  It still needed the handshaking added.
*/
  usleep(spread);
  rslt = pthread_create(&fft_thread, NULL, (void * (*)(void *)) fftfunc, NULL);
  if (rslt != 0) {
    printf("Error %i creating FFT thread.\n",rslt);
    exit(1);
  } else {
    printf("Created FFT thread\n");
  }        
  usleep(spread);
  rslt = pthread_create(&dpy_thread, NULL, (void * (*)(void *)) dpyfunc, NULL);
  if (rslt != 0) {
    printf("Error %i creating display thread.\n",rslt);
    exit(1);
  } else {
    printf("Created display thread\n");
  }
// rflock,ftlock,dpylock
}

void xinit(void) {  // not the real xinit, bad name choice
  XEvent e; // a struct
  XSetWindowAttributes wa;
  int i;
// copied from fpanel, st3.c
  dpy = XOpenDisplay(NULL);
  if (dpy == NULL) {
    printf("XOpenDisplay failed.\n");
    exit(1);
  }
  Visual *visual=DefaultVisual(dpy, 0);
  if (visual == NULL) {
    printf("Getting the display's default visual failed.\n");
    exit(1);
  }
  Screen *scrn = DefaultScreenOfDisplay(dpy);
  if (scrn == NULL) {
    printf("Error getting default screen\n");
    exit(1);
  }
  swidth = WidthOfScreen(scrn);
  sheight = HeightOfScreen(scrn);
  specbot = splitat - 1; // need to do when changing panes (top)
  specheight = specbot; // since it starts at 0
  wftop = splitat + 1; // (bottom)
//  wwidth = 0.6 * swidth; // initial test value, expose later
wwidth = 640; // for video (and conveniently out of the way)
  wheight = 0.75 * sheight;

/*
  turn on backingstore WhenMapped  (can't do this with simple window create)
  XChangeWindowAttributes maybe 
  
  define   CWBackingStore              (1L<<6) [attribute]
  
  not sure of this:
  you or the attributes together so just CWBackingStore might work
  attributes and valuemask are confusing
  
  Maybe see https://tronche.com/gui/x/xlib/window/attributes/
  
  uses a valuemask and an attributes (XSetWindowAttributes)
  
  Don't really need backing store, just blank on expose and start over.  Not
  that simple with xor drawing cycles.   See the expose event handler.
*/ 
printf("wwidth: %i wheight: %i\n",wwidth,wheight);  
   w = XCreateSimpleWindow(dpy, RootWindow(dpy, 0), 0, 0, wwidth, \
    wheight, 1, 0, 0);
  if(visual->class!=TrueColor) {
    printf("Sorry, this only works on TrueColor\n");
    exit(1);
  }
  // there are 1000s of events, mask out some of them
  XSelectInput(dpy, w, KeyPressMask|ButtonPressMask|ExposureMask);
  copygc = DefaultGC(dpy, 0);
  ongc = DefaultGC(dpy, 0);
  for (i=0; i<YBINS; i++) {  // make 32 bit colors
    wfc[i] = (pal[i][2]<<24) + (pal[i][1]<<16) + pal[i][0];
  }
  XSetFunction(dpy,ongc,GXxor); // sets xor mode
  XMapWindow(dpy,w);
  whitecolor = WhitePixel(dpy, DefaultScreen(dpy));
  blackcolor = BlackPixel(dpy, DefaultScreen(dpy));
  x11_fd = ConnectionNumber(dpy);
  printf("xinit created and mapped a window\n");  // I see this
// trying a mapnotify wait loop copied from rlines
// can maybe use XSelectInput to mask & only get MapNotify?
// yes, see Tronche https://tronche.com/gui/x/xlib-tutorial/2nd-program-anatomy.html
// seems to work, I was worried about missing this event in the noise
// But this undoes the last XSelectInput I think. [I only need to detect map]
  XSelectInput(dpy, w, StructureNotifyMask);
  for (;;) {
    XNextEvent(dpy,&e);  // fetch an event
    if (e.type == MapNotify)  // done when we find one
      break;
  }
  printf("xinit after mapping\n"); // doesn't get here
}

/*
  Use ExposureMask and look for an Expose event
  
  and/or set window backing store to WhenMapped [no: too cpu-intensive]
*/

void processEvent(void) {
  XEvent ev;
  XNextEvent(dpy, &ev);
  if (ev.type == Expose) {
    printf("Expose event (old)\n");
//    XClearWindow(dpy,w);

/*    
 xclearwindow may be working, need to undo the xor too?
 If you draw an odd number of times it won't go away (except by clearing)
 but if you clear halfway through then resume it comes back permanantly.
 
 And this set I'm drawing is half erase (evens).  Could clear then reset so
 the evens didn't get drawn to erase (which draws if blank).
 
 Clearing works, but the odd/even xor stuff gets messed up.  The next routine
 line drawn will never get erased because it's unpaired.
 
 Send a semaphore to the drawing routine?
 The expose event just sets the semaphore, the drawing detects the semephore,
 clears and restarts from scratch?  Need to synch them I think.
 
 An atomic variable which the drawing routines can check in their drawing
 cycles would do the trick.  Expose event sets it, finishing a normal draw 
 clears it.
 
 Good discussion:
 http://math.msu.su/%7Evvb/2course/Borisenko/CppProjects/GWindow/xintro.html

*/
    
    
/*    
  blanks by filling the window with black:
    XSetForeground(dpy,ongc,blackcolor);
    XFillRectangle(dpy,w,ongc,0,0,wwidth,wheight);
    XFlush(dpy);
*/
  }
}

// XSetWMName  somewhere sometime, needs an XString

int main(void) {
  XInitThreads();  // absolutely first
  xinit(); // not the real one :)
  queuesetup(); // starts threads
  XSelectInput(dpy, w, ExposureMask);
  while (all_stop == 0) { // main loop [eats CPU, a sleep in it helps]
//    gainloop();  // doesn't accomplish a lot
    while (XPending(dpy)) processEvent();  // my (old) handler
//    sleep(1); // free cpu
    usleep(50000);
//    printf("usleep in main()\n");
/*  
sleep(300); // free the cpu this long
printf("waiting loop in main()\n");
*/
  }  // while not all_stop

  sleep(2); // stay open a bit, main() thread ends
  return 0;
} // end main

Heater
Posts: 10299
Joined: Tue Jul 17, 2012 3:02 pm

Re: So when does xlib actually do work?

Thu Sep 06, 2018 6:36 pm

ab1jx,
I'm not looking for a pthreads tutorial. I have most of the program working so far, and X is a small part of it I've only recently added. But it's how to shoehorn my realworld events into the X event stream I don't get. X is event-driven, I get that, but it's so busy running in circles I'm not sure how to throw data in, and I have a lot of data.
Sorry I missed a part from my post. I meant to write something like "There is much discussion and example code in this document about using pthreads with xlib"

The problem you have there is very common. There is the GUI with it's event loop reacting to keyboard, mouse etc events but you also have a "real-time" thing going on in its own thread. How to connect those two together? I believe that document discusses how to do that at the low level of the xlib and pthreads libraries.

I also believe that a higher level of abstraction, like the Qt GUI libraries, provides solutions to that problem. I have done such things with Qt before, though perhaps not at the data rates you have there.

It's a shame you cannot look at the source code of baudline http://www.baudline.com/ which has been doing this kind of FFT waterfall for a couple of decades now.

bzt
Posts: 218
Joined: Sat Oct 14, 2017 9:57 pm

Re: So when does xlib actually do work?

Fri Sep 07, 2018 3:13 pm

ab1jx wrote:
Thu Sep 06, 2018 3:30 am
But where does the business/user code go? I'm working on an SDR program, so far I have 3 threads for getting IQ data from the dongle, doing FFT on it, and displaying it. I have 2 queues set up, mutexes, semaphores but none of that involves any XEvents (except drawing). Do I have to create an event when I have data to draw? Or is there a place in the event loop where some actual work can get done while X is idle?
This loop is just a basic event loop, there's no place for business logic there. That's usually implemented (partially, as long as the view layer concerned) in the redraw() function. The XNextEvent call will block until an event arrives. You can use other functions like XCheckWindowEvent, which does not block, and create a real-time busy loop (like the ones usually found in games), and do the logic in the loop, but I'd not recommend that.
Reading from the dongle is slow because there's only a certain amount of throughput, but then a semaphore tells the FFT thread that there's data for it and the dongle acquisition continues the next buffer full while the FFT is being processed. When the FFT finishes a buffer full it sends a semaphore to the display thread which does some scaling, then maybe it should generate an event. That could get X's attention to plot it.
Exactly.
None of the program examples I can find seem to give consideration to the idea that I want to heavily load the machine completely independently of mouse, cursor, expose events. I'm actually thinking of attaching a terminal emulator window and having the user interface be curses-based since it'll save using widgets. Just use X for the spectrum and waterfall. I'm at the point of adding the waterfall, I've been able to keep CPU usage to about 5% for both my program and Xorg, but the waterfall is a mess. I think there's some waiting/polling going on, but I'm doing a lot of XCopyArea, the load could be real.
No, deep inside xlib XNextEvent uses a blocking socket read (either unix socket or network). Considering this, maybe it would be better to have a child process for handling the user interface. That way there's no problem if the OS (unaware of user space threads) blocks the entire process when you call XNextEvent.
I saw one mention that I should really be using the X Tookit, Iintrinsics, but that seems like another hefty reading chore since I've never used it at all.
X could seem a bit hard, I give you that. That's mostly because of the underlying client-server model and lots of legacy stuff, which you won't use. For example I'm pretty sure you never have to deal with black and white visuals, and you've forgotten to call XFlushDisplay :-)

Here's my suggestion to keep it as simple as possible:
1. you create a shared buffer with mmap()
2. you open a window
3. you do a fork() call: the child will be the ui process, the parent remains the main process with pthreads and business logic
4. when the main process has updated the shared buffer, it sends an Expose event to the window (because you've opened the window before forking, your main process will know the window id)
5. your ui process is blocked waiting for XEvents in a loop (independently to your logic). If it receives a mouse or keyboard event, it acts accordingly. If it receives an Expose event, it will redraw the window using data from the shared buffer. It doesn't matter if the Expose originated from the window manager or from your main process, thus your code will be kept simple. Also this way you can keep the main logic separated from the ui stuff.

Hopefully this helps,
bzt

User avatar
ab1jx
Posts: 702
Joined: Thu Sep 26, 2013 1:54 pm
Location: Heath, MA USA
Contact: Website

Re: So when does xlib actually do work?

Fri Sep 07, 2018 6:39 pm

I took my XFlush(dpy)s out, because they seemed like something you only needed to do it you were in a hurry and X would repaint when it got around to it anyway. (It runs without them) But thank you. I haven't dealt with black and white visuals since maybe 1995 but I did have a few Hercules setups, maybe I got X going on one. Yes, there's a lot of cruft from those days, color maps and all that, including GCs. I have a reasonably complete set of the O'reilly X books as PDFs and spent lots of time studying stuff I didn't need. Neat to see how it works, and the history, though.

I've tried GTK tutorials, and Glade, and QT, but the most useful was an Xlib tutorial by Tronche. Which I turned into a 20 line or so template https://www.raspberrypi.org/forums/view ... ?p=1317849 and later studied how the parts worked. About 1993 I taught myself 8088 assembly language (after learning BASIC, Pascal, C) and I enjoyed that because I could make things happen (relatively) instantaneously. Reading a mouse position and moving the cursor in a loop was just too slow in C.

So I prefer low-level stuff when possible. I read there were drawbacks to fork (wasteful of resources I think). I wonder if I could have the Xlib stuff running in its own thread. I'm using 3 now. Prepare the XPoint and XSegment arrays in dpyfunc, then set a semaphore that it's ready to plot (generate an Xevent?). And don't change the arrays for the next cycle until the Xlib thread sets a semaphore that it's done. I went to using semaphores in addition to mutexes and pthread_cond_t because it's possible to wait for one without wasting CPU in a polling loop (If I remember right).

What puzzles me now is that colors in the waterfall change randomly instead of being set once and copying down and off the screen. I'll find it. There's a mistake in my bit-shifting math I fixed. Doing XCopyArea with overlapping areas I've never been sure about. Setting up an intermediate offscreen pixmap is maybe my next step. I've done it all in framebuffer graphics with memoves, this is an experiment at working within the Xlib graphics world.

bzt
Posts: 218
Joined: Sat Oct 14, 2017 9:57 pm

Re: So when does xlib actually do work?

Sat Sep 08, 2018 10:09 am

Okay, good resources and experience always help :-)

Well, fork wastes resources only if you don't know how to use it correctly. In this scenario I suggest to create an mmap buffer and a window, the bare minimum that needs to be shared among the processes. Open files and allocate everything else *after* the fork in your main process, that way you won't waste memory or duplicate file descriptors. Also you won't do fork all the time, but call it once on application startup, so fork's slow performance is not an issue either.

You could try to mess around with threads and Xlib, but I'm sure it's easier and better to do one single fork and let the OS block the child process. Much simpler solution and requires less modification to your existing code. You know when XNextEvent was first implemented, there was no such thing as pthread, so I'm not sure it can be done at all. The main problem here is, reading from an empty socket blocks the whole process, and as pthread is implemented in a user space library, the kernel does not know about your threads. This is opposed to Solaris' lightweight processes, where the kernel is aware of the multiple threads, so it can block one of them and keep the rest running. But if you insist to avoid fork, then you'll have to use a non-blocking XEvent check in your Xlib thread, and your code is going to waste more CPU for sure.

About colors, I recommend to forget about hardware palettes and color maps, these days visuals use true color only anyway. The universal solution here would be something like this (should work for hicolor visuals too, the point is to have packed pixel format):

Code: Select all

// initialization
vsl = DefaultVisual(display, screen);
if(vsl->bits_per_rgb!=8){
   // not a truecolor visual, very unlikely
   exit(1);
}
red_shift=green_shift=blue_shift=24;
for (i=vsl->red_mask;!(i&0x80000000);i<<=1) red_shift--;
for (i=vsl->green_mask;!(i&0x80000000);i<<=1) green_shift--;
for (i=vsl->blue_mask;!(i&0x80000000);i<<=1) blue_shift--;
Now you can convert an R,G,B triplet into a pixel for your visual (regardless if your visual is 24 or 32 bits, and if channel order is RGB or BGR):

Code: Select all

unsigned long int MkPixelForVisual(int r, int g, int b) {
  if (red_shift>=0) r<<=red_shift; else r>>=-red_shift;
  if (green_shift>=0) g<<=green_shift; else g>>=-green_shift;
  if (blue_shift>=0) b<<=blue_shift; else b>>=-blue_shift;
  r&=vsl->red_mask;
  g&=vsl->green_mask;
  b&=vsl->blue_mask;
  return r|g|b;
}
I suggest to calculate the 16 colors you need in a pixel array on startup once, and then use that as a software palette.

There are several ways (including setting up a GC and using XLine), but for a graph I'd personally prefer to create an XImage (in the same format as your visual), manually plot pixels in ximage->data buffer, then use XPutImage to render it on the window. It's very fast, and you can even have two ximages to avoid glitches (your Expose handler uses one of the ximages to display, while you update the other one's buffer in the background (maybe in the main process if you like). When you finish with the update, you swap the pointers of the ximages and send an Expose event to the window. No semaphores needed, better performance).

Using XImage has another advatage that you don't need the redraw the entire window, only the part that's been exposed from the Ximage cache, which gives you much much better user experience. Your redraw become as simple as:

Code: Select all

XNextEvent(display, &report);
if(report.type==Expose){
  XPutImage(display, win, gc, ximages[active],
   report.xexpose.x,report.xexpose.y,
   report.xexpose.x,report.xexpose.y,
   report.xexpose.width, report.xexpose.height
  );
  XSync(display,0);
}
Of course this is just a suggestion, you can use an offscreen Zpixmap and use XCopyArea. I've found Ximage easier to handle, but maybe it's just me.

bzt

User avatar
ab1jx
Posts: 702
Joined: Thu Sep 26, 2013 1:54 pm
Location: Heath, MA USA
Contact: Website

Re: So when does xlib actually do work?

Sat Sep 08, 2018 1:23 pm

Xlib has known about threads since x11r6, there's an XInitThreads() you call first, then you can use XLockDisplay(). I haven't tried that, you can also get a file descriptor for the Display https://www.linuxquestions.org/question ... ost2431345 which you can use with select(). I used that once but ended up writing an event handler because I only needed to handle expose, mouse, keyboard. I haven't looked into trying to create an event and write a handler for it.

Or, I really don't need to try to display every bit of data. I could set a timer when I display a frame then on the timer event show the next one. I was thinking Pixmap because it's a drawable surface, but I think an Ximage can contain a pixmap. And yes, manipulating the offscreen image then switching is an old animation technique I'd forgotten about.

Colors, mostly I just use 24 bit colors the same as HTML colors. Except in the waterfall, because I use batch techniques to speed up drawing, and with XDrawPoints() they all have to be the same color. So I use 16 colors there, map a signal intensity to a color, and store the X locations in bins. Then for each bin I do an XSetForeground() and draw the points. The spectrum uses a similar batch technique, and I use XOR to erase old lines so even-numbered ones in the queue are for erasing and odd ones are new, then I call XDrawSegments() to redraw the spectrum.

I think it was in some pthreads primer/tutorial I read about the disadvantages of fork. I see now there's an issue with granularity, I don't want to pause my entire process, I just want to do my drawing when X expects it. I did this https://www.raspberrypi.org/forums/view ... 7#p1361527 which can run for hours but it does it on expose events. And uses select() so I'm not sure which is responsible for it working. On to version 7 of this.

Return to “C/C++”