为什么使用 XShmGetImage 捕获时 XImage 的数据指针为空?

Why is an XImage's data pointer null when capturing using XShmGetImage?

我正在编写一个程序,使用带有 C++ 的 Xlib 从一个 window 快速捕获图像、修改它们并将它们输出到另一个 window。我通过 XGetImage 工作:

#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

const int TEST_SIZE = 512;

int main()
{
  // Open default display
  Display *display = XOpenDisplay(nullptr);
  int screen = DefaultScreen(display);
  Window rootWin = RootWindow(display, screen);
  GC graphicsContext = DefaultGC(display, screen);

  // Create new window and subscribe to events
  long blackPixel = BlackPixel(display, screen);
  long whitePixel = WhitePixel(display, screen);
  Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
  XMapWindow(display, newWin);
  XSelectInput(display, newWin, ExposureMask | KeyPressMask);

  // Main event loop for new window
  XImage *image;
  XEvent event;
  bool exposed = false;
  bool killWindow = false;
  while (!killWindow)
  {
    // Handle pending events
    if (XPending(display) > 0)
    {
      XNextEvent(display, &event);
      if (event.type == Expose)
      {
        exposed = true;
      } else if (event.type == NoExpose)
      {
        exposed = false;
      } else if (event.type == KeyPress)
      {
        killWindow = true;
      }
    }

    // Capture the original image
    image = XGetImage(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, AllPlanes, ZPixmap);

    // Modify the image
    if (image->data != nullptr)
    {
      long pixel = 0;
      for (int x = 0; x < image->width; x++)
      {
        for (int y = 0; y < image->height; y++)
        {
          // Invert the color of each pixel
          pixel = XGetPixel(image, x, y);
          XPutPixel(image, x, y, ~pixel);
        }
      }
    }

    // Output the modified image
    if (exposed && killWindow == false)
    {
      XPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, TEST_SIZE, TEST_SIZE);
    }
    XDestroyImage(image);
  }

  // Goodbye
  XCloseDisplay(display);
}

它生成这样的输出:https://streamable.com/hovg9

我很高兴能走到这一步,但据我所知,性能不会很好地扩展,因为它必须在每一帧为新图像分配 space。事实上,如果没有在循环结束时调用 XDestroyImage,这个程序会在几秒钟内填满我机器上的所有 16GB 内存!

这里推荐的方法似乎是设置一个共享内存space X 可以写入每个帧的内容,我的程序可以随后读取和修改它们而无需任何额外分配。由于对 XShmGetImage 的调用会阻塞并等待 IPC,我相信这意味着我不必担心共享 space.

中的任何并发问题

我尝试使用以下代码实现 XShmGetImage 方法:

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>

const int TEST_SIZE = 512;

int main()
{
  // Open default display
  Display *display = XOpenDisplay(nullptr);
  int screen = DefaultScreen(display);
  Window rootWin = RootWindow(display, screen);
  GC graphicsContext = DefaultGC(display, screen);

  // Create new window and subscribe to events
  long blackPixel = BlackPixel(display, screen);
  long whitePixel = WhitePixel(display, screen);
  Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
  XMapWindow(display, newWin);
  XSelectInput(display, newWin, ExposureMask | KeyPressMask);

  // Allocate shared memory for image capturing
  Visual *visual = DefaultVisual(display, 0);
  XShmSegmentInfo shminfo;
  int depth = DefaultDepth(display, screen);
  XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
  shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0666);
  shmat(shminfo.shmid, nullptr, 0);
  shminfo.shmaddr = image->data;
  shminfo.readOnly = False;
  XShmAttach(display, &shminfo);

  // Main event loop for new window
  XEvent event;
  bool exposed = false;
  bool killWindow = false;
  while (!killWindow)
  {
    // Handle pending events
    if (XPending(display) > 0)
    {
      XNextEvent(display, &event);
      if (event.type == Expose)
      {
        exposed = true;
      } else if (event.type == NoExpose)
      {
        exposed = false;
      } else if (event.type == KeyPress)
      {
        killWindow = true;
      }
    }

    // Capture the original image
    XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);

    // Modify the image
    if (image->data != nullptr) // NEVER TRUE.  DATA IS ALWAYS NULL!
    {
      long pixel = 0;
      for (int x = 0; x < image->width; x++)
      {
        for (int y = 0; y < image->height; y++)
        {
          // Invert the color of each pixel
          pixel = XGetPixel(image, x, y);
          XPutPixel(image, x, y, ~pixel);
        }
      }
    }

    // Output the modified image
    if (exposed && killWindow == false)
    {
      XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
    }
  }

  // Goodbye
  XFree(image);
  XCloseDisplay(display);
}

