操作系統(tǒng):Windows8.1

顯卡:Nivida GTX965M

開發(fā)工具:Visual Studio 2017


Introduction

頂點(diǎn)緩沖區(qū)現(xiàn)在已經(jīng)可以正常工作,但相比于顯卡內(nèi)部讀取數(shù)據(jù),單純從CPU訪問內(nèi)存數(shù)據(jù)的方式性能不是最佳的。最佳的方式是采用VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT標(biāo)志位,通常來說用在專用的圖形卡,CPU是無法訪問的。在本章節(jié)我們創(chuàng)建兩個(gè)頂點(diǎn)緩沖區(qū)。一個(gè)緩沖區(qū)提供給CPU-HOST內(nèi)存訪問使用,用于從頂點(diǎn)數(shù)組中提交數(shù)據(jù),另一個(gè)頂點(diǎn)緩沖區(qū)用于設(shè)備local內(nèi)存。我們將會(huì)使用緩沖區(qū)拷貝的命令將數(shù)據(jù)從暫存緩沖區(qū)拷貝到實(shí)際的圖形卡內(nèi)存中。

Transfer queue


緩沖區(qū)拷貝的命令需要隊(duì)列簇支持傳輸操作,可以通過VK_QUEUE_TRANSFER_BIT標(biāo)志位指定。好消息是任何支持VK_QUEUE_GRAPHICS_BIT 或者 VK_QUEUE_COMPUTE_BIT標(biāo)志位功能的隊(duì)列簇都默認(rèn)支持VK_QUEUE_TRANSFER_BIT操作。這部分的實(shí)現(xiàn)不需要在queueFlags顯示的列出。

 

如果需要挑戰(zhàn),甚至可以嘗試為不同的隊(duì)列簇指定具體的傳輸操作。這部分實(shí)現(xiàn)需要對代碼做出如下修改:

  • 修改QueueFamilyIndicesfindQueueFamilies,明確指定隊(duì)列簇需要具備VK_QUEUE_TRANSFER標(biāo)志位,而不是VK_QUEUE_GRAPHICS_BIT。

  • 修改createLogicalDevice函數(shù),請求一個(gè)傳輸隊(duì)列句柄。

  • 創(chuàng)建兩個(gè)命令對象池分配命令緩沖區(qū),用于向傳輸隊(duì)列簇提交命令。

  • 修改資源的sharingModeVK_SHARING_MODE_CONCURRENT,并指定為graphics和transfer隊(duì)列簇。

  • 提交任何傳輸命令,諸如vkCmdCopyBuffer(本章節(jié)使用)到傳輸隊(duì)列,而不是圖形隊(duì)列。

需要一些額外的工作,但是它我們更清楚的了解資源在不同隊(duì)列簇如何共享的。

Abstracting buffer creation


考慮到我們在本章節(jié)需要?jiǎng)?chuàng)建多個(gè)緩沖區(qū),比較理想的是創(chuàng)建輔助函數(shù)來完成。新增函數(shù)createBuffer并將createVertexBuffer中的部分代碼(不包括映射)移入該函數(shù)。 

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) {
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;    if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {        throw std::runtime_error("failed to create buffer!");
    }

    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);    if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {        throw std::runtime_error("failed to allocate buffer memory!");
    }

    vkBindBufferMemory(device, buffer, bufferMemory, 0);
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 該函數(shù)需要傳遞緩沖區(qū)大小,內(nèi)存屬性和usage最終創(chuàng)建不同類型的緩沖區(qū)。最后兩個(gè)參數(shù)保存輸出的句柄。

 

我們可以從createVertexBuffer函數(shù)中移除創(chuàng)建緩沖區(qū)和分配內(nèi)存的代碼,并使用createBuffer替代:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

void createVertexBuffer() {
    VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
    createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory);    void* data;
    vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, vertices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, vertexBufferMemory);
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

運(yùn)行程序確保頂點(diǎn)緩沖區(qū)仍然正常工作。

Using a staging buffer


我們現(xiàn)在改變createVertexBuffer函數(shù),僅僅使用host緩沖區(qū)作為臨時(shí)緩沖區(qū),并且使用device緩沖區(qū)作為最終的頂點(diǎn)緩沖區(qū)。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

void createVertexBuffer() {
    VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, vertices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, stagingBufferMemory);

    createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

我們使用stagingBufferMemory劃分新的stagingBuffer暫存緩沖區(qū)用來映射、拷貝頂點(diǎn)數(shù)據(jù)。在本章節(jié)我們使用兩個(gè)新的緩沖區(qū)usage標(biāo)致類型:

  • VK_BUFFER_USAGE_TRANSFER_SRC_BIT:緩沖區(qū)可以用于源內(nèi)存?zhèn)鬏敳僮鳌?/p>

  • VK_BUFFER_USAGE_TRANSFER_DST_BIT:緩沖區(qū)可以用于目標(biāo)內(nèi)存?zhèn)鬏敳僮鳌?/p>

vertexBuffer現(xiàn)在使用device類型作為分配的內(nèi)存類型,意味著我們不可以使用vkMapMemory內(nèi)存映射。然而我們可以從stagingBuffervertexBuffer拷貝數(shù)據(jù)。我們需要指定stagingBuffer的傳輸源標(biāo)志位,還要為頂點(diǎn)緩沖區(qū)vertexBuffer的usage設(shè)置傳輸目標(biāo)的標(biāo)志位。

 

