实现窗口大小改变时刷新图像:
示例代码:
//标记窗口大小是否发生改变:
bool framebufferResized = false;
//为静态函数才能将其用作回调函数
static void framebufferResizeCallback(GLFWwindow* window,int width ,
int height){
auto app =
reinterpret_cast<HelloTriangle*>(glfwGetWindowUserPointer(window));
app->framebufferResized = true;
}
///初始化glfw
void initWindow(){
glfwInit();//初始化glfw库
//显示阻止自动创建opengl上下文
glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
//禁止窗口大小改变
//glfwWindowHint(GLFW_RESIZABLE,GLFW_FALSE);
/**
glfwCreateWindow 函数:
前三个参数指定了要创建的窗口的宽度,高度和标题.
第四个参数用于指定在哪个显示器上打开窗口,
最后一个参数与 OpenGL 相关
*/
//创建窗口
window = glfwCreateWindow(WIDTH,HEIGHT,"vulakn",
nullptr,nullptr);
//存储在 GLFW 窗口相关的数据
glfwSetWindowUserPointer(window , this);
//窗口大小改变的回调函数
glfwSetFramebufferSizeCallback(window,framebufferResizeCallback);
}
//绘制图像--12
void drawFrame(){
/**
vkWaitForFences 函数可以用来等待一组栅栏 (fence) 中的一个或
全部栅栏 (fence) 发出信号。上面代码中我们对它使用的 VK_TRUE
参数用来指定它等待所有在数组中指定的栅栏 (fence)。我们现在只有
一个栅栏 (fence) 需要等待,所以不使用 VK_TRUE 也是可以的。和
vkAcquireNextImageKHR 函数一样,vkWaitForFences 函数也有一个超时
参数。和信号量不同,等待栅栏发出信号后,我们需要调用 vkResetFences
函数手动将栅栏 (fence) 重置为未发出信号的状态。
*/
//等待我们当前帧所使用的指令缓冲结束执行
vkWaitForFences(device,1,&inFlightFences[currentFrame],
VK_TRUE,std::numeric_limits<uint64_t>::max());
vkResetFences(device , 1 , &inFlightFences[currentFrame]);
uint32_t imageIndex;
/**
vkAcquireNextImageKHR参数:
1.使用的逻辑设备对象
2.我们要获取图像的交换链,
3.图像获取的超时时间,我们可以通过使用无符号 64 位整型所能表示的
最大整数来禁用图像获取超时
4,5.指定图像可用后通知的同步对象.可以指定一个信号量对象或栅栏对象,
或是同时指定信号量和栅栏对象进行同步操作。
在这里,我们指定了一个叫做 imageAvailableSemaphore 的信号量对象
6.用于输出可用的交换链图像的索引,我们使用这个索引来引用我们的
swapChainImages数组中的VkImage对象,并使用这一索引来提交对应的指令缓冲
*/
//从交换链获取一张图像
//交换链是一个扩展特性,所以与它相关的操作都会有 KHR 这一扩展后缀
VkResult result =vkAcquireNextImageKHR(device,swapChain,
std::numeric_limits<uint64_t>::max(),
imageAvailableSemaphores[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!");
}
//提交信息给指令队列
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
//这里需要写入颜色数据到图像,所以我们指定等待图像管线到达可以写入颜色附着的管线阶段
VkPipelineStageFlags waitStages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
/**
waitSemaphoreCount、pWaitSemaphores 和pWaitDstStageMask 成员变量用于指定
队列开始执行前需要等待的信号量,以及需要等待的管线阶段
*/
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
//waitStages 数组中的条目和 pWaitSemaphores 中相同索引的信号量相对应。
submitInfo.pWaitDstStageMask = waitStages;
//指定实际被提交执行的指令缓冲对象
//我们应该提交和我们刚刚获取的交换链图像相对应的指令缓冲对象
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
VkSemaphore signalSemaphores [ ] = {
renderFinishedSemaphores[currentFrame]};
//指定在指令缓冲执行结束后发出信号的信号量对象
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
/**
vkQueueSubmit 函数使用vkQueueSubmit结构体数组作为参数,可以同时大批量提交数.。
vkQueueSubmit 函数的最后一个参数是一个可选的栅栏对象,
可以用它同步提交的指令缓冲执行结束后要进行的操作。
在这里,我们使用信号量进行同步,没有使用它,将其设置为VK_NULL_HANDLE
*/
/**
* @brief vkResetFences
* 在重建交换链时,重置栅栏 (fence) 对象,
* 以防导致我们使用的栅栏 (fence) 处于我们不能确定得状态
*/
vkResetFences(device,1,&inFlightFences[currentFrame]);
//提交指令缓冲给图形指令队列
if(vkQueueSubmit(graphicsQueue,1,&submitInfo,
inFlightFences[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 [ ] = {swapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
/**
我们可以通过 pResults 成员变量获取每个交换链的呈现操作是否成功
的信息。在这里,由于我们只使用了一个交换链,可以直接使用呈现函数
的返回值来判断呈现操作是否成功
*/
presentInfo.pResults = nullptr;
/**
vkQueuePresentKHR函数返回的信息来判定交换链是否需要重建:
VK_ERROR_OUT_OF_DATE_KHR:交换链不能继续使用。通常发生在窗口大小改变后
VK_SUBOPTIMAL_KHR:交换链仍然可以使用,但表面属性已经不能准确匹配
*/
//请求交换链进行图像呈现操作
result = vkQueuePresentKHR( presentQueue , &presentInfo ) ;
if(result == VK_ERROR_OUT_OF_DATE_KHR ||
result == VK_SUBOPTIMAL_KHR || framebufferResized){
//交换链不完全匹配时也重建交换链
framebufferResized = false;
recreateSwapChain();
}else if(result != VK_SUCCESS){
throw std::runtime_error("failed to present swap chain image!");
}
/**
如果开启校验层后运行程序,观察应用程序的内存使用情况,
可以发现我们的应用程序的内存使用量一直在慢慢增加。这是由于我
们的 drawFrame 函数以很快地速度提交指令,但却没有在下一次指令
提交时检查上一次提交的指令是否已经执行结束。也就是说 CPU 提交
指令快过 GPU 对指令的处理速度,造成 GPU 需要处理的指令大量堆
积。更糟糕的是这种情况下,我们实际上对多个帧同时使用了相同的
imageAvailableSemaphore 和 renderFinishedSemaphore 信号量。
最简单的解决上面这一问题的方法是使用 vkQueueWaitIdle 函数来等
待上一次提交的指令结束执行,再提交下一帧的指令:
但这样做,是对 GPU 计算资源的大大浪费。图形管线可能大部分时
间都处于空闲状态.
*/
//等待一个特定指令队列结束执行
//vkQueueWaitIdle ( presentQueue ) ;
//更新currentFrame
currentFrame = (currentFrame+1) %MAX_FRAMES_IN_FLIGHT;
}
//重建交换链
void recreateSwapChain(){
//设置应用程序在窗口最小化后停止渲染,直到窗口重新可见时重建交换链
int width=0,height = 0;
while(width == 0 || height == 0){
glfwGetFramebufferSize(window,&width,&height);
glfwWaitEvents();
}
//等待设备处于空闲状态,避免在对象的使用过程中将其清除重建
vkDeviceWaitIdle(device);
cleanupSwapChain();//清除交换链相关
//重新创建了交换链
createSwapChain();
//图形视图是直接依赖于交换链图像的,所以也需要被重建
createImageViews();
//渲染流程依赖于交换链图像的格式,窗口大小改变不会引起使用的交换链图像格式改变
createRenderPass();
//视口和裁剪矩形在管线创建时被指定,窗口大小改变,这些设置也需要修改
//我们可以通过使用动态状态来设置视口和裁剪矩形来避免重建管线
createGraphicsPipeline();
//帧缓冲和指令缓冲直接依赖于交换链图像
createFramebuffers();
createCommandBuffers();
}
//清除交换链相关
void cleanupSwapChain(){
//销毁帧缓冲对象
for(auto framebuffer : swapChainFramebuffers){
vkDestroyFramebuffer(device,framebuffer,nullptr);
}
//清除分配的指令缓冲对象
vkFreeCommandBuffers(device,commandPool ,
static_cast<uint32_t>(commandBuffers.size()),commandBuffers.data());
//销毁管线对象
vkDestroyPipeline ( device , graphicsPipeline , nullptr );
//销毁管线布局对象
vkDestroyPipelineLayout ( device , pipelineLayout , nullptr);
//销毁渲染流程对象
vkDestroyRenderPass ( device , renderPass , nullptr );
//销毁图像视图
for(auto imageView : swapChainImageViews){
vkDestroyImageView(device,imageView,nullptr);
}
//销毁交换链对象,在逻辑设备被清除前调用
vkDestroySwapchainKHR(device,swapChain,nullptr);
}