Как я могу рисовать пиксели внутри окна Linux вручную с максимальной производительностью?
Я не хочу писать напрямую в буфер кадра, но я также не хочу использовать OpenGL или подобные библиотеки/API, которые делают все за вас. Можно ли создать 2D-массив цветных пикселей, а затем распечатать их внутри окна?
Вот так (но с большим количеством цветов):
_________________
| My App -ox|
_________________|
|RRRRGGBBRRRGGBBB|
|RRGGRGRGGRGRGGRR|
|RRGGGGBBBBRRGGBB|
|________________|
Привет, на самом деле это не имеет значения, хотя я бы предпочел GTK. Но любой ответ ценен!
Лучшая производительность довольно расплывчата. Какова цель приложения? Это приложение для рисования? видеоплеер? VNC прямой эфир? В зависимости от отрисовываемого контента, частоты обновлений и того, какую часть изображения нужно отрисовывать снова, ответ на вопрос о том, что наиболее эффективно, может быть разным. Например, если вы можете избежать обновления всего изображения, вы можете добиться более высокой производительности с менее производительными методами, если это необходимо. В зависимости от этих вопросов выше, ответ на лучший метод производительности будет другим.
Что касается GTK, похоже, что GTK перестал предоставлять функции рисования в версии 3. Но, согласно этому, вы все еще можете делать это с помощью Cairo и пиксельного буфера. stackoverflow.com/a/8019736/2585788
Как я могу рисовать пиксели внутри окна Linux вручную?
Linux как таковой не знает об окнах (или любой графике, кроме экранных фреймбуферов, если уж на то пошло).
Вы имеете в виду X11, Wayland (или Мир, или DirectFB — последние два в наши дни практически не используются).
OpenGL или подобные библиотеки
OpenGL — это не библиотека. Это API, который позволяет вам общаться более или менее напрямую с графическим процессором (за кулисами ведется большая бухгалтерия). Если вам нужен еще более удобный API, используйте Vulkan. Это самые прямые способы использования графического процессора, за исключением написания собственных драйверов.
Можно ли писать что-то попиксельно в окне?
Да, это так, но это ужасно неэффективно, так как выполнение этого попиксельно потребует от вашей программы полного прохождения через несколько уровней абстракции, пока она не достигнет места назначения.
Гораздо эффективнее просто отправлять полные изображения или запрашивать необработанный доступ к фреймбуферу для прямой записи в него. Конечно, чтобы быть действительно эффективным, вы хотите использовать возможности графического процессора. И для этого потребуется поговорить с ним через API, например OpenGL или Vulkan.
Что вы можете сделать с X11, так это создать растровое изображение X MIT-SHM и сопоставить его с адресным пространством вашего процесса и напрямую манипулировать его содержимым. Чтобы показать его на экране, вы затем используете XPutImage, чтобы скопировать его в окно.
С помощью Wayland вы можете получить отображение самого фреймбуфера окна, так что вам не нужно делать этот дополнительный шаг копирования.
В этом примере показано, как получить с помощью 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: Это хорошо или плохо?
@datenwolf Обычно это хорошо! Хороший ответ, кстати.
Что вы используете для создания окна? ГТК? Квт? Прямая связь с X? и т. д.