Vulkan 學習(20)---- UniformBuffer 的使用

目錄

      • UniformBuffer
        • DescriptorSetLayout 和 VkBuffer
        • 頂點著色器定義
        • 描述符布局(DescriptorSetLayout)
        • 創建 UniformBuffer
        • 描述符池(DescriptorSet Pool)
        • 描述符集(DescriptorSet)
        • 更新描述符集
        • 使用描述符集
        • 使用多個 Descriptor

UniformBuffer

本篇文檔是通過 Uniform Buffer 的使用進一步加深對 DescriptorSet 的理解
Vulkan 中,描述符是一種在著色器中訪問資源(比如緩沖區,圖像,采樣器等)的機制或者協議

每個描述符(Descriptor)對應一個資源,代表 GPU 內存中的資源,比如 Uniform Bufferstorage Buffer, TextureSampler

Vulkan 描述符集(VkDescriptorSet)表示著色器可以與之交互的資源的集合,著色器是通過描述符讀取和解析資源中的數據,著色器中的綁定點和相應的描述符集中的綁定點必須一一對應
描述符集

DescriptorSetLayout 和 VkBuffer

現在我們已經可以傳遞頂點的屬性(坐標和顏色等)給到頂點著色器,對于一些所有頂點都共享的屬性,比如頂點的變換矩陣,將其作為頂點屬性為每一個頂點都傳遞一份顯然是很低效的
Vulkan 提供了資源描述符(resource descriptor)來解決這個問題,資源描述符是用來在著色器中訪問緩沖和圖像數據的一種方式,我們可以將變換矩陣存儲在一個緩沖中,然后通過描述符在著色器中訪問它,使用描述符需要進行下面三部分的設置:

  • 在管線(pipeline Creation)創建時指定描述符布局(DescriptorSetLayout)
  • 從描述符池(DescriptorSet Pool)中份分配描述符集(DescriptorSet)
  • 渲染時綁定描述符集(update DescriptorSet)

描述符布局(DescriptorSetLayout)用于指定可以被管線訪問的資源類型,類似于渲染流程指定可以被訪問的附著類型

描述符集指定要綁定到描述符上的緩沖和圖像資源,類似于幀緩存指定綁定到渲染流程附著上的圖像視圖
(just like a framebuffer specifies the actual image views to bind to render pass attachments)

Note: 本質上是一種定義資源如何訪問的機制或者協議

最后將描述符集綁定到繪制的指令上,類似綁定頂點緩沖和幀緩存到繪制指令上

有多種類型的描述符,在這里, 只使用到了 Uniform 緩沖對象(UBO), 也有其他類型的描述符,它們的使用方式和 Uniform 緩沖對象類似

我們先用結構體定義我們在著色器中使用的 Uniform 的數據:

struct UniformBufferObject {glm::mat4 model;glm::mat4 view;glm::mat4 proj;
}

我們將要使用的 uniform 數據復制到 VkBuffer 中,然后通過一個 uniform 緩沖對象描述符(DescriptorSet)在頂點著色器中訪問它:

layout(binding = 0) uniform UniformBufferObejct {mat4 model;mat4 view;mat4 proj;
}void main() {gl_Position = ubo.proj * ubo.view *ubo.model*vec4(inPostion, 0.01.0)fragColor = inColor;
}

在現在的 demo 中,我們在每一幀更新模型(Model),視圖(View),投影矩陣(Projection),可以讓矩陣在三維空間內進行旋轉

頂點著色器定義
#version 450
#extension GL_ARB_separate_shader_object :enablelayout(binding = 0) uniform UniformBufferObject {mat4 model;mat4 view;mat4 proj;
}layout(location = 0) in vec2 inPostion;
layout(location = 1) in vec3 inColor;layout(location = 0) out vec3 fragColorout gl_PerVertex {vec4 gl_Postion;
}void main() {gl_Position = ubo.proj + ubo.view + ubo.model * vec4(inPostion, 001.0);fragColor = inColor;
}

uniforminout 定義在著色器中出現的順序可以是任意的,任意代碼中 binding 修飾符類似于我們對頂點屬性使用的 location 修飾符,我們會在描述符布局引用這個 binding

gl_Position 使用變換矩陣最終得到矩形在三維空間內的裁剪坐標

