Vulkan大闷锅之核心机制

到此,队列(Queue)的信息已经准备好,但这里仍需验证物理设备和GUI组建。

创建Surface

这里可选的组建非常多,以下只演示XCB的创建方式。你可能需要一个宏定义:

#define VK_USE_PLATFORM_XCB_KHR

以下是XCB Surface的信息结构:

typedef struct VkXcbSurfaceCreateInfoKHR {
    VkStructureType               sType;
    const void*                   pNext;
    VkXcbSurfaceCreateFlagsKHR    flags;
    xcb_connection_t*             connection;
    xcb_window_t                  window;
} VkXcbSurfaceCreateInfoKHR;

我们需要事先拿到connectionwindow两个变量:
[showhide more_text=”show more” less_text=”show less”]

xcb_connection_t *connection;
xcb_window_t window;
xcb_screen_t *screen;
xcb_intern_atom_reply_t *atom_wm_delete_window;
// init connection
const xcb_setup_t *setup;
xcb_screen_iterator_t iter;
int scr;

connection = xcb_connect(NULL, &scr);
if (connection == NULL || xcb_connection_has_error(connection))
{
  std::cout << "Unable to make an XCB connection\n"; 
  exit(-1); 
} 
setup = xcb_get_setup(connection); 
iter = xcb_setup_roots_iterator(setup); 
while (scr-- > 0)
  xcb_screen_next(&iter);

screen = iter.data;
// init window

uint32_t value_mask, value_list[32];

window = xcb_generate_id(connection);

value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
value_list[0] = screen->black_pixel;
value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE;
const uint32_t WIDTH = 600;
const uint32_t HEIGHT = 400
xcb_create_window(connection, XCB_COPY_FROM_PARENT, window, screen->root, 0,
                  0, WIDTH, HEIHGT, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
                  screen->root_visual, value_mask, value_list);

/* Magic code that will send notification when window is destroyed */
xcb_intern_atom_cookie_t cookie =
    xcb_intern_atom(connection, 1, 12, "WM_PROTOCOLS");
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookie, 0);

xcb_intern_atom_cookie_t cookie2 =
    xcb_intern_atom(connection, 0, 16, "WM_DELETE_WINDOW");
atom_wm_delete_window = xcb_intern_atom_reply(connection, cookie2, 0);

xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, (*reply).atom,
                    4, 32, 1, &(*atom_wm_delete_window).atom);

free(reply);

xcb_map_window(connection, window);

// Force the x/y coordinates to 100,100 results are identical in consecutive
// runs
const uint32_t coords[] = {100, 100};
xcb_configure_window(connection, window,
                      XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords);
xcb_flush(connection);

xcb_generic_event_t *e;
while ((e = xcb_wait_for_event(connection)))
{
  if ((e->response_type & ~0x80) == XCB_EXPOSE)
    break;
}

[/showhide]
为了方便,你也可以使用第三方GUI库。接下来创建Surface:

VkXcbSurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.connection = connection;
createInfo.window = window;

VkSurfaceKHR surface = {};
res = vkCreateXcbSurfaceKHR(inst, &createInfo, NULL, &surface);
assert(res == VK_SUCCESS);

验证物理设备和Surface

这里主要验证两项内容,队列是否支持图形指令以及Present操作(由于验证是可选的,这里代码略有重复):

 VkBool32 *pSupportsPresent =
      (VkBool32 *)malloc(queue_family_count * sizeof(VkBool32));

  for (uint32_t i = 0; i < queue_family_count; ++i)
  {
    vkGetPhysicalDeviceSurfaceSupportKHR(gpus[0], i, surface,
                                         &pSupportsPresent[i]);
    // assert(res);
  }

