Files
test/intern/ghost/intern/GHOST_ContextVK.cpp
Jeroen Bakker 6dac345a64 GHOST: Vulkan Backend.
This adds a vulkan backend to GHOST. The code was extracted from the
tmp-vulkan branch. The main difference with the original code is that
GHOST isn't responsible for fallback. For Metal backend there is already
an idea that the GPU module is responsible for the fallback, not the system.

For Blender we target Vulkan 1.2 at the time of this patch.
MoltenVK (needed to convert Vulkan calls to Metal) has been added as
a separate package.

This patch isn't useful for end-users, currently when starting blender with
`--gpu-backend vulkan` it would crash as the `VBBackend` doesn't initialize
the expected global structs in the GPU module.

Validated to be working on Windows and Apple. Linux still needs to be tested.

Reviewed By: fclem

Differential Revision: https://developer.blender.org/D13155
2022-11-22 11:29:09 +01:00

976 lines
33 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup GHOST
*/
#include "GHOST_ContextVK.h"
#ifdef _WIN32
# include <vulkan/vulkan_win32.h>
#elif defined(__APPLE__)
# include <MoltenVK/vk_mvk_moltenvk.h>
#else /* X11 */
# include <vulkan/vulkan_xlib.h>
# ifdef WITH_GHOST_WAYLAND
# include <vulkan/vulkan_wayland.h>
# endif
#endif
#include <vector>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <iostream>
/* Set to 0 to allow devices that do not have the required features.
* This allows development on OSX until we really needs these features. */
#define STRICT_REQUIREMENTS 1
using namespace std;
static const char *vulkan_error_as_string(VkResult result)
{
#define FORMAT_ERROR(X) \
case X: { \
return "" #X; \
}
switch (result) {
FORMAT_ERROR(VK_NOT_READY);
FORMAT_ERROR(VK_TIMEOUT);
FORMAT_ERROR(VK_EVENT_SET);
FORMAT_ERROR(VK_EVENT_RESET);
FORMAT_ERROR(VK_INCOMPLETE);
FORMAT_ERROR(VK_ERROR_OUT_OF_HOST_MEMORY);
FORMAT_ERROR(VK_ERROR_OUT_OF_DEVICE_MEMORY);
FORMAT_ERROR(VK_ERROR_INITIALIZATION_FAILED);
FORMAT_ERROR(VK_ERROR_DEVICE_LOST);
FORMAT_ERROR(VK_ERROR_MEMORY_MAP_FAILED);
FORMAT_ERROR(VK_ERROR_LAYER_NOT_PRESENT);
FORMAT_ERROR(VK_ERROR_EXTENSION_NOT_PRESENT);
FORMAT_ERROR(VK_ERROR_FEATURE_NOT_PRESENT);
FORMAT_ERROR(VK_ERROR_INCOMPATIBLE_DRIVER);
FORMAT_ERROR(VK_ERROR_TOO_MANY_OBJECTS);
FORMAT_ERROR(VK_ERROR_FORMAT_NOT_SUPPORTED);
FORMAT_ERROR(VK_ERROR_FRAGMENTED_POOL);
FORMAT_ERROR(VK_ERROR_UNKNOWN);
FORMAT_ERROR(VK_ERROR_OUT_OF_POOL_MEMORY);
FORMAT_ERROR(VK_ERROR_INVALID_EXTERNAL_HANDLE);
FORMAT_ERROR(VK_ERROR_FRAGMENTATION);
FORMAT_ERROR(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS);
FORMAT_ERROR(VK_ERROR_SURFACE_LOST_KHR);
FORMAT_ERROR(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR);
FORMAT_ERROR(VK_SUBOPTIMAL_KHR);
FORMAT_ERROR(VK_ERROR_OUT_OF_DATE_KHR);
FORMAT_ERROR(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR);
FORMAT_ERROR(VK_ERROR_VALIDATION_FAILED_EXT);
FORMAT_ERROR(VK_ERROR_INVALID_SHADER_NV);
FORMAT_ERROR(VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT);
FORMAT_ERROR(VK_ERROR_NOT_PERMITTED_EXT);
FORMAT_ERROR(VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT);
FORMAT_ERROR(VK_THREAD_IDLE_KHR);
FORMAT_ERROR(VK_THREAD_DONE_KHR);
FORMAT_ERROR(VK_OPERATION_DEFERRED_KHR);
FORMAT_ERROR(VK_OPERATION_NOT_DEFERRED_KHR);
FORMAT_ERROR(VK_PIPELINE_COMPILE_REQUIRED_EXT);
default:
return "Unknown Error";
}
}
#define __STR(A) "" #A
#define VK_CHECK(__expression) \
do { \
VkResult r = (__expression); \
if (r != VK_SUCCESS) { \
fprintf(stderr, \
"Vulkan Error : %s:%d : %s failled with %s\n", \
__FILE__, \
__LINE__, \
__STR(__expression), \
vulkan_error_as_string(r)); \
return GHOST_kFailure; \
} \
} while (0)
#define DEBUG_PRINTF(...) \
if (m_debug) { \
printf(__VA_ARGS__); \
}
/* Tripple buffering. */
const int MAX_FRAMES_IN_FLIGHT = 2;
GHOST_ContextVK::GHOST_ContextVK(bool stereoVisual,
#ifdef _WIN32
HWND hwnd,
#elif defined(__APPLE__)
CAMetalLayer *metal_layer,
#else
GHOST_TVulkanPlatformType platform,
/* X11 */
Window window,
Display *display,
/* Wayland */
wl_surface *wayland_surface,
wl_display *wayland_display,
#endif
int contextMajorVersion,
int contextMinorVersion,
int debug)
: GHOST_Context(stereoVisual),
#ifdef _WIN32
m_hwnd(hwnd),
#elif defined(__APPLE__)
m_metal_layer(metal_layer),
#else
m_platform(platform),
/* X11 */
m_display(display),
m_window(window),
/* Wayland */
m_wayland_surface(wayland_surface),
m_wayland_display(wayland_display),
#endif
m_context_major_version(contextMajorVersion),
m_context_minor_version(contextMinorVersion),
m_debug(debug),
m_instance(VK_NULL_HANDLE),
m_physical_device(VK_NULL_HANDLE),
m_device(VK_NULL_HANDLE),
m_command_pool(VK_NULL_HANDLE),
m_surface(VK_NULL_HANDLE),
m_swapchain(VK_NULL_HANDLE),
m_render_pass(VK_NULL_HANDLE)
{
}
GHOST_ContextVK::~GHOST_ContextVK()
{
if (m_device) {
vkDeviceWaitIdle(m_device);
}
destroySwapchain();
if (m_command_pool != VK_NULL_HANDLE) {
vkDestroyCommandPool(m_device, m_command_pool, NULL);
}
if (m_device != VK_NULL_HANDLE) {
vkDestroyDevice(m_device, NULL);
}
if (m_surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(m_instance, m_surface, NULL);
}
if (m_instance != VK_NULL_HANDLE) {
vkDestroyInstance(m_instance, NULL);
}
}
GHOST_TSuccess GHOST_ContextVK::destroySwapchain()
{
if (m_device != VK_NULL_HANDLE) {
vkDeviceWaitIdle(m_device);
}
m_in_flight_images.resize(0);
for (auto semaphore : m_image_available_semaphores) {
vkDestroySemaphore(m_device, semaphore, NULL);
}
for (auto semaphore : m_render_finished_semaphores) {
vkDestroySemaphore(m_device, semaphore, NULL);
}
for (auto fence : m_in_flight_fences) {
vkDestroyFence(m_device, fence, NULL);
}
for (auto framebuffer : m_swapchain_framebuffers) {
vkDestroyFramebuffer(m_device, framebuffer, NULL);
}
if (m_render_pass != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, m_render_pass, NULL);
}
for (auto command_buffer : m_command_buffers) {
vkFreeCommandBuffers(m_device, m_command_pool, 1, &command_buffer);
}
for (auto imageView : m_swapchain_image_views) {
vkDestroyImageView(m_device, imageView, NULL);
}
if (m_swapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(m_device, m_swapchain, NULL);
}
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::swapBuffers()
{
if (m_swapchain == VK_NULL_HANDLE) {
return GHOST_kFailure;
}
vkWaitForFences(m_device, 1, &m_in_flight_fences[m_currentFrame], VK_TRUE, UINT64_MAX);
VkResult result = vkAcquireNextImageKHR(m_device,
m_swapchain,
UINT64_MAX,
m_image_available_semaphores[m_currentFrame],
VK_NULL_HANDLE,
&m_currentImage);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
/* Swapchain is out of date. Recreate swapchain and skip this frame. */
destroySwapchain();
createSwapchain();
return GHOST_kSuccess;
}
else if (result != VK_SUCCESS) {
fprintf(stderr,
"Error: Failed to acquire swap chain image : %s\n",
vulkan_error_as_string(result));
return GHOST_kFailure;
}
/* Check if a previous frame is using this image (i.e. there is its fence to wait on) */
if (m_in_flight_images[m_currentImage] != VK_NULL_HANDLE) {
vkWaitForFences(m_device, 1, &m_in_flight_images[m_currentImage], VK_TRUE, UINT64_MAX);
}
m_in_flight_images[m_currentImage] = m_in_flight_fences[m_currentFrame];
VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT};
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &m_image_available_semaphores[m_currentFrame];
submit_info.pWaitDstStageMask = wait_stages;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &m_command_buffers[m_currentImage];
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &m_render_finished_semaphores[m_currentFrame];
vkResetFences(m_device, 1, &m_in_flight_fences[m_currentFrame]);
VK_CHECK(vkQueueSubmit(m_graphic_queue, 1, &submit_info, m_in_flight_fences[m_currentFrame]));
do {
result = vkWaitForFences(m_device, 1, &m_in_flight_fences[m_currentFrame], VK_TRUE, 10000);
} while (result == VK_TIMEOUT);
VK_CHECK(vkQueueWaitIdle(m_graphic_queue));
VkPresentInfoKHR present_info = {};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &m_render_finished_semaphores[m_currentFrame];
present_info.swapchainCount = 1;
present_info.pSwapchains = &m_swapchain;
present_info.pImageIndices = &m_currentImage;
present_info.pResults = NULL;
result = vkQueuePresentKHR(m_present_queue, &present_info);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
/* Swapchain is out of date. Recreate swapchain and skip this frame. */
destroySwapchain();
createSwapchain();
return GHOST_kSuccess;
}
else if (result != VK_SUCCESS) {
fprintf(stderr,
"Error: Failed to present swap chain image : %s\n",
vulkan_error_as_string(result));
return GHOST_kFailure;
}
m_currentFrame = (m_currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::getVulkanBackbuffer(void *image,
void *framebuffer,
void *command_buffer,
void *render_pass,
void *extent,
uint32_t *fb_id)
{
if (m_swapchain == VK_NULL_HANDLE) {
return GHOST_kFailure;
}
*((VkImage *)image) = m_swapchain_images[m_currentImage];
*((VkFramebuffer *)framebuffer) = m_swapchain_framebuffers[m_currentImage];
*((VkCommandBuffer *)command_buffer) = m_command_buffers[m_currentImage];
*((VkRenderPass *)render_pass) = m_render_pass;
*((VkExtent2D *)extent) = m_render_extent;
*fb_id = m_swapchain_id * 10 + m_currentFrame;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::getVulkanHandles(void *r_instance,
void *r_physical_device,
void *r_device,
uint32_t *r_graphic_queue_familly)
{
*((VkInstance *)r_instance) = m_instance;
*((VkPhysicalDevice *)r_physical_device) = m_physical_device;
*((VkDevice *)r_device) = m_device;
*r_graphic_queue_familly = m_queue_family_graphic;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::activateDrawingContext()
{
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::releaseDrawingContext()
{
return GHOST_kSuccess;
}
static vector<VkExtensionProperties> getExtensionsAvailable()
{
uint32_t extension_count = 0;
vkEnumerateInstanceExtensionProperties(NULL, &extension_count, NULL);
vector<VkExtensionProperties> extensions(extension_count);
vkEnumerateInstanceExtensionProperties(NULL, &extension_count, extensions.data());
return extensions;
}
static bool checkExtensionSupport(vector<VkExtensionProperties> &extensions_available,
const char *extension_name)
{
for (const auto &extension : extensions_available) {
if (strcmp(extension_name, extension.extensionName) == 0) {
return true;
}
}
return false;
}
static void requireExtension(vector<VkExtensionProperties> &extensions_available,
vector<const char *> &extensions_enabled,
const char *extension_name)
{
if (checkExtensionSupport(extensions_available, extension_name)) {
extensions_enabled.push_back(extension_name);
}
else {
fprintf(stderr, "Error: %s not found.\n", extension_name);
}
}
static vector<VkLayerProperties> getLayersAvailable()
{
uint32_t layer_count = 0;
vkEnumerateInstanceLayerProperties(&layer_count, NULL);
vector<VkLayerProperties> layers(layer_count);
vkEnumerateInstanceLayerProperties(&layer_count, layers.data());
return layers;
}
static bool checkLayerSupport(vector<VkLayerProperties> &layers_available, const char *layer_name)
{
for (const auto &layer : layers_available) {
if (strcmp(layer_name, layer.layerName) == 0) {
return true;
}
}
return false;
}
static void enableLayer(vector<VkLayerProperties> &layers_available,
vector<const char *> &layers_enabled,
const char *layer_name)
{
if (checkLayerSupport(layers_available, layer_name)) {
layers_enabled.push_back(layer_name);
}
else {
fprintf(stderr, "Error: %s not supported.\n", layer_name);
}
}
static bool device_extensions_support(VkPhysicalDevice device, vector<const char *> required_exts)
{
uint32_t ext_count;
vkEnumerateDeviceExtensionProperties(device, NULL, &ext_count, NULL);
vector<VkExtensionProperties> available_exts(ext_count);
vkEnumerateDeviceExtensionProperties(device, NULL, &ext_count, available_exts.data());
for (const auto &extension_needed : required_exts) {
bool found = false;
for (const auto &extension : available_exts) {
if (strcmp(extension_needed, extension.extensionName) == 0) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
GHOST_TSuccess GHOST_ContextVK::pickPhysicalDevice(vector<const char *> required_exts)
{
m_physical_device = VK_NULL_HANDLE;
uint32_t device_count = 0;
vkEnumeratePhysicalDevices(m_instance, &device_count, NULL);
vector<VkPhysicalDevice> physical_devices(device_count);
vkEnumeratePhysicalDevices(m_instance, &device_count, physical_devices.data());
int best_device_score = -1;
for (const auto &physical_device : physical_devices) {
VkPhysicalDeviceProperties device_properties;
vkGetPhysicalDeviceProperties(physical_device, &device_properties);
VkPhysicalDeviceFeatures features;
vkGetPhysicalDeviceFeatures(physical_device, &features);
DEBUG_PRINTF("%s : \n", device_properties.deviceName);
if (!device_extensions_support(physical_device, required_exts)) {
DEBUG_PRINTF(" - Device does not support required device extensions.\n");
continue;
}
if (m_surface != VK_NULL_HANDLE) {
uint32_t format_count;
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &format_count, NULL);
uint32_t present_count;
vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_count, NULL);
/* For now anything will do. */
if (format_count == 0 || present_count == 0) {
DEBUG_PRINTF(" - Device does not support presentation.\n");
continue;
}
}
if (!features.geometryShader) {
/* Needed for wide lines emulation and barycentric coords and a few others. */
DEBUG_PRINTF(" - Device does not support geometryShader.\n");
}
if (!features.dualSrcBlend) {
DEBUG_PRINTF(" - Device does not support dualSrcBlend.\n");
}
if (!features.logicOp) {
/* Needed by UI. */
DEBUG_PRINTF(" - Device does not support logicOp.\n");
}
#if STRICT_REQUIREMENTS
if (!features.geometryShader || !features.dualSrcBlend || !features.logicOp) {
continue;
}
#endif
int device_score = 0;
switch (device_properties.deviceType) {
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
device_score = 400;
break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
device_score = 300;
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
device_score = 200;
break;
case VK_PHYSICAL_DEVICE_TYPE_CPU:
device_score = 100;
break;
default:
break;
}
if (device_score > best_device_score) {
m_physical_device = physical_device;
best_device_score = device_score;
}
DEBUG_PRINTF(" - Device suitable.\n");
}
if (m_physical_device == VK_NULL_HANDLE) {
fprintf(stderr, "Error: No suitable Vulkan Device found!\n");
return GHOST_kFailure;
}
return GHOST_kSuccess;
}
static GHOST_TSuccess getGraphicQueueFamily(VkPhysicalDevice device, uint32_t *r_queue_index)
{
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, NULL);
vector<VkQueueFamilyProperties> queue_families(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
*r_queue_index = 0;
for (const auto &queue_family : queue_families) {
if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
return GHOST_kSuccess;
}
(*r_queue_index)++;
}
fprintf(stderr, "Couldn't find any Graphic queue familly on selected device\n");
return GHOST_kFailure;
}
static GHOST_TSuccess getPresetQueueFamily(VkPhysicalDevice device,
VkSurfaceKHR surface,
uint32_t *r_queue_index)
{
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, NULL);
vector<VkQueueFamilyProperties> queue_families(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
*r_queue_index = 0;
for (int i = 0; i < queue_family_count; i++) {
VkBool32 present_support = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, *r_queue_index, surface, &present_support);
if (present_support) {
return GHOST_kSuccess;
}
(*r_queue_index)++;
}
fprintf(stderr, "Couldn't find any Present queue familly on selected device\n");
return GHOST_kFailure;
}
static GHOST_TSuccess create_render_pass(VkDevice device,
VkFormat format,
VkRenderPass *r_renderPass)
{
VkAttachmentDescription colorAttachment = {};
colorAttachment.format = format;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference colorAttachmentRef = {};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
VkRenderPassCreateInfo renderPassInfo = {};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
VK_CHECK(vkCreateRenderPass(device, &renderPassInfo, NULL, r_renderPass));
return GHOST_kSuccess;
}
static GHOST_TSuccess selectPresentMode(VkPhysicalDevice device,
VkSurfaceKHR surface,
VkPresentModeKHR *r_presentMode)
{
uint32_t present_count;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_count, NULL);
vector<VkPresentModeKHR> presents(present_count);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_count, presents.data());
/* MAILBOX is the lowest latency V-Sync enabled mode so use it if available */
for (auto present_mode : presents) {
if (present_mode == VK_PRESENT_MODE_FIFO_KHR) {
*r_presentMode = present_mode;
return GHOST_kSuccess;
}
}
/* FIFO present mode is always available. */
for (auto present_mode : presents) {
if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) {
*r_presentMode = present_mode;
return GHOST_kSuccess;
}
}
fprintf(stderr, "Error: FIFO present mode is not supported by the swap chain!\n");
return GHOST_kFailure;
}
GHOST_TSuccess GHOST_ContextVK::createCommandBuffers()
{
m_command_buffers.resize(m_swapchain_image_views.size());
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = m_queue_family_graphic;
VK_CHECK(vkCreateCommandPool(m_device, &poolInfo, NULL, &m_command_pool));
VkCommandBufferAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.commandPool = m_command_pool;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandBufferCount = static_cast<uint32_t>(m_command_buffers.size());
VK_CHECK(vkAllocateCommandBuffers(m_device, &alloc_info, m_command_buffers.data()));
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::createSwapchain()
{
m_swapchain_id++;
VkPhysicalDevice device = m_physical_device;
uint32_t format_count;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_surface, &format_count, NULL);
vector<VkSurfaceFormatKHR> formats(format_count);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_surface, &format_count, formats.data());
/* TODO choose appropriate format. */
VkSurfaceFormatKHR format = formats[0];
VkPresentModeKHR present_mode;
if (!selectPresentMode(device, m_surface, &present_mode)) {
return GHOST_kFailure;
}
VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, m_surface, &capabilities);
m_render_extent = capabilities.currentExtent;
if (m_render_extent.width == UINT32_MAX) {
/* Window Manager is going to set the surface size based on the given size.
* Choose something between minImageExtent and maxImageExtent. */
m_render_extent.width = 1280;
m_render_extent.height = 720;
if (capabilities.minImageExtent.width > m_render_extent.width) {
m_render_extent.width = capabilities.minImageExtent.width;
}
if (capabilities.minImageExtent.height > m_render_extent.height) {
m_render_extent.height = capabilities.minImageExtent.height;
}
}
/* Driver can stall if only using minimal image count. */
uint32_t image_count = capabilities.minImageCount;
/* Note: maxImageCount == 0 means no limit. */
if (image_count > capabilities.maxImageCount && capabilities.maxImageCount > 0) {
image_count = capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
create_info.surface = m_surface;
create_info.minImageCount = image_count;
create_info.imageFormat = format.format;
create_info.imageColorSpace = format.colorSpace;
create_info.imageExtent = m_render_extent;
create_info.imageArrayLayers = 1;
create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
create_info.preTransform = capabilities.currentTransform;
create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
create_info.presentMode = present_mode;
create_info.clipped = VK_TRUE;
create_info.oldSwapchain = VK_NULL_HANDLE; /* TODO Window resize */
uint32_t queueFamilyIndices[] = {m_queue_family_graphic, m_queue_family_present};
if (m_queue_family_graphic != m_queue_family_present) {
create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
create_info.queueFamilyIndexCount = 2;
create_info.pQueueFamilyIndices = queueFamilyIndices;
}
else {
create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
create_info.queueFamilyIndexCount = 0;
create_info.pQueueFamilyIndices = NULL;
}
VK_CHECK(vkCreateSwapchainKHR(m_device, &create_info, NULL, &m_swapchain));
create_render_pass(m_device, format.format, &m_render_pass);
/* image_count may not be what we requested! Getter for final value. */
vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, NULL);
m_swapchain_images.resize(image_count);
vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, m_swapchain_images.data());
m_in_flight_images.resize(image_count, VK_NULL_HANDLE);
m_swapchain_image_views.resize(image_count);
m_swapchain_framebuffers.resize(image_count);
for (int i = 0; i < image_count; i++) {
VkImageViewCreateInfo view_create_info = {};
view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_create_info.image = m_swapchain_images[i];
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_create_info.format = format.format;
view_create_info.components = {
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
};
view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_create_info.subresourceRange.baseMipLevel = 0;
view_create_info.subresourceRange.levelCount = 1;
view_create_info.subresourceRange.baseArrayLayer = 0;
view_create_info.subresourceRange.layerCount = 1;
VK_CHECK(vkCreateImageView(m_device, &view_create_info, NULL, &m_swapchain_image_views[i]));
VkImageView attachments[] = {m_swapchain_image_views[i]};
VkFramebufferCreateInfo fb_create_info = {};
fb_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fb_create_info.renderPass = m_render_pass;
fb_create_info.attachmentCount = 1;
fb_create_info.pAttachments = attachments;
fb_create_info.width = m_render_extent.width;
fb_create_info.height = m_render_extent.height;
fb_create_info.layers = 1;
VK_CHECK(vkCreateFramebuffer(m_device, &fb_create_info, NULL, &m_swapchain_framebuffers[i]));
}
m_image_available_semaphores.resize(MAX_FRAMES_IN_FLIGHT);
m_render_finished_semaphores.resize(MAX_FRAMES_IN_FLIGHT);
m_in_flight_fences.resize(MAX_FRAMES_IN_FLIGHT);
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkSemaphoreCreateInfo semaphore_info = {};
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VK_CHECK(vkCreateSemaphore(m_device, &semaphore_info, NULL, &m_image_available_semaphores[i]));
VK_CHECK(vkCreateSemaphore(m_device, &semaphore_info, NULL, &m_render_finished_semaphores[i]));
VkFenceCreateInfo fence_info = {};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
VK_CHECK(vkCreateFence(m_device, &fence_info, NULL, &m_in_flight_fences[i]));
}
createCommandBuffers();
return GHOST_kSuccess;
}
const char *GHOST_ContextVK::getPlatformSpecificSurfaceExtension() const
{
#ifdef _WIN32
return VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
#elif defined(__APPLE__)
return VK_EXT_METAL_SURFACE_EXTENSION_NAME;
#else /* UNIX/Linux */
switch (m_platform) {
case GHOST_kVulkanPlatformX11:
return VK_KHR_XLIB_SURFACE_EXTENSION_NAME;
break;
# ifdef WITH_GHOST_WAYLAND
case GHOST_kVulkanPlatformWayland:
return VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
break;
# endif
}
#endif
return NULL;
}
GHOST_TSuccess GHOST_ContextVK::initializeDrawingContext()
{
#ifdef _WIN32
const bool use_window_surface = (m_hwnd != NULL);
#elif defined(__APPLE__)
const bool use_window_surface = (m_metal_layer != NULL);
#else /* UNIX/Linux */
bool use_window_surface = false;
switch (m_platform) {
case GHOST_kVulkanPlatformX11:
use_window_surface = (m_display != NULL) && (m_window != (Window)NULL);
break;
# ifdef WITH_GHOST_WAYLAND
case GHOST_kVulkanPlatformWayland:
use_window_surface = (m_wayland_display != NULL) && (m_wayland_surface != NULL);
break;
# endif
}
#endif
auto layers_available = getLayersAvailable();
auto extensions_available = getExtensionsAvailable();
vector<const char *> layers_enabled;
if (m_debug) {
enableLayer(layers_available, layers_enabled, "VK_LAYER_KHRONOS_validation");
}
vector<const char *> extensions_device;
vector<const char *> extensions_enabled;
if (use_window_surface) {
const char *native_surface_extension_name = getPlatformSpecificSurfaceExtension();
requireExtension(extensions_available, extensions_enabled, "VK_KHR_surface");
requireExtension(extensions_available, extensions_enabled, native_surface_extension_name);
extensions_device.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = "Blender";
app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.pEngineName = "Blender";
app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.apiVersion = VK_MAKE_VERSION(m_context_major_version, m_context_minor_version, 0);
VkInstanceCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pApplicationInfo = &app_info;
create_info.enabledLayerCount = static_cast<uint32_t>(layers_enabled.size());
create_info.ppEnabledLayerNames = layers_enabled.data();
create_info.enabledExtensionCount = static_cast<uint32_t>(extensions_enabled.size());
create_info.ppEnabledExtensionNames = extensions_enabled.data();
VK_CHECK(vkCreateInstance(&create_info, NULL, &m_instance));
if (use_window_surface) {
#ifdef _WIN32
VkWin32SurfaceCreateInfoKHR surface_create_info = {};
surface_create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
surface_create_info.hinstance = GetModuleHandle(NULL);
surface_create_info.hwnd = m_hwnd;
VK_CHECK(vkCreateWin32SurfaceKHR(m_instance, &surface_create_info, NULL, &m_surface));
#elif defined(__APPLE__)
VkMetalSurfaceCreateInfoEXT info = {};
info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
info.pNext = NULL;
info.flags = 0;
info.pLayer = m_metal_layer;
VK_CHECK(vkCreateMetalSurfaceEXT(m_instance, &info, nullptr, &m_surface));
#else
switch (m_platform) {
case GHOST_kVulkanPlatformX11: {
VkXlibSurfaceCreateInfoKHR surface_create_info = {};
surface_create_info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
surface_create_info.dpy = m_display;
surface_create_info.window = m_window;
VK_CHECK(vkCreateXlibSurfaceKHR(m_instance, &surface_create_info, NULL, &m_surface));
break;
}
# ifdef WITH_GHOST_WAYLAND
case GHOST_kVulkanPlatformWayland: {
VkWaylandSurfaceCreateInfoKHR surface_create_info = {};
surface_create_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
surface_create_info.display = m_wayland_display;
surface_create_info.surface = m_wayland_surface;
VK_CHECK(vkCreateWaylandSurfaceKHR(m_instance, &surface_create_info, NULL, &m_surface));
break;
}
# endif
}
#endif
}
if (!pickPhysicalDevice(extensions_device)) {
return GHOST_kFailure;
}
vector<VkDeviceQueueCreateInfo> queue_create_infos;
{
/* A graphic queue is required to draw anything. */
if (!getGraphicQueueFamily(m_physical_device, &m_queue_family_graphic)) {
return GHOST_kFailure;
}
float queue_priorities[] = {1.0f};
VkDeviceQueueCreateInfo graphic_queue_create_info = {};
graphic_queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
graphic_queue_create_info.queueFamilyIndex = m_queue_family_graphic;
graphic_queue_create_info.queueCount = 1;
graphic_queue_create_info.pQueuePriorities = queue_priorities;
queue_create_infos.push_back(graphic_queue_create_info);
}
if (use_window_surface) {
/* A present queue is required only if we render to a window. */
if (!getPresetQueueFamily(m_physical_device, m_surface, &m_queue_family_present)) {
return GHOST_kFailure;
}
float queue_priorities[] = {1.0f};
VkDeviceQueueCreateInfo present_queue_create_info = {};
present_queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
present_queue_create_info.queueFamilyIndex = m_queue_family_present;
present_queue_create_info.queueCount = 1;
present_queue_create_info.pQueuePriorities = queue_priorities;
/* Eash queue must be unique. */
if (m_queue_family_graphic != m_queue_family_present) {
queue_create_infos.push_back(present_queue_create_info);
}
}
VkPhysicalDeviceFeatures device_features = {};
#if STRICT_REQUIREMENTS
device_features.geometryShader = VK_TRUE;
device_features.dualSrcBlend = VK_TRUE;
device_features.logicOp = VK_TRUE;
#endif
VkDeviceCreateInfo device_create_info = {};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.queueCreateInfoCount = static_cast<uint32_t>(queue_create_infos.size());
device_create_info.pQueueCreateInfos = queue_create_infos.data();
/* layers_enabled are the same as instance extensions.
* This is only needed for 1.0 implementations. */
device_create_info.enabledLayerCount = static_cast<uint32_t>(layers_enabled.size());
device_create_info.ppEnabledLayerNames = layers_enabled.data();
device_create_info.enabledExtensionCount = static_cast<uint32_t>(extensions_device.size());
device_create_info.ppEnabledExtensionNames = extensions_device.data();
device_create_info.pEnabledFeatures = &device_features;
VK_CHECK(vkCreateDevice(m_physical_device, &device_create_info, NULL, &m_device));
vkGetDeviceQueue(m_device, m_queue_family_graphic, 0, &m_graphic_queue);
if (use_window_surface) {
vkGetDeviceQueue(m_device, m_queue_family_present, 0, &m_present_queue);
createSwapchain();
}
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_ContextVK::releaseNativeHandles()
{
return GHOST_kSuccess;
}