描述符布局(DescriptorSetLayout)

我們需要在管線創建的時候提供著色器使用的每一個描述符綁定信息,
首先需要使用 createDescriptorSetLayout 的函數,并在管線創建前調用

void initVulkan() {createDecriptorSetLayout();createGraphicPipeline();
}void createDescriptorSetLayout() {
}

使用 vkDescriptorSetLayoutBinding 結構體來描述每一個綁定操作

void createDescriptorSetLayout() {VkSescriptorSetLayoutBinding ubolayoutBinding = {};uboLayoutBinding.binding = 0;uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;uboLayoutBinding.descriptorCount = 1;
}

bindingdescriptorType 用于指定著色器使用的描述符綁定和描述符類型,這里我們指定的是一個 uniform 緩沖對象,
也可以使用 uniform 數組傳遞到著色器中,我們可以使用數組來制定骨骼動畫(skeletal aniamtion)中使用的所有變換矩陣,
我們的 MVP 矩陣只需要使用一個 uniform 緩沖對象,所以我們將 descriptorCount 的值設置為 1

uboLayoutBinding.pImmutableSamplers = nullptr;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;

我們還需要指定描述符在哪一個著色器階段被使用,stageFlags 這里我們只是在 Vertex Shader 中使用,
pImmutableSamplers 成員變量僅用于和圖像采樣相關的描述符

調用 vkCreateDescriptorSetLayout 函數創建 VkDescriptorSetLayout 對象,vkCreateDescriptorSetLayout 函數以 VkDescriptorSetLayoutCreateInfo結構體作為參數

VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {throw std::runtime_error("failed to create descriptor set layout!");
}

同時我們需要在創建 GraphicPipeline 的時候指定 DescriptorSetLayout,也可以指定多個 DescriptorSetLayout

VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
創建 UniformBuffer

我們需要創建包含 UniformBuffer 的緩沖對象(Uniform Buffer),然后在每一幀中將新的 UBO 數據復制到 uniform 緩沖,由于需要頻繁的更新數據,使用暫存并不會帶來性能的提升

由于我們需要并行渲染多幀的緣故,我們需要多個 uniform 緩沖,來滿足多幀并行渲染的需要,我們可以并行渲染每一幀或者一個交換鏈圖像使用獨立的 uniform 緩沖對象

VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;std::vector<VkBuffer> uniformBuffers;
std::vector<VkDeviceMemory> uniformBuffersMemory;void createUniformBuffer() {VkDeviceSize bufferSize = sizeof(UniformBufferObject);uniformBuffers.resize(swapChainImages.size());uniformBuffersMemory.resize(swapChainImages.size());for (size_t i = 0; i < swapChainImages.size(); i++) {createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);}
}

最后更新 UniformBuffer 只需要將數據拷貝到 UniformBuffer Memory 對象的虛擬地址空間中

void* data;
vkMapMemory(device, uniformBuffersMemory[currentImage]0sizeof(ubo)0&data);
memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(device, uniformBuffersMemory[currentImage]);
描述符池(DescriptorSet Pool)

描述符集不能被直接創建,需要通過描述符池(DescriptorSet Pool)來分配,這里使用 createDescriptorPool 的函數來進行描述符池的創建

我們使用 VkDescriptorPoolSize 來決定 我們使用的 DescriptorSet 類型和數量
poolSize 是根據 swapChainImages 中的 image 的數量來決定的

VkDescriptorPoolSize poolSize = {};
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSize.descriptorCount = static_cast<uint32_t>(swapChainImages.size());VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = static_cast<uint32_t>(swapChainImages.size());VkDescriptorPool descriptorPool;...if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {throw std::runtime_error("failed to create descriptor pool!");
}
描述符集(DescriptorSet)

DescriptorSet 的分配(Allocate)需要我們使用 vkAllocateDescriptorSets 分配出來,我們使用 VkDescriptorSetAllocateInfo 結構體
需要指定分配 DescriptorSet 使用的 DescriptorSetPool,需要分配的描述符集數量,以及它們使用的 DescriptorSetLayout

std::vector<VkDescriptorSetLayout>
layouts(swapChainImages.size(), descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(swapChainImages.size());
allocInfo.pSetLayouts = layouts.data();VkDescriptorPool descriptorPool;
std::vector<VkDescriptorSet> descriptorSets;
...
descriptorSets.resize(swapChainImages.size());
if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets[0]) != VK_SUCCESS) {throw std::runtime_error("failed to allocate descriptor sets!");
}

