Dynamic window resizing

This commit is contained in:
2026-02-20 21:25:33 +00:00
parent e4124c0aaf
commit 11783743de
9 changed files with 143 additions and 60 deletions

Binary file not shown.

View File

@@ -75,25 +75,18 @@ namespace cve {
vertexInputInfo.pVertexBindingDescriptions = bindingDescriptions.data(); vertexInputInfo.pVertexBindingDescriptions = bindingDescriptions.data();
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data();
VkPipelineViewportStateCreateInfo viewportInfo{};
viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportInfo.viewportCount = 1;
viewportInfo.pViewports = &configInfo.viewport;
viewportInfo.scissorCount = 1;
viewportInfo.pScissors = &configInfo.scissor;
VkGraphicsPipelineCreateInfo pipelineInfo{}; VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2; pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages; pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo; pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
pipelineInfo.pViewportState = &viewportInfo; pipelineInfo.pViewportState = &configInfo.viewportInfo;
pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo; pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo;
pipelineInfo.pMultisampleState = &configInfo.multisampleInfo; pipelineInfo.pMultisampleState = &configInfo.multisampleInfo;
pipelineInfo.pColorBlendState = &configInfo.colorBlendInfo; pipelineInfo.pColorBlendState = &configInfo.colorBlendInfo;
pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo; pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo;
pipelineInfo.pDynamicState = nullptr; pipelineInfo.pDynamicState = &configInfo.dynamicStateInfo;
pipelineInfo.layout = configInfo.pipelineLayout; pipelineInfo.layout = configInfo.pipelineLayout;
pipelineInfo.renderPass = configInfo.renderPass; pipelineInfo.renderPass = configInfo.renderPass;
@@ -123,22 +116,16 @@ namespace cve {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
} }
PipelineConfigInfo CvePipeline::defaultPipelineConfigInfo(uint32_t width, uint32_t height) { void CvePipeline::defaultPipelineConfigInfo(PipelineConfigInfo& configInfo) {
PipelineConfigInfo configInfo{};
configInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; configInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
configInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; configInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
configInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE; configInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
configInfo.viewport.x = 0.0f; configInfo.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
configInfo.viewport.y = 0.0f; configInfo.viewportInfo.viewportCount = 1;
configInfo.viewport.width = static_cast<float>(width); configInfo.viewportInfo.pViewports = nullptr;
configInfo.viewport.height = static_cast<float>(height); configInfo.viewportInfo.scissorCount = 1;
configInfo.viewport.minDepth = 0.0f; configInfo.viewportInfo.pScissors = nullptr;
configInfo.viewport.maxDepth = 1.0f;
configInfo.scissor.offset = {0, 0};
configInfo.scissor.extent = {width, height};
configInfo.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; configInfo.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
configInfo.rasterizationInfo.depthClampEnable = VK_FALSE; configInfo.rasterizationInfo.depthClampEnable = VK_FALSE;
@@ -192,6 +179,11 @@ namespace cve {
configInfo.depthStencilInfo.front = {}; // Optional configInfo.depthStencilInfo.front = {}; // Optional
configInfo.depthStencilInfo.back = {}; // Optional configInfo.depthStencilInfo.back = {}; // Optional
return configInfo; configInfo.dynamicStateEnables = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
configInfo.dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
configInfo.dynamicStateInfo.pDynamicStates = configInfo.dynamicStateEnables.data();
configInfo.dynamicStateInfo.dynamicStateCount =
static_cast<uint32_t>(configInfo.dynamicStateEnables.size());
configInfo.dynamicStateInfo.flags = 0;
} }
} }

View File

