Oliver
Oliver

Reputation: 441

Draw pixels in a linux window without OpenGL in C

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|
|________________|

Upvotes: 3

Views: 3603

Answers (1)

datenwolf
datenwolf

Reputation: 162327

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;
}

Upvotes: 9

Related Questions