DescriptorSet 會在 DescriptorSetPool 銷毀的時候自動被銷毀,所以不需要我們顯式的清除
vkAllocateDescriptorSets 函數分配地描述符集對象,每一個都帶有 uniform 緩沖描述符(對應一個 uniformvkBuffer)

我們通過 vkDescriptorBufferInfo 結構體來配置引用的 vkBuffer

VkDescriptorBufferInfo 結構體可指定緩沖對象和可以訪問的數據范圍

for (size_t i = 0; i < swapChainImages.size(); i++) {VkDescriptorBufferInfo bufferInfo = {};bufferInfo.buffer = uniformBuffers[i];bufferInfo.offset = 0;bufferInfo.range = sizeof(UniformBufferObject);
}

如果需要使用整個緩沖,可以使將 range 成員變量范圍設置為 VK_WHOLE_SIZE

更新描述符集
VkWriteDescriptorSet descriptorWrite = {};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSets[i];
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;
descriptorWrite.pBufferInfo = &bufferInfo;
descriptorWrite.pImageInfo = nullptr; // Optional
descriptorWrite.pTexelBufferView = nullptr; // Optional

dstSetdstBinding 成員變量用于指定要更新的 DescriptorSet 和 綁定點(bindings)
需要注意的是DescriptorSet可以使用數組,所以我們需要指定數組的第一個元素作為索引,這里我們沒有使用,所以將索引指定為 0

pBufferInfo 成員變量用于指定描述符引用的緩沖數據,pImageInfo 成員變量用于指定描述符引用的圖像數據,
pTexelBufferView 成員變量 用于指定描述符引用的緩沖視圖,這里我們只使用了 pBufferInfo 成員變量

最后使用 vkUpdateDescriptorSets 更新描述符集

vkUpdateDescriptorSets(device, 1&descriptorWrite, 0, nullptr);

vkUpdateDescriptorSets 函數可以接受兩個數組作為參數;
VkWriteDescriptorSet 結構體數組和 VkCopyDescriptorSet 結構體數組,后者被用來復制(copy)描述符對

使用描述符集

現在修改 createCommandBuffer 函數為每個交換鏈圖像綁定對應的描述符集,這需要調用 cmdBindDescriptorSets 完成,需要在調用 vkCmdDrawIndexed 函數之前調用這個函數

vkCmdBindDescriptorSets(commandBuffers[i],
VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 01&descriptorSets[i]0, nullptr);
vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size())1000);

和頂點緩沖,索引緩沖不同,描述符集合并不是圖像管線所獨有的,所以我們需要指定我們綁定的是圖形管線還是計算管線,管線之后的參數是描述符使用的布局
后面的三個參數用于指定: 描述符集的第一個元素索引,綁定的描述符集的個數,以及用于綁定的描述符集數組,最后兩個參數用于指定動態描述符的數組偏移

使用多個 Descriptor

DescriptorSet 本身就是集合的概念,也就是可以創建 Descriptor 數組對應到一個 DescriptorSet 的綁定點上

VkDescriptorSetLayoutBinding binding = {};
binding.binding = 0; // 綁定點
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
binding.descriptorCount = 8; // 綁定了 8 個 uniform buffer
binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &binding;
vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout);// glsl access descriptorset array
layout(set = 0, binding = 0) uniform UniformBuffer {mat4 model;vec4 color;
} ubo[8];void main() {mat4 modelMatrix = ubo[3].model; // 訪問第4個元素vec4 objectColor = ubo[gl_InstanceIndex].color; // 按實例索引訪問
}

也可以在一個綁定點上使用不同的 DescriptorSet index,對應的 glsl 代碼如下

// 三個不同的descriptor set,但都使用binding = 0
layout(set = 0, binding = 0) uniform UniformBuffer { ... } cameraUBO;
layout(set = 1, binding = 0) uniform UniformBuffer { ... } modelUBO;  
layout(set = 2, binding = 0) uniform sampler2D albedoTexture;

