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.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{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &configInfo.inputAssemblyInfo;
pipelineInfo.pViewportState = &viewportInfo;
pipelineInfo.pViewportState = &configInfo.viewportInfo;
pipelineInfo.pRasterizationState = &configInfo.rasterizationInfo;
pipelineInfo.pMultisampleState = &configInfo.multisampleInfo;
pipelineInfo.pColorBlendState = &configInfo.colorBlendInfo;
pipelineInfo.pDepthStencilState = &configInfo.depthStencilInfo;
pipelineInfo.pDynamicState = nullptr;
pipelineInfo.pDynamicState = &configInfo.dynamicStateInfo;
pipelineInfo.layout = configInfo.pipelineLayout;
pipelineInfo.renderPass = configInfo.renderPass;
@@ -123,22 +116,16 @@ namespace cve {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
}
PipelineConfigInfo CvePipeline::defaultPipelineConfigInfo(uint32_t width, uint32_t height) {
PipelineConfigInfo configInfo{};
void CvePipeline::defaultPipelineConfigInfo(PipelineConfigInfo& configInfo) {
configInfo.inputAssemblyInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
configInfo.inputAssemblyInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
configInfo.inputAssemblyInfo.primitiveRestartEnable = VK_FALSE;
configInfo.viewport.x = 0.0f;
configInfo.viewport.y = 0.0f;
configInfo.viewport.width = static_cast<float>(width);
configInfo.viewport.height = static_cast<float>(height);
configInfo.viewport.minDepth = 0.0f;
configInfo.viewport.maxDepth = 1.0f;
configInfo.scissor.offset = {0, 0};
configInfo.scissor.extent = {width, height};
configInfo.viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
configInfo.viewportInfo.viewportCount = 1;
configInfo.viewportInfo.pViewports = nullptr;
configInfo.viewportInfo.scissorCount = 1;
configInfo.viewportInfo.pScissors = nullptr;
configInfo.rasterizationInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
configInfo.rasterizationInfo.depthClampEnable = VK_FALSE;
@@ -192,6 +179,11 @@ namespace cve {
configInfo.depthStencilInfo.front = {}; // 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 {
struct PipelineConfigInfo {
VkViewport viewport;
VkRect2D scissor;
PipelineConfigInfo(const PipelineConfigInfo&) = delete;
PipelineConfigInfo& operator=(const PipelineConfigInfo&) = delete;
VkPipelineViewportStateCreateInfo viewportInfo;
VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
VkPipelineRasterizationStateCreateInfo rasterizationInfo;
VkPipelineMultisampleStateCreateInfo multisampleInfo;
VkPipelineColorBlendAttachmentState colorBlendAttachment;
VkPipelineColorBlendStateCreateInfo colorBlendInfo;
VkPipelineDepthStencilStateCreateInfo depthStencilInfo;
std::vector<VkDynamicState> dynamicStateEnables;
VkPipelineDynamicStateCreateInfo dynamicStateInfo;
VkPipelineLayout pipelineLayout = nullptr;
VkRenderPass renderPass = nullptr;
uint32_t subpass = 0;
@@ -29,7 +33,7 @@ namespace cve {
CvePipeline& operator=(const CvePipeline&) = delete;
void bind(VkCommandBuffer commandBuffer);
static PipelineConfigInfo defaultPipelineConfigInfo(uint32_t width, uint32_t height);
static void defaultPipelineConfigInfo(PipelineConfigInfo& configInfo);
private:
static std::vector<char> readFile(const std::string& filepath);

View File

@@ -13,6 +13,18 @@ namespace cve {
CveSwapChain::CveSwapChain(CveDevice &deviceRef, VkExtent2D 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();
createImageViews();
createRenderPass();
@@ -162,7 +174,7 @@ void CveSwapChain::createSwapChain() {
createInfo.presentMode = presentMode;
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) {
throw std::runtime_error("failed to create swap chain!");

View File

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

View File

@@ -20,6 +20,8 @@ namespace cve {
window = glfwCreateWindow(width, height,
windowName.c_str(), nullptr, nullptr);
glfwSetWindowUserPointer(window, this);
glfwSetFramebufferSizeCallback(window, framebufferResizedCallback);
}
void CveWindow::createWindowSurface(VkInstance instance, VkSurfaceKHR *surface) {
@@ -27,4 +29,11 @@ namespace cve {
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); };
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);
private:
static void framebufferResizedCallback(GLFWwindow *window, int width, int height);
void initWindow();
const int width;
const int height;
int width;
int height;
bool framebufferResized = false;
std::string windowName;
GLFWwindow *window;

View File

@@ -7,7 +7,7 @@ namespace cve {
FirstApp::FirstApp() {
loadModels();
createPipelineLayout();
createPipeline();
recreateSwapChain();
createCommandBuffers();
}
@@ -48,8 +48,12 @@ namespace cve {
}
void FirstApp::createPipeline() {
auto pipelineConfig = CvePipeline::defaultPipelineConfigInfo(cveSwapChain.width(), cveSwapChain.height());
pipelineConfig.renderPass = cveSwapChain.getRenderPass();
assert(cveSwapChain != nullptr && "Cannot create pipeline before swap chain");
assert(pipelineLayout != nullptr && "Cannot create pipeline before pipeline layout");
PipelineConfigInfo pipelineConfig{};
CvePipeline::defaultPipelineConfigInfo(pipelineConfig);
pipelineConfig.renderPass = cveSwapChain->getRenderPass();
pipelineConfig.pipelineLayout = pipelineLayout;
cvePipeline = std::make_unique<CvePipeline>(
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() {
commandBuffers.resize(cveSwapChain.imageCount());
commandBuffers.resize(cveSwapChain->imageCount());
VkCommandBufferAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
@@ -72,50 +99,78 @@ namespace cve {
VK_SUCCESS) {
throw std::runtime_error("Failed to allocate command buffers");
}
}
for (int i=0; i < commandBuffers.size(); i++) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
void FirstApp::freeCommandBuffers() {
vkFreeCommandBuffers(cveDevice.device(), cveDevice.getCommandPool(),
static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
commandBuffers.clear();
}
if (vkBeginCommandBuffer(commandBuffers[i], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("Failed to begin command buffer recording");
}
void FirstApp::recordCommandBuffer(int imageIndex) {
VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = cveSwapChain.getRenderPass();
renderPassInfo.framebuffer = cveSwapChain.getFrameBuffer(i);
if (vkBeginCommandBuffer(commandBuffers[imageIndex], &beginInfo) != VK_SUCCESS) {
throw std::runtime_error("Failed to begin command buffer recording");
}
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = cveSwapChain.getSwapChainExtent();
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = cveSwapChain->getRenderPass();
renderPassInfo.framebuffer = cveSwapChain->getFrameBuffer(imageIndex);
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f};
clearValues[1].depthStencil = {1.0f, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
renderPassInfo.renderArea.offset = {0, 0};
renderPassInfo.renderArea.extent = cveSwapChain->getSwapChainExtent();
vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
std::array<VkClearValue, 2> clearValues{};
clearValues[0].color = {0.1f, 0.1f, 0.1f, 1.0f};
clearValues[1].depthStencil = {1.0f, 0};
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues = clearValues.data();
cvePipeline->bind(commandBuffers[i]);
cveModel->bind(commandBuffers[i]);
cveModel->draw(commandBuffers[i]);
vkCmdBeginRenderPass(commandBuffers[imageIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdEndRenderPass(commandBuffers[i]);
if (vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS) {
throw std::runtime_error("Failed to record command buffer!");
}
VkViewport viewport{};
viewport.x = 0.0f;
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);
cvePipeline->bind(commandBuffers[imageIndex]);
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!");
}
}
void FirstApp::drawFrame() {
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) {
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) {
throw std::runtime_error("Failed to present swap chain image!");
}

View File

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