在 C 中不使用 OpenGL 在 linux window 中绘制像素

Draw pixels in a linux window without OpenGL in C

如何在 linux window 中手动绘制像素以获得最佳性能?

我不想直接写入帧缓冲区,但我也不想使用 OpenGL 或类似的库/API 来为您完成所有工作。 是否可以创建彩色像素的二维数组,然后将它们打印在 window?

像这样(但颜色更多):

_________________
| My App      -ox|
_________________|
|RRRRGGBBRRRGGBBB|
|RRGGRGRGGRGRGGRR|
|RRGGGGBBBBRRGGBB|
|________________|

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

Linux 本身不知道 windows(或任何超出屏幕帧缓冲区的图形)。

你是在处理 X11、Wayland(或 Mir 或 DirectFB——后两者现在几乎不使用了)。

OpenGL or similar libraries

OpenGL 不是库。它是一个 API,允许您或多或少地直接与 GPU 对话(幕后有很多簿记)。如果您想要更多动手操作 API,请使用 Vulkan。这些是使用 GPU 的最直接方法,无需编写自己的驱动程序。

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

是的,但它非常低效,因为逐个像素地执行此操作将涉及从您的程序通过多个抽象层直到到达目的地的完整旅程。

只发送完整的图片或请求获得对帧缓冲区的原始访问权限以直接写入它会更有效率。当然,要真正高效,您需要利用 GPU 的功能。这将需要通过 API 作为 OpenGL 或 Vulkan 与之对话。

使用 X11,您可以创建一个 X MIT-SHM 像素图并将其映射到您的进程地址 space 并直接操作其内容。要在屏幕上显示它,然后使用 XPutImage 将其复制到 window.

使用 Wayland,您可以获得 window 的帧缓冲区本身的映射,这样您就不必执行额外的复制步骤。

如何将 MIT-SHM 与 Xcb 一起使用的更新/最小示例

此示例展示了如何使用 X11 MIT-SHM 获取进程地址 spaced 帧缓冲区以进行直接像素操作。基于我添加了自己的代码的 Xcb 教程 https://xcb.freedesktop.org/tutorial/basicwindowsanddrawing/

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