Vulkan 下的指令,比如繪制指令和記憶體傳輸指令并不是直接通過函數調用執行的。我們需要将所有要執行的操作記錄在一個指令緩沖對象,然後送出給可以執行這些操作的隊列才能執行。這使得我們可以在程式初始化時就準備好所有要指定的指令序列,在渲染時直接送出執行。也使得多線程送出指令變得更加容易。我們隻需要在需要指定執行的使用,将指令緩沖對象送出給 Vulkan 處理接口。
示例demo:
//指令池對象,管理指令緩沖對象使用的記憶體,并負責指令緩沖對象的配置設定--11
VkCommandPool commandPool ;
//存儲建立的指令緩沖對象,指令緩沖對象會在指令池對象被清除時自動被清除--11
std::vector<VkCommandBuffer> commandBuffers;
//建立指令池--11
void createCommandPool(){
/**
指令緩沖對象在被送出給我們之前擷取的隊列後,被 Vulkan 執行。每
個指令池對象配置設定的指令緩沖對象隻能送出給一個特定類型的隊列。在這
裡,我們使用的是繪制指令,它可以被送出給支援圖形操作的隊列。
有下面兩種用于指令池對象建立的标記,
可以提供有用的資訊給Vulkan的驅動程式進行一定優化處理:
VK_COMMAND_POOL_CREATE_TRANSIENT_BIT:
使用它配置設定的指令緩沖對象被頻繁用來記錄新的指令
(使用這一标記可能會改變幀緩沖對象的記憶體配置設定政策)
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT:
指令緩沖對象之間互相獨立,不會被一起重置。
不使用這一标記,指令緩沖對象會被放在一起重置
這裡我們隻在程式初始化時記錄指令到指令緩沖對象,
然後在程式的主循環中執行指令,是以,我們不使用上面這兩個标記
*/
QueueFamilyIndices queueFamilyIndices =
findQueueFamilies(physicalDevice);
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;
poolInfo.flags = 0;
//指令池對象的建立
if(vkCreateCommandPool(device,&poolInfo,nullptr,
&commandPool) != VK_SUCCESS){
throw std::runtime_error("failed to create command pool!");
}
}
/**
指令緩沖對象,用來記錄繪制指令
由于繪制操作是在幀緩沖上進行的,我們需要為交換鍊中的每一個圖像配置設定一個指令緩沖對象
*/
//建立指令緩沖對象--11
void createCommandBuffers(){
commandBuffers.resize(swapChainFramebuffers.size());
//指定配置設定使用的指令池和需要配置設定的指令緩沖對象個數
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType =
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = commandPool;
/**
level 成員變量用于指定配置設定的指令緩沖對象是主要指令緩沖對象還是輔助指令緩沖對象:
VK_COMMAND_BUFFER_LEVEL_PRIMARY:
可以被送出到隊列進行執行,但不能被其它指令緩沖對象調用。
VK_COMMAND_BUFFER_LEVEL_SECONDARY:
不能直接被送出到隊列進行執行,但可以被主要指令緩沖對象調用執行
在這裡,我們沒有使用輔助指令緩沖對象,但輔助治理給緩沖對象的
好處是顯而易見的,我們可以把一些常用的指令存儲在輔助指令緩沖對象,
然後在主要指令緩沖對象中調用執行
*/
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = (uint32_t)commandBuffers.size();
//配置設定指令緩沖對象
if(vkAllocateCommandBuffers(device,&allocInfo,
commandBuffers.data())!= VK_SUCCESS){
throw std::runtime_error("failed to allocate command buffers!");
}
//記錄指令到指令緩沖
for(size_t i=0;i<commandBuffers.size();i++){
//指定一些有關指令緩沖的使用細節
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType =
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
/**
flags 成員變量用于指定我們将要怎樣使用指令緩沖。它的值可以是下面這些:
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT:
指令緩沖在執行一次後,就被用來記錄新的指令.
VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT:
這是一個隻在一個渲染流程内使用的輔助指令緩沖.
VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT:
在指令緩沖等待執行時,仍然可以送出這一指令緩沖
*/
beginInfo.flags =
VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
//用于輔助指令緩沖,可以用它來指定從調用它的主要指令緩沖繼承的狀态
beginInfo.pInheritanceInfo = nullptr;
//指令緩沖對象記錄指令後,調用vkBeginCommandBuffer函數會重置指令緩沖對象
//開始指令緩沖的記錄操作
if(vkBeginCommandBuffer(commandBuffers[i],&beginInfo)!=VK_SUCCESS){
throw std::runtime_error(
"failed to begin recording command buffer.");
}
//指定使用的渲染流程對象
VkRenderPassBeginInfo renderPassInfo = {};
renderPassInfo.sType =VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
//指定使用的渲染流程對象
renderPassInfo.renderPass = renderPass;
//指定使用的幀緩沖對象
renderPassInfo.framebuffer = swapChainFramebuffers[i];
/**
renderArea指定用于渲染的區域。位于這一區域外的像素資料會處于未定義狀态。
通常,我們将這一區域設定為和我們使用的附着大小完全一樣.
*/
renderPassInfo.renderArea.offset = {0,0};
renderPassInfo.renderArea.extent = swapChainExtent;
VkClearValue clearColor = {0.0f, 0.0f, 0.0f, 1.0f};
//指定标記後,使用的清除值
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
/**
所有可以記錄指令到指令緩沖的函數的函數名都帶有一個 vkCmd 字首,
并且這些函數的傳回值都是 void,也就是說在指令記錄操作完全結束前,
不用進行任何錯誤處理。
這類函數的第一個參數是用于記錄指令的指令緩沖對象。第二個參數
是使用的渲染流程的資訊。最後一個參數是用來指定渲染流程如何提供繪
制指令的标記,它可以是下面這兩個值之一:
VK_SUBPASS_CONTENTS_INLINE:
所有要執行的指令都在主要指令緩沖中,沒有輔助指令緩沖需要執行
VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS:
有來自輔助指令緩沖的指令需要執行。
*/
//開始一個渲染流程
vkCmdBeginRenderPass( commandBuffers[i], &renderPassInfo,
VK_SUBPASS_CONTENTS_INLINE) ;
//綁定圖形管線,第二個參數用于指定管線對象是圖形管線還是計算管線
vkCmdBindPipeline(commandBuffers[i],VK_PIPELINE_BIND_POINT_GRAPHICS,
graphicsPipeline ) ;
/**
至此,我們已經送出了需要圖形管線執行的指令,以及片段着色器使用的附着
*/
/**
vkCmdDraw參數:
1.記錄有要執行的指令的指令緩沖對象
2. vertexCount:
盡管這裡我們沒有使用頂點緩沖,但仍然需要指定三個頂點用于三角形的繪制。
3.instanceCount:用于執行個體渲染,為 1 時表示不進行執行個體渲染
4.firstVertex:用于定義着色器變量 gl_VertexIndex 的值
5.firstInstance:用于定義着色器變量 gl_InstanceIndex 的值
*/
//開始調用指令進行三角形的繪制操作
vkCmdDraw( commandBuffers [ i ] , 3 , 1 , 0 , 0) ;
//結束渲染流程
vkCmdEndRenderPass( commandBuffers [ i ] ) ;
//結束記錄指令到指令緩沖
if(vkEndCommandBuffer(commandBuffers[i]) != VK_SUCCESS){
throw std::runtime_error("failed to record command buffer!");
}
}
}
//銷毀指令池對象--11
vkDestroyCommandPool(device,commandPool,nullptr);
基本指令緩沖區操作
在其他圖形API中,應用程式可以通過進行API調用來設定諸如線寬之類的屬性glLineWidth()。在幕後,驅動程式将此API調用轉換為特定于GPU的指令,并将指令放入指令緩沖區。驅動程式還通過在應用程式視圖之外建立和銷毀指令緩沖區來管理指令緩沖區。最終,驅動程式将指令緩沖區“送出”到GPU以處理指令。
在Vulkan中,您建立一個指令緩沖區并進行類似的Vulkan API調用vkCmdSetLineWidth()以向指令緩沖區添加指令。由于每個GPU都有自己的“指令集”,是以驅動程式仍需要做一些工作來生成GPU特定的指令來設定線寬。

