Multiple displays

It can be quite tricky to create and manage an application that uses multiple displays, especially when you consider threading, performance, and graphics optimization. Fortunately, Screen API provides the necessary functionality to let you create applications that write to multiple windows and displays simultaneously.

In our vsync application, an hour glass is placed in the top-left corner of an application window while a vertical bar sweeps from left to right across the screen. This code sample queries the context to determine the number of displays that are currently attached to the system. When the bar reaches the right edge of the application window, instead of returning to the left-hand side of the current display, the application sends focus to the next display in the list and the bar continues its sweep at the left-hand side of that other display.

Figure 1. The sample vsync application
The application uses a struct to store the state (either detached, attached, or focused) of each display.
struct {
   pthread_mutex_t mutex;
   pthread_cond_t cond;
   enum { detached, attached, focused } state;
} *displays;
			
Before any of the drawing is done, the application iterates though each attached display, and uses the screen_get_display_property_iv() property to return the state of the current display. For each attached display, the application initializes a mutex and calls pthread_create(), passing in a display() function, to spawn a child thread
This display() function handles all graphics operations for the current display, meaning that each display will be written to and updated within its own process. This allows the graphics processor to handle any intensive operations, and ensures that if an error occurs or a display becomes detached, the application will not fail.
displays = calloc(ndisplays, sizeof(*displays));
for (i = 0; i < ndisplays; i++) {
   int active = 0;
   screen_get_display_property_iv(screen_dpy[i], SCREEN_PROPERTY_ATTACHED, &active);
   if (active) {
      if (idx == -1) {
         displays[i].state = focused;
         idx = i;
      } else {
        displays[i].state = attached;
      }
   } else {
      displays[i].state = detached;
   }
   pthread_mutex_init(&displays[i].mutex, NULL);
   pthread_cond_init(&displays[i].cond, NULL);

   pthread_t thread;
   pthread_create(&thread, NULL, display, (void *)i);
}
               

The display() function sets up the current display and window, then locks the mutex to determine whether or not the current display is active and has focus.

pthread_mutex_lock(&displays[idx].mutex);
attached = displays[idx].state != detached ? 1 : 0;
focus = displays[idx].state == focused ? 1 : 0;
pthread_mutex_unlock(&displays[idx].mutex);
        

A while loop checks conditions and handles the flow of execution for each display. If the display is currently attached, the display and window properties are initialized. A buffer is created, a handle to the buffer is returned, and the background color is blitted to the buffer. If the display has focus, the bar is blitted and written to the buffer at the current position. Next, the hourglass is written to the buffer and the window is posted.

The pos variable is incremented continuously, causing the bar to scan from left to right across the current application window. When the bar reaches the right-most edge of the screen, the mutex of the next display in the displays structure is locked and the state of the next attached display is set to focused. This causes the bar to appear at the left-most edge of the next display in the display list. It then scans across the screen and repeats this behavior on the next display in the list of displays.

while (1) {
    if (attached) {
        if (!realized) {
            screen_get_display_property_iv(screen_dpy[idx], SCREEN_PROPERTY_SIZE, rect+2);
            screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, rect+2);
            screen_create_window_buffers(screen_win, 2);
            realized = 1;
        }

        screen_buffer_t screen_buf[2];
        screen_get_window_property_pv(screen_win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)screen_buf);

        int bg[] = { SCREEN_BLIT_COLOR, 0xffffff00, SCREEN_BLIT_END };
        screen_fill(screen_ctx, screen_buf[0], bg);

        if (focus > 0) {
            int bar[] = {
                SCREEN_BLIT_COLOR, 0xff0000ff,
                SCREEN_BLIT_DESTINATION_X, pos,
                SCREEN_BLIT_DESTINATION_WIDTH, barwidth,
                SCREEN_BLIT_END };

            screen_fill(screen_ctx, screen_buf[0], bar);

            if (++pos > rect[2] - barwidth) {
                for (i = (idx+1) % ndisplays; i != idx; i = (i+1) % ndisplays) {
                    pthread_mutex_lock(&displays[i].mutex);
                    if (displays[i].state == attached) {
                        displays[i].state = focused;
                        pthread_cond_signal(&displays[i].cond);
                        pthread_mutex_unlock(&displays[i].mutex);
                        break;
                    }
                    pthread_mutex_unlock(&displays[i].mutex);
                }
                if (i != idx) {
                    pthread_mutex_lock(&displays[idx].mutex);
                    displays[idx].state = attached;
                    pthread_mutex_unlock(&displays[idx].mutex);
                    focus = -1;
                }
                pos = 0;
            }
        } else {
            focus = 0;
        }

        int hg[] = {
            SCREEN_BLIT_SOURCE_WIDTH, 100,
            SCREEN_BLIT_SOURCE_HEIGHT, 100,
            SCREEN_BLIT_DESTINATION_X, 10,
            SCREEN_BLIT_DESTINATION_Y, 10,
            SCREEN_BLIT_DESTINATION_WIDTH, 100,
            SCREEN_BLIT_DESTINATION_HEIGHT, 100,
            SCREEN_BLIT_TRANSPARENCY, SCREEN_TRANSPARENCY_SOURCE_OVER,
            SCREEN_BLIT_END
        };

        screen_blit(screen_ctx, screen_buf[0], screen_pbuf, hg);
        screen_post_window(screen_win, screen_buf[0], 1, rect, 0);
    }

    if (!attached && realized) {
        screen_destroy_window_buffers(screen_win);
        screen_flush_context(screen_ctx, 0);
        realized = 0;
    }

    if (focus != -1) {
        pthread_mutex_lock(&displays[idx].mutex);
        if (!focus) {
            printf("%s[%d]: idx=%d\n", __FUNCTION__, __LINE__, idx);
            pthread_cond_wait(&displays[idx].cond, &displays[idx].mutex);
            pos = 0;
        }
        attached = displays[idx].state != detached ? 1 : 0;
        focus = displays[idx].state == focused ? 1 : 0;
        pthread_mutex_unlock(&displays[idx].mutex);
    }
}
        

The main body of the application handles window and display events. It updates the displays struct every time a display is attached or detached. In addition to controlling the flow of execution for the application, it also prints out debug information about the current execution.

While this is the most complicated of the sample applications, it does provide many useful best practices for handling and writing to multiple displays.