不知何故,图像仍在被捕获并发送到新的 window,但 XImage 中的数据指针始终为空。我无法修改任何内容,并且 XGetPixel 和 XPutPixel 宏的任何使用都将失败。请注意,在此视频中,none 的颜色像以前一样反转:https://streamable.com/dckyv

这对我来说没有任何意义。很明显,数据仍在 windows 之间传输,但它在 XImage 结构中的什么位置?如何通过代码访问它?

原来我没有正确阅读 shmat 的签名。它没有 void return 类型,它 return 是一个空指针。这需要直接分配给 XImage 的数据指针,以便数据指针执行任何操作。

在对 XShmPutImage 的调用中,共享内存位置是在内部引用的,而不是 XImage 的数据指针,这就是为什么即使不使用来自 shmat 的 return 值它仍然有效的原因。

这是工作代码,以及我为错误处理和拆卸所做的其他一些补充:

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>

const int TEST_SIZE = 512;

static int handleXError(Display *display, XErrorEvent *event)
{
  printf("XErrorEvent triggered!\n");
  printf("error_code: %d", event->error_code);
  printf("minor_code: %d", event->minor_code);
  printf("request_code: %d", event->request_code);
  printf("resourceid: %lu", event->resourceid);
  printf("serial: %d", event->error_code);
  printf("type: %d", event->type);
  return 0;
}

int main()
{
  // Open default display
  XSetErrorHandler(handleXError);
  Display *display = XOpenDisplay(nullptr);
  int screen = DefaultScreen(display);
  Window rootWin = RootWindow(display, screen);
  GC graphicsContext = DefaultGC(display, screen);

  // Create new window and subscribe to events
  long blackPixel = BlackPixel(display, screen);
  long whitePixel = WhitePixel(display, screen);
  Window newWin = XCreateSimpleWindow(display, rootWin, 0, 0, TEST_SIZE, TEST_SIZE, 1, blackPixel, whitePixel);
  XMapWindow(display, newWin);
  XSelectInput(display, newWin, ExposureMask | KeyPressMask);

  // Allocate shared memory for image capturing
  XShmSegmentInfo shminfo;
  Visual *visual = DefaultVisual(display, screen);
  int depth = DefaultDepth(display, screen);
  XImage *image = XShmCreateImage(display, visual, depth, ZPixmap, nullptr, &shminfo, TEST_SIZE, TEST_SIZE);
  shminfo.shmid = shmget(IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0777);
  image->data = (char*)shmat(shminfo.shmid, 0, 0);
  shminfo.shmaddr = image->data;
  shminfo.readOnly = False;
  XShmAttach(display, &shminfo);
  XSync(display, false);
  shmctl(shminfo.shmid, IPC_RMID, 0);

  // Main event loop for new window
  XEvent event;
  bool exposed = false;
  bool killWindow = false;
  while (!killWindow)
  {
    // Handle pending events
    if (XPending(display) > 0)
    {
      XNextEvent(display, &event);
      if (event.type == Expose)
      {
        exposed = true;
      } else if (event.type == NoExpose)
      {
        exposed = false;
      } else if (event.type == KeyPress)
      {
        killWindow = true;
      }
    }

    // Capture the original image
    XShmGetImage(display, rootWin, image, 0, 0, AllPlanes);

    // Modify the image
    if(image->data != nullptr) // NEVER TRUE.  DATA IS ALWAYS NULL!
    {
      long pixel = 0;
      for (int x = 0; x < image->width; x++)
      {
        for (int y = 0; y < image->height; y++)
        {
          // Invert the color of each pixel
          pixel = XGetPixel(image, x, y);
          XPutPixel(image, x, y, ~pixel);
        }
      }
    }

    // Output the modified image
    if (exposed && killWindow == false)
    {
      XShmPutImage(display, newWin, graphicsContext, image, 0, 0, 0, 0, 512, 512, false);
    }
  }

  // Goodbye
  XShmDetach(display, &shminfo);
  XDestroyImage(image);
  shmdt(shminfo.shmaddr);
  XCloseDisplay(display);
}