blob: 78b950cc7a319041ffa77a2ce801293f5f8c88eb [file] [log] [blame]
/*
* 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;
}