Properly set up transitioning image layouts to pair well with dynamic rendering, refactor some code.

This commit is contained in:
Lillian Salehi 2024-11-05 05:50:51 -06:00
parent 7770063537
commit 4a8f6909a8
5 changed files with 670 additions and 548 deletions

View File

@ -3,330 +3,386 @@
namespace device_libs { namespace device_libs {
VkPhysicalDeviceProperties deviceProperties; VkPhysicalDeviceProperties deviceProperties;
std::vector<VkImage> swapChainImages; std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat; VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent; VkExtent2D swapChainExtent;
struct SwapChainSupportDetails { struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities; VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats; std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes; std::vector<VkPresentModeKHR> presentModes;
}; };
const std::vector<const char*> deviceExtensions = { const std::vector<const char *> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME VK_KHR_SWAPCHAIN_EXTENSION_NAME};
}; SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
/* Swap chains are weird ngl, it's another one of those Vulkan platform agnosticity. /* Swap chains are weird ngl, it's another one of those Vulkan platform
The swapchain is basically a wrapper for GDI+, DXGI, X11, Wayland, etc. agnosticity. The swapchain is basically a wrapper for GDI+, DXGI, X11,
It lets us use the swap chain rather than create a different framebuffer Wayland, etc. It lets us use the swap chain rather than create a different
handler for every targeted platform. Swap chains handle the ownership framebuffer handler for every targeted platform. Swap chains handle the
of buffers before sending them to the presentation engine. (still no ownership of buffers before sending them to the presentation engine. (still
fucking clue how it works though) */ no fucking clue how it works though) */
SwapChainSupportDetails details; SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, Global::surface, &details.capabilities); vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, Global::surface,
&details.capabilities);
uint32_t formatCount; uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, nullptr); vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount,
nullptr);
if(formatCount != 0) { if (formatCount != 0) {
details.formats.resize(formatCount); details.formats.resize(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount, details.formats.data()); vkGetPhysicalDeviceSurfaceFormatsKHR(device, Global::surface, &formatCount,
} details.formats.data());
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, Global::surface, &presentModeCount, details.presentModes.data());
if(presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, Global::surface, &presentModeCount, details.presentModes.data());
}
return details;
} }
bool checkDeviceExtensionSupport(VkPhysicalDevice device) { uint32_t presentModeCount;
uint32_t extensionCount; vkGetPhysicalDeviceSurfacePresentModesKHR(
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); device, Global::surface, &presentModeCount, details.presentModes.data());
std::vector<VkExtensionProperties> availableExtensions(extensionCount); if (presentModeCount != 0) {
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); details.presentModes.resize(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(device, Global::surface,
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); &presentModeCount,
details.presentModes.data());
for (const auto& extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
} }
bool isDeviceSuitable(VkPhysicalDevice device) { return details;
// These two are simple, create a structure to hold the apiVersion, driverVersion, vendorID, deviceID and type, name, and a few other settings. }
// Then populate it by passing in the device and the structure reference.
vkGetPhysicalDeviceProperties(device, &deviceProperties);
// Similarly, we can pass in the device and a deviceFeatures struct, this is quite special, it holds a struct of optional features the GPU can perform.
// Some, like a geometry shader, and stereoscopic rendering (multiViewport) we want, so we dont return true without them.
VkPhysicalDeviceFeatures supportedFeatures;
vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
// We need to find a device that supports graphical operations, or else we cant do much with it! This function just runs over all the queueFamilies and sees if there
// is a queue family with the VK_QUEUE_GRAPHICS_BIT flipped!
Global::QueueFamilyIndices indices = Global::findQueueFamilies(device);
bool extensionSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if(extensionSupported) { bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); uint32_t extensionCount;
swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
} nullptr);
return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU std::vector<VkExtensionProperties> availableExtensions(extensionCount);
&& supportedFeatures.samplerAnisotropy vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
&& indices.isComplete() availableExtensions.data());
&& extensionSupported
&& swapChainAdequate; std::set<std::string> requiredExtensions(deviceExtensions.begin(),
deviceExtensions.end());
for (const auto &extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
} }
// -------------------------------------- Swap Chain Settings ----------------------------------------- //
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
// One of three settings we can set, Surface Format controls the color space and format.
for (const auto& availableFormat : availableFormats) { return requiredExtensions.empty();
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { }
// sRGB & 32bit BGRA
return availableFormat; bool isDeviceSuitable(VkPhysicalDevice device) {
} // These two are simple, create a structure to hold the apiVersion,
} // driverVersion, vendorID, deviceID and type, name, and a few other settings.
return availableFormats[0]; // Then populate it by passing in the device and the structure reference.
vkGetPhysicalDeviceProperties(device, &deviceProperties);
// Similarly, we can pass in the device and a deviceFeatures struct, this is
// quite special, it holds a struct of optional features the GPU can perform.
// Some, like a geometry shader, and stereoscopic rendering (multiViewport) we
// want, so we dont return true without them.
VkPhysicalDeviceFeatures supportedFeatures;
vkGetPhysicalDeviceFeatures(device, &supportedFeatures);
// We need to find a device that supports graphical operations, or else we
// cant do much with it! This function just runs over all the queueFamilies
// and sees if there is a queue family with the VK_QUEUE_GRAPHICS_BIT flipped!
Global::QueueFamilyIndices indices = Global::findQueueFamilies(device);
bool extensionSupported = checkDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !swapChainSupport.formats.empty() &&
!swapChainSupport.presentModes.empty();
} }
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
// The second of the three settings, arguably the most important, the presentation mode! This dictates how images are displayed.
// MAILBOX is basically equivalent to triple buffering, it avoids screen tearing with fairly low latency,
// However, it is not always supported, so in the case that it isn't, currently we will default to FIFO,
// This is most similarly to standard V-Sync.
for(const auto& availablePresentMode : availablePresentModes) {
if(availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentMode;
}
}
return VK_PRESENT_MODE_FIFO_KHR; return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
} supportedFeatures.samplerAnisotropy && indices.isComplete() &&
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, GLFWwindow* window) { extensionSupported && swapChainAdequate;
// Swap Extent is just a fancy way of saying the resolution of the swap images to display. }
// This is almost always going to equal the resolution of the window in pixels. // -------------------------------------- Swap Chain Settings
// ----------------------------------------- //
VkSurfaceFormatKHR chooseSwapSurfaceFormat(
const std::vector<VkSurfaceFormatKHR> &availableFormats) {
// One of three settings we can set, Surface Format controls the color space
// and format.
// The max int32 value tells us that the window manager lets us change the windth and height to what we wish! for (const auto &availableFormat : availableFormats) {
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB &&
return capabilities.currentExtent; availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
} else { // sRGB & 32bit BGRA
int width, height; return availableFormat;
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D actualExtent = {
static_cast<uint32_t>(width),
static_cast<uint32_t>(height)
};
// Clamp the image size to the minimum extent values specified by vulkan for our window manager.
actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
return actualExtent;
} }
} }
// --------------------------------------- External Functions ----------------------------------------- // return availableFormats[0];
void DeviceControl::pickPhysicalDevice(VkInstance& instance) { }
uint32_t deviceCount = 0; VkPresentModeKHR chooseSwapPresentMode(
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); const std::vector<VkPresentModeKHR> &availablePresentModes) {
// The second of the three settings, arguably the most important, the
if(deviceCount == 0) { // presentation mode! This dictates how images are displayed. MAILBOX is
throw std::runtime_error("Failed to find GPU's with Vulkan Support!!"); // basically equivalent to triple buffering, it avoids screen tearing with
} // fairly low latency, However, it is not always supported, so in the case
std::vector<VkPhysicalDevice> devices(deviceCount); // Direct Initialization is weird af, yo // that it isn't, currently we will default to FIFO, This is most similarly to
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); // standard V-Sync.
for (const auto &availablePresentMode : availablePresentModes) {
for(const auto& device : devices) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
if(isDeviceSuitable(device)) { return availablePresentMode;
//Once we have buttons or such, maybe ask the user or write a config file for which GPU to use?
Global::physicalDevice = device;
break;
}
}
if(Global::physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("Failed to find a suitable GPU!");
} }
} }
void DeviceControl::destroySurface(VkInstance& instance) {
vkDestroySurfaceKHR(instance, Global::surface, nullptr); return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities,
GLFWwindow *window) {
// Swap Extent is just a fancy way of saying the resolution of the swap images
// to display. This is almost always going to equal the resolution of the
// window in pixels.
// The max int32 value tells us that the window manager lets us change the
// windth and height to what we wish!
if (capabilities.currentExtent.width !=
std::numeric_limits<uint32_t>::max()) {
return capabilities.currentExtent;
} else {
int width, height;
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D actualExtent = {static_cast<uint32_t>(width),
static_cast<uint32_t>(height)};
// Clamp the image size to the minimum extent values specified by vulkan for
// our window manager.
actualExtent.width =
std::clamp(actualExtent.width, capabilities.minImageExtent.width,
capabilities.maxImageExtent.width);
actualExtent.height =
std::clamp(actualExtent.height, capabilities.minImageExtent.height,
capabilities.maxImageExtent.height);
return actualExtent;
} }
void DeviceControl::createSurface(VkInstance& instance, GLFWwindow* window) { }
if(glfwCreateWindowSurface(instance, window, nullptr, &Global::surface) != VK_SUCCESS) { // --------------------------------------- External Functions
throw std::runtime_error("Failed to create window surface!!"); // ----------------------------------------- //
void DeviceControl::pickPhysicalDevice(VkInstance &instance) {
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
throw std::runtime_error("Failed to find GPU's with Vulkan Support!!");
}
std::vector<VkPhysicalDevice> devices(
deviceCount); // Direct Initialization is weird af, yo
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
for (const auto &device : devices) {
if (isDeviceSuitable(device)) {
// Once we have buttons or such, maybe ask the user or write a config file
// for which GPU to use?
Global::physicalDevice = device;
break;
} }
} }
void DeviceControl::createLogicalDevice() { if (Global::physicalDevice == VK_NULL_HANDLE) {
// Describe how many queues we want for a single family (1) here, right now we are solely interested in graphics capabilites, throw std::runtime_error("Failed to find a suitable GPU!");
// but Compute Shaders, transfer ops, decode and encode operations can also queued with setup! We also assign each queue a priority. }
// We do this by looping over all the queueFamilies and sorting them by indices to fill the queue at the end! }
Global::QueueFamilyIndices indices = Global::findQueueFamilies(Global::physicalDevice); void DeviceControl::destroySurface(VkInstance &instance) {
vkDestroySurfaceKHR(instance, Global::surface, nullptr);
}
void DeviceControl::createSurface(VkInstance &instance, GLFWwindow *window) {
if (glfwCreateWindowSurface(instance, window, nullptr, &Global::surface) !=
VK_SUCCESS) {
throw std::runtime_error("Failed to create window surface!!");
}
}
void DeviceControl::createLogicalDevice() {
// Describe how many queues we want for a single family (1) here, right now we
// are solely interested in graphics capabilites, but Compute Shaders,
// transfer ops, decode and encode operations can also queued with setup! We
// also assign each queue a priority. We do this by looping over all the
// queueFamilies and sorting them by indices to fill the queue at the end!
Global::QueueFamilyIndices indices =
Global::findQueueFamilies(Global::physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = { std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(),
indices.graphicsFamily.value(), indices.presentFamily.value()};
indices.presentFamily.value()
};
float queuePriority = 1.0f; float queuePriority = 1.0f;
for(uint32_t queueFamily : uniqueQueueFamilies) { for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateSingularInfo = {}; VkDeviceQueueCreateInfo queueCreateSingularInfo = {};
queueCreateSingularInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateSingularInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateSingularInfo.queueFamilyIndex = queueFamily; queueCreateSingularInfo.queueFamilyIndex = queueFamily;
queueCreateSingularInfo.queueCount = 1; queueCreateSingularInfo.queueCount = 1;
queueCreateSingularInfo.pQueuePriorities = &queuePriority; queueCreateSingularInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateSingularInfo); queueCreateInfos.push_back(queueCreateSingularInfo);
} }
VkPhysicalDeviceVulkan13Features features13 { VkPhysicalDeviceVulkan13Features features13{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
.pNext = nullptr, .pNext = nullptr,
.synchronization2 = true,
.dynamicRendering = true, .dynamicRendering = true,
}; };
VkPhysicalDeviceFeatures featuresBase { VkPhysicalDeviceFeatures featuresBase{
.samplerAnisotropy = true, .samplerAnisotropy = true,
}; };
VkPhysicalDeviceFeatures2 deviceFeatures { VkPhysicalDeviceFeatures2 deviceFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &features13, .pNext = &features13,
.features = featuresBase, .features = featuresBase,
}; };
VkDeviceCreateInfo createDeviceInfo = {}; VkDeviceCreateInfo createDeviceInfo = {};
createDeviceInfo.pNext = &deviceFeatures; createDeviceInfo.pNext = &deviceFeatures;
createDeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createDeviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createDeviceInfo.pQueueCreateInfos = queueCreateInfos.data(); createDeviceInfo.pQueueCreateInfos = queueCreateInfos.data();
createDeviceInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); createDeviceInfo.queueCreateInfoCount =
createDeviceInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); static_cast<uint32_t>(queueCreateInfos.size());
createDeviceInfo.ppEnabledExtensionNames = deviceExtensions.data(); createDeviceInfo.enabledExtensionCount =
static_cast<uint32_t>(deviceExtensions.size());
createDeviceInfo.ppEnabledExtensionNames = deviceExtensions.data();
if(vkCreateDevice(Global::physicalDevice, &createDeviceInfo, nullptr, &Global::device) != VK_SUCCESS) { if (vkCreateDevice(Global::physicalDevice, &createDeviceInfo, nullptr,
throw std::runtime_error("Failed to create logical device"); &Global::device) != VK_SUCCESS) {
} throw std::runtime_error("Failed to create logical device");
vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0, &Global::graphicsQueue);
vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0, &Global::presentQueue);
} }
void DeviceControl::createSwapChain(GLFWwindow* window) { vkGetDeviceQueue(Global::device, indices.graphicsFamily.value(), 0,
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(Global::physicalDevice); &Global::graphicsQueue);
vkGetDeviceQueue(Global::device, indices.presentFamily.value(), 0,
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); &Global::presentQueue);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities, window);
// Number of images to hold in the swap chain, 1 over the minimum guarantees we won't have to wait on the driver to complete
// internal operations before acquiring another image. Absolutely a TODO to determine the best amount to queue.
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
// Make sure not to queue more than the max! 0 indicates that there is no maximum.
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR createSwapChainInfo{};
createSwapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createSwapChainInfo.surface = Global::surface;
createSwapChainInfo.minImageCount = imageCount;
createSwapChainInfo.imageFormat = surfaceFormat.format;
createSwapChainInfo.imageColorSpace = surfaceFormat.colorSpace;
createSwapChainInfo.imageExtent = extent;
// Image array layers is always 1 unless we are developing for VR (Spoiler: we are, we will use a build flag.)
// Image Usage specifies what operations you use the images for, COLOR_ATTACH means we render directly to them,
// if you wanted to render to separate images for things like post processing, you can use TRANSFER_DST and use a
// memory operation to transfer the image to a swap chain, this is also a TODO item eventually.
createSwapChainInfo.imageArrayLayers = 1;
createSwapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
// This handles swap chain images across multiple queue families, ie, if the graphics queue family is different from the present queue
Global::QueueFamilyIndices indices = Global::findQueueFamilies(Global::physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};
// Usage across multiple queue families without explicit transfer of ownership if they are different queue families.
// Otherwise, no sharing without explicit handoffs, faster, but not easily supported with multiple families.
// Presentation and Graphics families are usually merged on most hardware.
if (indices.graphicsFamily != indices.presentFamily) {
createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createSwapChainInfo.queueFamilyIndexCount = 2;
createSwapChainInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
// Transformation of image support.
createSwapChainInfo.preTransform = swapChainSupport.capabilities.currentTransform;
// Do NOT blend with other windows on the system.
createSwapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createSwapChainInfo.presentMode = presentMode;
// This is interesting, clip pixels that are obscured for performance, but that means you wont be able to read them reliably..
// I am curious if this would affect screen-space rendering techniques, may be something to note.
createSwapChainInfo.clipped = VK_TRUE;
// This is something that needs to be implemented later, operations like resizing the window invalidate the swap chain and
// require you to recreate it and reference the old one specified here, will revisit in a few days.
//createSwapChainInfo.oldSwapchain = VK_NULL_HANDLE;
if(vkCreateSwapchainKHR(Global::device, &createSwapChainInfo, nullptr, &Global::swapChain) != VK_SUCCESS) {
throw std::runtime_error("Failed to create the swap chain!!");
}
vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount, swapChainImages.data());
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
}
void DeviceControl::destroySwapChain() {
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
}
VkImageView DeviceControl::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipLevels) {
// This defines the parameters of a newly created image object!
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.subresourceRange.aspectMask = flags;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.subresourceRange.levelCount = mipLevels;
VkImageView imageView;
if (vkCreateImageView(Global::device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
throw std::runtime_error("failed to create image view!");
}
return imageView;
}
void DeviceControl::createImageViews() {
Global::swapChainImageViews.resize(swapChainImages.size());
for (uint32_t i = 0; i < swapChainImages.size(); i++) {
Global::swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
}
}
void DeviceControl::destroyImageViews() {
for (auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
}
// --------------------------------------- Getters & Setters ------------------------------------------ //
VkFormat* DeviceControl::getImageFormat() {
return &swapChainImageFormat;
}
VkExtent2D DeviceControl::getSwapChainExtent() {
return swapChainExtent;
}
std::vector<VkImage> DeviceControl::getSwapChainImages() {
return swapChainImages;
}
} }
void DeviceControl::createSwapChain(GLFWwindow *window) {
SwapChainSupportDetails swapChainSupport =
querySwapChainSupport(Global::physicalDevice);
VkSurfaceFormatKHR surfaceFormat =
chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode =
chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities, window);
// Number of images to hold in the swap chain, 1 over the minimum guarantees
// we won't have to wait on the driver to complete internal operations before
// acquiring another image. Absolutely a TODO to determine the best amount to
// queue.
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
// Make sure not to queue more than the max! 0 indicates that there is no
// maximum.
if (swapChainSupport.capabilities.maxImageCount > 0 &&
imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR createSwapChainInfo{};
createSwapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createSwapChainInfo.surface = Global::surface;
createSwapChainInfo.minImageCount = imageCount;
createSwapChainInfo.imageFormat = surfaceFormat.format;
createSwapChainInfo.imageColorSpace = surfaceFormat.colorSpace;
createSwapChainInfo.imageExtent = extent;
// Image array layers is always 1 unless we are developing for VR (Spoiler: we
// are, we will use a build flag.) Image Usage specifies what operations you
// use the images for, COLOR_ATTACH means we render directly to them, if you
// wanted to render to separate images for things like post processing, you
// can use TRANSFER_DST and use a memory operation to transfer the image to a
// swap chain, this is also a TODO item eventually.
createSwapChainInfo.imageArrayLayers = 1;
createSwapChainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
// This handles swap chain images across multiple queue families, ie, if the
// graphics queue family is different from the present queue
Global::QueueFamilyIndices indices =
Global::findQueueFamilies(Global::physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(),
indices.presentFamily.value()};
// Usage across multiple queue families without explicit transfer of ownership
// if they are different queue families. Otherwise, no sharing without
// explicit handoffs, faster, but not easily supported with multiple families.
// Presentation and Graphics families are usually merged on most hardware.
if (indices.graphicsFamily != indices.presentFamily) {
createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createSwapChainInfo.queueFamilyIndexCount = 2;
createSwapChainInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createSwapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
// Transformation of image support.
createSwapChainInfo.preTransform =
swapChainSupport.capabilities.currentTransform;
// Do NOT blend with other windows on the system.
createSwapChainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createSwapChainInfo.presentMode = presentMode;
// This is interesting, clip pixels that are obscured for performance, but
// that means you wont be able to read them reliably.. I am curious if this
// would affect screen-space rendering techniques, may be something to note.
createSwapChainInfo.clipped = VK_TRUE;
// This is something that needs to be implemented later, operations like
// resizing the window invalidate the swap chain and require you to recreate
// it and reference the old one specified here, will revisit in a few days.
// createSwapChainInfo.oldSwapchain = VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(Global::device, &createSwapChainInfo, nullptr,
&Global::swapChain) != VK_SUCCESS) {
throw std::runtime_error("Failed to create the swap chain!!");
}
vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount,
nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(Global::device, Global::swapChain, &imageCount,
swapChainImages.data());
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
}
void DeviceControl::destroySwapChain() {
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
}
VkImageView DeviceControl::createImageView(VkImage image, VkFormat format,
VkImageAspectFlags flags,
uint32_t mipLevels) {
// This defines the parameters of a newly created image object!
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = image;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.subresourceRange.aspectMask = flags;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.subresourceRange.levelCount = mipLevels;
VkImageView imageView;
if (vkCreateImageView(Global::device, &viewInfo, nullptr, &imageView) !=
VK_SUCCESS) {
throw std::runtime_error("failed to create image view!");
}
return imageView;
}
void DeviceControl::createImageViews() {
Global::swapChainImageViews.resize(swapChainImages.size());
for (uint32_t i = 0; i < swapChainImages.size(); i++) {
Global::swapChainImageViews[i] = createImageView(
swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
}
}
void DeviceControl::destroyImageViews() {
for (auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
}
// --------------------------------------- Getters & Setters
// ------------------------------------------ //
VkFormat *DeviceControl::getImageFormat() { return &swapChainImageFormat; }
VkExtent2D DeviceControl::getSwapChainExtent() { return swapChainExtent; }
std::vector<VkImage> DeviceControl::getSwapChainImages() {
return swapChainImages;
}
} // namespace device_libs

View File

@ -7,11 +7,10 @@ VkInstance vulkaninstance;
void EntryApp::setFramebufferResized(bool setter) { void EntryApp::setFramebufferResized(bool setter) {
framebufferResized = setter; framebufferResized = setter;
} }
bool EntryApp::getFramebufferResized() const { bool EntryApp::getFramebufferResized() const { return framebufferResized; }
return framebufferResized; static void framebufferResizeCallback(GLFWwindow *window, int width,
} int height) {
static void framebufferResizeCallback(GLFWwindow* window, int width, int height) { auto app = reinterpret_cast<EntryApp *>(glfwGetWindowUserPointer(window));
auto app = reinterpret_cast<EntryApp*>(glfwGetWindowUserPointer(window));
app->setFramebufferResized(true); app->setFramebufferResized(true);
} }
@ -20,8 +19,9 @@ static void framebufferResizeCallback(GLFWwindow* window, int width, int height)
void initWindow() { void initWindow() {
glfwInit(); glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
// Settings for the window are set, create window reference. // Settings for the window are set, create window reference.
Global::window = glfwCreateWindow(Global::WIDTH, Global::HEIGHT, "Trimgles :o", nullptr, nullptr); Global::window = glfwCreateWindow(Global::WIDTH, Global::HEIGHT,
"Trimgles :o", nullptr, nullptr);
glfwSetWindowUserPointer(Global::window, &EntryApp::getInstance()); glfwSetWindowUserPointer(Global::window, &EntryApp::getInstance());
glfwSetFramebufferSizeCallback(Global::window, framebufferResizeCallback); glfwSetFramebufferSizeCallback(Global::window, framebufferResizeCallback);
} }
@ -29,33 +29,48 @@ void initWindow() {
void createInstance() { void createInstance() {
// Set application info for the vulkan instance! // Set application info for the vulkan instance!
VkApplicationInfo appInfo{}; VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Tell vulkan that appInfo is a Application Info structure appInfo.sType =
appInfo.pApplicationName = "Triangle Test"; // Give the struct a name to use VK_STRUCTURE_TYPE_APPLICATION_INFO; // Tell vulkan that appInfo is a
appInfo.applicationVersion = VK_MAKE_VERSION(1,0,0); // Create a Major Minor Patch version number for the application! // Application Info structure
appInfo.pEngineName = "Agnosia Engine"; // Give an internal name for the engine running appInfo.pApplicationName = "Triangle Test"; // Give the struct a name to use
appInfo.engineVersion = VK_MAKE_VERSION(1,0,0); // Similar to the App version, give vulkan an *engine* version appInfo.applicationVersion = VK_MAKE_VERSION(
appInfo.apiVersion = VK_API_VERSION_1_3; // Tell vulkan what the highest API version we will allow this program to run on 1, 0,
0); // Create a Major Minor Patch version number for the application!
appInfo.pEngineName =
"Agnosia Engine"; // Give an internal name for the engine running
appInfo.engineVersion = VK_MAKE_VERSION(
1, 0, 0); // Similar to the App version, give vulkan an *engine* version
appInfo.apiVersion =
VK_API_VERSION_1_3; // Tell vulkan what the highest API version we will
// allow this program to run on
// This gets a little weird, Vulkan is platform agnostic, so you need to figure out what extensions to interface with the current system are needed // This gets a little weird, Vulkan is platform agnostic, so you need to
// So, to figure out what extension codes and how many to use, feed the pointer into *glfwGetRequiredInstanceExtensions*, which will get the necessary extensions! // figure out what extensions to interface with the current system are needed
// From there, we can send that over to our createInfo Vulkan info struct to make it fully platform agnostic! // So, to figure out what extension codes and how many to use, feed the
// pointer into *glfwGetRequiredInstanceExtensions*, which will get the
// necessary extensions! From there, we can send that over to our createInfo
// Vulkan info struct to make it fully platform agnostic!
uint32_t glfwExtensionCount = 0; uint32_t glfwExtensionCount = 0;
const char** glfwExtensions; const char **glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); std::vector<const char *> extensions(glfwExtensions,
glfwExtensions + glfwExtensionCount);
VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info structure createInfo.sType =
createInfo.pApplicationInfo = &appInfo; // We just created a new appInfo structure, so we pass the pointer to it. VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info
// structure
createInfo.pApplicationInfo =
&appInfo; // We just created a new appInfo structure, so we pass the
// pointer to it.
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.ppEnabledExtensionNames = extensions.data();
if (vkCreateInstance(&createInfo, nullptr, &vulkaninstance) != VK_SUCCESS) { if (vkCreateInstance(&createInfo, nullptr, &vulkaninstance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!"); throw std::runtime_error("failed to create instance!");
} }
} }
void initVulkan() { void initVulkan() {
@ -94,12 +109,12 @@ void mainLoop() {
void cleanup() { void cleanup() {
render_present::Render::cleanupSwapChain(); render_present::Render::cleanupSwapChain();
graphics_pipeline::Graphics::destroyGraphicsPipeline(); graphics_pipeline::Graphics::destroyGraphicsPipeline();
//graphics_pipeline::Graphics::destroyRenderPass();
buffers_libs::Buffers::destroyUniformBuffer(); buffers_libs::Buffers::destroyUniformBuffer();
buffers_libs::Buffers::destroyDescriptorPool(); buffers_libs::Buffers::destroyDescriptorPool();
texture_libs::Texture::destroyTextureSampler(); texture_libs::Texture::destroyTextureSampler();
texture_libs::Texture::destroyTextureImage(); texture_libs::Texture::destroyTextureImage();
vkDestroyDescriptorSetLayout(Global::device, Global::descriptorSetLayout, nullptr); vkDestroyDescriptorSetLayout(Global::device, Global::descriptorSetLayout,
nullptr);
buffers_libs::Buffers::destroyBuffers(); buffers_libs::Buffers::destroyBuffers();
render_present::Render::destroyFenceSemaphores(); render_present::Render::destroyFenceSemaphores();
graphics_pipeline::Graphics::destroyCommandPool(); graphics_pipeline::Graphics::destroyCommandPool();
@ -112,18 +127,14 @@ void cleanup() {
} }
// External Functions // External Functions
EntryApp& EntryApp::getInstance() { EntryApp &EntryApp::getInstance() {
static EntryApp instance; static EntryApp instance;
return instance; return instance;
} }
EntryApp::EntryApp() : initialized(false), framebufferResized(false) {} EntryApp::EntryApp() : initialized(false), framebufferResized(false) {}
void EntryApp::initialize() { void EntryApp::initialize() { initialized = true; }
initialized = true; bool EntryApp::isInitialized() const { return initialized; }
}
bool EntryApp::isInitialized() const {
return initialized;
}
void EntryApp::run() { void EntryApp::run() {
initWindow(); initWindow();
@ -131,4 +142,3 @@ void EntryApp::run() {
mainLoop(); mainLoop();
cleanup(); cleanup();
} }

View File

@ -2,60 +2,66 @@
namespace Global { namespace Global {
VkSurfaceKHR surface; VkSurfaceKHR surface;
VkDevice device; VkDevice device;
VkPhysicalDevice physicalDevice; VkPhysicalDevice physicalDevice;
VkSwapchainKHR swapChain; VkSwapchainKHR swapChain;
VkCommandPool commandPool; VkCommandPool commandPool;
std::vector<VkCommandBuffer> commandBuffers; std::vector<VkCommandBuffer> commandBuffers;
VkQueue graphicsQueue; VkQueue graphicsQueue;
VkQueue presentQueue; VkQueue presentQueue;
GLFWwindow* window; GLFWwindow *window;
VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSetLayout descriptorSetLayout;
std::vector<VkDescriptorSet> descriptorSets; std::vector<VkDescriptorSet> descriptorSets;
uint32_t currentFrame = 0; uint32_t currentFrame = 0;
VkImageView textureImageView; VkImageView textureImageView;
VkSampler textureSampler; VkSampler textureSampler;
VkImageView depthImageView; VkImageView depthImageView;
VkImage depthImage; VkImage depthImage;
VkDeviceMemory depthImageMemory; VkDeviceMemory depthImageMemory;
std::vector<VkImageView> swapChainImageViews; std::vector<VkImageView> swapChainImageViews;
std::vector<Vertex> vertices; std::vector<Vertex> vertices;
// Index buffer definition, showing which points to reuse. // Index buffer definition, showing which points to reuse.
std::vector<uint32_t> indices; std::vector<uint32_t> indices;
Global::QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { Global::QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
// First we feed in a integer we want to use to hold the number of queued items, that fills it, then we create that amount of default constructed *VkQueueFamilyProperties* structs. // First we feed in a integer we want to use to hold the number of queued
// These store the flags, the amount of queued items in the family, and timestamp data. Queue families are simply group collections of tasks we want to get done. // items, that fills it, then we create that amount of default constructed
// Next, we check the flags of the queueFamily item, use a bitwise and to see if they match, i.e. support graphical operations, then return that to notify that we have at least one family that supports VK_QUEUE_GRAPHICS_BIT. // *VkQueueFamilyProperties* structs. These store the flags, the amount of
// Which means this device supports graphical operations! // queued items in the family, and timestamp data. Queue families are simply
// We also do the same thing for window presentation, just check to see if its supported. // group collections of tasks we want to get done. Next, we check the flags of
Global::QueueFamilyIndices indices; // the queueFamily item, use a bitwise and to see if they match, i.e. support
// graphical operations, then return that to notify that we have at least one
// family that supports VK_QUEUE_GRAPHICS_BIT. Which means this device
// supports graphical operations! We also do the same thing for window
// presentation, just check to see if its supported.
Global::QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
uint32_t queueFamilyCount = 0; std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount,
queueFamilies.data());
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); int i = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); for (const auto &queueFamily : queueFamilies) {
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
int i = 0; indices.graphicsFamily = i;
for(const auto& queueFamily : queueFamilies) {
if(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Global::surface, &presentSupport);
if(presentSupport) {
indices.presentFamily = i;
}
if(indices.isComplete()) {
break;
}
i++;
} }
return indices;
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, Global::surface,
&presentSupport);
if (presentSupport) {
indices.presentFamily = i;
}
if (indices.isComplete()) {
break;
}
i++;
} }
return indices;
} }
} // namespace Global

