Initial procedural 3D engine setup
- Updated README.md with modern project structure and features - Cleaned up Android build files (not needed for desktop engine) - Restructured as procedural 3D engine with ImGui integration - Based on Sascha Willems Vulkan framework with dynamic rendering - Added comprehensive build instructions and camera system docs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ca9be0c589
commit
09ba229353
2429 changed files with 7751 additions and 112835 deletions
589
base/device/VulkanDevice.cpp
Normal file
589
base/device/VulkanDevice.cpp
Normal file
|
|
@ -0,0 +1,589 @@
|
|||
/*
|
||||
* Vulkan device class
|
||||
*
|
||||
* Encapsulates a physical Vulkan device and its logical representation
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT))
|
||||
// SRS - Enable beta extensions and make VK_KHR_portability_subset visible
|
||||
#define VK_ENABLE_BETA_EXTENSIONS
|
||||
#endif
|
||||
#include "VulkanDevice.h"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param physicalDevice Physical device that is to be used
|
||||
*/
|
||||
VulkanDevice::VulkanDevice(VkPhysicalDevice physicalDevice)
|
||||
{
|
||||
assert(physicalDevice);
|
||||
this->physicalDevice = physicalDevice;
|
||||
|
||||
// Store Properties features, limits and properties of the physical device for later use
|
||||
// Device properties also contain limits and sparse properties
|
||||
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
|
||||
// Features should be checked by the examples before using them
|
||||
vkGetPhysicalDeviceFeatures(physicalDevice, &features);
|
||||
// Memory properties are used regularly for creating all kinds of buffers
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
// Queue family properties, used for setting up requested queues upon device creation
|
||||
uint32_t queueFamilyCount;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
|
||||
assert(queueFamilyCount > 0);
|
||||
queueFamilyProperties.resize(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
|
||||
|
||||
// Get list of supported extensions
|
||||
uint32_t extCount = 0;
|
||||
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, nullptr);
|
||||
if (extCount > 0)
|
||||
{
|
||||
std::vector<VkExtensionProperties> extensions(extCount);
|
||||
if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, &extensions.front()) == VK_SUCCESS)
|
||||
{
|
||||
for (auto& ext : extensions)
|
||||
{
|
||||
supportedExtensions.push_back(ext.extensionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default destructor
|
||||
*
|
||||
* @note Frees the logical device
|
||||
*/
|
||||
VulkanDevice::~VulkanDevice()
|
||||
{
|
||||
if (commandPool)
|
||||
{
|
||||
vkDestroyCommandPool(logicalDevice, commandPool, nullptr);
|
||||
}
|
||||
if (logicalDevice)
|
||||
{
|
||||
vkDestroyDevice(logicalDevice, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of a memory type that has all the requested property bits set
|
||||
*
|
||||
* @param typeBits Bit mask with bits set for each memory type supported by the resource to request for (from VkMemoryRequirements)
|
||||
* @param properties Bit mask of properties for the memory type to request
|
||||
* @param (Optional) memTypeFound Pointer to a bool that is set to true if a matching memory type has been found
|
||||
*
|
||||
* @return Index of the requested memory type
|
||||
*
|
||||
* @throw Throws an exception if memTypeFound is null and no memory type could be found that supports the requested properties
|
||||
*/
|
||||
uint32_t VulkanDevice::getMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, VkBool32 *memTypeFound) const
|
||||
{
|
||||
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
|
||||
{
|
||||
if ((typeBits & 1) == 1)
|
||||
{
|
||||
if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
||||
{
|
||||
if (memTypeFound)
|
||||
{
|
||||
*memTypeFound = true;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
typeBits >>= 1;
|
||||
}
|
||||
|
||||
if (memTypeFound)
|
||||
{
|
||||
*memTypeFound = false;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Could not find a matching memory type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of a queue family that supports the requested queue flags
|
||||
* SRS - support VkQueueFlags parameter for requesting multiple flags vs. VkQueueFlagBits for a single flag only
|
||||
*
|
||||
* @param queueFlags Queue flags to find a queue family index for
|
||||
*
|
||||
* @return Index of the queue family index that matches the flags
|
||||
*
|
||||
* @throw Throws an exception if no queue family index could be found that supports the requested flags
|
||||
*/
|
||||
uint32_t VulkanDevice::getQueueFamilyIndex(VkQueueFlags queueFlags) const
|
||||
{
|
||||
// Dedicated queue for compute
|
||||
// Try to find a queue family index that supports compute but not graphics
|
||||
if ((queueFlags & VK_QUEUE_COMPUTE_BIT) == queueFlags)
|
||||
{
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
|
||||
{
|
||||
if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dedicated queue for transfer
|
||||
// Try to find a queue family index that supports transfer but not graphics and compute
|
||||
if ((queueFlags & VK_QUEUE_TRANSFER_BIT) == queueFlags)
|
||||
{
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
|
||||
{
|
||||
if ((queueFamilyProperties[i].queueFlags & VK_QUEUE_TRANSFER_BIT) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) && ((queueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) == 0))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For other queue types or if no separate compute queue is present, return the first one to support the requested flags
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++)
|
||||
{
|
||||
if ((queueFamilyProperties[i].queueFlags & queueFlags) == queueFlags)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("Could not find a matching queue family index");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the logical device based on the assigned physical device, also gets default queue family indices
|
||||
*
|
||||
* @param enabledFeatures Can be used to enable certain features upon device creation
|
||||
* @param pNextChain Optional chain of pointer to extension structures
|
||||
* @param useSwapChain Set to false for headless rendering to omit the swapchain device extensions
|
||||
* @param requestedQueueTypes Bit flags specifying the queue types to be requested from the device
|
||||
*
|
||||
* @return VkResult of the device creation call
|
||||
*/
|
||||
VkResult VulkanDevice::createLogicalDevice(VkPhysicalDeviceFeatures enabledFeatures, std::vector<const char*> enabledExtensions, void* pNextChain, bool useSwapChain, VkQueueFlags requestedQueueTypes)
|
||||
{
|
||||
// Desired queues need to be requested upon logical device creation
|
||||
// Due to differing queue family configurations of Vulkan implementations this can be a bit tricky, especially if the application
|
||||
// requests different queue types
|
||||
|
||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos{};
|
||||
|
||||
// Get queue family indices for the requested queue family types
|
||||
// Note that the indices may overlap depending on the implementation
|
||||
|
||||
const float defaultQueuePriority(0.0f);
|
||||
|
||||
// Graphics queue
|
||||
if (requestedQueueTypes & VK_QUEUE_GRAPHICS_BIT)
|
||||
{
|
||||
queueFamilyIndices.graphics = getQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT);
|
||||
VkDeviceQueueCreateInfo queueInfo{};
|
||||
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueInfo.queueFamilyIndex = queueFamilyIndices.graphics;
|
||||
queueInfo.queueCount = 1;
|
||||
queueInfo.pQueuePriorities = &defaultQueuePriority;
|
||||
queueCreateInfos.push_back(queueInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
queueFamilyIndices.graphics = 0;
|
||||
}
|
||||
|
||||
// Dedicated compute queue
|
||||
if (requestedQueueTypes & VK_QUEUE_COMPUTE_BIT)
|
||||
{
|
||||
queueFamilyIndices.compute = getQueueFamilyIndex(VK_QUEUE_COMPUTE_BIT);
|
||||
if (queueFamilyIndices.compute != queueFamilyIndices.graphics)
|
||||
{
|
||||
// If compute family index differs, we need an additional queue create info for the compute queue
|
||||
VkDeviceQueueCreateInfo queueInfo{};
|
||||
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueInfo.queueFamilyIndex = queueFamilyIndices.compute;
|
||||
queueInfo.queueCount = 1;
|
||||
queueInfo.pQueuePriorities = &defaultQueuePriority;
|
||||
queueCreateInfos.push_back(queueInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we use the same queue
|
||||
queueFamilyIndices.compute = queueFamilyIndices.graphics;
|
||||
}
|
||||
|
||||
// Dedicated transfer queue
|
||||
if (requestedQueueTypes & VK_QUEUE_TRANSFER_BIT)
|
||||
{
|
||||
queueFamilyIndices.transfer = getQueueFamilyIndex(VK_QUEUE_TRANSFER_BIT);
|
||||
if ((queueFamilyIndices.transfer != queueFamilyIndices.graphics) && (queueFamilyIndices.transfer != queueFamilyIndices.compute))
|
||||
{
|
||||
// If transfer family index differs, we need an additional queue create info for the transfer queue
|
||||
VkDeviceQueueCreateInfo queueInfo{};
|
||||
queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueInfo.queueFamilyIndex = queueFamilyIndices.transfer;
|
||||
queueInfo.queueCount = 1;
|
||||
queueInfo.pQueuePriorities = &defaultQueuePriority;
|
||||
queueCreateInfos.push_back(queueInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we use the same queue
|
||||
queueFamilyIndices.transfer = queueFamilyIndices.graphics;
|
||||
}
|
||||
|
||||
// Create the logical device representation
|
||||
std::vector<const char*> deviceExtensions(enabledExtensions);
|
||||
if (useSwapChain)
|
||||
{
|
||||
// If the device will be used for presenting to a display via a swapchain we need to request the swapchain extension
|
||||
deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo = {};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());;
|
||||
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
|
||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
|
||||
// If a pNext(Chain) has been passed, we need to add it to the device creation info
|
||||
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
|
||||
if (pNextChain) {
|
||||
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
physicalDeviceFeatures2.features = enabledFeatures;
|
||||
physicalDeviceFeatures2.pNext = pNextChain;
|
||||
deviceCreateInfo.pEnabledFeatures = nullptr;
|
||||
deviceCreateInfo.pNext = &physicalDeviceFeatures2;
|
||||
}
|
||||
|
||||
#if (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT)) && defined(VK_KHR_portability_subset)
|
||||
// SRS - When running on iOS/macOS with MoltenVK and VK_KHR_portability_subset is defined and supported by the device, enable the extension
|
||||
if (extensionSupported(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME))
|
||||
{
|
||||
deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (deviceExtensions.size() > 0)
|
||||
{
|
||||
for (const char* enabledExtension : deviceExtensions)
|
||||
{
|
||||
if (!extensionSupported(enabledExtension)) {
|
||||
std::cerr << "Enabled device extension \"" << enabledExtension << "\" is not present at device level\n";
|
||||
}
|
||||
}
|
||||
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
|
||||
}
|
||||
|
||||
this->enabledFeatures = enabledFeatures;
|
||||
|
||||
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &logicalDevice);
|
||||
if (result != VK_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create a default command pool for graphics command buffers
|
||||
commandPool = createCommandPool(queueFamilyIndices.graphics);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a buffer on the device
|
||||
*
|
||||
* @param usageFlags Usage flag bit mask for the buffer (i.e. index, vertex, uniform buffer)
|
||||
* @param memoryPropertyFlags Memory properties for this buffer (i.e. device local, host visible, coherent)
|
||||
* @param size Size of the buffer in byes
|
||||
* @param buffer Pointer to the buffer handle acquired by the function
|
||||
* @param memory Pointer to the memory handle acquired by the function
|
||||
* @param data Pointer to the data that should be copied to the buffer after creation (optional, if not set, no data is copied over)
|
||||
*
|
||||
* @return VK_SUCCESS if buffer handle and memory have been created and (optionally passed) data has been copied
|
||||
*/
|
||||
VkResult VulkanDevice::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, VkBuffer *buffer, VkDeviceMemory *memory, void *data)
|
||||
{
|
||||
// Create the buffer handle
|
||||
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
|
||||
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VK_CHECK_RESULT(vkCreateBuffer(logicalDevice, &bufferCreateInfo, nullptr, buffer));
|
||||
|
||||
// Create the memory backing up the buffer handle
|
||||
VkMemoryRequirements memReqs;
|
||||
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
||||
vkGetBufferMemoryRequirements(logicalDevice, *buffer, &memReqs);
|
||||
memAlloc.allocationSize = memReqs.size;
|
||||
// Find a memory type index that fits the properties of the buffer
|
||||
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
|
||||
// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
|
||||
VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
|
||||
if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
|
||||
allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
|
||||
allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memAlloc.pNext = &allocFlagsInfo;
|
||||
}
|
||||
VK_CHECK_RESULT(vkAllocateMemory(logicalDevice, &memAlloc, nullptr, memory));
|
||||
|
||||
// If a pointer to the buffer data has been passed, map the buffer and copy over the data
|
||||
if (data != nullptr)
|
||||
{
|
||||
void *mapped;
|
||||
VK_CHECK_RESULT(vkMapMemory(logicalDevice, *memory, 0, size, 0, &mapped));
|
||||
memcpy(mapped, data, size);
|
||||
// If host coherency hasn't been requested, do a manual flush to make writes visible
|
||||
if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
|
||||
{
|
||||
VkMappedMemoryRange mappedRange = vks::initializers::mappedMemoryRange();
|
||||
mappedRange.memory = *memory;
|
||||
mappedRange.offset = 0;
|
||||
mappedRange.size = size;
|
||||
vkFlushMappedMemoryRanges(logicalDevice, 1, &mappedRange);
|
||||
}
|
||||
vkUnmapMemory(logicalDevice, *memory);
|
||||
}
|
||||
|
||||
// Attach the memory to the buffer object
|
||||
VK_CHECK_RESULT(vkBindBufferMemory(logicalDevice, *buffer, *memory, 0));
|
||||
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a buffer on the device
|
||||
*
|
||||
* @param usageFlags Usage flag bit mask for the buffer (i.e. index, vertex, uniform buffer)
|
||||
* @param memoryPropertyFlags Memory properties for this buffer (i.e. device local, host visible, coherent)
|
||||
* @param buffer Pointer to a vk::Vulkan buffer object
|
||||
* @param size Size of the buffer in bytes
|
||||
* @param data Pointer to the data that should be copied to the buffer after creation (optional, if not set, no data is copied over)
|
||||
*
|
||||
* @return VK_SUCCESS if buffer handle and memory have been created and (optionally passed) data has been copied
|
||||
*/
|
||||
VkResult VulkanDevice::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, vks::Buffer *buffer, VkDeviceSize size, void *data)
|
||||
{
|
||||
buffer->device = logicalDevice;
|
||||
|
||||
// Create the buffer handle
|
||||
VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
|
||||
VK_CHECK_RESULT(vkCreateBuffer(logicalDevice, &bufferCreateInfo, nullptr, &buffer->buffer));
|
||||
|
||||
// Create the memory backing up the buffer handle
|
||||
VkMemoryRequirements memReqs;
|
||||
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
||||
vkGetBufferMemoryRequirements(logicalDevice, buffer->buffer, &memReqs);
|
||||
memAlloc.allocationSize = memReqs.size;
|
||||
// Find a memory type index that fits the properties of the buffer
|
||||
memAlloc.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
|
||||
// If the buffer has VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT set we also need to enable the appropriate flag during allocation
|
||||
VkMemoryAllocateFlagsInfoKHR allocFlagsInfo{};
|
||||
if (usageFlags & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) {
|
||||
allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR;
|
||||
allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
|
||||
memAlloc.pNext = &allocFlagsInfo;
|
||||
}
|
||||
VK_CHECK_RESULT(vkAllocateMemory(logicalDevice, &memAlloc, nullptr, &buffer->memory));
|
||||
|
||||
buffer->alignment = memReqs.alignment;
|
||||
buffer->size = size;
|
||||
buffer->usageFlags = usageFlags;
|
||||
buffer->memoryPropertyFlags = memoryPropertyFlags;
|
||||
|
||||
// If a pointer to the buffer data has been passed, map the buffer and copy over the data
|
||||
if (data != nullptr)
|
||||
{
|
||||
VK_CHECK_RESULT(buffer->map());
|
||||
memcpy(buffer->mapped, data, size);
|
||||
if ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
|
||||
buffer->flush();
|
||||
|
||||
buffer->unmap();
|
||||
}
|
||||
|
||||
// Initialize a default descriptor that covers the whole buffer size
|
||||
buffer->setupDescriptor();
|
||||
|
||||
// Attach the memory to the buffer object
|
||||
return buffer->bind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy buffer data from src to dst using VkCmdCopyBuffer
|
||||
*
|
||||
* @param src Pointer to the source buffer to copy from
|
||||
* @param dst Pointer to the destination buffer to copy to
|
||||
* @param queue Pointer
|
||||
* @param copyRegion (Optional) Pointer to a copy region, if NULL, the whole buffer is copied
|
||||
*
|
||||
* @note Source and destination pointers must have the appropriate transfer usage flags set (TRANSFER_SRC / TRANSFER_DST)
|
||||
*/
|
||||
void VulkanDevice::copyBuffer(vks::Buffer *src, vks::Buffer *dst, VkQueue queue, VkBufferCopy *copyRegion)
|
||||
{
|
||||
assert(dst->size >= src->size);
|
||||
assert(src->buffer);
|
||||
VkCommandBuffer copyCmd = createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
|
||||
VkBufferCopy bufferCopy{};
|
||||
if (copyRegion == nullptr)
|
||||
{
|
||||
bufferCopy.size = src->size;
|
||||
}
|
||||
else
|
||||
{
|
||||
bufferCopy = *copyRegion;
|
||||
}
|
||||
|
||||
vkCmdCopyBuffer(copyCmd, src->buffer, dst->buffer, 1, &bufferCopy);
|
||||
|
||||
flushCommandBuffer(copyCmd, queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a command pool for allocation command buffers from
|
||||
*
|
||||
* @param queueFamilyIndex Family index of the queue to create the command pool for
|
||||
* @param createFlags (Optional) Command pool creation flags (Defaults to VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT)
|
||||
*
|
||||
* @note Command buffers allocated from the created pool can only be submitted to a queue with the same family index
|
||||
*
|
||||
* @return A handle to the created command buffer
|
||||
*/
|
||||
VkCommandPool VulkanDevice::createCommandPool(uint32_t queueFamilyIndex, VkCommandPoolCreateFlags createFlags)
|
||||
{
|
||||
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
||||
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
cmdPoolInfo.queueFamilyIndex = queueFamilyIndex;
|
||||
cmdPoolInfo.flags = createFlags;
|
||||
VkCommandPool cmdPool;
|
||||
VK_CHECK_RESULT(vkCreateCommandPool(logicalDevice, &cmdPoolInfo, nullptr, &cmdPool));
|
||||
return cmdPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a command buffer from the command pool
|
||||
*
|
||||
* @param level Level of the new command buffer (primary or secondary)
|
||||
* @param pool Command pool from which the command buffer will be allocated
|
||||
* @param (Optional) begin If true, recording on the new command buffer will be started (vkBeginCommandBuffer) (Defaults to false)
|
||||
*
|
||||
* @return A handle to the allocated command buffer
|
||||
*/
|
||||
VkCommandBuffer VulkanDevice::createCommandBuffer(VkCommandBufferLevel level, VkCommandPool pool, bool begin)
|
||||
{
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(pool, level, 1);
|
||||
VkCommandBuffer cmdBuffer;
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(logicalDevice, &cmdBufAllocateInfo, &cmdBuffer));
|
||||
// If requested, also start recording for the new command buffer
|
||||
if (begin)
|
||||
{
|
||||
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
||||
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo));
|
||||
}
|
||||
return cmdBuffer;
|
||||
}
|
||||
|
||||
VkCommandBuffer VulkanDevice::createCommandBuffer(VkCommandBufferLevel level, bool begin)
|
||||
{
|
||||
return createCommandBuffer(level, commandPool, begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish command buffer recording and submit it to a queue
|
||||
*
|
||||
* @param commandBuffer Command buffer to flush
|
||||
* @param queue Queue to submit the command buffer to
|
||||
* @param pool Command pool on which the command buffer has been created
|
||||
* @param free (Optional) Free the command buffer once it has been submitted (Defaults to true)
|
||||
*
|
||||
* @note The queue that the command buffer is submitted to must be from the same family index as the pool it was allocated from
|
||||
* @note Uses a fence to ensure command buffer has finished executing
|
||||
*/
|
||||
void VulkanDevice::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, VkCommandPool pool, bool free)
|
||||
{
|
||||
if (commandBuffer == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
|
||||
|
||||
VkSubmitInfo submitInfo = vks::initializers::submitInfo();
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
// Create fence to ensure that the command buffer has finished executing
|
||||
VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo(VK_FLAGS_NONE);
|
||||
VkFence fence;
|
||||
VK_CHECK_RESULT(vkCreateFence(logicalDevice, &fenceInfo, nullptr, &fence));
|
||||
// Submit to the queue
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
|
||||
// Wait for the fence to signal that command buffer has finished executing
|
||||
VK_CHECK_RESULT(vkWaitForFences(logicalDevice, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT));
|
||||
vkDestroyFence(logicalDevice, fence, nullptr);
|
||||
if (free)
|
||||
{
|
||||
vkFreeCommandBuffers(logicalDevice, pool, 1, &commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDevice::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free)
|
||||
{
|
||||
return flushCommandBuffer(commandBuffer, queue, commandPool, free);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an extension is supported by the (physical device)
|
||||
*
|
||||
* @param extension Name of the extension to check
|
||||
*
|
||||
* @return True if the extension is supported (present in the list read at device creation time)
|
||||
*/
|
||||
bool VulkanDevice::extensionSupported(std::string extension)
|
||||
{
|
||||
return (std::find(supportedExtensions.begin(), supportedExtensions.end(), extension) != supportedExtensions.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the best-fit depth format for this device from a list of possible depth (and stencil) formats
|
||||
*
|
||||
* @param checkSamplingSupport Check if the format can be sampled from (e.g. for shader reads)
|
||||
*
|
||||
* @return The depth format that best fits for the current device
|
||||
*
|
||||
* @throw Throws an exception if no depth format fits the requirements
|
||||
*/
|
||||
VkFormat VulkanDevice::getSupportedDepthFormat(bool checkSamplingSupport)
|
||||
{
|
||||
// All depth formats may be optional, so we need to find a suitable depth format to use
|
||||
std::vector<VkFormat> depthFormats = { VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D32_SFLOAT, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT, VK_FORMAT_D16_UNORM };
|
||||
for (auto& format : depthFormats)
|
||||
{
|
||||
VkFormatProperties formatProperties;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties);
|
||||
// Format must support depth stencil attachment for optimal tiling
|
||||
if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||||
{
|
||||
if (checkSamplingSupport) {
|
||||
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Could not find a matching depth format");
|
||||
}
|
||||
|
||||
};
|
||||
69
base/device/VulkanDevice.h
Normal file
69
base/device/VulkanDevice.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Vulkan device class
|
||||
*
|
||||
* Encapsulates a physical Vulkan device and its logical representation
|
||||
*
|
||||
* Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../memory/VulkanBuffer.h"
|
||||
#include "../core/VulkanTools.h"
|
||||
#include "vulkan/vulkan.h"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <exception>
|
||||
|
||||
namespace vks
|
||||
{
|
||||
struct VulkanDevice
|
||||
{
|
||||
/** @brief Physical device representation */
|
||||
VkPhysicalDevice physicalDevice;
|
||||
/** @brief Logical device representation (application's view of the device) */
|
||||
VkDevice logicalDevice;
|
||||
/** @brief Properties of the physical device including limits that the application can check against */
|
||||
VkPhysicalDeviceProperties properties;
|
||||
/** @brief Features of the physical device that an application can use to check if a feature is supported */
|
||||
VkPhysicalDeviceFeatures features;
|
||||
/** @brief Features that have been enabled for use on the physical device */
|
||||
VkPhysicalDeviceFeatures enabledFeatures;
|
||||
/** @brief Memory types and heaps of the physical device */
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
/** @brief Queue family properties of the physical device */
|
||||
std::vector<VkQueueFamilyProperties> queueFamilyProperties;
|
||||
/** @brief List of extensions supported by the device */
|
||||
std::vector<std::string> supportedExtensions;
|
||||
/** @brief Default command pool for the graphics queue family index */
|
||||
VkCommandPool commandPool = VK_NULL_HANDLE;
|
||||
/** @brief Contains queue family indices */
|
||||
struct
|
||||
{
|
||||
uint32_t graphics;
|
||||
uint32_t compute;
|
||||
uint32_t transfer;
|
||||
} queueFamilyIndices;
|
||||
operator VkDevice() const
|
||||
{
|
||||
return logicalDevice;
|
||||
};
|
||||
explicit VulkanDevice(VkPhysicalDevice physicalDevice);
|
||||
~VulkanDevice();
|
||||
uint32_t getMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, VkBool32 *memTypeFound = nullptr) const;
|
||||
uint32_t getQueueFamilyIndex(VkQueueFlags queueFlags) const;
|
||||
VkResult createLogicalDevice(VkPhysicalDeviceFeatures enabledFeatures, std::vector<const char *> enabledExtensions, void *pNextChain, bool useSwapChain = true, VkQueueFlags requestedQueueTypes = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT);
|
||||
VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, VkBuffer *buffer, VkDeviceMemory *memory, void *data = nullptr);
|
||||
VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, vks::Buffer *buffer, VkDeviceSize size, void *data = nullptr);
|
||||
void copyBuffer(vks::Buffer *src, vks::Buffer *dst, VkQueue queue, VkBufferCopy *copyRegion = nullptr);
|
||||
VkCommandPool createCommandPool(uint32_t queueFamilyIndex, VkCommandPoolCreateFlags createFlags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
|
||||
VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, VkCommandPool pool, bool begin = false);
|
||||
VkCommandBuffer createCommandBuffer(VkCommandBufferLevel level, bool begin = false);
|
||||
void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, VkCommandPool pool, bool free = true);
|
||||
void flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free = true);
|
||||
bool extensionSupported(std::string extension);
|
||||
VkFormat getSupportedDepthFormat(bool checkSamplingSupport);
|
||||
};
|
||||
} // namespace vks
|
||||
553
base/device/VulkanSwapChain.cpp
Normal file
553
base/device/VulkanSwapChain.cpp
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
/*
|
||||
* Class wrapping access to the swap chain
|
||||
*
|
||||
* A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#include "VulkanSwapChain.h"
|
||||
|
||||
/** @brief Creates the platform specific surface abstraction of the native platform window used for presentation */
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
void VulkanSwapChain::initSurface(void* platformHandle, void* platformWindow)
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
void VulkanSwapChain::initSurface(ANativeWindow* window)
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
void VulkanSwapChain::initSurface(IDirectFB* dfb, IDirectFBSurface* window)
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
void VulkanSwapChain::initSurface(wl_display *display, wl_surface *window)
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
void VulkanSwapChain::initSurface(xcb_connection_t* connection, xcb_window_t window)
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
||||
void VulkanSwapChain::initSurface(void* view)
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
void VulkanSwapChain::initSurface(CAMetalLayer* metalLayer)
|
||||
#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
|
||||
void VulkanSwapChain::initSurface(uint32_t width, uint32_t height)
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
void VulkanSwapChain::initSurface(screen_context_t screen_context, screen_window_t screen_window)
|
||||
#endif
|
||||
{
|
||||
VkResult err = VK_SUCCESS;
|
||||
|
||||
// Create the os-specific surface
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.hinstance = (HINSTANCE)platformHandle;
|
||||
surfaceCreateInfo.hwnd = (HWND)platformWindow;
|
||||
err = vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.window = window;
|
||||
err = vkCreateAndroidSurfaceKHR(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_IOS_MVK)
|
||||
VkIOSSurfaceCreateInfoMVK surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.pView = view;
|
||||
err = vkCreateIOSSurfaceMVK(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
|
||||
VkMacOSSurfaceCreateInfoMVK surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.pView = view;
|
||||
err = vkCreateMacOSSurfaceMVK(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.pLayer = metalLayer;
|
||||
err = vkCreateMetalSurfaceEXT(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#elif defined(_DIRECT2DISPLAY)
|
||||
createDirect2DisplaySurface(width, height);
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
VkDirectFBSurfaceCreateInfoEXT surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_DIRECTFB_SURFACE_CREATE_INFO_EXT;
|
||||
surfaceCreateInfo.dfb = dfb;
|
||||
surfaceCreateInfo.surface = window;
|
||||
err = vkCreateDirectFBSurfaceEXT(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
VkWaylandSurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.display = display;
|
||||
surfaceCreateInfo.surface = window;
|
||||
err = vkCreateWaylandSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
VkXcbSurfaceCreateInfoKHR surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceCreateInfo.connection = connection;
|
||||
surfaceCreateInfo.window = window;
|
||||
err = vkCreateXcbSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_HEADLESS_EXT)
|
||||
VkHeadlessSurfaceCreateInfoEXT surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT;
|
||||
PFN_vkCreateHeadlessSurfaceEXT fpCreateHeadlessSurfaceEXT = (PFN_vkCreateHeadlessSurfaceEXT)vkGetInstanceProcAddr(instance, "vkCreateHeadlessSurfaceEXT");
|
||||
if (!fpCreateHeadlessSurfaceEXT){
|
||||
vks::tools::exitFatal("Could not fetch function pointer for the headless extension!", -1);
|
||||
}
|
||||
err = fpCreateHeadlessSurfaceEXT(instance, &surfaceCreateInfo, nullptr, &surface);
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
VkScreenSurfaceCreateInfoQNX surfaceCreateInfo = {};
|
||||
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_SCREEN_SURFACE_CREATE_INFO_QNX;
|
||||
surfaceCreateInfo.pNext = NULL;
|
||||
surfaceCreateInfo.flags = 0;
|
||||
surfaceCreateInfo.context = screen_context;
|
||||
surfaceCreateInfo.window = screen_window;
|
||||
err = vkCreateScreenSurfaceQNX(instance, &surfaceCreateInfo, NULL, &surface);
|
||||
#endif
|
||||
|
||||
if (err != VK_SUCCESS) {
|
||||
vks::tools::exitFatal("Could not create surface!", err);
|
||||
}
|
||||
|
||||
// Get available queue family properties
|
||||
uint32_t queueCount;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL);
|
||||
assert(queueCount >= 1);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueProps(queueCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data());
|
||||
|
||||
// Iterate over each queue to learn whether it supports presenting:
|
||||
// Find a queue with present support
|
||||
// Will be used to present the swap chain images to the windowing system
|
||||
std::vector<VkBool32> supportsPresent(queueCount);
|
||||
for (uint32_t i = 0; i < queueCount; i++)
|
||||
{
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &supportsPresent[i]);
|
||||
}
|
||||
|
||||
// Search for a graphics and a present queue in the array of queue
|
||||
// families, try to find one that supports both
|
||||
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t presentQueueNodeIndex = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < queueCount; i++)
|
||||
{
|
||||
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
|
||||
{
|
||||
if (graphicsQueueNodeIndex == UINT32_MAX)
|
||||
{
|
||||
graphicsQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if (supportsPresent[i] == VK_TRUE)
|
||||
{
|
||||
graphicsQueueNodeIndex = i;
|
||||
presentQueueNodeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (presentQueueNodeIndex == UINT32_MAX)
|
||||
{
|
||||
// If there's no queue that supports both present and graphics
|
||||
// try to find a separate present queue
|
||||
for (uint32_t i = 0; i < queueCount; ++i)
|
||||
{
|
||||
if (supportsPresent[i] == VK_TRUE)
|
||||
{
|
||||
presentQueueNodeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if either a graphics or a presenting queue hasn't been found
|
||||
if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX)
|
||||
{
|
||||
vks::tools::exitFatal("Could not find a graphics and/or presenting queue!", -1);
|
||||
}
|
||||
|
||||
if (graphicsQueueNodeIndex != presentQueueNodeIndex)
|
||||
{
|
||||
vks::tools::exitFatal("Separate graphics and presenting queues are not supported yet!", -1);
|
||||
}
|
||||
|
||||
queueNodeIndex = graphicsQueueNodeIndex;
|
||||
|
||||
// Get list of supported surface formats
|
||||
uint32_t formatCount;
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL));
|
||||
assert(formatCount > 0);
|
||||
|
||||
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, surfaceFormats.data()));
|
||||
|
||||
// We want to get a format that best suits our needs, so we try to get one from a set of preferred formats
|
||||
// Initialize the format to the first one returned by the implementation in case we can't find one of the preffered formats
|
||||
VkSurfaceFormatKHR selectedFormat = surfaceFormats[0];
|
||||
std::vector<VkFormat> preferredImageFormats = {
|
||||
VK_FORMAT_B8G8R8A8_UNORM,
|
||||
VK_FORMAT_R8G8B8A8_UNORM,
|
||||
VK_FORMAT_A8B8G8R8_UNORM_PACK32
|
||||
};
|
||||
|
||||
for (auto& availableFormat : surfaceFormats) {
|
||||
if (std::find(preferredImageFormats.begin(), preferredImageFormats.end(), availableFormat.format) != preferredImageFormats.end()) {
|
||||
selectedFormat = availableFormat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
colorFormat = selectedFormat.format;
|
||||
colorSpace = selectedFormat.colorSpace;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::setContext(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device)
|
||||
{
|
||||
this->instance = instance;
|
||||
this->physicalDevice = physicalDevice;
|
||||
this->device = device;
|
||||
}
|
||||
|
||||
void VulkanSwapChain::create(uint32_t& width, uint32_t& height, bool vsync, bool fullscreen)
|
||||
{
|
||||
assert(physicalDevice);
|
||||
assert(device);
|
||||
assert(instance);
|
||||
|
||||
// Store the current swap chain handle so we can use it later on to ease up recreation
|
||||
VkSwapchainKHR oldSwapchain = swapChain;
|
||||
|
||||
// Get physical device surface properties and formats
|
||||
VkSurfaceCapabilitiesKHR surfCaps;
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfCaps));
|
||||
|
||||
VkExtent2D swapchainExtent = {};
|
||||
// If width (and height) equals the special value 0xFFFFFFFF, the size of the surface will be set by the swapchain
|
||||
if (surfCaps.currentExtent.width == (uint32_t)-1)
|
||||
{
|
||||
// If the surface size is undefined, the size is set to the size of the images requested
|
||||
swapchainExtent.width = width;
|
||||
swapchainExtent.height = height;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the surface size is defined, the swap chain size must match
|
||||
swapchainExtent = surfCaps.currentExtent;
|
||||
width = surfCaps.currentExtent.width;
|
||||
height = surfCaps.currentExtent.height;
|
||||
}
|
||||
|
||||
|
||||
// Select a present mode for the swapchain
|
||||
uint32_t presentModeCount;
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, NULL));
|
||||
assert(presentModeCount > 0);
|
||||
|
||||
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
|
||||
VK_CHECK_RESULT(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data()));
|
||||
|
||||
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
|
||||
// This mode waits for the vertical blank ("v-sync")
|
||||
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
||||
|
||||
// If v-sync is not requested, try to find a mailbox mode
|
||||
// It's the lowest latency non-tearing present mode available
|
||||
if (!vsync)
|
||||
{
|
||||
for (size_t i = 0; i < presentModeCount; i++)
|
||||
{
|
||||
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
{
|
||||
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
||||
break;
|
||||
}
|
||||
if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||||
{
|
||||
swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the number of images
|
||||
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
|
||||
if ((surfCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
|
||||
{
|
||||
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
|
||||
}
|
||||
|
||||
// Find the transformation of the surface
|
||||
VkSurfaceTransformFlagsKHR preTransform;
|
||||
if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
|
||||
{
|
||||
// We prefer a non-rotated transform
|
||||
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
}
|
||||
else
|
||||
{
|
||||
preTransform = surfCaps.currentTransform;
|
||||
}
|
||||
|
||||
// Find a supported composite alpha format (not all devices support alpha opaque)
|
||||
VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
// Simply select the first composite alpha format available
|
||||
std::vector<VkCompositeAlphaFlagBitsKHR> compositeAlphaFlags = {
|
||||
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
|
||||
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
|
||||
};
|
||||
for (auto& compositeAlphaFlag : compositeAlphaFlags) {
|
||||
if (surfCaps.supportedCompositeAlpha & compositeAlphaFlag) {
|
||||
compositeAlpha = compositeAlphaFlag;
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
VkSwapchainCreateInfoKHR swapchainCI = {};
|
||||
swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
swapchainCI.surface = surface;
|
||||
swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
|
||||
swapchainCI.imageFormat = colorFormat;
|
||||
swapchainCI.imageColorSpace = colorSpace;
|
||||
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
|
||||
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
|
||||
swapchainCI.imageArrayLayers = 1;
|
||||
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
swapchainCI.queueFamilyIndexCount = 0;
|
||||
swapchainCI.presentMode = swapchainPresentMode;
|
||||
// Setting oldSwapChain to the saved handle of the previous swapchain aids in resource reuse and makes sure that we can still present already acquired images
|
||||
swapchainCI.oldSwapchain = oldSwapchain;
|
||||
// Setting clipped to VK_TRUE allows the implementation to discard rendering outside of the surface area
|
||||
swapchainCI.clipped = VK_TRUE;
|
||||
swapchainCI.compositeAlpha = compositeAlpha;
|
||||
|
||||
// Enable transfer source on swap chain images if supported
|
||||
if (surfCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) {
|
||||
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
|
||||
// Enable transfer destination on swap chain images if supported
|
||||
if (surfCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) {
|
||||
swapchainCI.imageUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
VK_CHECK_RESULT(vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapChain));
|
||||
|
||||
// If an existing swap chain is re-created, destroy the old swap chain and the ressources owned by the application (image views, images are owned by the swap chain)
|
||||
if (oldSwapchain != VK_NULL_HANDLE) {
|
||||
for (auto i = 0; i < images.size(); i++) {
|
||||
vkDestroyImageView(device, imageViews[i], nullptr);
|
||||
}
|
||||
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
||||
}
|
||||
VK_CHECK_RESULT(vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr));
|
||||
|
||||
// Get the swap chain images
|
||||
images.resize(imageCount);
|
||||
VK_CHECK_RESULT(vkGetSwapchainImagesKHR(device, swapChain, &imageCount, images.data()));
|
||||
|
||||
// Get the swap chain buffers containing the image and imageview
|
||||
imageViews.resize(imageCount);
|
||||
for (auto i = 0; i < images.size(); i++)
|
||||
{
|
||||
VkImageViewCreateInfo colorAttachmentView = {};
|
||||
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
colorAttachmentView.pNext = NULL;
|
||||
colorAttachmentView.format = colorFormat;
|
||||
colorAttachmentView.components = {
|
||||
VK_COMPONENT_SWIZZLE_R,
|
||||
VK_COMPONENT_SWIZZLE_G,
|
||||
VK_COMPONENT_SWIZZLE_B,
|
||||
VK_COMPONENT_SWIZZLE_A
|
||||
};
|
||||
colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
colorAttachmentView.subresourceRange.baseMipLevel = 0;
|
||||
colorAttachmentView.subresourceRange.levelCount = 1;
|
||||
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
|
||||
colorAttachmentView.subresourceRange.layerCount = 1;
|
||||
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
colorAttachmentView.flags = 0;
|
||||
colorAttachmentView.image = images[i];
|
||||
VK_CHECK_RESULT(vkCreateImageView(device, &colorAttachmentView, nullptr, &imageViews[i]));
|
||||
}
|
||||
}
|
||||
|
||||
VkResult VulkanSwapChain::acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t& imageIndex)
|
||||
{
|
||||
// By setting timeout to UINT64_MAX we will always wait until the next image has been acquired or an actual error is thrown
|
||||
// With that we don't have to handle VK_NOT_READY
|
||||
return vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, &imageIndex);
|
||||
}
|
||||
|
||||
VkResult VulkanSwapChain::queuePresent(VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore)
|
||||
{
|
||||
VkPresentInfoKHR presentInfo = {};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.pNext = NULL;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = &swapChain;
|
||||
presentInfo.pImageIndices = &imageIndex;
|
||||
// Check if a wait semaphore has been specified to wait for before presenting the image
|
||||
if (waitSemaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
presentInfo.pWaitSemaphores = &waitSemaphore;
|
||||
presentInfo.waitSemaphoreCount = 1;
|
||||
}
|
||||
return vkQueuePresentKHR(queue, &presentInfo);
|
||||
}
|
||||
|
||||
|
||||
void VulkanSwapChain::cleanup()
|
||||
{
|
||||
if (swapChain != VK_NULL_HANDLE) {
|
||||
for (auto i = 0; i < images.size(); i++) {
|
||||
vkDestroyImageView(device, imageViews[i], nullptr);
|
||||
}
|
||||
vkDestroySwapchainKHR(device, swapChain, nullptr);
|
||||
}
|
||||
if (surface != VK_NULL_HANDLE) {
|
||||
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||
}
|
||||
surface = VK_NULL_HANDLE;
|
||||
swapChain = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
#if defined(_DIRECT2DISPLAY)
|
||||
/**
|
||||
* Create direct to display surface
|
||||
*/
|
||||
void VulkanSwapChain::createDirect2DisplaySurface(uint32_t width, uint32_t height)
|
||||
{
|
||||
uint32_t displayPropertyCount;
|
||||
|
||||
// Get display property
|
||||
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertyCount, NULL);
|
||||
VkDisplayPropertiesKHR* pDisplayProperties = new VkDisplayPropertiesKHR[displayPropertyCount];
|
||||
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertyCount, pDisplayProperties);
|
||||
|
||||
// Get plane property
|
||||
uint32_t planePropertyCount;
|
||||
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planePropertyCount, NULL);
|
||||
VkDisplayPlanePropertiesKHR* pPlaneProperties = new VkDisplayPlanePropertiesKHR[planePropertyCount];
|
||||
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planePropertyCount, pPlaneProperties);
|
||||
|
||||
VkDisplayKHR display = VK_NULL_HANDLE;
|
||||
VkDisplayModeKHR displayMode;
|
||||
VkDisplayModePropertiesKHR* pModeProperties;
|
||||
bool foundMode = false;
|
||||
|
||||
for(uint32_t i = 0; i < displayPropertyCount;++i)
|
||||
{
|
||||
display = pDisplayProperties[i].display;
|
||||
uint32_t modeCount;
|
||||
vkGetDisplayModePropertiesKHR(physicalDevice, display, &modeCount, NULL);
|
||||
pModeProperties = new VkDisplayModePropertiesKHR[modeCount];
|
||||
vkGetDisplayModePropertiesKHR(physicalDevice, display, &modeCount, pModeProperties);
|
||||
|
||||
for (uint32_t j = 0; j < modeCount; ++j)
|
||||
{
|
||||
const VkDisplayModePropertiesKHR* mode = &pModeProperties[j];
|
||||
|
||||
if (mode->parameters.visibleRegion.width == width && mode->parameters.visibleRegion.height == height)
|
||||
{
|
||||
displayMode = mode->displayMode;
|
||||
foundMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundMode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
delete [] pModeProperties;
|
||||
}
|
||||
|
||||
if(!foundMode)
|
||||
{
|
||||
vks::tools::exitFatal("Can't find a display and a display mode!", -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for a best plane we can use
|
||||
uint32_t bestPlaneIndex = UINT32_MAX;
|
||||
VkDisplayKHR* pDisplays = NULL;
|
||||
for(uint32_t i = 0; i < planePropertyCount; i++)
|
||||
{
|
||||
uint32_t planeIndex=i;
|
||||
uint32_t displayCount;
|
||||
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &displayCount, NULL);
|
||||
if (pDisplays)
|
||||
{
|
||||
delete [] pDisplays;
|
||||
}
|
||||
pDisplays = new VkDisplayKHR[displayCount];
|
||||
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, planeIndex, &displayCount, pDisplays);
|
||||
|
||||
// Find a display that matches the current plane
|
||||
bestPlaneIndex = UINT32_MAX;
|
||||
for(uint32_t j = 0; j < displayCount; j++)
|
||||
{
|
||||
if(display == pDisplays[j])
|
||||
{
|
||||
bestPlaneIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(bestPlaneIndex != UINT32_MAX)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(bestPlaneIndex == UINT32_MAX)
|
||||
{
|
||||
vks::tools::exitFatal("Can't find a plane for displaying!", -1);
|
||||
return;
|
||||
}
|
||||
|
||||
VkDisplayPlaneCapabilitiesKHR planeCap;
|
||||
vkGetDisplayPlaneCapabilitiesKHR(physicalDevice, displayMode, bestPlaneIndex, &planeCap);
|
||||
VkDisplayPlaneAlphaFlagBitsKHR alphaMode = (VkDisplayPlaneAlphaFlagBitsKHR)0;
|
||||
|
||||
if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR;
|
||||
}
|
||||
else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR;
|
||||
}
|
||||
else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR;
|
||||
}
|
||||
else if (planeCap.supportedAlpha & VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR)
|
||||
{
|
||||
alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
|
||||
}
|
||||
|
||||
VkDisplaySurfaceCreateInfoKHR surfaceInfo{};
|
||||
surfaceInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
|
||||
surfaceInfo.pNext = NULL;
|
||||
surfaceInfo.flags = 0;
|
||||
surfaceInfo.displayMode = displayMode;
|
||||
surfaceInfo.planeIndex = bestPlaneIndex;
|
||||
surfaceInfo.planeStackIndex = pPlaneProperties[bestPlaneIndex].currentStackIndex;
|
||||
surfaceInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
surfaceInfo.globalAlpha = 1.0;
|
||||
surfaceInfo.alphaMode = alphaMode;
|
||||
surfaceInfo.imageExtent.width = width;
|
||||
surfaceInfo.imageExtent.height = height;
|
||||
|
||||
VkResult result = vkCreateDisplayPlaneSurfaceKHR(instance, &surfaceInfo, NULL, &surface);
|
||||
if (result !=VK_SUCCESS) {
|
||||
vks::tools::exitFatal("Failed to create surface!", result);
|
||||
}
|
||||
|
||||
delete[] pDisplays;
|
||||
delete[] pModeProperties;
|
||||
delete[] pDisplayProperties;
|
||||
delete[] pPlaneProperties;
|
||||
}
|
||||
#endif
|
||||
101
base/device/VulkanSwapChain.h
Normal file
101
base/device/VulkanSwapChain.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Class wrapping access to the swap chain
|
||||
*
|
||||
* A swap chain is a collection of framebuffers used for rendering and presentation to the windowing system
|
||||
*
|
||||
* Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
|
||||
*
|
||||
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
#include "../core/VulkanTools.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include "VulkanAndroid.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <sys/utsname.h>
|
||||
#endif
|
||||
|
||||
class VulkanSwapChain
|
||||
{
|
||||
private:
|
||||
VkInstance instance{ VK_NULL_HANDLE };
|
||||
VkDevice device{ VK_NULL_HANDLE };
|
||||
VkPhysicalDevice physicalDevice{ VK_NULL_HANDLE };
|
||||
VkSurfaceKHR surface{ VK_NULL_HANDLE };
|
||||
public:
|
||||
VkFormat colorFormat{};
|
||||
VkColorSpaceKHR colorSpace{};
|
||||
VkSwapchainKHR swapChain{ VK_NULL_HANDLE };
|
||||
std::vector<VkImage> images{};
|
||||
std::vector<VkImageView> imageViews{};
|
||||
uint32_t queueNodeIndex{ UINT32_MAX };
|
||||
uint32_t imageCount{ 0 };
|
||||
|
||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||
void initSurface(void* platformHandle, void* platformWindow);
|
||||
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||
void initSurface(ANativeWindow* window);
|
||||
#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT)
|
||||
void initSurface(IDirectFB* dfb, IDirectFBSurface* window);
|
||||
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||
void initSurface(wl_display* display, wl_surface* window);
|
||||
#elif defined(VK_USE_PLATFORM_XCB_KHR)
|
||||
void initSurface(xcb_connection_t* connection, xcb_window_t window);
|
||||
#elif (defined(VK_USE_PLATFORM_IOS_MVK) || defined(VK_USE_PLATFORM_MACOS_MVK))
|
||||
void initSurface(void* view);
|
||||
#elif defined(VK_USE_PLATFORM_METAL_EXT)
|
||||
void initSurface(CAMetalLayer* metalLayer);
|
||||
#elif (defined(_DIRECT2DISPLAY) || defined(VK_USE_PLATFORM_HEADLESS_EXT))
|
||||
void initSurface(uint32_t width, uint32_t height);
|
||||
#if defined(_DIRECT2DISPLAY)
|
||||
void createDirect2DisplaySurface(uint32_t width, uint32_t height);
|
||||
#endif
|
||||
#elif defined(VK_USE_PLATFORM_SCREEN_QNX)
|
||||
void initSurface(screen_context_t screen_context, screen_window_t screen_window);
|
||||
#endif
|
||||
/* Set the Vulkan objects required for swapchain creation and management, must be called before swapchain creation */
|
||||
void setContext(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device);
|
||||
/**
|
||||
* Create the swapchain and get its images with given width and height
|
||||
*
|
||||
* @param width Pointer to the width of the swapchain (may be adjusted to fit the requirements of the swapchain)
|
||||
* @param height Pointer to the height of the swapchain (may be adjusted to fit the requirements of the swapchain)
|
||||
* @param vsync (Optional, default = false) Can be used to force vsync-ed rendering (by using VK_PRESENT_MODE_FIFO_KHR as presentation mode)
|
||||
*/
|
||||
void create(uint32_t& width, uint32_t& height, bool vsync = false, bool fullscreen = false);
|
||||
/**
|
||||
* Acquires the next image in the swap chain
|
||||
*
|
||||
* @param presentCompleteSemaphore (Optional) Semaphore that is signaled when the image is ready for use
|
||||
* @param imageIndex Pointer to the image index that will be increased if the next image could be acquired
|
||||
*
|
||||
* @note The function will always wait until the next image has been acquired by setting timeout to UINT64_MAX
|
||||
*
|
||||
* @return VkResult of the image acquisition
|
||||
*/
|
||||
VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t& imageIndex);
|
||||
/**
|
||||
* Queue an image for presentation
|
||||
*
|
||||
* @param queue Presentation queue for presenting the image
|
||||
* @param imageIndex Index of the swapchain image to queue for presentation
|
||||
* @param waitSemaphore (Optional) Semaphore that is waited on before the image is presented (only used if != VK_NULL_HANDLE)
|
||||
*
|
||||
* @return VkResult of the queue presentation
|
||||
*/
|
||||
VkResult queuePresent(VkQueue queue, uint32_t imageIndex, VkSemaphore waitSemaphore = VK_NULL_HANDLE);
|
||||
/* Free all Vulkan resources acquired by the swapchain */
|
||||
void cleanup();
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue