简体   繁体   中英

Why does it matter when you draw in XWindows?

I got this XWindows "hello, world" off the net. I have behavior I don't understand in a more complex program, but the simple program here also displays it:

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
   Display *d;
   Window w;
   XEvent e;
   const char *msg = "Hello, World!";
   int s;
   int x;

   d = XOpenDisplay(NULL);
   if (d == NULL) {
      fprintf(stderr, "Cannot open display\n");
      exit(1);
   }

   s = DefaultScreen(d);
   w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                           BlackPixel(d, s), WhitePixel(d, s));
   XSelectInput(d, w, ExposureMask | KeyPressMask);
   XMapWindow(d, w);

   XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));

   //XFlush(d);

   while (1) {
      XNextEvent(d, &e);
      if (e.type == Expose) {
//         XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));
      }
      if (e.type == KeyPress) break;
   }

   XCloseDisplay(d);
   return 0;
}

So the first XDrawString() call does not actually write to the window, but uncommenting the one in the expose event does. I find this behavior confusing. At the time of the first XDrawString(), the display, screen and window are all set up. Yet that does not draw and the one that appears in the event handler does. I also tried XFlush()'ing the queue, it makes no difference.

This effect had bearing on the (much) more complex code I am working on, which is placing characters on screen. I place them at the desired location in a pixmap that backs the screen, and then I do the same draw to the real screen to keep it in sync. The expose handler will copy the pixmap to the screen, which updates it, but that expose event won't happen until later, and besides, copying the entire pixmap back to the screen is more expensive than placing a single character.

I suspect I need to follow the draw to buffer with an operation flagging the rectangle under the character as invalid (this would be the MS windows way) and letting the event handler take care of it, but I would like to understand what goes on inside X11 to make this happen.

X servers are not required to provide backing store by themselves, so Expose events are sent when the window needs to be drawn. This is the same functionality that happens in other environments also. In the beginning you can draw, but the X server may not have set up the window properly yet and will later report to you when it's ready to have contents drawn on to screen. The drawing is kind of directly on screen, not to a backing buffer, so it has to be done every time the window is brought in front.

As for requesting a redraw yourself you can use XSendEvent to send an Expose event yourself and then X will relay it to your event loop. So it's practically exactly the same as Windows does.

Remember that you are in client/server environment. Means that there are delays in between your code requests and their realizations. Returning from call to XMapWindow does not means that your window is really ready to accept drawings only that the XServer has to do it, so you need to wait the first Expose event to draw, which means that the XServer did the job. It is mandatory, because Expose is the event representing the fact that the window (or a portion of it) is viewable on screen.

Always remember : UIs are event-driven.

Just to add a bit to the good reply from Sami.

Thinking deeply, the way X11 works is the only correct one when multiple windows, which can overlap each other, are used (not counting a lot of other things). This is why this mechanism (invalidate and expose | draw | paint) is used by many frameworks (X11, windows, gtk, qt...).

It's all about optimizing the drawing. You should not draw blindly, because your drawing could be discarded; may be your window is invisible, may be the user is moving the window without repainting its content, or whatever.

Instead, X11 (or other toolkit/framework) knows very well when data has to be displayed, and can be displayed. In that situation, X11 tells you to redraw (via an expose event). When X11 asks to repaint, your drawing operations will not be discarded.

On the other hands, a good X11 program should be prepared to repaint when needed: it will happen often, if the user drags windows around, or changes virtual desktops, iconifies the application, and so on.

Combine the two aspects: the program must draw when asked to, and it will be asked for sure just after having created its first window. So, it is perfect to draw only in the expose event.

This basic mechanism can be improved by backing store: X11 can cache the drawing data, and use them later instead of asking the program again and again and again for the same data. Sometimes backing store is faster, but sometimes it only wastes memory.

Ok, I thought about this last night and realized that an old, classic program called "lines" illustrates the issue I am talking about:

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int randr(int upper)

{

   return rand() % (upper+1);

}   

int main(void) {

    Display *d;
    Window w;
    XEvent e;
    const char *msg = "Hello, World!";
    int s;
    XEvent se;

    d = XOpenDisplay(NULL);
    if (d == NULL) {
       fprintf(stderr, "Cannot open display\n");
       exit(1);
    }

    s = DefaultScreen(d);
    w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 400, 400, 1,
                            BlackPixel(d, s), WhitePixel(d, s));
    XSelectInput(d, w, ExposureMask | KeyPressMask);
    XMapWindow(d, w);

    se.type = Expose;
    se.xexpose.type = Expose;
    se.xexpose.serial = 0;
    se.xexpose.send_event = 1;
    se.xexpose.window = w;
    se.xexpose.x = 0;
    se.xexpose.y = 0;
    se.xexpose.width = 400;
    se.xexpose.height = 400;
    se.xexpose.count = 0;

    while (1) {
       XNextEvent(d, &e);
       if (e.type == Expose) {
          XSetForeground(d, DefaultGC(d, s), randr(255)<<16 | randr(255)<<8 | randr(255));
          XDrawLine(d, w, DefaultGC(d, s), randr(400), randr(400), randr(400), randr(400));
          XSendEvent(d, w, 0, ExposureMask, &se);
       }
       if (e.type == KeyPress) break;
    }

    XCloseDisplay(d);

    return 0;
}

Lines is an old program that goes back to the days of the Cromemco Dazzler of the late 1970's. It just draws random lines, it does it quite fast. It has no need of input, it just draws and draws, and it can serve as a performance test as well.

As you see here I have tricked X11 into continuously processing the expose event by posting another one in the expose event itself. In (for example) MS windows this would not be necessary. You can draw on the window at any time, and not reading your event queue is not nice, but the program still works.

This program works in X11, and (on my machine) works so rapidly that it basically fills the window almost immediately.

Now if I wanted to make that demo work as fast as possible I would need to account for the major time sinks in the program, which, as far as I can tell, is the need to continually post the new expose events.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM