• Vulkan-实践第一弹


     上一篇文章中,我们浅析了Vulkan对传统图形API的优势,主要就是在其性能和精细化操控GPU上,具体可参考Vulkan-性能及精细化

    今天我们就来用个简单的例子,亲身感受下Vulkan的开发“魅力”。

    1. #include <iostream>
    2. #include <chrono>
    3. #include <cstring>
    4. #include <glm/mat4x4.hpp>
    5. #include <glm/gtx/transform.hpp>
    6. #include "VK_UniformBuffer.h"
    7. #include "VK_Context.h"
    8. #include "VK_Pipeline.h"
    9. #include "VK_DynamicState.h"
    10. using namespace std;
    11. const std::vector<uint32_t> indices = {
    12. 0, 1, 2
    13. };
    14. const std::vector<float> vertices = {
    15. 0.0f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f,
    16. 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.5f,
    17. -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.5
    18. };
    19. VK_Context *context = nullptr;
    20. VK_Pipeline *pipeline = nullptr;
    21. // 窗口大小改变时通过onFrameSizeChanged更新窗口大小
    22. void onFrameSizeChanged(int width, int height)
    23. {
    24. pipeline->getDynamicState()->applyDynamicViewport({0, 0, (float)width, (float)height, 0, 1});
    25. }
    26. //实时更新uniform值
    27. uint32_t updateUniformBufferData(char *&data, uint32_t size)
    28. {
    29. static auto start = std::chrono::high_resolution_clock::now();
    30. auto current = std::chrono::high_resolution_clock::now();
    31. float time = std::chrono::duration<float, std::chrono::seconds::period>
    32. (current - start).count();
    33. glm::mat4 model = glm::rotate(glm::mat4(1.0f), time * glm::radians(30.0f), glm::vec3(0.0f, 0.0f,
    34. 1.0f));
    35. memcpy(data, &model[0][0], size);
    36. return sizeof(model);
    37. }
    38. int main()
    39. {
    40. VK_ContextConfig config;
    41. config.debug = true;
    42. config.name = "Uniform Demo";
    43. context = createVkContext(config);
    44. context->createWindow(640, 640, true);
    45. context->setOnFrameSizeChanged(onFrameSizeChanged);
    46. VK_Context::VK_Config vkConfig;
    47. context->initVulkanDevice(vkConfig);
    48. auto shaderSet = context->createShaderSet();
    49. shaderSet->addShader("../jianghuxiuxing/shader/mvp/vertex.spv", VK_SHADER_STAGE_VERTEX_BIT);
    50. shaderSet->addShader("../jianghuxiuxing/shader/mvp/fragmeng.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
    51. shaderSet->appendAttributeDescription(0, sizeof (float) * 3, VK_FORMAT_R32G32B32_SFLOAT, 0);
    52. shaderSet->appendAttributeDescription(1, sizeof (float) * 4, VK_FORMAT_R32G32B32A32_SFLOAT,
    53. sizeof(float) * 3);
    54. shaderSet->appendVertexInputBindingDescription(7 * sizeof(float), 0, VK_VERTEX_INPUT_RATE_VERTEX);
    55. VkDescriptorSetLayoutBinding uniformBinding = VK_ShaderSet::createDescriptorSetLayoutBinding(0,
    56. VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT);
    57. shaderSet->addDescriptorSetLayoutBinding(uniformBinding);
    58. if (!shaderSet->isValid()) {
    59. std::cerr << "invalid shaderSet" << std::endl;
    60. shaderSet->release();
    61. context->release();
    62. return -1;
    63. }
    64. auto ubo = shaderSet->addUniformBuffer(0, sizeof(float) * 16);
    65. ubo->setWriteDataCallback(updateUniformBufferData);
    66. context->initVulkanContext();
    67. pipeline = context->createPipeline(shaderSet);
    68. pipeline->getDynamicState()->addDynamicState(VK_DYNAMIC_STATE_VIEWPORT);
    69. pipeline->create();
    70. pipeline->getDynamicState()->applyDynamicViewport({0, 0, 480, 480, 0, 1});
    71. auto buffer = context->createVertexBuffer(vertices, 3 + 4, indices);
    72. pipeline->addRenderBuffer(buffer);
    73. context->createCommandBuffers();
    74. context->run();
    75. context->release();
    76. return 0;
    77. }

    Vertex Shader

    1. #version 450
    2. layout(location = 0) in vec3 position;
    3. layout(location = 1) in vec4 color;
    4. layout(location = 0) out vec4 fragColor;
    5. layout(binding = 0) uniform UniformBufferObject {
    6. mat4 model;
    7. } mvp;
    8. void main() {
    9. gl_Position = mvp.model * vec4(position, 1.0);
    10. fragColor = color;
    11. }

    Fragment Shader

    1. #version 450
    2. layout(location = 0) in vec4 fragColor;
    3. layout(location = 0) out vec4 outColor;
    4. void main() {
    5. outColor = fragColor;
    6. }

    效果:

     从上面的代码实现可以看出Vulkan在加载spv字节码,显示指定shader各属性方面都做了更繁琐的限制,开发者需要关注的程序控制和执行时机也变的更多了,这也印证了我们上一篇文章里说的Vulkan摒弃了传统图形API的大包大揽,而将更多的校验和权利下放给了程序设计者。

    Vulkan的渲染流程

    • 创建程序窗口
    • 初始化Vulkan实例 - vulkan库的初始化
    • 初始化输出表面 - 指明渲染显示目的地
    • 选择满足要求的物理设备(通常多个,对于渲染程序,通常需要物理设备支持图形队列以及表面呈现队列)
    • 创建逻辑设备 - 根据指定物理设备即可创建逻辑设备(逻辑设备和物理设备相对应,但并不唯一)
    • 创建指令池对象 - vulkan中指令提交和传输需要通过指令缓冲来操作,对于指令缓冲需要构建指令池对象
    • 创建交换链 - vulkan中不存在默认帧缓冲的概念,需要一个缓存渲染缓冲的组件,这就是交换链。交换链本质上一个包含了若干等待呈现的图像的队列
    • 创建交换队列图像视图 - 有了交换链还不够,需要一组图形来保存渲染数据
    • 创建渲染通道 - 渲染通道用于表示帧缓冲,其是子通道以及子通道关系的集合。深度模板附件、颜色附件、帧附件都是在此阶段被创建的
    • 创建描述符布局 - 描述符是一个特殊的不透明的着色器变量,着色器使用它以间接的方式访问缓冲区和图像资源。描述符集合是描述一个管线使用到的描述符集合。描述符布局则用于刻画其布局。
    • 创建管线布局 - 管线布局包含一个描述符集合布局的列表
    • 创建帧缓冲 - 作为附件的图像依赖交换链用于呈现时返回的图像。这意味着我们必须为交换链中的所有图像创建一个帧缓冲区,并在绘制的时候使用对应的图像。其附件必须和渲染通道中使用的附件相匹配。
    • 创建描述符池 -描述符需要从池中分配,不能直接创建
    • 创建描述符集 - 描述符池是根据交换链帧个数以及Shader中描述符数量和数据来创建的,Shader属性类型和其中涉及的uninform、location等信息是在这个阶段被传入的
    • 分配和更行描述符集 - 交换链帧个数、uniform数据以及图形视图是在这个阶段被处理的
    • 创建管线 - 根据Shader、管线布局、渲染通道以及其他相关信息即可构造管线(需要设置各种属性关联的Shader,顶点输入格式 ,视口状态,光栅化,多重采样可选等)
    • 创建命令缓冲 - 根据命令池、渲染通道、交换链帧个数即可分配并使用命令缓冲,其中对管线的绑定、描述符集的绑定以及开始和结束渲染通道是在这个阶段完成的。
    • 渲染循环-从交换链中取图像(利用 vkAcquireNextImageKHR),更新uniform数据- vkMapMemory、vkUnmapMemory,提交指令缓冲 (利用vkQueueSubmit),图像回传交换链作呈现。(这个过程非常复杂,由于Vulkan的多线程需要保证同步等)
    • 管线重建-管线并不始终有效,如果渲染呈现丢失或者窗口大小发生变化或者管线配置发生变化时都需要重新构造交换链和管线。(相关的节点也需要重建,比如交换链,渲染通道,缓冲池等)

  • 相关阅读:
    【小沐学QT】QT学习之OpenGL开发笔记
    【两篇就够】异步相关(二)
    以技术面试官的经验分享毕业生及三年以下的程序员通过面试的技巧
    类型多样的石膏PBR多通道贴图素材,速来收藏!
    JQuery系列之事件
    【Linux】第十五章 多线程(线程池)
    java计算机毕业设计vue架构云餐厅美食订餐系统MyBatis+系统+LW文档+源码+调试部署
    TrieTree结构
    SRT服务端的搭建
    Spring Boot 笔记
  • 原文地址:https://blog.csdn.net/jh1988abc/article/details/125485240