Search code examples
c++linuxx11xlibxcb

Drawing an image using xcb


I am trying to make an X11 window dump the content of a buffer using xcb.The code should fill the window with a white background, and a filled circle. Similar code works with XLib - so I am confident with the basic drawing. My problem is with the blitting to screen.

Basically:

  • I create a H*W buffer (which I paint white).
  • I call xcb_image_create_native() with that buffer.
  • Then I create a pixmap, dump the native image to it - and then draw the pixmap on window.
  • Dumping the image into the window will also fail (both pixmaps and windows are drawable in XLib, so it should work anyway).

I am missing something trivial - which I cannot understand.

#include <cassert>
#include <cstring>
#include <cstdint>
#include <xcb/xcb.h>
#include <xcb/xcb_image.h>

constexpr auto size_width = 640;
constexpr auto size_height = 480;

int32_t buf[size_height*size_height];

auto put_pixel(int x, int y, uint32_t color) -> void {
    buf[(y * size_width) + x] = color;
}

auto fill_circle(int x, int y, int r, uint32_t c) -> void {
    for (int dy = -r; dy < r; dy++) {
        for (int dx = -r; dx < r; dx++) {
            if (dx * dx + dy * dy <= r * r)
                put_pixel(x + dx, y + dy, c);
        }
    }
}

int main()
{
    xcb_connection_t *connection;
    xcb_screen_t *screen;
    xcb_gcontext_t graphics_context;
    xcb_window_t window_xcb;
    xcb_image_t* xcb_image;
    auto format = XCB_IMAGE_FORMAT_Z_PIXMAP;
    uint8_t depth;
    uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    uint32_t values[2];

    connection = xcb_connect(NULL, NULL);
    assert(connection);
    screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
    depth = screen->root_depth;
    values[0] = screen->black_pixel;
    values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS;

    window_xcb = xcb_generate_id(connection);
    xcb_create_window(connection, XCB_COPY_FROM_PARENT, window_xcb, screen->root, 0, 0, size_width,
                      size_height, 10, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, mask, values);
    xcb_map_window(connection, window_xcb);


    graphics_context = xcb_generate_id(connection);
    xcb_create_gc(connection, graphics_context, window_xcb, mask, values);
    xcb_image =
        xcb_image_create_native(connection, size_width, size_height,
                                format, depth, (uint8_t *)buf,
                                size_width * size_height * 4,
                                (uint8_t *)buf);


    xcb_flush(connection);
    memset(buf, size_width*size_height*4, 0xff);
    fill_circle(100, 100, 20, 0xff0000);

    {
        xcb_generic_event_t *event;

        while (true) {
            event = xcb_wait_for_event(connection);
            if (event == nullptr) {
                continue;
            }

            switch (event->response_type) {
            case XCB_EXPOSE: {
                xcb_expose_event_t *ev = (xcb_expose_event_t *)event;

                auto depth = screen->root_depth;
                xcb_pixmap_t pixmap = xcb_generate_id(connection);
                xcb_create_pixmap(connection, depth, pixmap, ev->window,
                                  size_width, size_height);
                xcb_image_put(connection, pixmap, graphics_context, xcb_image, 0, 0, 0);
                xcb_copy_area(connection, pixmap, window_xcb, graphics_context, 0, 0, 0, 0,
                        size_width, size_height);
                xcb_flush(connection);
            } break;
            case XCB_DESTROY_NOTIFY:
                break;
            default:
                //            xcb_free_event(event);
                break;
            }
        }
    }

    return 0;
}

Solution

  • I ran the program under xtrace and saw that its CreateGC request was failing with a BadPixmap error:

    000:<:0003: 16: Request(55): CreateGC cid=0x03a00001 drawable=0x03a00000 values={}
    000:>:0003:Error 4=Pixmap: major=55, minor=0, bad=0x00000000, seq=0003
    

    The code was passing the same values to CreateGC as for CreateWindow.

    Also, the error handling is weird. The code goes into an endless loop if the X11 connection breaks. I fixed that by making the program exit instead.

    Now, why does the connection break? Maximum request size!

    printf("Maximum request size is 4 * %d bytes while we will be trying to send 4 * %d bytes of data (plus request overhead)\n",
        xcb_get_maximum_request_length(connection), size_width * size_height);
    

    Output says that the program is sending too much data:

    Maximum request size is 4 * 4194303 bytes while we will be trying to send 4 * 307200 bytes of data (plus request overhead)
    

    I shrank the image size to 100 x 100. Now I get a red circle.

    Splitting the PutImage request into smaller parts is left as an exercise to the reader.

    In total, I did these changes:

    --- t.c.orig    2023-05-06 14:22:14.160130873 +0200
    +++ t.c 2023-05-06 14:33:04.777321692 +0200
    @@ -1,11 +1,12 @@
     #include <cassert>
     #include <cstring>
     #include <cstdint>
    +#include <cstdio>
     #include <xcb/xcb.h>
     #include <xcb/xcb_image.h>
     
    -constexpr auto size_width = 640;
    -constexpr auto size_height = 480;
    +constexpr auto size_width = 100;
    +constexpr auto size_height = 100;
     
     int32_t buf[size_height*size_height];
     
    @@ -48,17 +49,19 @@ int main()
     
     
         graphics_context = xcb_generate_id(connection);
    -    xcb_create_gc(connection, graphics_context, window_xcb, mask, values);
    +    xcb_create_gc(connection, graphics_context, window_xcb, 0, nullptr);
         xcb_image =
             xcb_image_create_native(connection, size_width, size_height,
                                     format, depth, (uint8_t *)buf,
                                     size_width * size_height * 4,
                                     (uint8_t *)buf);
     
    +    printf("Maximum request size is 4 * %d bytes while we will be trying to send 4 * %d bytes of data (plus request overhead)\n", xcb_get_maximum_request_length(connection), size_width * size_height);
    +
     
         xcb_flush(connection);
         memset(buf, size_width*size_height*4, 0xff);
    -    fill_circle(100, 100, 20, 0xff0000);
    +    fill_circle(50, 50, 20, 0xff0000);
     
         {
             xcb_generic_event_t *event;
    @@ -66,10 +69,12 @@ int main()
             while (true) {
                 event = xcb_wait_for_event(connection);
                 if (event == nullptr) {
    -                continue;
    +                printf("Connection broke with status %d\n", xcb_connection_has_error(connection));
    +                break;
                 }
    +            printf("Got event of type %d\n", event->response_type);
     
    -            switch (event->response_type) {
    +            switch (event->response_type & 0x7f) {
                 case XCB_EXPOSE: {
                     xcb_expose_event_t *ev = (xcb_expose_event_t *)event;
     
    

    Some hint / example on how to split up a too large PutImage request: https://gitlab.freedesktop.org/cairo/cairo/-/blob/8d6586f49f1c977318af7f7f9e4f24221c9122fc/src/cairo-xcb-connection-core.c#L104-144