Vulkan-官方示例解读-First triangle
前言
从头创建vulkan框架太过复杂,选择直接用官方的示例架构,从这里直接创建项目,第一步就是解读官方示例代码
一、Base
1、Base的基本内容
base的核心是对原本vulkan所用到的一些列代码进行封装,之后在同一个解决方案下直接创建项目,这样重新创建项目的时候会轻松得多。
2、Base的文件内容
- 文件内容:包括几何变换,基本减半鼠标操作,vulkan需要用到的实例,物理单元,交换链,模型加载的操作。
- VulkanExampleBase:VulkanExampleBase是整个项目所用到的基础类。它负责类基础vulkan结构包括命令池等对象的创建。
二、Triagle
1.VulkanExample类创建:
代码如下(示例):基于VulkanExampleBase类创建三角形用到的一系列东西。
基础变量内容包括:Vertex, Vertex buffer,Index buffer, Uniform buffer,ubo,pipeline layout管线布局,pipeline等
class VulkanExample : public VulkanExampleBase
{
public:
// Vertex layout used in this example
struct Vertex {
float position[3];
float color[3];
};
// Vertex buffer and attributes
struct {
VkDeviceMemory memory; // Handle to the device memory for this buffer
VkBuffer buffer; // Handle to the Vulkan buffer object that the memory is bound to
} vertices;
// Index buffer
struct {
VkDeviceMemory memory;
VkBuffer buffer;
uint32_t count;
} indices;
// Uniform buffer block object
struct {
VkDeviceMemory memory;
VkBuffer buffer;
VkDescriptorBufferInfo descriptor;
} uniformBufferVS;
struct {
glm::mat4 projectionMatrix;
glm::mat4 modelMatrix;
glm::mat4 viewMatrix;
} uboVS;
// The pipeline layout is used by a pipeline to access the descriptor sets
// It defines interface (without binding any actual data) between the shader stages used by the pipeline and the shader resources
// A pipeline layout can be shared among multiple pipelines as long as their interfaces match
VkPipelineLayout pipelineLayout;
// Pipelines (often called "pipeline state objects") are used to bake all states that affect a pipeline
// While in OpenGL every state can be changed at (almost) any time, Vulkan requires to layout the graphics (and compute) pipeline states upfront
// So for each combination of non-dynamic pipeline states you need a new pipeline (there are a few exceptions to this not discussed here)
// Even though this adds a new dimension of planning ahead, it's a great opportunity for performance optimizations by the driver
VkPipeline pipeline;
// The descriptor set layout describes the shader binding layout (without actually referencing descriptor)
// Like the pipeline layout it's pretty much a blueprint and can be used with different descriptor sets as long as their layout matches
VkDescriptorSetLayout descriptorSetLayout;
// The descriptor set stores the resources bound to the binding points in a shader
// It connects the binding points of the different shaders with the buffers and images used for those bindings
VkDescriptorSet descriptorSet;
// Synchronization primitives
// Synchronization is an important concept of Vulkan that OpenGL mostly hid away. Getting this right is crucial to using Vulkan.
// Semaphores
// Used to coordinate operations within the graphics queue and ensure correct command ordering
VkSemaphore presentCompleteSemaphore;
VkSemaphore renderCompleteSemaphore;
通过构造函数给成员赋值(~析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,而析构函数就是对象在销毁的时候自动调用的的):
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
{
title = "Vulkan Example - Basic indexed triangle";
// To keep things simple, we don't use the UI overlay
settings.overlay = false;
// Setup a default look-at camera
camera.type = Camera::CameraType::lookat;
camera.setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
camera.setRotation(glm::vec3(0.0f));
camera.setPerspective(60.0f, (float)width / (float)height, 1.0f, 256.0f);
// Values not set here are initialized in the base class constructor
}
~VulkanExample()
{
// Clean up used Vulkan resources
// Note: Inherited destructor cleans up resources stored in base class
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
vkDestroyBuffer(device, vertices.buffer, nullptr);
vkFreeMemory(device, vertices.memory, nullptr);
vkDestroyBuffer(device, indices.buffer, nullptr);
vkFreeMemory(device, indices.memory, nullptr);
vkDestroyBuffer(device, uniformBufferVS.buffer, nullptr);
vkFreeMemory(device, uniformBufferVS.memory, nullptr);
vkDestroySemaphore(device, presentCompleteSemaphore, nullptr);
vkDestroySemaphore(device, renderCompleteSemaphore, nullptr);
for (auto& fence : waitFences)
{
vkDestroyFence(device, fence, nullptr);
}
}
2.Triangle类用到的函数
-
请求支持我们请求的所有属性标志的设备内存类型
// This function is used to request a device memory type that supports all the property flags we request (e.g. device local, host visible) // Upon success it will return the index of the memory type that fits our requested memory properties // This is necessary as implementations can offer an arbitrary number of memory types with different // memory properties. uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) { ..... }
-
创建此示例中使用的 Vulkan 同步基元
// Create the Vulkan synchronization primitives used in this example void prepareSynchronizationPrimitives() { ... }
-
创建新的命令buffer和命令池
// Get a new command buffer from the command pool // If begin is true, the command buffer is also started so we can start adding commands VkCommandBuffer getCommandBuffer(bool begin) { VkCommandBuffer cmdBuffer; ... return cmdBuffer; }
-
之后又包括:
- 结束命令缓冲区并将其提交到队列,使用栅栏确保命令缓冲区在删除之前已完成执行;
- 为每个帧缓冲器映像构建单独的命令缓冲区(与OpenGL不同,所有渲染命令都记录到命令缓冲区中一次,然后重新提交到队列中这允许从多个线程预先生成工作,这是Vulkan的最大优势之一),在这里边完成renderPassBeginInfo的创建,通过drawCmdBuffers的循环赋值,并进行绘制。结束渲染通道将添加一个隐式障碍,将帧缓冲区颜色附件转换为用于将其呈现给窗口系统的VK_IMAGE_LAYOUT_PRESENT_SRC_KHR。
- draw():获取交换链中的下一个图像(后/前缓冲区);使用栅栏等待命令缓冲区完成执行,然后再使用它;使用 submitInfo 提交信息结构指定命令缓冲区队列提交批处理;将当前缓冲区提供给交换链,将命令缓冲区提交发出的信号量从提交信息作为交换链演示的等待信号量传递,可确保在提交所有命令之前不会将映像呈现给窗口系统。
- void prepareVertices(bool useStagingBuffers):为索引三角形准备顶点和索引缓冲区,还使用暂存将它们上传到设备本地内存,并初始化顶点输入和属性绑定以匹配顶点着色器。用到了data将索引数据复制到主机可见的缓冲区。
- setupDescriptorPool()、setupDescriptorPool()、setupDescriptorSetLayout():创建描述符。
- setupDepthStencil():创建帧缓冲器使用的深度(和模具)缓冲区附件(覆盖基类中的虚函数,并从 VulkanExampleBase::p repare 中调用)。
- setupFrameBuffer(): 为每个交换链图像创建一个帧缓冲区(覆盖基类中的虚函数,并从 VulkanExampleBase::p repare 中调用)。
- setupRenderPass()渲染通道设置: 渲染通道是 Vulkan 中的一个新概念。它们描述渲染期间使用的附件,并且可能包含多个具有附件依赖性的子通道,这使驱动程序能够预先知道渲染的外观,并且是优化的好机会,尤其是在基于图块的渲染器(具有多个子通道)上。使用子传递依赖项还会为使用的附件添加隐式布局过渡,因此我们不需要添加显式图像内存屏障来转换它们.
- loadSPIRVShader():Vulkan 从称为 SPIR-V 的直接二进制表示加载其着色器着色器从例如GLSL使用参考glslang编译器离线编译.
- void preparePipelines():创建此示例中使用的图形管道;Vulkan使用渲染管道的概念来封装固定状态,取代了OpenGL的复杂状态机。然后,在GPU上存储和散列管道,使管道更改非常快。在这里经过属性输入,深度,颜色顶点绑定,以及顶点和片段的shader的设置等使用指定状态创建渲染管线。创建图形管道后,删除shaderStage。
- void prepareUniformBuffers():准备并初始化包含着色器制服的统一缓冲区块。像OpenGL这样的单一制服在Vulkan中不再存在。所有着色器制服都通过均匀缓冲区块传递。
- updateUniformBuffers()。在每次进行prepareUniformBuffers都会执行该函数
3.具体执行
准备以及render代码
void prepare()
{
VulkanExampleBase::prepare();
prepareSynchronizationPrimitives();
prepareVertices(USE_STAGING);
prepareUniformBuffers();
setupDescriptorSetLayout();
preparePipelines();
setupDescriptorPool();
setupDescriptorSet();
buildCommandBuffers();
prepared = true;
}
virtual void render()
{
if (!prepared)
return;
draw();
}
virtual void viewChanged()
{
// This function is called by the base example class each time the view is changed by user input
updateUniformBuffers();
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
{
for (size_t i = 0; i < __argc; i++) { VulkanExample::args.push_back(__argv[i]); };
vulkanExample = new VulkanExample();
vulkanExample->initVulkan();
vulkanExample->setupWindow(hInstance, WndProc);
vulkanExample->prepare();
vulkanExample->renderLoop();
delete(vulkanExample);
return 0;
}
注意这个VulkanExampleBase类的renderloop中能调用VulkanExample中的render就是引文人的人是虚函数。
void VulkanExampleBase::renderLoop()
{
if (benchmark.active) {
benchmark.run([=] { render(); }, vulkanDevice->properties);
vkDeviceWaitIdle(device);
if (benchmark.filename != "") {
benchmark.saveResults();
}
return;
}
总结
无