用vulkan写个引擎 (二)vk组件

这篇文章开始展开介绍工程的组织方式和组件模块。首先从vulkan组件开始。大部分文章都是把官网例子重复一遍,或是罗列接口说明,根本不足以用在生产环境。

注意,文章主要表述组件的功能和要点,和它们之间的关联配合,并不会大面积的罗列代码,如果想参考源码,可以直接拉取仓库查看。

仓库:https://bitbucket.org/mm_longcheng/mm-vulkan

扣扣交流群:554329899

我们的接口需要做到哪些功能:
1.vulkan运行时环境的启动和关闭。
2.与原生os层接口交互,创建并桥接View和surface。
3.我们需要配置文件,图片,模型等资源。

组件需要满足的特性:
1.basis硬件纹理压缩
2.draco模型数据压缩
3.常用图片格式加载
4.图集打包功能
5.跨平台支持,windows,android,ios,macos

一、主要部件,渲染器

struct mmVKRenderer
{
    struct mmVKInstance vInstance;
    struct mmVKPhysicalDevice vPhysicalDevice;
    struct mmVKDevice vDevice;
    struct mmVKUploader vUploader;
    VkCommandPool graphicsCmdPool;
    VkCommandPool transferCmdPool;
    VmaAllocator vmaAllocator;
    uint32_t vApiVersion;
    uint32_t vAppVersion;
    const VkAllocationCallbacks* pAllocator;
};

里面有两个仅需配置但很少引用的类型:

一个是mmVKInstance,详情可以看源码,其中比较重要的是需要在构建mmVKInstance时视情况开启验证层,并且将日志接入我的logger系统,因为在安卓上日志接口会不太一样,抽象logger是必要的。

一个是mmVKPhysicalDevice。构建要点是需要以某种方式挑选一个缺省的设备,我是按独显优先,显存大的优先挑的第一个,并且支持传入设备索引由应用层挑选。另外,我需要支持硬件纹理压缩basis,所以需要按设备得到支持的压缩纹理格式。

mmVKDevice是用的比较多的部件,这部分的工作主要是挑选队列,筛选需要支持图形和呈现(推到交换琏)。然后获取并缓存了交换琏的的接口函数。

mmVKUploader是从ktx-software里面抄的,主要提供将ktx贴图资源上传到vulkan抵达硬件的功能。贴图资源是一个大模块,我使用了两个系列的第三方库,一个是FreeImage,一个就是ktx。

FreeImage主要提供常用格式图片文件的加载,比如png、jpeg、webp、tiff等。它依赖了比较多的图片解码库,主要工作是给这些第三方库搭建多平台编译工程,这部分留到mm-libx仓库再说。

ktx是一个vulkan,opengl官方推荐使用的纹理数据容器格式,除了将编码后的图片资源打包,还会将渲染api常用的参数作为元数据打包。还有比较重要的是,新版本还提供了basis(basis_universal)硬件纹理压缩方案。并且内部携带一份vulkan的图片资源上载的组件。真的是非常的省心了。单是不能直接拿来使用,需要原样复制一份,然后将image和buffer的创建接口改成英伟达开源的vma。注意需要合并一下最新的版本改动,我用的时候发现当前master版本(4.0.0)有内存泄露。

VmaAllocator是英伟达开源的vma资源管理库主要部件。它会把小块分配需求合并,并且按块整合申请,虽然是c++写的,体积有点大,但好在它的接口是c接口。并且十分好用。

二、主要部件,资源

struct mmVKAssets
{
    struct mmPackageAssets* pPackageAssets;
    struct mmFileContext* pFileContext;
    struct mmVKUploader* pUploader;
    struct mmVKDevice* pDevice;

    struct mmVKPipelineCache vPipelineCache;
    struct mmVKSamplerPool vSamplerPool;

    struct mmVKSheetLoader vSheetLoader;
    struct mmVKImageLoader vImageLoader;
    struct mmVKShaderLoader vShaderLoader;
    struct mmVKLayoutLoader vLayoutLoader;
    struct mmVKPassLoader vPassLoader;
    struct mmVKPipelineLoader vPipelineLoader;
    struct mmVKPoolLoader vPoolLoader;

    struct mmVKAssetsDefault vAssetsDefault;
};

mmPackageAssets是一个跨平台的部件,它会分平台地得到可用的读写路径,这部分在ios和安卓平台尤其重要。我们需要保存PipelineCache的缓存二进制文件,需要这个部件。