這裡,驅動程式确定要插入指令緩沖區的适當二進制GPU指令,以訓示GPU使用5的線寬來繪制後續行。您不需要檢視實際的指令緩沖區内容,因為驅動程式正在為您執行這部分GPU程式設計。
指令緩沖池
由于建立和銷毀單個指令緩沖區可能很昂貴,是以Vulkan使用指令緩沖池來管理指令緩沖區。使用指令緩沖池的動機包括:
1.某些應用程式使用短指令指令緩沖區,這意味着它們經常被建立和銷毀。專用池配置設定器通常可以更有效地處理這些配置設定模式。
2.指令緩沖區記憶體的特殊之處在于它必須對CPU和GPU都可見。在許多系統中,記憶體到處理器(CPU或GPU)的映射隻能使用大粒度來完成,這意味着小的指令緩沖區可能會浪費大量記憶體。
3.記憶體映射很昂貴,因為它通常涉及修改頁表和使TLB緩存無效。最好映射一個較大的指令緩沖池并在其中配置設定單個指令緩沖區,而不是單獨映射每個指令緩沖區。
指令緩沖池和隊列系列
驅動程式使用适合于讀取指令緩沖存儲器的GPU硬體的記憶體配置設定屬性來配置設定指令緩沖池。此類屬性的示例包括記憶體對齊要求和緩存行為。
如果GPU硬體中存在多個硬體隊列(如實體裝置隊列系列所述),則驅動程式可能需要配置設定具有不同記憶體配置設定屬性的指令緩沖池,這些屬性特定于每個GPU硬體隊列。隻要驅動程式知道包含指令緩沖區将使用的隊列的隊列系列,驅動程式就會為您處理這些詳細資訊。
使用指令緩沖區
建立指令緩沖區後,通過調用開始“錄制” vkBeginCommandBuffer()。調用此函數會将指令緩沖區置于“記錄”狀态,并允許您調用将指令插入指令緩沖區的許多“vkCmd *”函數之一。您已經vkCmdSetLineWidth()在本節中看到了此示例。另一個例子是vkCmdDraw(),它告訴GPU繪制一些頂點。當您完成将指令插入指令緩沖區後,您将調用 vkEndCommandBuffer()以訓示您已完成并将指令緩沖區從記錄狀态中取出并使其可供使用。
您将在後面的部分中看到實際填充指令緩沖區的代碼。
完成指令緩沖區記錄不會使GPU做任何事情。為了讓GPU處理指令緩沖區,您必須使用它将其送出到GPU的隊列中vkQueueSubmit()。在向GPU送出指令緩沖區之前,還有很多事情需要設定,這将在本教程的最後一節中進行
建立指令緩沖區
///5.建立指令緩沖區
//使用建立裝置時要使用的隊列
//實際上,您必須為應用程式打算使用的每個唯一隊列系列建立指令緩沖池。
//由于在建立裝置時僅指定了一個隊列系列,是以一個指令緩沖池足以滿足這些樣本的要求
uint32_t graphics_queue_family_index=queue_info.queueFamilyIndex;
//指令緩沖池隻能與一個隊列系列相關聯,建構指令緩沖區資訊
VkCommandPoolCreateInfo cmd_pool_info = {};
cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmd_pool_info.pNext = NULL;
//指定隊列系列索
cmd_pool_info.queueFamilyIndex = graphics_queue_family_index;
cmd_pool_info.flags = 0;
VkCommandPool cmd_pool;
//建立指令緩沖池
res = vkCreateCommandPool(device, &cmd_pool_info, NULL, &cmd_pool);
assert(res == VK_SUCCESS);
//從指令緩沖池中配置設定指令緩沖區
VkCommandBufferAllocateInfo cmd = {};
cmd.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmd.pNext = NULL;
cmd.commandPool = cmd_pool;
cmd.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmd.commandBufferCount = 1;//個數
VkCommandBuffer cmdBuffer;
//建立指令緩沖區
res = vkAllocateCommandBuffers(device, &cmd, &cmdBuffer);
assert(res == VK_SUCCESS);
VkCommandBuffer cmd_bufs[1] = {cmdBuffer};
vkFreeCommandBuffers(device, cmd_pool, 1, cmd_bufs);//釋放資源
vkDestroyCommandPool(device, cmd_pool, NULL);//銷毀資源