@@ -7,14 +7,18 @@
namespace cve { namespace cve {
struct PipelineConfigInfo { struct PipelineConfigInfo {
VkViewport viewport; PipelineConfigInfo(const PipelineConfigInfo&) = delete;
VkRect2D scissor; PipelineConfigInfo& operator=(const PipelineConfigInfo&) = delete;
VkPipelineViewportStateCreateInfo viewportInfo;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo; VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
VkPipelineRasterizationStateCreateInfo rasterizationInfo; VkPipelineRasterizationStateCreateInfo rasterizationInfo;
VkPipelineMultisampleStateCreateInfo multisampleInfo; VkPipelineMultisampleStateCreateInfo multisampleInfo;
VkPipelineColorBlendAttachmentState colorBlendAttachment; VkPipelineColorBlendAttachmentState colorBlendAttachment;
VkPipelineColorBlendStateCreateInfo colorBlendInfo; VkPipelineColorBlendStateCreateInfo colorBlendInfo;
VkPipelineDepthStencilStateCreateInfo depthStencilInfo; VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
std::vector<VkDynamicState> dynamicStateEnables;
VkPipelineDynamicStateCreateInfo dynamicStateInfo;
VkPipelineLayout pipelineLayout = nullptr; VkPipelineLayout pipelineLayout = nullptr;
VkRenderPass renderPass = nullptr; VkRenderPass renderPass = nullptr;
uint32_t subpass = 0; uint32_t subpass = 0;
@@ -29,7 +33,7 @@ namespace cve {
CvePipeline& operator=(const CvePipeline&) = delete; CvePipeline& operator=(const CvePipeline&) = delete;
void bind(VkCommandBuffer commandBuffer); void bind(VkCommandBuffer commandBuffer);
static PipelineConfigInfo defaultPipelineConfigInfo(uint32_t width, uint32_t height); static void defaultPipelineConfigInfo(PipelineConfigInfo& configInfo);
private: private:
static std::vector<char> readFile(const std::string& filepath); static std::vector<char> readFile(const std::string& filepath);

View File

@@ -13,6 +13,18 @@ namespace cve {
CveSwapChain::CveSwapChain(CveDevice &deviceRef, VkExtent2D extent) CveSwapChain::CveSwapChain(CveDevice &deviceRef, VkExtent2D extent)
: device{deviceRef}, windowExtent{extent} { : device{deviceRef}, windowExtent{extent} {
init();
}
CveSwapChain::CveSwapChain(CveDevice &deviceRef, VkExtent2D extent, std::shared_ptr<CveSwapChain> previous)
: device{deviceRef}, windowExtent{extent}, oldSwapChain{previous} {
init();
// Clean up old swap chain
oldSwapChain = nullptr;
}
void CveSwapChain::init() {
createSwapChain(); createSwapChain();
createImageViews(); createImageViews();
createRenderPass(); createRenderPass();
@@ -162,7 +174,7 @@ void CveSwapChain::createSwapChain() {
createInfo.presentMode = presentMode; createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE; createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE; createInfo.oldSwapchain = oldSwapChain ? oldSwapChain->swapChain : VK_NULL_HANDLE;
if (vkCreateSwapchainKHR(device.device(), &createInfo, nullptr, &swapChain) != VK_SUCCESS) { if (vkCreateSwapchainKHR(device.device(), &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("failed to create swap chain!"); throw std::runtime_error("failed to create swap chain!");

View File

@@ -6,6 +6,7 @@
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
// std lib headers // std lib headers
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -16,6 +17,7 @@ class CveSwapChain {
static constexpr int MAX_FRAMES_IN_FLIGHT = 2; static constexpr int MAX_FRAMES_IN_FLIGHT = 2;
CveSwapChain(CveDevice &deviceRef, VkExtent2D windowExtent); CveSwapChain(CveDevice &deviceRef, VkExtent2D windowExtent);
CveSwapChain(CveDevice &deviceRef, VkExtent2D windowExtent, std::shared_ptr<CveSwapChain> previous);
~CveSwapChain(); ~CveSwapChain();
CveSwapChain(const CveSwapChain &) = delete; CveSwapChain(const CveSwapChain &) = delete;
@@ -39,6 +41,7 @@ class CveSwapChain {
VkResult submitCommandBuffers(const VkCommandBuffer *buffers, uint32_t *imageIndex); VkResult submitCommandBuffers(const VkCommandBuffer *buffers, uint32_t *imageIndex);
private: private:
void init();
void createSwapChain(); void createSwapChain();
void createImageViews(); void createImageViews();
void createDepthResources(); void createDepthResources();
@@ -69,6 +72,7 @@ class CveSwapChain {
VkExtent2D windowExtent; VkExtent2D windowExtent;
VkSwapchainKHR swapChain; VkSwapchainKHR swapChain;
std::shared_ptr<CveSwapChain> oldSwapChain;
std::vector<VkSemaphore> imageAvailableSemaphores; std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores; std::vector<VkSemaphore> renderFinishedSemaphores;

View File

@@ -20,6 +20,8 @@ namespace cve {
window = glfwCreateWindow(width, height, window = glfwCreateWindow(width, height,
windowName.c_str(), nullptr, nullptr); windowName.c_str(), nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, framebufferResizedCallback);
} }
void CveWindow::createWindowSurface(VkInstance instance, VkSurfaceKHR *surface) { void CveWindow::createWindowSurface(VkInstance instance, VkSurfaceKHR *surface) {
@@ -27,4 +29,11 @@ namespace cve {
throw std::runtime_error("Failed to create window surface"); throw std::runtime_error("Failed to create window surface");
} }
} }
void CveWindow::framebufferResizedCallback(GLFWwindow *window, int width, int height) {
auto cveWindow = reinterpret_cast<CveWindow *>(glfwGetWindowUserPointer(window));
cveWindow->framebufferResized = true;
cveWindow->width = width;
cveWindow->height = height;
}
} }

View File

@@ -16,13 +16,17 @@ class CveWindow {
bool shouldClose() { return glfwWindowShouldClose(window); }; bool shouldClose() { return glfwWindowShouldClose(window); };
VkExtent2D getExtent() { return { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; }; VkExtent2D getExtent() { return { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; };
bool wasWindowResized() { return framebufferResized; };
void resetWindowResizedFlag() { framebufferResized = false; };
void createWindowSurface(VkInstance instance, VkSurfaceKHR *surface); void createWindowSurface(VkInstance instance, VkSurfaceKHR *surface);
private: private:
static void framebufferResizedCallback(GLFWwindow *window, int width, int height);
void initWindow(); void initWindow();
const int width; int width;
const int height; int height;
bool framebufferResized = false;
std::string windowName; std::string windowName;
GLFWwindow *window; GLFWwindow *window;

View File

@@ -7,7 +7,7 @@ namespace cve {
FirstApp::FirstApp() { FirstApp::FirstApp() {
loadModels(); loadModels();
createPipelineLayout(); createPipelineLayout();
createPipeline(); recreateSwapChain();
createCommandBuffers(); createCommandBuffers();
} }
@@ -48,8 +48,12 @@ namespace cve {
} }
void FirstApp::createPipeline() { void FirstApp::createPipeline() {
auto pipelineConfig = CvePipeline::defaultPipelineConfigInfo(cveSwapChain.width(), cveSwapChain.height()); assert(cveSwapChain != nullptr && "Cannot create pipeline before swap chain");
pipelineConfig.renderPass = cveSwapChain.getRenderPass(); assert(pipelineLayout != nullptr && "Cannot create pipeline before pipeline layout");
PipelineConfigInfo pipelineConfig{};
CvePipeline::defaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.renderPass = cveSwapChain->getRenderPass();
pipelineConfig.pipelineLayout = pipelineLayout; pipelineConfig.pipelineLayout = pipelineLayout;
cvePipeline = std::make_unique<CvePipeline>( cvePipeline = std::make_unique<CvePipeline>(
cveDevice, cveDevice,
@@ -59,8 +63,31 @@ namespace cve {
); );
} }
void FirstApp::recreateSwapChain() {
auto extent = cveWindow.getExtent();
while (extent.width == 0 || extent.height == 0) {
extent = cveWindow.getExtent();
glfwWaitEvents();
}
vkDeviceWaitIdle(cveDevice.device());
if (cveSwapChain == nullptr) {
cveSwapChain = std::make_unique<CveSwapChain>(cveDevice, extent);
} else {
cveSwapChain = std::make_unique<CveSwapChain>(cveDevice, extent, std::move(cveSwapChain));
if (cveSwapChain->imageCount() != commandBuffers.size()) {
freeCommandBuffers();
createCommandBuffers();
}
}
// Check if render pass is compatible
createPipeline();
}
void FirstApp::createCommandBuffers() { void FirstApp::createCommandBuffers() {
commandBuffers.resize(cveSwapChain.imageCount()); commandBuffers.resize(cveSwapChain->imageCount());
VkCommandBufferAllocateInfo allocInfo{}; VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
@@ -72,22 +99,29 @@ namespace cve {
VK_SUCCESS) { VK_SUCCESS) {
throw std::runtime_error("Failed to allocate command buffers"); throw std::runtime_error("Failed to allocate command buffers");
} }
}
for (int i=0; i < commandBuffers.size(); i++) { void FirstApp::freeCommandBuffers() {
vkFreeCommandBuffers(cveDevice.device(), cveDevice.getCommandPool(),
static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
commandBuffers.clear();
}
void FirstApp::recordCommandBuffer(int imageIndex) {
VkCommandBufferBeginInfo beginInfo{}; VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) { if (vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("Failed to begin command buffer recording"); throw std::runtime_error("Failed to begin command buffer recording");
} }
VkRenderPassBeginInfo renderPassInfo{}; VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = cveSwapChain.getRenderPass(); renderPassInfo.renderPass = cveSwapChain->getRenderPass();
renderPassInfo.framebuffer = cveSwapChain.getFrameBuffer(i); renderPassInfo.framebuffer = cveSwapChain->getFrameBuffer(imageIndex);
renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = cveSwapChain.getSwapChainExtent(); renderPassInfo.renderArea.extent = cveSwapChain->getSwapChainExtent();
std::array<VkClearValue, 2> clearValues{}; std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f}; clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f};
@@ -95,27 +129,48 @@ namespace cve {
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size()); renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data(); renderPassInfo.pClearValues = clearValues.data();
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
cvePipeline->bind(commandBuffers[i]); VkViewport viewport{};
cveModel->bind(commandBuffers[i]); viewport.x = 0.0f;
cveModel->draw(commandBuffers[i]); viewport.y = 0.0f;
viewport.width = static_cast<float>(cveSwapChain->getSwapChainExtent().width);
viewport.height = static_cast<float>(cveSwapChain->getSwapChainExtent().height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor{{0, 0}, cveSwapChain->getSwapChainExtent()};
vkCmdSetViewport(commandBuffers[imageIndex], 0, 1, &viewport);
vkCmdSetScissor(commandBuffers[imageIndex], 0, 1, &scissor);
vkCmdEndRenderPass(commandBuffers[i]); cvePipeline->bind(commandBuffers[imageIndex]);
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) { cveModel->bind(commandBuffers[imageIndex]);
cveModel->draw(commandBuffers[imageIndex]);
vkCmdEndRenderPass(commandBuffers[imageIndex]);
if (vkEndCommandBuffer(commandBuffers[imageIndex]) != VK_SUCCESS) {
throw std::runtime_error("Failed to record command buffer!"); throw std::runtime_error("Failed to record command buffer!");
} }
} }
}
void FirstApp::drawFrame() { void FirstApp::drawFrame() {
uint32_t imageIndex; uint32_t imageIndex;
auto result = cveSwapChain.acquireNextImage(&imageIndex); auto result = cveSwapChain->acquireNextImage(&imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
}
if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("Failed to acquire swap chain image!"); throw std::runtime_error("Failed to acquire swap chain image!");
} }
result = cveSwapChain.submitCommandBuffers(&commandBuffers[imageIndex], &imageIndex); recordCommandBuffer(imageIndex);
result = cveSwapChain->submitCommandBuffers(&commandBuffers[imageIndex], &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || cveWindow.wasWindowResized()) {
cveWindow.resetWindowResizedFlag();
recreateSwapChain();
return;
}
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to present swap chain image!"); throw std::runtime_error("Failed to present swap chain image!");
} }

View File

@@ -27,11 +27,14 @@ namespace cve {
void createPipelineLayout(); void createPipelineLayout();
void createPipeline(); void createPipeline();
void createCommandBuffers(); void createCommandBuffers();
void freeCommandBuffers();
void drawFrame(); void drawFrame();
void recreateSwapChain();
void recordCommandBuffer(int imageIndex);
CveWindow cveWindow{WIDTH, HEIGHT, "Hello Vulkan!"}; CveWindow cveWindow{WIDTH, HEIGHT, "Hello Vulkan!"};
CveDevice cveDevice{cveWindow}; CveDevice cveDevice{cveWindow};
CveSwapChain cveSwapChain{cveDevice, cveWindow.getExtent()}; std::unique_ptr<CveSwapChain> cveSwapChain;
std::unique_ptr<CvePipeline> cvePipeline; std::unique_ptr<CvePipeline> cvePipeline;
VkPipelineLayout pipelineLayout; VkPipelineLayout pipelineLayout;
std::vector<VkCommandBuffer> commandBuffers; std::vector<VkCommandBuffer> commandBuffers;