1

How can I draw pixels inside of a linux window manually with best performance?

I don't want to write directly to the frame buffer, but I also don't want to use OpenGL or similar libraries / APIs that do everything for you. Is it possible to create an 2D array of colored pixels, and then print them inside of a window?

Like this (but with more colors):

_________________
| My App      -ox|
_________________|
|RRRRGGBBRRRGGBBB|
|RRGGRGRGGRGRGGRR|
|RRGGGGBBBBRRGGBB|
|________________|
Oliver
  • 341
  • 2
  • 4
  • 21
  • 1
    What are you using to create a window? GTK? Qt? Direct communication with X? etc. – MikeCAT Dec 11 '20 at 13:13
  • Hello, it doesn't really matter even though I'd prefer GTK. But any answer is appreciated! – Oliver Dec 11 '20 at 14:10
  • Best performance is kind of vague. What is the application purpose? Is this a paintbrush app? a video player? a vnc livescreen? Depending on the content being drawn, the frequency of updates, and how much of the image needs to be drawn again the answer to what is most performant may be different. For example, if you can avoid updating the entire image you can realize higher performance with less than performant methods if desired. Depending on the those questions above, the answer for best performance method is going to be different. – Joshua Briefman Dec 18 '20 at 23:21
  • As far as GTK, it appears GTK stopped providing drawing functions in version 3. But, according to this you can still do it with Cairo and a pixel buffer. https://stackoverflow.com/a/8019736/2585788 – Joshua Briefman Dec 18 '20 at 23:26

1 Answers1

7

How can I draw pixels inside of a linux window manually?

Linux per-se doesn't know about windows (or any graphics beyond screen framebuffers for that matter).

Are you addressing X11, Wayland (or Mir or DirectFB – those later two are hardly used these days).

OpenGL or similar libraries

OpenGL is not a library. It's an API that allows you to talk more or less directly to the GPU (there's a lot of bookkeeping behind the scenes). If you want an even more hands-on API then use Vulkan. Those are the most direct ways to use a GPU, short of writing your own drivers.

Is it possible to write something pixel-by-pixel in the window?

Yes it is, but it's terribly inefficient, since doing it pixel-by-pixel will involve a complete trip from your program through several abstraction layer until it reaches the destination.

It's much more efficient to just send complete pictures, or ask for getting raw access to the framebuffer to write to it directly. Of course to be really efficient you want to make use of the GPU's capabilities. And that will require talking to it through an API as OpenGL or Vulkan.

With X11 what you can do it create an X MIT-SHM pixmap and map it into your process' address space and directly manipulate it's contents. To show it on screen you then use XPutImage to copy it into the window.

With Wayland you can get a mapping of the window's framebuffer itself, so that you don't have to do that additional copy step.

Update / Minimal example of how to use MIT-SHM with Xcb

This example shows how to obtain with X11 MIT-SHM a process address spaced framebuffer for direct pixel manipulation. Based on the Xcb tutorial https://xcb.freedesktop.org/tutorial/basicwindowsanddrawing/ to which I added my own code.

/* 
 * compile with 
 * 
 * gcc -o minimal_xcb_shm_image \
 *        minimal_xcb_shm_image.c \
 *     -lxcb -lxcb-image -lxcb-shm
 */
#include <stdlib.h>
#include <stdio.h>

#include <sys/ipc.h>
#include <sys/shm.h>

#include <xcb/xcb.h>
#include <xcb/shm.h>
#include <xcb/xcb_image.h>

#if __ORDER_LITTLE_ENDIAN == __BYTE_ORDER__
# define NATIVE_XCB_IMAGE_ORDER    XCB_IMAGE_ORDER_LSB_FIRST
#else
# define NATIVE_XCB_IMAGE_ORDER    XCB_IMAGE_ORDER_MSB_FIRST
#endif

enum  {
    IMAGE_WIDTH  = 512,
    IMAGE_HEIGHT = 512,
    IMAGE_DEPTH  = 24,
};