这里诊断总是失败,但不影响结果。官方示例没有在这里诊断(bug?)。

  uint32_t graphics_queue_family_index = UINT32_MAX;
  uint32_t present_queue_family_index = UINT32_MAX;
  for (uint32_t i = 0; i < queue_family_count; ++i)
  {
    if ((queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
    {
      if (graphics_queue_family_index == UINT32_MAX)
      {
        graphics_queue_family_index = i;
      }
      if (pSupportsPresent[i] == VK_TRUE)
      {
        graphics_queue_family_index = i;
        present_queue_family_index = i;
        break;
      }
    }
  }
  if (present_queue_family_index == UINT32_MAX)
  {
    for (size_t i = 0; i < queue_family_count; ++i)
    {
      if (pSupportsPresent[i] == VK_TRUE)
      {
        present_queue_family_index = i;
        break;
      }
    }
  }
  free(pSupportsPresent);

  if (graphics_queue_family_index == UINT32_MAX ||
      present_queue_family_index == UINT32_MAX)
  {
    std::cout << "could not find a queues for graphics and present"
              << std::endl;
    exit(-1);
  }

验证Surface是否支持VK_FORMAT_B8G8R8A8_UNORM格式:

uint32_t formatCount;
res = vkGetPhysicalDeviceSurfaceFormatsKHR(gpus[0], surface, &formatCount,
                                            nullptr);
assert(res == VK_SUCCESS);

VkSurfaceFormatKHR *surfFormats =
    (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR));
res = vkGetPhysicalDeviceSurfaceFormatsKHR(gpus[0], surface, &formatCount,
                                            surfFormats);
assert(res == VK_SUCCESS);
VkFormat format;
if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED)
{
  format = VK_FORMAT_B8G8R8A8_UNORM;
}
else
{
  assert(formatCount >= 1);
  format = surfFormats[0].format;
}
free(surfFormats);

验证不是必须的。

创建逻辑设备

VkDeviceCreateInfo device_info = {};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.pNext = nullptr;
device_info.queueCreateInfoCount = 1;
device_info.pQueueCreateInfos = &queue_info;
// use extension
std::vector<const char *> device_extension_names;
device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
device_info.enabledExtensionCount = device_extension_names.size();
device_info.ppEnabledExtensionNames = device_extension_names.data();

device_info.enabledLayerCount = 0;
device_info.ppEnabledLayerNames = nullptr;
device_info.pEnabledFeatures = nullptr;

VkDevice device;
res = vkCreateDevice(gpus[0], &device_info, nullptr, &device);
assert(res == VK_SUCCESS);

Vulkan的接口是C风格,vkAllocateCommandBuffers(device, &cmd, &cmd_buf)就相当于device.vkAllocateCommandBuffers(&cmd, &cmd_buf),Device其实就是我们核心交互的Class。另一个交互频繁的Class是Command Buffer。

创建Command Buffer

如前文所说指令通常暂存其中,然后异步提交给GPU。像这种同时暴露给CPU和GPU且易改动的内存,通常都会使用池,Vulkan自带了Command Pool:

VkCommandPoolCreateInfo cmd_pool_info = {};
cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmd_pool_info.pNext = nullptr;
cmd_pool_info.queueFamilyIndex = queue_info.queueFamilyIndex;
cmd_pool_info.flags = 0;
VkCommandPool cmd_pool;
res = vkCreateCommandPool(device, &cmd_pool_info, nullptr, &cmd_pool);
assert(res == VK_SUCCESS);

Command Buffer是有等级的,这里设置为VK_COMMAND_BUFFER_LEVEL_PRIMARY:

VkCommandBufferAllocateInfo cmd = {};
cmd.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmd.pNext = nullptr;
cmd.commandPool = cmd_pool;
cmd.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmd.commandBufferCount = 1;

VkCommandBuffer cmd_buf;
res = vkAllocateCommandBuffers(device, &cmd, &cmd_buf);
assert(res == VK_SUCCESS);

到此本系列的核心——窗口,逻辑设备和CommandBuffer已经创建好,同时我们为后续的工作准备了Command Buffer,接下来和传统图形接口就有点联系了。由于篇幅限制,有关Swapchain的内容将在下一章演示。

Leave a Reply

Your email address will not be published. Required fields are marked *