| /* |
| * Copyright 2016 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| // Intel's Vulkan driver exposes an unregistered extension function, |
| // vkCreateDmaBufImageINTEL. There is no extension associated with the |
| // function (as of 2016-09-21). The Intel developers added the function during |
| // Vulkan's early early days, in Mesa's first Vulkan commit on 2015-05-08, to |
| // provide a convenient way to test the driver. The Vulkan API had no extension |
| // mechanism yet in that early pre-1.0 timeframe. |
| // |
| // We use vkCreateDmaBufImageINTEL, despite its ambiguous status, because there |
| // does not yet exist a good alternative for importing a dma_buf as a VkImage. |
| // |
| // The Vulkan validation layer (VK_LAYER_LUNARG_standard_validation) does not |
| // understand vkCreateDmaBufImageINTEL. To run with the validation layer, |
| // #undef USE_vkCreateDmaBufImageINTEL and rebuild. |
| #define USE_vkCreateDmaBufImageINTEL 1 |
| |
| #include <math.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <vulkan/vulkan.h> |
| |
| #ifdef USE_vkCreateDmaBufImageINTEL |
| #include <vulkan/vulkan_intel.h> |
| #endif |
| |
| #include "bs_drm.h" |
| |
| // Used for double-buffering. |
| struct frame { |
| struct gbm_bo *bo; |
| int bo_prime_fd; |
| uint32_t drm_fb_id; |
| VkDeviceMemory vk_memory; |
| VkImage vk_image; |
| VkImageView vk_image_view; |
| VkFramebuffer vk_framebuffer; |
| VkCommandBuffer vk_cmd_buf; |
| }; |
| |
| #define check_vk_success(result, vk_func) \ |
| __check_vk_success(__FILE__, __LINE__, __func__, (result), (vk_func)) |
| |
| static void |
| __check_vk_success(const char *file, int line, const char *func, |
| VkResult result, const char *vk_func) |
| { |
| if (result == VK_SUCCESS) |
| return; |
| |
| bs_debug_print("ERROR", func, file, line, |
| "%s failed with VkResult(%d)", vk_func, result); |
| exit(EXIT_FAILURE); |
| } |
| |
| static void |
| page_flip_handler(int fd, unsigned int frame, unsigned int sec, |
| unsigned int usec, void *data) |
| { |
| bool *waiting_for_flip = data; |
| *waiting_for_flip = false; |
| } |
| |
| // Choose the first physical device. Exit on failure. |
| VkPhysicalDevice |
| choose_physical_device(VkInstance inst) |
| { |
| uint32_t n_phys_devs; |
| VkResult res; |
| |
| res = vkEnumeratePhysicalDevices(inst, &n_phys_devs, NULL); |
| check_vk_success(res, "vkEnumeratePhysicalDevices"); |
| |
| if (n_phys_devs == 0) { |
| fprintf(stderr, "No available VkPhysicalDevices\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| VkPhysicalDevice phys_devs[n_phys_devs]; |
| res = vkEnumeratePhysicalDevices(inst, &n_phys_devs, phys_devs); |
| check_vk_success(res, "vkEnumeratePhysicalDevices"); |
| |
| // Print information about all available devices. This helps debugging |
| // when bringing up Vulkan on a new system. |
| printf("Available VkPhysicalDevices:\n"); |
| |
| for (uint32_t i = 0; i < n_phys_devs; ++i) { |
| VkPhysicalDeviceProperties props; |
| |
| vkGetPhysicalDeviceProperties(phys_devs[i], &props); |
| |
| printf(" VkPhysicalDevice %u:\n", i); |
| printf(" apiVersion: %u.%u.%u\n", |
| VK_VERSION_MAJOR(props.apiVersion), |
| VK_VERSION_MINOR(props.apiVersion), |
| VK_VERSION_PATCH(props.apiVersion)); |
| printf(" driverVersion: %u\n", props.driverVersion); |
| printf(" vendorID: 0x%x\n", props.vendorID); |
| printf(" deviceID: 0x%x\n", props.deviceID); |
| printf(" deviceName: %s\n", props.deviceName); |
| printf(" pipelineCacheUUID: %x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x\n", |
| props.pipelineCacheUUID[0], |
| props.pipelineCacheUUID[1], |
| props.pipelineCacheUUID[2], |
| props.pipelineCacheUUID[3], |
| props.pipelineCacheUUID[4], |
| props.pipelineCacheUUID[5], |
| props.pipelineCacheUUID[6], |
| props.pipelineCacheUUID[7], |
| props.pipelineCacheUUID[8], |
| props.pipelineCacheUUID[9], |
| props.pipelineCacheUUID[10], |
| props.pipelineCacheUUID[11], |
| props.pipelineCacheUUID[12], |
| props.pipelineCacheUUID[13], |
| props.pipelineCacheUUID[14], |
| props.pipelineCacheUUID[15]); |
| } |
| |
| printf("Chose VkPhysicalDevice 0\n"); |
| fflush(stdout); |
| |
| return phys_devs[0]; |
| } |
| |
| // Return the index of a graphics-enabled queue family. Return UINT32_MAX on |
| // failure. |
| uint32_t |
| choose_gfx_queue_family(VkPhysicalDevice phys_dev) |
| { |
| uint32_t family_idx = UINT32_MAX; |
| VkQueueFamilyProperties *props = NULL; |
| uint32_t n_props = 0; |
| |
| vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &n_props, NULL); |
| |
| props = calloc(sizeof(props[0]), n_props); |
| if (!props) { |
| bs_debug_error("out of memory"); |
| exit(EXIT_FAILURE); |
| } |
| |
| vkGetPhysicalDeviceQueueFamilyProperties(phys_dev, &n_props, props); |
| |
| // Choose the first graphics queue. |
| for (uint32_t i = 0; i < n_props; ++i) { |
| if ((props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && |
| props[i].queueCount > 0) { |
| family_idx = i; |
| break; |
| } |
| } |
| |
| free(props); |
| return family_idx; |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| VkInstance inst; |
| VkPhysicalDevice phys_dev; |
| uint32_t gfx_queue_family_idx; |
| VkDevice dev; |
| VkQueue gfx_queue; |
| VkRenderPass pass; |
| VkCommandPool cmd_pool; |
| VkResult res; |
| struct frame frames[2]; |
| int err; |
| |
| int dev_fd = bs_drm_open_main_display(); |
| if (dev_fd < 0) { |
| bs_debug_error("failed to open display device"); |
| exit(EXIT_FAILURE); |
| } |
| |
| struct gbm_device *gbm = gbm_create_device(dev_fd); |
| if (!gbm) { |
| bs_debug_error("failed to create gbm_device"); |
| exit(EXIT_FAILURE); |
| } |
| |
| struct bs_drm_pipe pipe = { 0 }; |
| if (!bs_drm_pipe_make(dev_fd, &pipe)) { |
| bs_debug_error("failed to make drm pipe"); |
| exit(EXIT_FAILURE); |
| } |
| |
| drmModeConnector *connector = drmModeGetConnector(dev_fd, pipe.connector_id); |
| if (!connector) { |
| bs_debug_error("drmModeGetConnector failed"); |
| exit(EXIT_FAILURE); |
| } |
| |
| drmModeModeInfo *mode = &connector->modes[0]; |
| |
| res = vkCreateInstance( |
| &(VkInstanceCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, |
| .pApplicationInfo = &(VkApplicationInfo) { |
| .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, |
| .apiVersion = VK_MAKE_VERSION(1, 0, 0), |
| }, |
| }, |
| /*pAllocator*/ NULL, |
| &inst); |
| check_vk_success(res, "vkCreateInstance"); |
| |
| phys_dev = choose_physical_device(inst); |
| |
| gfx_queue_family_idx = choose_gfx_queue_family(phys_dev); |
| if (gfx_queue_family_idx == UINT32_MAX) { |
| bs_debug_error("VkPhysicalDevice exposes no VkQueueFamilyProperties " |
| "with graphics"); |
| exit(EXIT_FAILURE); |
| } |
| |
| res = vkCreateDevice(phys_dev, |
| &(VkDeviceCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, |
| .queueCreateInfoCount = 1, |
| .pQueueCreateInfos = (VkDeviceQueueCreateInfo[]) { |
| { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| .queueFamilyIndex = gfx_queue_family_idx, |
| .queueCount = 1, |
| .pQueuePriorities = (float[]) { 1.0f }, |
| |
| }, |
| }, |
| }, |
| /*pAllocator*/ NULL, |
| &dev); |
| check_vk_success(res, "vkCreateDevice"); |
| |
| #if USE_vkCreateDmaBufImageINTEL |
| PFN_vkCreateDmaBufImageINTEL bs_vkCreateDmaBufImageINTEL = |
| (void *) vkGetDeviceProcAddr(dev, "vkCreateDmaBufImageINTEL"); |
| if (bs_vkCreateDmaBufImageINTEL == NULL) { |
| bs_debug_error("vkGetDeviceProcAddr(\"vkCreateDmaBufImageINTEL\') failed"); |
| exit(EXIT_FAILURE); |
| } |
| #endif |
| |
| vkGetDeviceQueue(dev, gfx_queue_family_idx, /*queueIndex*/ 0, &gfx_queue); |
| |
| res = vkCreateCommandPool(dev, |
| &(VkCommandPoolCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
| .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | |
| VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, |
| .queueFamilyIndex = gfx_queue_family_idx, |
| }, |
| /*pAllocator*/ NULL, |
| &cmd_pool); |
| check_vk_success(res, "vkCreateCommandPool"); |
| |
| res = vkCreateRenderPass(dev, |
| &(VkRenderPassCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, |
| .attachmentCount = 1, |
| .pAttachments = (VkAttachmentDescription[]) { |
| { |
| .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32, |
| .samples = 1, |
| .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, |
| .storeOp = VK_ATTACHMENT_STORE_OP_STORE, |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
| .finalLayout = VK_IMAGE_LAYOUT_GENERAL, |
| }, |
| }, |
| .subpassCount = 1, |
| .pSubpasses = (VkSubpassDescription[]) { |
| { |
| .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, |
| .colorAttachmentCount = 1, |
| .pColorAttachments = (VkAttachmentReference[]) { |
| { |
| .attachment = 0, |
| .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, |
| }, |
| }, |
| }, |
| }, |
| }, |
| /*pAllocator*/ NULL, |
| &pass); |
| check_vk_success(res, "vkCreateRenderPass"); |
| |
| for (int i = 0; i < BS_ARRAY_LEN(frames); ++i) { |
| struct frame *fr = &frames[i]; |
| |
| fr->bo = gbm_bo_create(gbm, mode->hdisplay, mode->vdisplay, |
| GBM_FORMAT_XBGR8888, |
| GBM_BO_USE_SCANOUT | |
| GBM_BO_USE_RENDERING); |
| if (fr->bo == NULL) { |
| bs_debug_error("failed to create framebuffer's gbm_bo"); |
| return 1; |
| } |
| |
| fr->drm_fb_id = bs_drm_fb_create_gbm(fr->bo); |
| if (fr->drm_fb_id == 0) { |
| bs_debug_error("failed to create drm framebuffer id"); |
| return 1; |
| } |
| |
| fr->bo_prime_fd = gbm_bo_get_fd(fr->bo); |
| if (fr->bo_prime_fd < 0) { |
| bs_debug_error("failed to get prime fd for gbm_bo"); |
| return 1; |
| } |
| |
| #if USE_vkCreateDmaBufImageINTEL |
| res = bs_vkCreateDmaBufImageINTEL(dev, |
| &(VkDmaBufImageCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_DMA_BUF_IMAGE_CREATE_INFO_INTEL, |
| .fd = fr->bo_prime_fd, |
| .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32, |
| .extent = (VkExtent3D) { mode->hdisplay, mode->hdisplay, 1 }, |
| .strideInBytes = gbm_bo_get_stride(fr->bo), |
| }, |
| /*pAllocator*/ NULL, |
| &fr->vk_memory, |
| &fr->vk_image); |
| check_vk_success(res, "vkCreateDmaBufImageINTEL"); |
| #else |
| res = vkCreateImage(dev, |
| &(VkImageCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, |
| .imageType = VK_IMAGE_TYPE_2D, |
| .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32, |
| .extent = (VkExtent3D) { mode->hdisplay, mode->hdisplay, 1 }, |
| .mipLevels = 1, |
| .arrayLayers = 1, |
| .samples = 1, |
| .tiling = VK_IMAGE_TILING_LINEAR, |
| .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, |
| .queueFamilyIndexCount = 1, |
| .pQueueFamilyIndices = (uint32_t[]) { gfx_queue_family_idx }, |
| .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, |
| }, |
| /*pAllocator*/ NULL, |
| &fr->vk_image); |
| check_vk_success(res, "vkCreateImage"); |
| |
| VkMemoryRequirements mem_reqs; |
| vkGetImageMemoryRequirements(dev, fr->vk_image, &mem_reqs); |
| |
| res = vkAllocateMemory(dev, |
| &(VkMemoryAllocateInfo) { |
| .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, |
| .allocationSize = mem_reqs.size, |
| |
| // Simply choose the first available memory type. |
| // We need neither performance nor mmap, so all |
| // memory types are equally good. |
| .memoryTypeIndex = ffs(mem_reqs.memoryTypeBits) - 1, |
| }, |
| /*pAllocator*/ NULL, |
| &fr->vk_image_memory); |
| check_vk_success(res, "vkAllocateMemory"); |
| |
| res = vkBindImageMemory(dev, fr->vk_image, fr->vk_image_memory, |
| /*memoryOffset*/ 0); |
| check_vk_success(res, "vkBindImageMemory"); |
| #endif |
| |
| res = vkCreateImageView(dev, |
| &(VkImageViewCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, |
| .image = fr->vk_image, |
| .viewType = VK_IMAGE_VIEW_TYPE_2D, |
| .format = VK_FORMAT_A8B8G8R8_UNORM_PACK32, |
| .components = (VkComponentMapping) { |
| .r = VK_COMPONENT_SWIZZLE_IDENTITY, |
| .b = VK_COMPONENT_SWIZZLE_IDENTITY, |
| .g = VK_COMPONENT_SWIZZLE_IDENTITY, |
| .a = VK_COMPONENT_SWIZZLE_IDENTITY, |
| }, |
| .subresourceRange = (VkImageSubresourceRange) { |
| .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
| .baseMipLevel = 0, |
| .levelCount = 1, |
| .baseArrayLayer = 0, |
| .layerCount = 1, |
| }, |
| }, |
| /*pAllocator*/ NULL, |
| &fr->vk_image_view); |
| check_vk_success(res, "vkCreateImageView"); |
| |
| res = vkCreateFramebuffer(dev, |
| &(VkFramebufferCreateInfo) { |
| .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, |
| .renderPass = pass, |
| .attachmentCount = 1, |
| .pAttachments = (VkImageView[]) { fr->vk_image_view }, |
| .width = mode->hdisplay, |
| .height = mode->vdisplay, |
| .layers = 1, |
| }, |
| /*pAllocator*/ NULL, |
| &fr->vk_framebuffer); |
| check_vk_success(res, "vkCreateFramebuffer"); |
| |
| res = vkAllocateCommandBuffers(dev, |
| &(VkCommandBufferAllocateInfo) { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, |
| .commandPool = cmd_pool, |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, |
| .commandBufferCount = 1, |
| }, |
| &fr->vk_cmd_buf); |
| check_vk_success(res, "vkAllocateCommandBuffers"); |
| } |
| |
| // We set the screen mode using framebuffer 0. Then the first page flip |
| // waits on framebuffer 1. |
| err = drmModeSetCrtc(dev_fd, pipe.crtc_id, frames[0].drm_fb_id, |
| /*x*/ 0, /*y*/ 0, |
| &pipe.connector_id, /*connector_count*/ 1, mode); |
| if (err) { |
| bs_debug_error("drmModeSetCrtc failed: %d", err); |
| exit(EXIT_FAILURE); |
| } |
| |
| // We set an upper bound on the render loop so we can run this in |
| // from a testsuite. |
| for (int i = 1; i < 500; ++i) { |
| struct frame *fr = &frames[i % BS_ARRAY_LEN(frames)]; |
| |
| // vkBeginCommandBuffer implicity resets the command buffer due |
| // to VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT. |
| res = vkBeginCommandBuffer(fr->vk_cmd_buf, |
| &(VkCommandBufferBeginInfo) { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, |
| .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, |
| }); |
| check_vk_success(res, "vkBeginCommandBuffer"); |
| |
| // Cycle along the circumference of the RGB color wheel. |
| VkClearValue clear_color = { |
| .color = { |
| .float32 = { |
| 0.5f + 0.5f * sinf(2*M_PI * i / 240.0f), |
| 0.5f + 0.5f * sinf(2*M_PI * i / 240.0f + (2.0f/3.0f * M_PI)), |
| 0.5f + 0.5f * sinf(2*M_PI * i / 240.0f + (4.0f/3.0f * M_PI)), |
| 1.0f, |
| }, |
| }, |
| }; |
| |
| vkCmdBeginRenderPass(fr->vk_cmd_buf, |
| &(VkRenderPassBeginInfo) { |
| .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, |
| .renderPass = pass, |
| .framebuffer = fr->vk_framebuffer, |
| .renderArea = (VkRect2D) { |
| .offset = { 0, 0 }, |
| .extent = { mode->hdisplay, mode->vdisplay }, |
| }, |
| .clearValueCount = 1, |
| .pClearValues = (VkClearValue[]) { clear_color }, |
| }, |
| VK_SUBPASS_CONTENTS_INLINE); |
| vkCmdEndRenderPass(fr->vk_cmd_buf); |
| |
| res = vkEndCommandBuffer(fr->vk_cmd_buf); |
| check_vk_success(res, "vkEndCommandBuffer"); |
| |
| res = vkQueueSubmit(gfx_queue, |
| /*submitCount*/ 1, |
| (VkSubmitInfo[]) { |
| { |
| .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, |
| .commandBufferCount = 1, |
| .pCommandBuffers = (VkCommandBuffer[]) { fr->vk_cmd_buf }, |
| }, |
| }, |
| VK_NULL_HANDLE); |
| check_vk_success(res, "vkQueueSubmit"); |
| |
| res = vkQueueWaitIdle(gfx_queue); |
| check_vk_success(res, "vkQueueWaitIdle"); |
| |
| bool waiting_for_flip = true; |
| err = drmModePageFlip(dev_fd, pipe.crtc_id, fr->drm_fb_id, |
| DRM_MODE_PAGE_FLIP_EVENT, |
| &waiting_for_flip); |
| if (err) { |
| bs_debug_error("failed page flip: error=%d", err); |
| exit(EXIT_FAILURE); |
| } |
| |
| while (waiting_for_flip) { |
| drmEventContext ev_ctx = { |
| .version = DRM_EVENT_CONTEXT_VERSION, |
| .page_flip_handler = page_flip_handler, |
| }; |
| |
| fd_set fds; |
| FD_ZERO(&fds); |
| FD_SET(dev_fd, &fds); |
| |
| int n_fds = select(dev_fd + 1, &fds, NULL, NULL, NULL); |
| if (n_fds < 0) { |
| bs_debug_error("select() failed on page flip: %s", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } else if (n_fds == 0) { |
| bs_debug_error("select() timeout on page flip"); |
| exit(EXIT_FAILURE); |
| } |
| |
| err = drmHandleEvent(dev_fd, &ev_ctx); |
| if (err) { |
| bs_debug_error("drmHandleEvent failed while " |
| "waiting for page flip: error=%d", |
| err); |
| exit(EXIT_FAILURE); |
| } |
| } |
| } |
| |
| return 0; |
| } |