mmFileContext是一份既可读取目录,也可以读取zip压缩包,并且可以加搜索路径的文件读取组件。fopen并不总是可用的,这一点在安卓上尤其如此。实际上所有的随包资源都是用它来读取。

mmVKPipelineCache是个简单的管线存储和加载的部件。

mmVKSamplerPool是采样器的池,考虑到很多情况下采样器参数基本一致,那么对于使用同一参数创建的采样器,循环利用是廉价的。

mmVKSheetLoader是图集打包资源的加载器。ui图集会通过TexturePackage打包,并且输出一份类tsv的文本数据。加载图集描述文件需要这个部件。

mmVKImageLoader是图片资源的加载和上传到vulkan接口的管理器,它整合了FreeImage和ktx的接口,提供了简单的按文件路径加载的接口。

mmVKShaderLoader是spv着色器资源的加载器。

mmVKLayoutLoader是VkDescriptorSetBinds的配置文件加载器,我用json来描述描述符集绑定布局。加载完毕后可以使用名称索引它。

mmVKPassLoader是RenderPass的配置加载器,由于现在只有使用交换链来创建的一份pass,所以实际上它还没有相应的json配置格式。但是它能使用名称索引这份资源,为Pipeline的json配置提供了前提条件。

mmVKPipelineLoader是使用描述符集布局,着色器,RenderPass,和着色器入参来描述的资源,它也使用json格式描述。

mmVKPoolLoader是VkDescriptorSet描述符集的池子加载器,它会使用Layout作为布局初始化按特定大小的描述符池的分组,当一个分组分配完会创建新的分组。简单扩容。

mmVKAssetsDefault会创建几份默认的图片资源,比如全白和透明图片,用于特定情况。

三、主要部件,交换琏和视图

mmVKSurface和mmVKSwapchain并不在Renderer里,而是平级的关系。因为通常一个系统里只有一个Renderer,但可能会有多个窗口,一个窗口一份Surface和其配套的Swapchain。

这部分是由nwsi系统完成的。它相当于glfw库,它是嵌入式非强占的设计,更轻量和特化,并且支持安卓和ios。我们以os原生接口创建app和主view。并将这个主视图通过对应平台的Surface部件桥接,最后得到一份交换琏。

特别说一下我是如何支持多平台的。

首先我创建os目录,并在这个目录下创建不同平台的文件夹。把平台相关的实现分开放入,在编译时通过包含特定目录来将物理上不在一起的文件夹合并为逻辑上在一块。这样就无需写平台宏来区分代码块,保证了源码的清晰和整洁。

四、主要部件,管线渲染对象

mmVKPipelineTarget主要维护了渲染缓冲区的资源framebuffer。它也是独立于渲染器存在的,并且它不属于交换琏。这样做有很多好处。比如隶属于同一RenderPass的多个管线可以将结果渲染到一起。是渲染结果的承载对象,并且维护了每帧的主级别CommandBuffer,也维护了将cmd推送到设备队列并做切帧的功能。

五、主要部件,gltf加载器

mmVKTFModel,主要维护了gltf的静态资源,如图片,定点数据缓冲区,定点数据缓冲区等,但不包含动态数据,如动画状态数据是没有的。

mmVKTFObject,主要维护了gltf更新时产生的动态数据,模型动画的状态会保存在这里。一份TFModel可以创建多个TFObject,这样可以复用静态数据资源。

目前gltf的部分还不完善,仅支持特定顶点参数格式。并且仅提供了一份基本款的pbr着色。但好在可以与blender导出插件配合工作,并且优先支持了模型骨骼动画,有一定的可用性。可以满足基本需求。

六、次要部件和工具

mmVKDebugger是挂载在vulkan实例上的日志输出回调处理组件。

mmVKCamera仅保存了投影矩阵的视图矩阵,是相机的数据。

mmVKUniformType用来构建ubo数据布局,我们需要特定的对齐方式来保证传入着色器数据的正确性。

mmVKNode是场景节点位置信息和节点关联信息的数据。

mmVKJSON是json文件解析为vulkan结构的转换解码部件。

