如果 "pure XCB" OpenGL 是不可能的,那么在 xcb/glx.h 中找到的 XCB/GLX API 有什么用?

If "pure XCB" OpenGL is impossible, then what's the use of the XCB/GLX API found in xcb/glx.h?

XCB 官方文档告诉我们using OpenGL purely with XCB is impossible:还必须使用 Xlib。

Bart Massey(XCB 的创建者)的

This post 并不表示这应该是不可能的。但我确定我遗漏了一些东西。

我花了几个小时浏览 xcb/glx.h,它组织得很好 here。在我看来,它就像一个成熟的 API。但是我无法让它工作。

问题

  1. XCB/GLXAPI没用吗? xcb/glx.h 的目的是什么?
  2. 如何使用 API?

(注意。这是了解 XCB 工作原理的持续努力的一部分。)

相关SO thread.


次要的、啰嗦的东西

如果有人愿意破解它,这里是 XCB 邮件列表中原始 post 的源代码,稍微精简并放入一个文件中。

你会注意到 xcb_glx_make_context_current returns error 169(不知道那是什么意思),但是 只有当 xcb_glx_create_window 最后两个参数采用 0NULL。这些参数涉及一个属性数组,它似乎是由函数 xcb_glx_create_window_attribs 编辑的 return,但我不知道如何使用它...

int main()之前的长辅助函数只表示两个return两个整数,xcb_glx_fbconfig_t fbconfigxcb_visualid_t glx_visual,对应第一个"matching"帧缓冲配置。在我的平台上,它们是 0xa70x24。它们正是 Xlib/GLX 例程(实际上有效)return,所以我知道我选择的帧缓冲区配置很好。

所以,问题 似乎 发生在 xcb_glx_create_windowxcb_glx_make_context_current 之间的某个地方...

// gcc main2.c -o main -lxcb -lxcb-glx -lGL && ./main2
// TODO free replies

#include <stdio.h>
#include <stdlib.h>
#include <xcb/glx.h>
#include <GL/gl.h>

#define W 1024
#define H 1024

// parameter types returned by xcb_glx_get_fb_configs
#define GLX_DRAWABLE_TYPE               0x8010
#define GLX_RENDER_TYPE                 0x8011
#define GLX_DOUBLEBUFFER                5
#define GLX_RED_SIZE                    8
#define GLX_GREEN_SIZE                  9
#define GLX_BLUE_SIZE                   10
#define GLX_RGBA_BIT                    0x00000001
#define GLX_RGBA_TYPE                   0x8014
#define GLX_STENCIL_SIZE                13
#define GLX_DEPTH_SIZE                  12
#define GLX_BUFFER_SIZE                 2
#define GLX_ALPHA_SIZE                  11
#define GLX_X_RENDERABLE                0x8012

#define GLX_FBCONFIG_ID                 0x8013
#define GLX_VISUAL_ID                   0x800b

#define GLX_WINDOW_BIT                  0x00000001
#define GLX_PIXMAP_BIT                  0x00000002
#define GLX_PBUFFER_BIT                 0x00000004


// ------------------------------------------------------------------------------------------------
// fbconfig and visual?
uint32_t glx_attrs[] = {
  GLX_DOUBLEBUFFER, 1,
  GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT|GLX_PIXMAP_BIT|GLX_PBUFFER_BIT,
  GLX_X_RENDERABLE, 1,  
  GLX_RED_SIZE, 8,
  GLX_GREEN_SIZE, 8,
  GLX_BLUE_SIZE, 8,
  GLX_ALPHA_SIZE, 8,
  GLX_STENCIL_SIZE, 8,
  GLX_DEPTH_SIZE, 24,
  GLX_BUFFER_SIZE, 32,
  GLX_RENDER_TYPE, GLX_RGBA_BIT,
};

// ------------------------------------------------------------------------------------------------
// This function searches for an @param prop_name in the @param property list of properties of size @param prop. Prop is property count and not buffer size.
uint32_t glx_get_property(const uint32_t* property, const uint props, uint32_t prop_name){
  uint i=0;
  while(i < props*2){
      if(property[i] == prop_name)
        return property[i+1];
      else i += 2;
  }
  return -1;
}

// This function chooses and returns specific fbconfig id depending on attributes specified in 
// @param attrib list. @param attribsz is the number of properties(not list size)
int32_t glx_choose_fbconfig(xcb_connection_t* connection, uint32_t screen_num, uint32_t* attrib, uint32_t attribsz){
  xcb_generic_error_t* xerror;

  xcb_glx_get_fb_configs_reply_t* fbconfigs = xcb_glx_get_fb_configs_reply(connection, xcb_glx_get_fb_configs(connection, screen_num), NULL);
  uint32_t* prop = xcb_glx_get_fb_configs_property_list(fbconfigs);

  uint32_t* fbconfig_line   = prop;
  uint32_t  fbconfig_linesz = fbconfigs->num_properties * 2;

  for(uint i=0 ; i<fbconfigs->num_FB_configs; i++){  // for each fbconfig line
      uint good_fbconfig = 1;

    for(uint j=0 ; j<attribsz*2; j += 2){  // for each attrib
    // if property found != property given
        if(glx_get_property(fbconfig_line, fbconfigs->num_properties, attrib[j]) != attrib[j+1]) {
            good_fbconfig = 0; // invalidate this fbconfig entry, sine one of the attribs doesn't match
            break;
        }
    }

    // if all attribs matched, return with fid
    if(good_fbconfig){
        uint32_t fbconfig_id = glx_get_property(fbconfig_line, fbconfigs->num_properties , GLX_FBCONFIG_ID);
        free(fbconfigs);
        return fbconfig_id;
    }

    fbconfig_line += fbconfig_linesz; // next fbconfig line;
  }
  return -1;
}

