题外话
以往学习也好工作也好,在处理三维的时候都是借助现成的开源库搭建对应的三维场景,限于时间因此对这些开源库底层的细节处理鲜有关注,此次借助学习vulkan的机会做一个简易的显示应用并记录下整个过程中的些许细节片段。
开发环境
开发环境:VS2017+Vulkan1.3.236.0+Qt5.14.2+ObjectArx2017。
关于环境搭建部分在这里不在展开讨论,需要说明的是选择VS2017是因为后续考虑结合Objectarx2019/2020来做些简单的小工具,而Objectarx2019/2020对应的开发环境为VS2017。另外关于Qt的版本,在5.10之后的版本才支持vulkan,并且Objectarx中使用的界面是MFC,为了将Qt嵌入MFC中需要对Qt进行框架迁移,这个在后续的与Objectarx结合时再做详细记录。
向Qt中添加Vulkan
Qt自带的处理类
Qt本身实现有相应的类来支持Vulkan,例如QVulkanInstance、QVulkanWindow、QVulkanWindowRenderer、QVulkanWindowPrivate等,具体的实例可以参照Qt目录Examples\Qt-5.14.2\vulkan下的具体代码。
关于自身对Qt-vulkan相关类的选择
由于Qt自身实现的QVulkanWindowRenderer及QVulkanWindowPrivate实现的仅为基本的渲染功能,如需要更复杂的渲染需求时仍需要对其进行改造,因此除了使用了QVulkanInstance外其余的就直接选择自己实现好方便后续修改。
实现在Qt中绘制Vulkan渲染结果
Qt界面中实现vulkan的流程同OpenGL相似,可参考QVulkanInstance类的帮助文档,但文档仅含显示的主要流程,窗口大小改变、窗口最小化及关闭窗口等处理需要自行补充,以下是整个过程的简要记录:
- 初始化vulkan实例及设置QWindow的surface和instance
实现一个QWindow的派生类,此次练习对应的派生类为MLINView3D,在派生类的初始化函数中主要做两件事,一是设置QWindow支持vulkan,二是创建一个vulkan实例对象,参考代码如下
//! 描述:验证回调函数
bool DebugFilter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object,
size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage)
{
std::cout << pMessage << std::endl;
return false;
}
//---------------------------------------------------------------------------------------
setSurfaceType(QSurface::VulkanSurface);//设置窗口支持vulkan
m_VkIns.setLayers(QByteArrayList()//添加需要开启的验证,当发布程序时可删除
<< "VK_LAYER_LUNARG_parameter_validation"
<< "VK_LAYER_LUNARG_object_tracker"
<< "VK_LAYER_LUNARG_core_validation"
<< "VK_LAYER_LUNARG_image"
<< "VK_LAYER_LUNARG_swapchain"
<< "VK_LAYER_KHRONOS_validation");
m_VkIns.installDebugOutputFilter(DebugFilter);//开启验证层并设置对应的回调函数
if (!m_VkIns.create())//创建对应的vulkan实例
{
qDebug() << "Failed to create instance";
}
setVulkanInstance(&m_VkIns);//设置QWindow支持的vulkan实例`
- 在QWindow的exposeEvent()中实现对vulkan渲染资源的初始化,相关参考代码如下:
if (isExposed())
{
if (m_State == STATE0)//标记初始状态,表示没有任何vulkan实现
{
m_State = STATE1;
//初始化vulkan资源,如Swapchain、framebuffer、pipeline、renderpass等所有的渲染资源
m_pVulkanPrivate->initVulkanInstance(m_VkIns.vkInstance());
m_pVulkanPrivate->initShapeResource();
}
}
- 在QWindow的resizeEvent()事件中实现对交换链的重建:
每当屏幕大小变化时需要重建交换链中涉及的资源,其主要为与屏幕大小相关的图片、视图、交换链、帧缓冲等。
void MLINView3D::resizeEvent(QResizeEvent* pEvent)
{
if (m_State == STATE0)
{
return;
}
QSize winSize = this->size();
m_pVulkanPrivate->recreateSwapChain();//重建交换链相关资源
m_Camera.SetOrthoProjection(this->width(), this->height(), m_Camera.m_ProjBoxSize.x(),
m_Camera.m_ProjBoxSize.y(), m_Camera.m_NearPlane, m_Camera.m_FarPlane);
m_pVulkanPrivate->updateProjectionUniformBufferData();
m_pVulkanPrivate->drawFrame();//更新屏幕
}
- 实现QWindow::event()事件
bool MLINView3D::event(QEvent* pEvent)
{
switch (pEvent->type())
{
case QEvent::UpdateRequest: //处理刷新事件
m_pVulkanPrivate->drawFrame();//对当前场景进行渲染
break;
case QEvent::Hide: //处理隐藏事件,如最小化
m_pVulkanPrivate->hideOrShow(false);//停止渲染
break;
case QEvent::Show: //处理显示事件
if (m_pVulkanPrivate->canDraw())
{
m_pVulkanPrivate->hideOrShow(true);
requestUpdate();//请求绘制下一帧
}
break;
case QEvent::PlatformSurface:
if (static_cast<QPlatformSurfaceEvent *>(pEvent)->surfaceEventType() ==
QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)//处理窗口关闭事件
{
m_pVulkanPrivate->cleanup();//清楚vulkan资源
m_State = STATE0;
}
break;
}
return QWindow::event(pEvent);
}
- 将MLINView3D作为widget组件添加到相应的界面位置,
MLINVK3D::MLINVK3D(QWidget *parent) : QMainWindow(parent)
{
ui.setupUi(this);
QSplitter* pMainSplitter = new QSplitter(Qt::Horizontal, nullptr);
ui.horizontalLayout->addWidget(pMainSplitter);
mNavigation = new NavigationBar(pMainSplitter);//场景节点窗口
QSplitter *pRighterLitter = new QSplitter(Qt::Vertical, pMainSplitter);
mView3D = new MLINView3D();//创建一个支持vulkan的QWindow对象
QWidget* pView = QWidget::createWindowContainer(mView3D);//将QWindow对象生成对应的Widget
pRighterLitter->addWidget(pView);//将支持vulkan的组件添加到窗口的图像显示区域
mCmd = new CommandBar();//命令行窗口
pRighterLitter->addWidget(mCmd);
pMainSplitter->setStretchFactor(0, 2);
pMainSplitter->setStretchFactor(1, 8);
pRighterLitter->setStretchFactor(0, 8);
pRighterLitter->setStretchFactor(1, 2);
}