static xcb_format_t const *query_xcb_format_for_depth(
    xcb_connection_t *const connection,
    unsigned depth )
{
    xcb_setup_t const *const setup = xcb_get_setup(connection);
    xcb_format_iterator_t it;
    for( it = xcb_setup_pixmap_formats_iterator(setup)
       ; it.rem
       ; xcb_format_next(&it)
    ){
        xcb_format_t const *const format = it.data;
        if( format->depth == depth ){
            return format;
        }
    }
    return NULL;
}

typedef struct shm_xcb_image_t {
    xcb_connection_t *connection;
    xcb_image_t  *image;
    xcb_shm_seg_t shm_seg;
    int           shm_id;
} shm_xcb_image_t;

static shm_xcb_image_t *shm_xcb_image_create(
    xcb_connection_t *const connection,
    unsigned const width,
    unsigned const height,
    unsigned const depth )
{
    xcb_generic_error_t *error = NULL;

    shm_xcb_image_t *shmimg = calloc(1, sizeof(*shmimg));
    if( !shmimg ){ goto fail; }
    shmimg->connection = connection;

    xcb_format_t const *const format = query_xcb_format_for_depth(connection, depth);
    if( !format ){ goto fail; }
    shmimg->image = xcb_image_create(
        width, height,
        XCB_IMAGE_FORMAT_Z_PIXMAP,
        format->scanline_pad,
        format->depth, format->bits_per_pixel, 0,
        NATIVE_XCB_IMAGE_ORDER,
        XCB_IMAGE_ORDER_MSB_FIRST,
        NULL, ~0, 0);
    if( !shmimg->image ){ goto fail; }

    size_t const image_segment_size = shmimg->image->stride * shmimg->image->height;

    shmimg->shm_id = shmget(IPC_PRIVATE, image_segment_size, IPC_CREAT | 0600);
    if( 0 > shmimg->shm_id ){ goto fail; }

    shmimg->image->data = shmat(shmimg->shm_id, 0, 0);
    if( (void*)-1 == (void*)(shmimg->image->data) ){ goto fail; }

    shmimg->shm_seg = xcb_generate_id(connection),
    error = xcb_request_check(connection,
        xcb_shm_attach_checked(
            connection,
            shmimg->shm_seg, shmimg->shm_id, 0) );
fail:
    if( shmimg ){
        if( shmimg->image ){
            if( shmimg->image->data && error ){
                shmdt(shmimg->image->data);
                shmimg->image->data = NULL;
            }
            if( !shmimg->image->data ){
                shmctl(shmimg->shm_id, IPC_RMID, 0);
                shmimg->shm_id = -1;
            }
        }
        if( 0 > shmimg->shm_id ){
            xcb_image_destroy(shmimg->image);
            shmimg->image = NULL;
        }
        if( !shmimg->image ){
            free(shmimg);
            shmimg = NULL;
        }
    }
    free(error);

    return shmimg;
}

static void shm_xcb_image_destroy(shm_xcb_image_t *shmimg)
{
    xcb_shm_detach(shmimg->connection, shmimg->shm_seg);
    shmdt(shmimg->image->data);
    shmctl(shmimg->shm_id, IPC_RMID, 0);
    xcb_image_destroy(shmimg->image);
    free(shmimg);
}

static void generate_image(
    shm_xcb_image_t *shmimg,
    unsigned t )
{
    for( unsigned j = 0; j < shmimg->image->height; ++j ){
        uint8_t *const line = shmimg->image->data + j * shmimg->image->stride;
        for( unsigned i = 0; i < shmimg->image->width; ++i ){
            unsigned const bytes_per_pixel = shmimg->image->bpp/8;
            uint8_t *pixel = line + i * bytes_per_pixel;

            unsigned const a = (i + t);
            unsigned const b = (j + (i >> 8) & 0xFF);
            unsigned const c = (j >> 8) & 0xFF;

            switch( bytes_per_pixel ){
            case 4: pixel[3] = 0xFF; /* fallthrough */
            case 3: pixel[2] = a & 0xFF; /* fallthrough */
            case 2: pixel[1] = b & 0xFF; /* fallthrough */
            case 1: pixel[0] = c & 0xFF; /* fallthrough */
            default: break;
            }
        }
    }
}