我們新增函數(shù)copyBuffer,用于從一個(gè)緩沖區(qū)拷貝數(shù)據(jù)到另一個(gè)緩沖區(qū)。

void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {

}

使用命令緩沖區(qū)執(zhí)行內(nèi)存?zhèn)鬏數(shù)牟僮髅?,就像繪制命令一樣。因此我們需要分配一個(gè)臨時(shí)命令緩沖區(qū)?;蛟S在這里希望為短期的緩沖區(qū)分別創(chuàng)建command pool,那么可以考慮內(nèi)存分配的優(yōu)化策略,在command pool生成期間使用VK_COMMAND_POOL_CREATE_TRANSIENT_BIT標(biāo)志位。

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = commandPool;
    allocInfo.commandBufferCount = 1;

    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

立即使用命令緩沖過去進(jìn)行記錄:

VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

vkBeginCommandBuffer(commandBuffer, &beginInfo);

應(yīng)用于繪制命令緩沖區(qū)的VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT標(biāo)志位在此不必要,因?yàn)槲覀冎枰褂靡淮蚊罹彌_區(qū),等待該函數(shù)返回,直到復(fù)制操作完成。告知driver驅(qū)動(dòng)程序使用VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT是一個(gè)好的習(xí)慣。

VkBufferCopy copyRegion = {};
copyRegion.srcOffset = 0; // OptionalcopyRegion.dstOffset = 0; // OptionalcopyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

緩沖區(qū)內(nèi)容使用vkCmdCopyBuffer命令傳輸。它使用source和destination緩沖區(qū)及一個(gè)緩沖區(qū)拷貝的區(qū)域作為參數(shù)。這個(gè)區(qū)域被定義在VkBufferCopy結(jié)構(gòu)體中,描述源緩沖區(qū)的偏移量,目標(biāo)緩沖區(qū)的偏移量和對應(yīng)的大小。與vkMapMemory命令不同,這里不可以指定VK_WHOLE_SIZE。

vkEndCommandBuffer(commandBuffer);

此命令緩沖區(qū)僅包含拷貝命令,因此我們可以在此之后停止記錄?,F(xiàn)在執(zhí)行命令緩沖區(qū)完成傳輸:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;

vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(graphicsQueue);

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

與繪制命令不同的是,這個(gè)時(shí)候我們不需要等待任何事件。我們只是想立即在緩沖區(qū)執(zhí)行傳輸命令。這里有同樣有兩個(gè)方式等待傳輸命令完成。我們可以使用vkWaitForFences等待屏障fence,或者只是使用vkQueueWaitIdle等待傳輸隊(duì)列變?yōu)榭臻gidle。一個(gè)屏障允許安排多個(gè)連續(xù)的傳輸操作,而不是一次執(zhí)行一個(gè)。這給了驅(qū)動(dòng)程序更多的優(yōu)化空間。

vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);

不要忘記清理用于傳輸命令的命令緩沖區(qū)。

 

我們可以從createVertexBuffer函數(shù)中調(diào)用copyBuffer,拷貝頂點(diǎn)數(shù)據(jù)到設(shè)備緩沖區(qū)中:

createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, vertexBuffer, vertexBufferMemory);

copyBuffer(stagingBuffer, vertexBuffer, bufferSize)

當(dāng)從暫存緩沖區(qū)拷貝數(shù)據(jù)到圖形卡設(shè)備緩沖區(qū)完畢后,我們應(yīng)該清理它:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

 ...

    copyBuffer(stagingBuffer, vertexBuffer, bufferSize);

    vkDestroyBuffer(device, stagingBuffer, nullptr);
    vkFreeMemory(device, stagingBufferMemory, nullptr);
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開發(fā)培訓(xùn)

運(yùn)行程序確認(rèn)三角形繪制正常。它是可見的,但其頂點(diǎn)數(shù)據(jù)現(xiàn)在是從高性能的顯存中加載。當(dāng)我們開始渲染更復(fù)雜的幾何圖形時(shí),這個(gè)技術(shù)是非常重要。

 Conclusion


需要了解的是,在真實(shí)的生產(chǎn)環(huán)境中的應(yīng)用程序里,不建議為每個(gè)緩沖區(qū)調(diào)用vkAllocateMemory分配內(nèi)存。內(nèi)存分配的最大數(shù)量受到maxMemoryAllocationCount物理設(shè)備所限,及時(shí)在像NVIDIA GTX1080這樣的高端硬件上,也只能提供4096的大小。同一時(shí)間,為大量對象分配內(nèi)存的正確方法是創(chuàng)建一個(gè)自定義分配器,通過使用我們在許多函數(shù)中用到的偏移量offset,將一個(gè)大塊的可分配內(nèi)存區(qū)域劃分為多個(gè)可分配內(nèi)存塊,提供緩沖區(qū)使用。

 

也可以自己實(shí)現(xiàn)一個(gè)靈活的內(nèi)存分配器,或者使用GOUOpen提供的VulkanMemoryAllocator庫。然而,對于本教程,我們可以做到為每個(gè)資源使用單獨(dú)的分配,因?yàn)槲覀儾粫?huì)觸達(dá)任何資源限制條件。 

http://www.cnblogs.com/heitao/p/7057566.html