mmVKCommandBuffer是命令记录相关的部件,包含常用的辅助记录命令函数。

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的使用Vulkan API编写渲染管线的示例代码: ```c++ #include <vulkan/vulkan.h> #include <vector> #include <iostream> int main() { // 初始化Vulkan实例 VkInstance instance; VkInstanceCreateInfo instanceCreateInfo = {}; instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; vkCreateInstance(&instanceCreateInfo, nullptr, &instance); // 获取Vulkan设备列表 uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); std::vector<VkPhysicalDevice> physicalDevices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data()); // 选择第一个物理设备 VkPhysicalDevice physicalDevice = physicalDevices[0]; // 获取设备属性 VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); // 创建设备 VkDevice device; VkDeviceCreateInfo deviceCreateInfo = {}; deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device); // 获取队列族索引 uint32_t queueFamilyIndex = 0; VkQueueFamilyProperties queueFamilyProperties; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, &queueFamilyProperties); for (uint32_t i = 0; i < queueFamilyCount; i++) { if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { queueFamilyIndex = i; break; } } // 创建命令池 VkCommandPool commandPool; VkCommandPoolCreateInfo commandPoolCreateInfo = {}; commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; commandPoolCreateInfo.queueFamilyIndex = queueFamilyIndex; vkCreateCommandPool(device, &commandPoolCreateInfo, nullptr, &commandPool); // 创建渲染管线 VkShaderModule vertexShaderModule; VkShaderModuleCreateInfo vertexShaderCreateInfo = {}; // 加载顶点着色器GLSL代码 vertexShaderCreateInfo.codeSize = sizeof(vertexShaderCode); vertexShaderCreateInfo.pCode = vertexShaderCode; vkCreateShaderModule(device, &vertexShaderCreateInfo, nullptr, &vertexShaderModule); VkShaderModule fragmentShaderModule; VkShaderModuleCreateInfo fragmentShaderCreateInfo = {}; // 加载片段着色器GLSL代码 fragmentShaderCreateInfo.codeSize = sizeof(fragmentShaderCode); fragmentShaderCreateInfo.pCode = fragmentShaderCode; vkCreateShaderModule(device, &fragmentShaderCreateInfo, nullptr, &fragmentShaderModule); VkPipelineShaderStageCreateInfo shaderStages[2] = {}; shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shaderStages[0].module = vertexShaderModule; shaderStages[0].pName = "main"; shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shaderStages[1].module = fragmentShaderModule; shaderStages[1].pName = "main"; VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {}; vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {}; inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = WIDTH; viewport.height = HEIGHT; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; scissor.offset = {0, 0}; scissor.extent = {WIDTH, HEIGHT}; VkPipelineViewportStateCreateInfo viewportCreateInfo = {}; viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportCreateInfo.viewportCount = 1; viewportCreateInfo.pViewports = &viewport; viewportCreateInfo.scissorCount = 1; viewportCreateInfo.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo rasterizationCreateInfo = {}; rasterizationCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizationCreateInfo.polygonMode = VK_POLYGON_MODE_FILL; rasterizationCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT; rasterizationCreateInfo.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizationCreateInfo.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {}; multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; colorBlendAttachment.blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {}; colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlendCreateInfo.attachmentCount = 1; colorBlendCreateInfo.pAttachments = &colorBlendAttachment; VkPipelineLayout pipelineLayout; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {}; pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout); VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineCreateInfo.stageCount = 2; pipelineCreateInfo.pStages = shaderStages; pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; pipelineCreateInfo.pViewportState = &viewportCreateInfo; pipelineCreateInfo.pRasterizationState = &rasterizationCreateInfo; pipelineCreateInfo.pMultisampleState = &multisampleCreateInfo; pipelineCreateInfo.pColorBlendState = &colorBlendCreateInfo; pipelineCreateInfo.layout = pipelineLayout; pipelineCreateInfo.renderPass = renderPass; vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline); // 渲染 VkCommandBuffer commandBuffer; VkCommandBufferAllocateInfo commandBufferAllocateInfo = {}; commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; commandBufferAllocateInfo.commandPool = commandPool; commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; commandBufferAllocateInfo.commandBufferCount = 1; vkAllocateCommandBuffers(device, &commandBufferAllocateInfo, &commandBuffer); VkCommandBufferBeginInfo commandBufferBeginInfo = {}; commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo); vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdDraw(commandBuffer, 3, 1, 0, 0); vkCmdEndRenderPass(commandBuffer); vkEndCommandBuffer(commandBuffer); // 释放资源 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyShaderModule(device, vertexShaderModule, nullptr); vkDestroyShaderModule(device, fragmentShaderModule, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyPipeline(device, pipeline, nullptr); vkDestroyDevice(device, nullptr); vkDestroyInstance(instance, nullptr); return 0; } ``` 需要注意的是,上述示例代码仅仅是一个简单的渲染管线,实际应用中还需要添加更多的细节和逻辑,例如纹理加载、深度测试、光照等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值