最后再更新一下 DescriptorSet 的示意圖,加深理解:
DescriptorSet

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/921510.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/921510.shtml
英文地址,請注明出處:http://en.pswp.cn/news/921510.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[光學原理與應用-461]:波動光學 - 波片實現偏振態的轉換或調整

波片&#xff08;Wave Plate&#xff09;是一種基于雙折射效應的光學元件&#xff0c;其核心功能是通過控制光波中尋常光&#xff08;o光&#xff09;和非尋常光&#xff08;e光&#xff09;的相位差&#xff0c;實現偏振態的轉換或調整。以下是波片的主要功能及其原理的詳細說…

Flutter之riverpod狀態管理詳解

一、riverpod狀態管理中所涉及到的provider對比分析Provider 類型核心用途最佳適用場景優勢劣勢/注意事項Provider(v1)暴露一個恒定不變的&#xff08;或不需要Riverpod管理的&#xff09;對象或值。依賴注入&#xff08;如&#xff1a;Repository, Logger, ApiClient&#xff…

昇騰310i Pro固件說明

目錄 驅動和固件 驅動固件文件 firware固件 24.2版本對應的固件 驅動和固件共同文件 燒結到flash中的固件 總結 啟動流程 固件關系猜測 啟動關鍵信息 efuse atu大小 GPU的bar 總結 驅動和固件 以最新的25.2 對應的驅動和固件為例說明&#xff1a; 驅動固件文件…

【LeetCode熱題100道筆記】二叉樹的右視圖

題目描述 給定一個二叉樹的 根節點 root&#xff0c;想象自己站在它的右側&#xff0c;按照從頂部到底部的順序&#xff0c;返回從右側所能看到的節點值。 示例 1&#xff1a; 輸入&#xff1a;root [1,2,3,null,5,null,4] 輸出&#xff1a;[1,3,4] 解釋&#xff1a;示例 2&am…

Redis《RedisSerializer》

文章目錄RedisSerializer為什么要使用如何使用RedisSerializer總結RedisSerializer 為什么要使用 RedisTemplate 有默認的序列化器&#xff0c;但默認使用的 JdkSerializationRedisSerializer 存在一些問題&#xff1a; 序列化后的數據包含類信息等額外內容&#xff0c;導致…

基于開源AI大模型AI智能名片S2B2C商城小程序的文案引流與社交傳播運營策略研究

摘要&#xff1a;本文聚焦開源AI大模型AI智能名片S2B2C商城小程序&#xff0c;探討其文案引流與社交傳播運營策略。闡述文案在引流中的重要性&#xff0c;分析開源AI大模型AI智能名片S2B2C商城小程序的特性&#xff0c;研究文案設計策略、社交傳播機制及運營策略實施與效果評估…

NGINX vs HAProxy vs LVS:優勢與選型分析

目錄 1. 負載均衡的江湖:三巨頭初探 2. NGINX:全能選手的多面魅力 NGINX 核心優勢 NGINX 的短板 NGINX 實戰案例 3. HAProxy:調度大師的精細之道 HAProxy 核心優勢 HAProxy 的短板 HAProxy 實戰案例 4. LVS:內核猛獸的極致性能 LVS 核心優勢 LVS 的短板 LVS 實…

AI+ 行動意見解讀:音視頻直播SDK如何加速行業智能化

引言&#xff1a;國家戰略、技術基座與行業落地 8 月底&#xff0c;國務院發布了《“人工智能”行動意見》&#xff0c;明確將人工智能提升為繼“互聯網”之后的新一輪國家級戰略抓手。這份文件的關鍵詞已經不再是“連接”與“優化”&#xff0c;而是“重塑”與“躍遷”&#…

2025年華為HCIA人工智能認證發展前景如何?客觀分析!

大家好&#xff01;7月世界人工智能大會即將揭幕首款重載機器人&#xff0c;AI產業化進程再次加速。不少朋友開始轉移關注到和它有一點點關系的——華為HCIA-AI Solution認證&#xff08;人工智能解決方案工程師&#xff09;&#xff0c;但它是否真能搭上這趟技術快車&#xff…

AutoGPT 原理與實踐:從AI助理到“自主任務完成者” (人工智能入門系列)

Elon Musk 曾預言&#xff0c;“AIAgent 終將比人類聰明&#xff0c;并能自動完成大部分工作&#xff0c;這既是機遇也是威脅。” 而 AutoGPT&#xff0c;正是當前 AI 領域涌現出的、最能體現這一預言雛形的產品。它不再是那個需要你一句一句精確指令的“AI助手”&#xff0c;而…

