【问题标题】:Draw pixels in a linux window without OpenGL in C在 C 中没有 OpenGL 的 linux 窗口中绘制像素
【发布时间】:2020-12-11 13:04:23
【问题描述】:

如何以最佳性能手动在 linux 窗口内绘制像素?

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

像这样(但颜色更多):

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

【问题讨论】:

  • 你用什么来创建窗口? GTK?吗?与 X 直接通信?等
  • 您好,尽管我更喜欢 GTK,但这并不重要。但任何答案都值得赞赏!
  • 最佳性能有点模糊。申请目的是什么?这是画笔应用吗?视频播放器? vnc 实时屏幕?根据正在绘制的内容、更新频率以及需要再次绘制多少图像,对性能最高的答案可能会有所不同。例如,如果您可以避免更新整个图像,则可以根据需要使用性能较差的方法来实现更高的性能。根据上述问题,最佳性能方法的答案会有所不同。
  • 就 GTK 而言,GTK 似乎在版本 3 中停止提供绘图功能。但是,根据这一点,您仍然可以使用 Cairo 和像素缓冲区来完成。 stackoverflow.com/a/8019736/2585788

标签: c linux graphics pixel


【解决方案1】:

如何在 linux 窗口内手动绘制像素?

Linux 本身不了解窗口(或任何超出屏幕帧缓冲区的图形)。

您是在使用 X11、Wayland(或 Mir 或 DirectFB - 现在几乎不使用后面的两个)。

OpenGL 或类似库

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

是否可以在窗口中逐像素写入内容?

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

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

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

使用 Wayland,您可以获得窗口帧缓冲区本身的映射,因此您无需执行额外的复制步骤。

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

这个例子展示了如何使用 X11 MIT-SHM 获得一个进程地址间隔的帧缓冲区,用于直接像素操作。基于 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;
}

【讨论】:

  • 感谢您的解释!你能给我一个例子,它将创建一个简单的红色像素二维数组(如 500x500)和两个 for() 循环,然后将图片(红色方块)发送到窗口?理想情况下是有效的方法:) 我希望它有意义,谢谢!
  • @Oliver:我为进程地址空间 MIT-SHM 帧缓冲区实现了一个最小示例。它设置了纯色窗口背景,这会导致重绘时闪烁。获得无闪烁更新本身就是一个主题。
  • @Oliver:您可能还对我几年前写的这个 StackOverflow 答案感兴趣。 stackoverflow.com/a/8777891/524368 – 请记住,这个答案早于 Vulkan 和 很多 在 Linux 图形堆栈上完成的工作。不过,一般的想法仍然适用。
  • @datenwolf 当我第一次开始阅读答案时,我觉得它是你的。
  • @karlphillip:这是好事还是坏事?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多