View File

@ -261,27 +261,35 @@ void Graphics::recordCommandBuffer(VkCommandBuffer commandBuffer,
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("failed to begin recording command buffer!"); throw std::runtime_error("failed to begin recording command buffer!");
} }
const VkImageMemoryBarrier2 imageMemoryBarrier{
const VkImageMemoryBarrier imageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .srcAccessMask = 0,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, .dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = device_libs::DeviceControl::getSwapChainImages()[imageIndex], .image = device_libs::DeviceControl::getSwapChainImages()[imageIndex],
.subresourceRange = { .subresourceRange =
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, {
.baseMipLevel = 0, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.levelCount = texture_libs::Texture::getMipLevels(), .baseMipLevel = 0,
.baseArrayLayer = 0, .levelCount = 1,
.layerCount = 1, .baseArrayLayer = 0,
}}; .layerCount = 1,
},
};
const VkDependencyInfo dependencyInfo{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pNext = nullptr,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &imageMemoryBarrier,
};
vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo);
vkCmdPipelineBarrier(commandBuffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0,
nullptr, 1, &imageMemoryBarrier
);
// ------------------- DYNAMIC RENDER INFO ---------------------- // // ------------------- DYNAMIC RENDER INFO ---------------------- //
const VkRenderingAttachmentInfo colorAttachmentInfo{ const VkRenderingAttachmentInfo colorAttachmentInfo{
@ -347,6 +355,36 @@ void Graphics::recordCommandBuffer(VkCommandBuffer commandBuffer,
vkCmdEndRendering(commandBuffer); vkCmdEndRendering(commandBuffer);
const VkImageMemoryBarrier2 prePresentImageBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.pNext = nullptr,
.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = device_libs::DeviceControl::getSwapChainImages()[imageIndex],
.subresourceRange =
{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
const VkDependencyInfo depInfo{
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pNext = nullptr,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &prePresentImageBarrier,
};
vkCmdPipelineBarrier2(Global::commandBuffers[Global::currentFrame], &depInfo);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
throw std::runtime_error("failed to record command buffer!"); throw std::runtime_error("failed to record command buffer!");
} }

View File

@ -1,162 +1,174 @@
#include "render.h"
#include "graphicspipeline.h"
#include "../devicelibrary.h" #include "../devicelibrary.h"
#include "../entrypoint.h" #include "../entrypoint.h"
#include "graphicspipeline.h"
#include "render.h"
#include "texture.h" #include "texture.h"
#include <vulkan/vulkan_core.h>
namespace render_present { namespace render_present {
std::vector<VkSemaphore> imageAvailableSemaphores; std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores; std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences; std::vector<VkFence> inFlightFences;
void recreateSwapChain() { void recreateSwapChain() {
int width = 0, height = 0; int width = 0, height = 0;
glfwGetFramebufferSize(Global::window, &width, &height);
while (width == 0 || height == 0) {
glfwGetFramebufferSize(Global::window, &width, &height); glfwGetFramebufferSize(Global::window, &width, &height);
while (width == 0 || height == 0) { glfwWaitEvents();
glfwGetFramebufferSize(Global::window, &width, &height);
glfwWaitEvents();
}
vkDeviceWaitIdle(Global::device);
// Don't really wanna do this but I also don't want to create an extra class instance just to call the cleanup function.
for(auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
device_libs::DeviceControl::createSwapChain(Global::window);
device_libs::DeviceControl::createImageViews();
texture_libs::Texture::createDepthResources();
} }
// At a high level, rendering in Vulkan consists of 5 steps: vkDeviceWaitIdle(Global::device);
// Wait for the previous frame, acquire a image from the swap chain // Don't really wanna do this but I also don't want to create an extra class
// record a comman d buffer which draws the scene onto that image // instance just to call the cleanup function.
// submit the recorded command buffer and present the image!
void Render::drawFrame() {
vkWaitForFences(Global::device, 1, &inFlightFences[Global::currentFrame], VK_TRUE, UINT64_MAX); for (auto imageView : Global::swapChainImageViews) {
vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]); vkDestroyImageView(Global::device, imageView, nullptr);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX, imageAvailableSemaphores[Global::currentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
}
buffers_libs::Buffers::updateUniformBuffer(Global::currentFrame);
vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]);
vkResetCommandBuffer(Global::commandBuffers[Global::currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
graphics_pipeline::Graphics::recordCommandBuffer(Global::commandBuffers[Global::currentFrame], imageIndex);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[Global::currentFrame]};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &Global::commandBuffers[Global::currentFrame];
VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[Global::currentFrame]};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (vkQueueSubmit(Global::graphicsQueue, 1, &submitInfo, inFlightFences[Global::currentFrame]) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {Global::swapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(Global::presentQueue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || EntryApp::getInstance().getFramebufferResized()) {
EntryApp::getInstance().setFramebufferResized(false);
recreateSwapChain();
} else if (result != VK_SUCCESS) {
throw std::runtime_error("failed to present swap chain image!");
}
Global::currentFrame = (Global::currentFrame + 1) % Global::MAX_FRAMES_IN_FLIGHT;
}
#pragma info
// SEMAPHORES
// Synchronization of execution on the GPU in Vulkan is *explicit* The Order of ops is up to us to
// define the how we want things to run.
// Similarly, Semaphores are used to add order between queue ops. There are 2 kinds of Semaphores; binary, and timeline.
// We are using Binary semaphores, which can be signaled or unsignaled.
// Semaphores are initizalized unsignaled, the way we use them to order queue operations is by providing the same semaphore in one queue op and a wait in another.
// For example:
// VkCommandBuffer QueueOne, QueueTwo = ...
// VkSemaphore semaphore = ...
// enqueue QueueOne, Signal semaphore when done, start now.
// vkQueueSubmit(work: QueueOne, signal: semaphore, wait: none)
// enqueue QueueTwo, wait on semaphore to start
// vkQueueSubmit(
// work: QueueTwo, signal: None, wait: semaphore)
// FENCES
// Fences are basically semaphores for the CPU! Otherwise known as the host. If the host needs to know when the GPU has finished a task, we use a fence.
// VkCommandBuffer cmndBuf = ...
// VkFence fence = ...
// Start work immediately, signal fence when done.
// vkQueueSubmit(work: cmndBuf, fence: fence)
// vkWaitForFence(fence)
// doStuffOnceFenceDone()
#pragma endinfo
void Render::createSyncObject() {
imageAvailableSemaphores.resize(Global::MAX_FRAMES_IN_FLIGHT);
renderFinishedSemaphores.resize(Global::MAX_FRAMES_IN_FLIGHT);
inFlightFences.resize(Global::MAX_FRAMES_IN_FLIGHT);
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) {
if(vkCreateSemaphore(Global::device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(Global::device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS ||
vkCreateFence(Global::device, &fenceInfo, nullptr, &inFlightFences[i]) != VK_SUCCESS) {
throw std::runtime_error("Failed to create semaphores!");
}
}
}
void Render::destroyFenceSemaphores() {
for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(Global::device, renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(Global::device, imageAvailableSemaphores[i], nullptr);
vkDestroyFence(Global::device, inFlightFences[i], nullptr);
}
}
void Render::cleanupSwapChain() {
vkDestroyImageView(Global::device, Global::depthImageView, nullptr);
vkDestroyImage(Global::device, Global::depthImage, nullptr);
vkFreeMemory(Global::device, Global::depthImageMemory, nullptr);
for(auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
} }
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
device_libs::DeviceControl::createSwapChain(Global::window);
device_libs::DeviceControl::createImageViews();
texture_libs::Texture::createDepthResources();
} }
// At a high level, rendering in Vulkan consists of 5 steps:
// Wait for the previous frame, acquire a image from the swap chain
// record a comman d buffer which draws the scene onto that image
// submit the recorded command buffer and present the image!
void Render::drawFrame() {
vkWaitForFences(Global::device, 1, &inFlightFences[Global::currentFrame],
VK_TRUE, UINT64_MAX);
vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]);
uint32_t imageIndex;
VkResult result =
vkAcquireNextImageKHR(Global::device, Global::swapChain, UINT64_MAX,
imageAvailableSemaphores[Global::currentFrame],
VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
}
buffers_libs::Buffers::updateUniformBuffer(Global::currentFrame);
vkResetFences(Global::device, 1, &inFlightFences[Global::currentFrame]);
vkResetCommandBuffer(Global::commandBuffers[Global::currentFrame],
/*VkCommandBufferResetFlagBits*/ 0);
graphics_pipeline::Graphics::recordCommandBuffer(
Global::commandBuffers[Global::currentFrame], imageIndex);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {
imageAvailableSemaphores[Global::currentFrame]};
VkPipelineStageFlags waitStages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &Global::commandBuffers[Global::currentFrame];
VkSemaphore signalSemaphores[] = {
renderFinishedSemaphores[Global::currentFrame]};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (vkQueueSubmit(Global::graphicsQueue, 1, &submitInfo,
inFlightFences[Global::currentFrame]) != VK_SUCCESS) {
throw std::runtime_error("failed to submit draw command buffer!");
}
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {Global::swapChain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
result = vkQueuePresentKHR(Global::presentQueue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR ||
EntryApp::getInstance().getFramebufferResized()) {
EntryApp::getInstance().setFramebufferResized(false);
recreateSwapChain();
} else if (result != VK_SUCCESS) {
throw std::runtime_error("failed to present swap chain image!");
}
Global::currentFrame =
(Global::currentFrame + 1) % Global::MAX_FRAMES_IN_FLIGHT;
}
#pragma info
// SEMAPHORES
// Synchronization of execution on the GPU in Vulkan is *explicit* The Order of
// ops is up to us to define the how we want things to run. Similarly,
// Semaphores are used to add order between queue ops. There are 2 kinds of
// Semaphores; binary, and timeline. We are using Binary semaphores, which can
// be signaled or unsignaled. Semaphores are initizalized unsignaled, the way we
// use them to order queue operations is by providing the same semaphore in one
// queue op and a wait in another. For example: VkCommandBuffer QueueOne,
// QueueTwo = ... VkSemaphore semaphore = ... enqueue QueueOne, Signal semaphore
// when done, start now. vkQueueSubmit(work: QueueOne, signal: semaphore, wait:
// none) enqueue QueueTwo, wait on semaphore to start vkQueueSubmit(
// work: QueueTwo, signal: None, wait: semaphore)
// FENCES
// Fences are basically semaphores for the CPU! Otherwise known as the host. If
// the host needs to know when the GPU has finished a task, we use a fence.
// VkCommandBuffer cmndBuf = ...
// VkFence fence = ...
// Start work immediately, signal fence when done.
// vkQueueSubmit(work: cmndBuf, fence: fence)
// vkWaitForFence(fence)
// doStuffOnceFenceDone()
#pragma endinfo
void Render::createSyncObject() {
imageAvailableSemaphores.resize(Global::MAX_FRAMES_IN_FLIGHT);
renderFinishedSemaphores.resize(Global::MAX_FRAMES_IN_FLIGHT);
inFlightFences.resize(Global::MAX_FRAMES_IN_FLIGHT);
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) {
if (vkCreateSemaphore(Global::device, &semaphoreInfo, nullptr,
&imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(Global::device, &semaphoreInfo, nullptr,
&renderFinishedSemaphores[i]) != VK_SUCCESS ||
vkCreateFence(Global::device, &fenceInfo, nullptr,
&inFlightFences[i]) != VK_SUCCESS) {
throw std::runtime_error("Failed to create semaphores!");
}
}
}
void Render::destroyFenceSemaphores() {
for (size_t i = 0; i < Global::MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(Global::device, renderFinishedSemaphores[i], nullptr);
vkDestroySemaphore(Global::device, imageAvailableSemaphores[i], nullptr);
vkDestroyFence(Global::device, inFlightFences[i], nullptr);
}
}
void Render::cleanupSwapChain() {
vkDestroyImageView(Global::device, Global::depthImageView, nullptr);
vkDestroyImage(Global::device, Global::depthImage, nullptr);
vkFreeMemory(Global::device, Global::depthImageMemory, nullptr);
for (auto imageView : Global::swapChainImageViews) {
vkDestroyImageView(Global::device, imageView, nullptr);
}
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
}
} // namespace render_present