Complete "Hello Triangle" program with resizing and minimization handled

This commit is contained in:
macmacmac 2024-08-31 13:47:29 -04:00
parent 54e69982eb
commit 75e9921d45
5 changed files with 168 additions and 60 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
.cache/
.vscode/
build/

View File

@ -42,6 +42,13 @@ add_executable(${EXE_NAME} ${CMAKE_SOURCE_DIR}/src/main.cc ${PROGRAM_SOURCES} ${
target_include_directories(${EXE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include)
add_dependencies(${EXE_NAME} shaders)
if ( CMAKE_COMPILER_IS_GNUCC )
target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra)
endif()
if ( MSVC )
target_compile_options(${EXE_NAME} PRIVATE /W4)
endif()
# Linking
target_link_libraries(${EXE_NAME} PRIVATE SDL2::SDL2)
target_link_libraries(${EXE_NAME} PRIVATE Vulkan::Vulkan)

View File

@ -17,6 +17,8 @@ const std::vector<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
const int MAX_FRAMES_IN_FLIGHT = 2;
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
@ -44,7 +46,7 @@ struct SwapChainSupportDetails {
class VulkanApp {
public:
VulkanApp(const uint32_t& _w, const uint32_t& _h) :
mWidth(_w), mHeight(_h), mWin(nullptr), mActive(false) {}
mWin(nullptr), mWidth(_w), mHeight(_h) {}
void init();
void loop();
@ -55,9 +57,11 @@ private:
SDL_Event mEvent;
// Not tied to library
const uint32_t mWidth;
const uint32_t mHeight;
bool mActive;
uint32_t mWidth;
uint32_t mHeight;
bool mActive = false;
bool mResized = false;
bool mMinimized = false;
// Vulkan
VkInstance mInstance;
@ -77,14 +81,14 @@ private:
VkPipeline mGraphicsPipeline;
VkCommandPool mCommandPool;
VkCommandBuffer mCommandBuffer;
std::vector<VkCommandBuffer> mCommandBuffers;
VkQueue mGraphicsQueue;
VkQueue mPresentQueue;
VkSemaphore mImageAvailableSemaphore;
VkSemaphore mRenderFinishedSemaphore;
VkFence mInFlightFence;
std::vector<VkSemaphore> mImageAvailableSemaphores;
std::vector<VkSemaphore> mRenderFinishedSemaphores;
std::vector<VkFence> mInFlightFences;
VkDebugUtilsMessengerEXT mDebugMessenger;
@ -92,7 +96,11 @@ private:
void createInstance();
void selectPhysicalDevice();
void createLogicalDevice();
void createSwapChain();
void recreateSwapChain();
void cleanupSwapChain();
void createImageViews();
void createRenderPass();
void createGraphicsPipeline();
@ -118,7 +126,15 @@ private:
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
fprintf(stderr, "Validation Layer: %s \n", pCallbackData->pMessage);
(void)pUserData;
if(messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
fprintf(stderr, "Validation Layer [Error/Warning]: %s \n", pCallbackData->pMessage);
} else if(messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) {
fprintf(stderr, "Validation Layer [General]: %s \n", pCallbackData->pMessage);
} else {
fprintf(stderr, "Validation Layer: %s \n", pCallbackData->pMessage);
}
return VK_FALSE;
}

View File

@ -2,13 +2,13 @@
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
const vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
const vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)

View File

@ -83,7 +83,7 @@ QueueFamilyIndices VulkanApp::findQueueFamilies(VkPhysicalDevice device) {
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
// Get queue families and check them
for(int i=0;i<queueFamilies.size();i++) {
for(size_t i=0;i<queueFamilies.size();i++) {
const VkQueueFamilyProperties& queueFamily = queueFamilies[i];
VkBool32 presentSupport = false;
@ -197,7 +197,7 @@ void VulkanApp::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imag
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = mSwapChainExtent;
VkClearValue clearColor = {{{0.5f, 0.0f, 0.5f, 1.0f}}};
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
@ -218,6 +218,7 @@ void VulkanApp::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imag
scissor.extent = mSwapChainExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
// Actually draw the vertices
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
vkCmdEndRenderPass(commandBuffer);
@ -227,32 +228,48 @@ void VulkanApp::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imag
}
void VulkanApp::drawFrame() {
vkWaitForFences(mLogicalDevice, 1, &mInFlightFence, VK_TRUE, UINT64_MAX);
vkResetFences(mLogicalDevice, 1, &mInFlightFence);
uint32_t currentFrame = 0;
VkResult result = VK_SUCCESS;
uint32_t imageIndex = 0;
uint32_t imageIndex;
vkAcquireNextImageKHR(mLogicalDevice, mSwapChain, UINT64_MAX, mImageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
vkWaitForFences(mLogicalDevice, 1, &mInFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
vkResetFences(mLogicalDevice, 1, &mInFlightFences[currentFrame]);
vkResetCommandBuffer(mCommandBuffer, /*VkCommandBufferResetFlagBits*/ 0);
recordCommandBuffer(mCommandBuffer, imageIndex);
result = vkAcquireNextImageKHR(mLogicalDevice,
mSwapChain,
UINT64_MAX,
mImageAvailableSemaphores[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("Could not get Vulkan swap chain image!");
}
vkResetFences(mLogicalDevice, 1, &mInFlightFences[currentFrame]);
vkResetCommandBuffer(mCommandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0);
recordCommandBuffer(mCommandBuffers[currentFrame], imageIndex);
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {mImageAvailableSemaphore};
VkSemaphore waitSemaphores[] = {mImageAvailableSemaphores[currentFrame]};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &mCommandBuffer;
submitInfo.pCommandBuffers = &mCommandBuffers[currentFrame];
VkSemaphore signalSemaphores[] = {mRenderFinishedSemaphore};
VkSemaphore signalSemaphores[] = {mRenderFinishedSemaphores[currentFrame]};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, mInFlightFence) != VK_SUCCESS) {
if (vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, mInFlightFences[currentFrame]) != VK_SUCCESS) {
throw std::runtime_error("Could not submit Vulkan command buffer!");
}
@ -268,7 +285,17 @@ void VulkanApp::drawFrame() {
presentInfo.pImageIndices = &imageIndex;
vkQueuePresentKHR(mPresentQueue, &presentInfo);
result = vkQueuePresentKHR(mPresentQueue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || mResized) {
mResized = false;
recreateSwapChain();
return;
} else if (result != VK_SUCCESS) {
throw std::runtime_error("Could not present Vulkan swap chain image!");
}
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
void VulkanApp::init() {
@ -276,18 +303,13 @@ void VulkanApp::init() {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
//SDL_Vulkan_LoadLibrary(nullptr);
#ifndef NDEBUG
// I really hate GNOME
SDL_VideoInit("x11");
#endif
// Create the window
mWin = SDL_CreateWindow("Vulkan+SDL2 Application",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
mWidth,
mHeight,
SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN
SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE
);
if(mWin == nullptr) {
@ -355,8 +377,12 @@ void VulkanApp::createInstance() {
debugCreateInfo = {};
debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debugCreateInfo.pfnUserCallback = debugCallback;
createInfo.pNext = reinterpret_cast<VkDebugUtilsMessengerCreateInfoEXT*>(&debugCreateInfo);
@ -415,12 +441,17 @@ void VulkanApp::createLogicalDevice() {
}
VkPhysicalDeviceFeatures deviceFeatures{};
vkGetPhysicalDeviceFeatures(mPhysicalDevice, &deviceFeatures);
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
@ -493,6 +524,28 @@ void VulkanApp::createSwapChain() {
mSwapChainExtent = extent;
}
void VulkanApp::recreateSwapChain() {
vkDeviceWaitIdle(mLogicalDevice);
cleanupSwapChain();
createSwapChain();
createImageViews();
createFramebuffers();
}
void VulkanApp::cleanupSwapChain() {
for (VkFramebuffer framebuffer : mSwapChainFramebuffers) {
vkDestroyFramebuffer(mLogicalDevice, framebuffer, nullptr);
}
for (VkImageView imageView : mSwapChainImageViews) {
vkDestroyImageView(mLogicalDevice, imageView, nullptr);
}
vkDestroySwapchainKHR(mLogicalDevice, mSwapChain, nullptr);
}
void VulkanApp::createImageViews() {
mSwapChainImageViews.resize(mSwapChainImages.size());
@ -652,7 +705,12 @@ void VulkanApp::createGraphicsPipeline() {
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
if (vkCreateGraphicsPipelines(mLogicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &mGraphicsPipeline) != VK_SUCCESS) {
if (vkCreateGraphicsPipelines(mLogicalDevice,
VK_NULL_HANDLE,
1,
&pipelineInfo,
nullptr,
&mGraphicsPipeline) != VK_SUCCESS) {
throw std::runtime_error("Could not create Vulkan graphics pipeline!");
}
@ -699,18 +757,24 @@ void VulkanApp::createCommandPool() {
}
void VulkanApp::createCommandBuffer() {
mCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = mCommandPool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
allocInfo.commandBufferCount = static_cast<uint32_t>(mCommandBuffers.size());
if (vkAllocateCommandBuffers(mLogicalDevice, &allocInfo, &mCommandBuffer) != VK_SUCCESS) {
if (vkAllocateCommandBuffers(mLogicalDevice, &allocInfo, mCommandBuffers.data()) != VK_SUCCESS) {
throw std::runtime_error("Could not allocate Vulkan command buffers!");
}
}
void VulkanApp::createSyncObjects() {
mImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
mRenderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
mInFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
@ -718,14 +782,15 @@ void VulkanApp::createSyncObjects() {
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
VkResult imageAvailableResult = vkCreateSemaphore(mLogicalDevice, &semaphoreInfo, nullptr, &mImageAvailableSemaphore);
VkResult renderFinishedResult = vkCreateSemaphore(mLogicalDevice, &semaphoreInfo, nullptr, &mRenderFinishedSemaphore);
VkResult inFlightResult = vkCreateFence(mLogicalDevice, &fenceInfo, nullptr, &mInFlightFence);
for(size_t i = 0;i<MAX_FRAMES_IN_FLIGHT;i++) {
VkResult imageAvailableResult = vkCreateSemaphore(mLogicalDevice, &semaphoreInfo, nullptr, &mImageAvailableSemaphores[i]);
VkResult renderFinishedResult = vkCreateSemaphore(mLogicalDevice, &semaphoreInfo, nullptr, &mRenderFinishedSemaphores[i]);
VkResult inFlightResult = vkCreateFence(mLogicalDevice, &fenceInfo, nullptr, &mInFlightFences[i]);
if(imageAvailableResult != VK_SUCCESS || renderFinishedResult != VK_SUCCESS || inFlightResult != VK_SUCCESS) {
throw std::runtime_error("Could not create needed semaphores or other memory handling objects for Vulkan!");
if(imageAvailableResult != VK_SUCCESS || renderFinishedResult != VK_SUCCESS || inFlightResult != VK_SUCCESS) {
throw std::runtime_error("Could not create needed semaphores or other memory handling objects for Vulkan!");
}
}
}
bool VulkanApp::checkDeviceExtensionSupport(VkPhysicalDevice device) {
@ -775,8 +840,12 @@ void VulkanApp::setupDebugMessenger() {
VkDebugUtilsMessengerCreateInfoEXT createInfo;
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
if (CreateDebugUtilsMessengerEXT(mInstance, &createInfo, nullptr, &mDebugMessenger) != VK_SUCCESS) {
@ -796,18 +865,37 @@ void VulkanApp::loop() {
// Main loop
while(mActive) {
while(SDL_PollEvent(&mEvent))
if(mEvent.type == SDL_QUIT) {
while(SDL_PollEvent(&mEvent)) {
switch (mEvent.type) {
case SDL_QUIT:
mActive = false;
break;
case SDL_WINDOWEVENT:
switch(mEvent.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
mResized = true;
break;
case SDL_WINDOWEVENT_MINIMIZED:
mMinimized = true;
break;
case SDL_WINDOWEVENT_RESTORED:
mMinimized = false;
break;
default:
break;
}
break;
default:
break;
}
}
//SDL_SetRenderDrawColor(rend, 255, 0, 0, 255);
//SDL_RenderClear(rend);
//SDL_RenderPresent(rend);
drawFrame();
SDL_ShowWindow(mWin);
if(!mMinimized) drawFrame();
}
vkDeviceWaitIdle(mLogicalDevice);
@ -815,30 +903,27 @@ void VulkanApp::loop() {
}
void VulkanApp::cleanup() {
vkDestroySemaphore(mLogicalDevice, mImageAvailableSemaphore, nullptr);
vkDestroySemaphore(mLogicalDevice, mRenderFinishedSemaphore, nullptr);
vkDestroyFence(mLogicalDevice, mInFlightFence, nullptr);
vkDestroyCommandPool(mLogicalDevice, mCommandPool, nullptr);
for (VkFramebuffer framebuffer : mSwapChainFramebuffers) {
vkDestroyFramebuffer(mLogicalDevice, framebuffer, nullptr);
}
cleanupSwapChain();
vkDestroyPipeline(mLogicalDevice, mGraphicsPipeline, nullptr);
vkDestroyPipelineLayout(mLogicalDevice, mPipelineLayout, nullptr);
vkDestroyRenderPass(mLogicalDevice, mRenderPass, nullptr);
for (VkImageView imageView : mSwapChainImageViews) {
vkDestroyImageView(mLogicalDevice, imageView, nullptr);
for(size_t i=0;i<MAX_FRAMES_IN_FLIGHT;i++) {
vkDestroySemaphore(mLogicalDevice, mImageAvailableSemaphores[i], nullptr);
vkDestroySemaphore(mLogicalDevice, mRenderFinishedSemaphores[i], nullptr);
vkDestroyFence(mLogicalDevice, mInFlightFences[i], nullptr);
}
vkDestroySwapchainKHR(mLogicalDevice, mSwapChain, nullptr);
vkDestroyCommandPool(mLogicalDevice, mCommandPool, nullptr);
vkDestroyDevice(mLogicalDevice, nullptr);
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr);
}
vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
vkDestroyInstance(mInstance, nullptr);