int main(int argc, char *argv[])
{
    /* Open the connection to the X server */
    xcb_connection_t *connection = xcb_connect(NULL, NULL);

    /* Check that X MIT-SHM is available (should be). */
    const xcb_query_extension_reply_t *shm_extension = xcb_get_extension_data(connection, &xcb_shm_id);
    if( !shm_extension || !shm_extension->present ){
        fprintf(stderr, "Query for X MIT-SHM extension failed.\n");
        return 1;
    }

    shm_xcb_image_t *shmimg = shm_xcb_image_create(connection, IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_DEPTH);
    if( !shmimg ){
        fprintf(stderr, "Creating shared memory image failed");
    }

    /* Get the first screen */
    xcb_screen_t *const screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;

    /* Create a window */
    uint32_t const window_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    uint32_t const window_values[] = { screen->white_pixel, XCB_EVENT_MASK_EXPOSURE};
    xcb_drawable_t const window = xcb_generate_id(connection);
    xcb_create_window(connection,
        XCB_COPY_FROM_PARENT,          /* depth */
        window,                        /* window Id */
        screen->root,                  /* parent window */
        0, 0,                          /* x, y */
        IMAGE_WIDTH, IMAGE_HEIGHT,     /* width, height */
        0,                             /* border_width */
        XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */
        screen->root_visual,           /* visual */
        window_mask, window_values     /* masks */
    );

    /* Create black (foreground) graphic context */
    xcb_gcontext_t const gc = xcb_generate_id( connection );
    uint32_t const gc_mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
    uint32_t const gc_values[] = {screen->black_pixel, 0};
    xcb_create_gc(connection, gc, window, gc_mask, gc_values);

    /* Map the window on the screen and flush*/
    xcb_map_window(connection, window);
    xcb_flush(connection);

    /* Event loop */
    unsigned counter = 0;
    for( xcb_generic_event_t *event
       ; (event = xcb_wait_for_event(connection))
       ; free(event)
    ){
        switch( event->response_type & ~0x80 ){
        case XCB_EXPOSE:
            generate_image(shmimg, counter++);
            xcb_shm_put_image(connection, window, gc,
                shmimg->image->width, shmimg->image->height, 0, 0,
                shmimg->image->width, shmimg->image->height, 0, 0,
                shmimg->image->depth, shmimg->image->format, 0, 
                shmimg->shm_seg, 0);

            /* flush the request */
            xcb_flush(connection);
            break;
        default:
            /* Unknown event type, ignore it */
            break;
        }

    }

    shm_xcb_image_destroy(shmimg);

    return 0;
}
datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • Thanks for explaining! Would you be able to give me an example, that will create simple two-dimensional array of red pixels (like 500x500) with two for() cycles, and then send the picture (red square) to the window? Ideally the efficient way :) I hope it makes sense, Thanks! – Oliver Dec 14 '20 at 12:01
  • 1
    @Oliver: I implemented a minimal example for a process address space MIT-SHM framebuffer. It sets a solid color window background, which causes flickering upon redraw. Getting flicker free updates is a topic on it's own. – datenwolf Dec 14 '20 at 15:55
  • 1
    @Oliver: You might also be interested in this StackOverflow answer I wrote a couple of years ago. https://stackoverflow.com/a/8777891/524368 – keep in mind that this answer predates Vulkan and *a lot* of the work that has been done on the Linux graphics stack. The general ideas are still applicable, though. – datenwolf Dec 15 '20 at 00:15
  • @datenwolf When I first started reading the answer, I had a feeling it was one of yours. – karlphillip Dec 18 '20 at 22:18
  • @karlphillip: Is that a good thing or a bad thing? – datenwolf Dec 18 '20 at 22:23
  • @datenwolf It's usually a good thing! Good answer, by the way. – karlphillip Dec 18 '20 at 22:25