// This function returns @param attrib value from a line containing GLX_FBCONFIG_ID of @param fid
// It kind of queries particular fbconfig line for a specific property.
uint32_t glx_get_attrib_from_fbconfig(xcb_connection_t* connection, uint32_t screen_num, uint32_t fid, uint32_t attrib){
  xcb_glx_get_fb_configs_reply_t* fbconfigs = xcb_glx_get_fb_configs_reply(connection, xcb_glx_get_fb_configs(connection, screen_num), NULL);
  uint32_t* prop   = xcb_glx_get_fb_configs_property_list(fbconfigs);

  uint i = 0;
  uint fid_found = 0;
  while(i < fbconfigs->length){
    if(prop[i] == GLX_FBCONFIG_ID) {
      if(prop[i+1] == fid){
        fid_found = 1;
        i -= i%(fbconfigs->num_properties * 2); // going to start of the fbconfig  line
        uint32_t attrib_value = glx_get_property(&prop[i], fbconfigs->num_properties, attrib);
        free(fbconfigs);
        return attrib_value;
      }
    }
    i+=2;
  }
  if(fid_found) printf("glx_get_attrib_from_fbconfig: no attrib %u was found in a fbconfig with GLX_FBCONFIG_ID %u\n", attrib, fid);
  else printf("glx_get_attrib_from_fbconfig: GLX_FBCONFIG_ID %u was not found!\n", fid);
  return -1;
}

// ------------------------------------------------------------------------------------------------
int main(){
  xcb_generic_error_t* xerror; // To hold errors!
  int screen_number;

  xcb_connection_t* connection = xcb_connect(NULL, &screen_number);
  xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;   // getting the default screen
  printf("screen %d  root %d\n", screen_number, screen->root);

  xcb_colormap_t    colormap    = xcb_generate_id(connection);  // generating XID's for our objects!
  xcb_window_t      window      = xcb_generate_id(connection);
  xcb_glx_context_t glx_context = xcb_generate_id(connection);
  xcb_glx_window_t  glx_window  = xcb_generate_id(connection);

  // ----------------------------------------------------------------------------------------------
  xcb_glx_query_version_reply_t* glx_version = xcb_glx_query_version_reply(connection, xcb_glx_query_version(connection, 0, 0), NULL);
  printf("glx %d.%d  response_type %x  pad0 %x  sequence %x  length %d\n",
    glx_version->major_version, glx_version->minor_version, glx_version->response_type,
    glx_version->pad0, glx_version->sequence, glx_version->length);

  // ----------------------------------------------------------------------------------------------
  xcb_glx_fbconfig_t fbconfig   = glx_choose_fbconfig(connection, screen_number, glx_attrs, sizeof(glx_attrs)/2/sizeof(uint32_t));
  xcb_visualid_t glx_visual     = glx_get_attrib_from_fbconfig(connection, screen_number, fbconfig, GLX_VISUAL_ID);
  printf("fbconfig %x  glx_visual %x\n", fbconfig, glx_visual);

  // ----------------------------------------------------------------------------------------------
  xcb_glx_create_new_context(connection, glx_context, fbconfig, screen_number, GLX_RGBA_TYPE, 0, 1);  // New-style context?
  // xcb_glx_create_context(connection, glx_context, glx_visual, 0, 0, 1);  // Alt method! Old-style context?

  if(!(xcb_glx_is_direct_reply(connection, xcb_glx_is_direct(connection, glx_context), NULL)->is_direct))
    puts("glx context is not direct!");

  // ----------------------------------------------------------------------------------------------
  xcb_create_colormap(connection , XCB_COLORMAP_ALLOC_NONE, colormap, screen->root, glx_visual);  // creating colormap

  // creating a window, using our new colormap
  uint32_t window_mask = XCB_CW_BACK_PIXEL|XCB_CW_EVENT_MASK|XCB_CW_COLORMAP;
  uint32_t window_attrs[] = {0x444444, XCB_EVENT_MASK_EXPOSURE|XCB_EVENT_MASK_KEY_PRESS, colormap};

  xcb_create_window(connection, screen->root_depth, window, screen->root, 0,0, W,H, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, glx_visual, window_mask, window_attrs);
  xcb_map_window(connection, window);

  xcb_glx_create_window(connection, screen_number, fbconfig, window, glx_window, 0, NULL);
  xcb_flush(connection);

  // ----------------------------------------------------------------------------------------------
  xcb_glx_make_context_current_reply_t* reply_ctx = xcb_glx_make_context_current_reply(connection, xcb_glx_make_context_current(connection, 0, glx_window, glx_window, glx_context), NULL);
  if(!reply_ctx)  puts("ERROR  xcb_glx_make_context_current returned NULL!");
  xcb_glx_context_tag_t glx_context_tag = reply_ctx->context_tag;

  // alternative ?
  // xcb_glx_make_current_reply_t* reply_mc = xcb_glx_make_current_reply(connection, xcb_glx_make_current(connection, glx_window, glx_context, 0), NULL);
  // xcb_glx_context_tag_t glx_context_tag = reply_mc->context_tag;

  // ----------------------------------------------------------------------------------------------
  xcb_glx_get_error_reply(connection, xcb_glx_get_error(connection, glx_context_tag), &xerror);
  if(xerror)  printf("\nERROR  xcb_glx_get_error %d\n", xerror->error_code);

  // ----------------------------------------------------------------------------------------------
  xcb_generic_event_t* event;
  uint running = 1;
  while(running){
    event = xcb_poll_for_event(connection);
    if(event){
      switch (event->response_type) {
        case XCB_EXPOSE:
          glClearColor(0, .5, 1, 1);  // Blue
          glFlush();
          xcb_glx_swap_buffers(connection, glx_context_tag, glx_window);
          puts("Expose!");
          break;
        case XCB_KEY_PRESS: // exit on key press
          running = 0;
          break;
      }
    }
    free(event);
  }

  xcb_disconnect(connection);
}