自適應濾波器:Ch4 最小均方(LMS)算法

隨機梯度下降算法簡介 之前的章節中介紹了利用最速下降算法可以實現維納濾波器的最優解&#xff08;LMMSE&#xff09;&#xff0c;其最優解的形式為&#xff1a; w0R?1Pw_{0} R^{- 1}Pw0?R?1P 它基于兩個假設&#xff1a;環境的聯合平穩&#xff0c;即輸入u(n)u(n)u(n)以及…

AI生成內容的版權問題解析與實操指南

針對個人使用AI工具生成視頻/音樂的版權問題深度解析&#xff0c;從法律歸屬、侵權邊界到確權實操&#xff0c;結合最新司法實踐提煉核心要點&#xff1a; 一、版權歸屬核心邏輯&#xff1a;人類智力投入的可視化 當用戶深度參與創作過程時&#xff0c;可主張版權。關鍵看操作…

4.2 機器學習 - 欠擬合和過擬合

模型訓練的核心挑戰是讓模型既 “學好” 訓練數據&#xff0c;又能 “適應” 新數據。欠擬合&#xff08;Underfitting&#xff09;和過擬合&#xff08;Overfitting&#xff09;是阻礙這一目標的兩大典型問題&#xff0c;其本質是 “模型復雜度” 與 “數據復雜度” 不匹配。本…

LeetCode 468. 驗證IP地址 - 詳細解析

文章目錄LeetCode 468. 驗證IP地址 - 詳細解析題目描述IPv4驗證規則&#xff1a;IPv6驗證規則&#xff1a;最優Java解決方案&#xff08;注釋完整版&#xff09;關鍵變量含義及代碼技巧代碼技巧詳解1. 前導零檢查的最佳實踐2. IPv6為什么不能用Character.isDigit()3. 針對性注釋…

新能源研發,用新型實驗記錄本:ELN

新能源&#xff08;材料&#xff09;研發如火如荼&#xff0c;競爭激烈。以電池為例&#xff0c;新能源汽車的崛起、儲能技術的突破&#xff0c;讓電池成為了能源領域的“新寵”。電池研發已經成為熱門賽場&#xff0c;各研發團隊都在與時間賽跑&#xff0c;試圖維持優勢或彎道…

大語言模型領域最新進展

CSDN大禮包《人工智能大模型課程》 CSDN大禮包《人工智能平臺設計開發課程課程》

【網安干貨】--計算機網絡知識梳理總結(二)

這是計算機網絡知識梳理的第二篇&#xff0c;真正去梳理才發現內容好多好多好多好多好多啊…怕是預計要寫四篇 注意&#xff1a;如果看不清可以右鍵復制圖片鏈接到瀏覽器訪問或另存為照片并放大查看 計算機網絡2 計算機網絡協議2.1 網絡協議的定義與核心要素2.1.1 協議的定義2.…

百度前端社招面經二

社招 百度 前端開發 二面 base 北京 react 17 和 18 的差異react的響應式原理&#xff0c;js是如何驅動模塊的webpacke 4 和 5 差異webpacke 熱更新原理。Tree Shaking 是干嘛的import 和 require 區別&#xff0c;都會被Tree Shaking嗎隱藏元素的幾種方式三欄布局&#xff0c;…

結合prompt分析NodeRAG的build過程

之前介紹了NodeRAG的節點類型和安裝過程。 linux環境conda安裝NodeRAG示例-CSDN博客 這里嘗試從prompt代碼角度分析NodeRAG如何將文檔轉化為節點、關系。 1 整體處理流程 NodeRAG定義了如下所示狀態及處理流程。 # define the state to pipeline mapping self.state_pipelin…

我改寫的二分法XML轉CSV文件程序速度追上了張澤鵬先生的

以下是美團龍貓初稿&#xff0c;我改正&#xff0c;DeepSeek重新格式化的代碼。 重要改正點&#xff1a; 1.二分查找用goto控制迭代&#xff0c;返回<row的正確位置 2.在緩沖區頭填上父標簽使expat能連續解析不報錯 #include <stdio.h> #include <stdlib.h> #in…