这里需要理解的重要一点是 XCB 函数直接映射到 X11 协议请求。这意味着 xcb_glx_* 函数直接映射到 X11 GLX 协议请求。请参阅 https://www.khronos.org/registry/OpenGL/specs/gl/glx1.4.pdf It lists all available GLX requests. For example glAreTexturesResident maps to xcb_glx_are_textures_resident_* APIs from xcb/glx.h ( https://xcb.freedesktop.org/manual/glx_8h_source.html 中的 "Chapter 4")。在 Khronos 规范中,您可以阅读什么请求做什么。

What's the purpose of xcb/glx.h?

XCB-GLX 仅与 X 服务器通信,它执行任何硬件初始化或接触 OpenGL 客户端状态。因此,XCB-GLX 不能用作 GLX API 的替代品。 [1]

硬件初始化和其他 GL 内容由 openGL 库完成。这就是实施规范的 "other half" 的地方。在 Linux 上,libGL 由 mesa (https://cgit.freedesktop.org/mesa/mesa/tree/src/glx) 提供。你可以看到 glx 目录中的文件包含 Xlib.h,所以我猜这就是 Xlib 依赖项的来源。这解释了 "GLX API is closely coupled with Xlib. As a result, an OpenGL application on the X Windows must use Xlib and thus can’t be done using only XCB." [1].

Is the XCB/GLX API useless?

尽管 XCB-GLX API 对最终用户 XCB 应用程序开发人员没有什么价值,但它可用于开发新的基于 XCB 的 OpenGL 和 GLX 实现。 XCB 可能会提高 OpenGL 库的速度和质量。 [1]

所以要获得纯 XCB GLX,有人需要在 openGL 库中重新实现 GLX:

  • 公开基于 XCB 的APIs 以初始化硬件等
  • 应该利用 xcb_glx APIs 与 X 服务器通信,而不是 Xlib。

来自 [1] 的文档说 "The GLX system has two roles, it communicates with the X server and initializes client-side and hardware state."

xcb-glx 负责通信角色。另一个角色(基于 XCB 的 OpenGL 和 GLX 实现)目前尚未实现,而且不太可能实现。

"The GLX API is specified in terms of Xlib, the glX functions use Xlib Displays, Windows, Visuals, etc. The GLX implementations are also built using Xlib."(参见 libGL.so 的导出符号)。为了完成第二个角色,我们需要一个使用 XCB 连接的相等 API,windows,视觉效果。

编辑:如果目标是编写一个不依赖于 Xlib 的 X11 应用程序,那么也许可以使用 Vulkan 进行渲染。

[1] https://xcb.freedesktop.org/opengl/

免责声明:这是我从我收集到的理解。

为了使接受的答案更加清晰:xcb_glx_* 函数完全没用。不要使用它们,甚至不要在文件中包含 xcb/glx.h(示例 on the xcb website 也不包含它们)。它们不会工作,因为供应商没有为它们实现全部功能。他们的存在是因为他们决定开始为 xcb 而不仅仅是 Xlib 实现这个功能。任何时候你需要一个 glx 函数,你的 only 选项是 Xlib,即使 some xcb 函数恰好工作并给出相同的输出。

显示的示例代码可能会在不同程度上起作用。在我的计算机上,window 打开但上下文无效。您可能会获得更多或更少的功能。在任何情况下,您的代码几乎肯定不会在其他地方正常工作,所以请不要使用 xcb_glx_*。我当然在这上面浪费了我自己的时间,所以你不必。