天天看點

Vulkan入門(14)-VkImage圖像的建立.md參考資料簡述一. 紋理貼圖

文章目錄

  • 參考資料
  • 簡述
  • 一. 紋理貼圖
    • 1.1 圖像庫
    • 1.1 讀取圖檔
    • 1.2 緩存讀取的圖檔
    • 1.3 紋理圖像(Texture Image)
      • 1.3.1 VkImageCreateInfo
      • 1.3.2 vkCreateImage
      • 1.3.3 createImage
    • 1.4 布局轉換
      • 1.4.1 VkImageMemoryBarrier
        • 1.4.1.1 VkImageSubresourceRange
        • 1.4.1.2 VkImageAspectFlags
      • 1.4.2 vkCmdPipelineBarrier
    • 1.5 拷貝緩存資料至Image
      • 1.5.1 VkBufferImageCopy
      • 1.5.2 vkCmdCopyBufferToImage
    • 1.6 準備紋理圖像
    • 1.7 轉換屏障的含義 VkAccessFlags
      • 1.7.1 VkAccessFlagBits
      • 1.7.2 VkPipelineStageFlags 管道階段
    • 1.8 清理
    • 1.9 總結
    • 1.10 Windows上的CMakefileLists.txt寫法

參考資料

簡述

在之前我們使用頂點着色器以及描述符來實作繪制有顔色的幾何體,還實作了旋轉動畫。接下來我們學習一下紋理貼圖,這個将是我們實作加載繪制基本3D模型的基礎。

添加紋理的基本步驟有:

  1. 建立由裝置記憶體支援的圖像對象
  2. 用圖像檔案中的像素填充建立的圖像對象
  3. 建立圖像采樣器
  4. 添加一個組合的圖像采樣器描述符來從紋理中采樣顔色

我們以前已經使用過圖像對象,但是這些對象是由swap chain擴充自動建立的。這次需要手動建立,建立圖像并填充資料類似于建立頂點緩沖區。我們将通過建立一個暫存資源和填充它與像素資料,然後我們複制這到我們将用于渲染的最終圖像對象。

可以建立一個暫存圖像,不過Vulkan允許将像素從VkBuffer複制到image中,而且這個API在某些硬體上實際上更快。我們将首先建立這個緩沖區并填充像素值,然後我們将建立一個圖像複制像素到。建立image與建立緩沖區并沒有太大的不同。它包括查詢記憶體需求、配置設定裝置記憶體并綁定它,就像我們之前看到的那樣。

圖像可以有不同的布局,影響像素在記憶體中的存儲方式。例如,由于圖形硬體的工作方式,簡單地逐行存儲像素可能不會帶來最好的性能。當對圖像執行任何操作時,確定它們具有在該操作中使用的最佳布局。比如指定渲染通道時其中一些布局有:

  1. VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: 适合呈現(present)
  2. VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:片段着色器中寫入顔色的最佳附件
  3. VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: 作為傳輸操作的最佳源,如vkCmdCopyImageToBuffer
  4. VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 作為傳輸操作的最佳目的地,如vkCmdCopyBufferToImage
  5. VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: 适合着色器采樣

轉換圖像布局的最常見方法之一是管道屏障(pipeline barrier)。管道屏障主要用于同步對資源的通路,例如確定在讀取圖像之前将其寫入。後面我們将了解如何将管道壁壘用于轉換布局。

使用VK_SHARING_MODE_EXCLUSIVE時,可以另外使用屏障來轉移隊列系列的所有權。

一. 紋理貼圖

1.1 圖像庫

有許多庫可用于加載圖像,您甚至可以編寫自己的代碼來加載BMP和PPM等簡單格式。 這裡我們将使用stb集合中的stb_image庫。 這樣做的好處是所有代碼都在一個檔案中,是以不需要任何棘手的建構配置。 下載下傳stb_image.h并将其存儲在友善的位置,例如儲存GLFW和GLM的目錄。 将位置添加到您的包含路徑。

stb_image庫位址: https://github.com/nothings/stb

下載下傳後解壓,放在指定目錄,然後修改我們的Makefile檔案:

VULKAN_SDK_PATH = /home/jh/Program/vulkan/1.2.170.0/x86_64
STB_IMAGE_PATH = /home/jh/Program/stb-image

CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include -I$(STB_IMAGE_PATH)
           

1.1 讀取圖檔

在shaders目錄旁邊建立一個新的目錄textures來存儲紋理圖像:

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-9AyMXavx-1617639060029)(assert/texture.jpg)]

首先添加頭檔案:

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
           

預設情況下,頭檔案隻定義函數的原型。一個代碼檔案需要包含STB_IMAGE_IMPLEMENTATION定義的頭檔案來包含函數體,否則會有連結錯誤。

void initVulkan() {
    ...
    createCommandPool();
    // 因為需要使用指令緩沖,是以在建立指令池之後調用
    createTextureImage();
    createVertexBuffer();
    ...
}

void createTextureImage() {
    int texWidth, texHeight, texChannels;
    // 加載texture.jpg圖像
    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    // 每個像素4個位元組
    VkDeviceSize imageSize = texWidth * texHeight * 4;
    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }
}
           

stbi_load函數将檔案路徑和要加載的通道數量作為參數。STBI_rgb_alpha值會強制為圖像加載Alpha通道,即使它沒有通道也是如此, 與其他紋理保持一緻性。中間的三個參數是輸出圖像中通道的寬度、高度和實際數量。傳回的指針是像素值數組中的第一個元素。在STBI_rgba_alpha中,像素逐行排列,每個像素4個位元組,總共texWidth * texHeight * 4個值。

1.2 緩存讀取的圖檔

現在,我們将在主機可見記憶體中建立一個緩沖區,以便我們可以使用vkMapMemory并将像素複制到其中。 将此臨時緩沖區的變量添加到createTextureImage函數:

void createTextureImage() {
    int texWidth, texHeight, texChannels;
    // 加載texture.jpg圖像
    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    // 每個像素4個位元組
    VkDeviceSize imageSize = texWidth * texHeight * 4;
    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    // 緩沖區應該在主機可見記憶體中,以便我們可以映射它,并且它應該可用作傳輸源,以便我們以後可以複制
    createBuffer(imageSize, 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, imageSize, 0, &data);
    memcpy(data, pixels, static_cast<size_t>(imageSize));
    vkUnmapMemory(device, stagingBufferMemory);
    // 最後釋放原始像素資料
    stbi_image_free(pixels);
}
           

1.3 紋理圖像(Texture Image)

盡管我們可以設定着色器來通路緩沖區中的像素值,但為此目的最好使用Vulkan中的圖像對象-VkImage。 通過使用2D坐标,圖像對象将使檢索顔色更加容易和快捷。 圖像對象中的像素稱為紋理像素:

VkImage textureImage;
VkDeviceMemory textureImageMemory;

void createTextureImage() {
    ...
    VkImageCreateInfo imageInfo = {};
    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType = VK_IMAGE_TYPE_2D; //二維圖像
    imageInfo.extent.width = static_cast<uint32_t>(texWidth);
    imageInfo.extent.height = static_cast<uint32_t>(texHeight);
    imageInfo.extent.depth = 1;
    // 圖像的最小采樣的細節級别
    imageInfo.mipLevels = 1;
    // 圖像中的層數
    imageInfo.arrayLayers = 1;
    // 指定圖像格式,對于像素像素,使用與緩沖區中像素相同的格式,否則複制操作将失敗
    imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
    // 圖像平鋪模式,這裡指定圖像像素最佳記憶體拼接布局
    // 與圖像的布局不同,平鋪模式不能在以後更改。如果希望能夠直接通路圖像記憶體中的texel,則必須使用VK_IMAGE_TILING_OPTIMAL
    imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;

    // 圖像的initialLayout隻有兩個可能的值:VK_IMAGE_LAYOUT_UNDEFINED || VK_IMAGE_LAYOUT_PREINITIALIZED
    imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

    imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
    // 圖像将僅由一個隊列族使用, 是以獨占模式
    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    // 圖像采樣,每個像素都采樣
    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.flags = 0; // Optional
    // 建立圖像
    if (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {
        throw std::runtime_error("failed to create image!");
    }

    // 同樣的,需要給Image配置設定記憶體空間
    VkMemoryRequirements memRequirements;
    vkGetImageMemoryRequirements(device, textureImage, &memRequirements);
    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex =
            findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate image memory!");
    }
    // 綁定圖像和記憶體
    vkBindImageMemory(device, textureImage, textureImageMemory, 0);
}
           

對于initialLayout,很少有情況需要在第一次過渡期間保留紋理像素,但是如果想将圖像與VK_IMAGE_TILING_LINEAR布局結合使用作為緩存圖像。 在這種情況下,将紋理像素資料上傳到其中,然後将圖像轉換為傳輸源而又不丢失資料。但是,我們首先将圖像轉換為傳輸目标,然後從緩沖區對象将紋理像素資料複制到該圖像,是以使用VK_IMAGE_LAYOUT_UNDEFINED。

對于usage, 與緩沖區建立期間的含義相同。 該圖像将用作緩沖區副本的目的地,是以應将其設定為傳輸目的地。 我們還希望能夠從着色器通路圖像來為網格着色,是以用法應包括VK_IMAGE_USAGE_SAMPLED_BIT。

采樣标志與多重采樣有關。 這僅與将用作附件的圖像有關,這裡使用一個樣本。 對于與稀疏圖像有關的圖像,有一些可選的标志。 稀疏圖像是其中實際上僅某些區域由記憶體支援的圖像。 例如,如果将3D紋理用于體素地形,則可以使用它來避免配置設定記憶體來存儲大量的“空”值,這裡我們設定為0。

1.3.1 VkImageCreateInfo

建立圖像的一系列參數是在VkImageCreateInfo中指明的:

typedef struct VkImageCreateInfo {
    VkStructureType          sType;
    const void*              pNext;
    VkImageCreateFlags       flags;
    VkImageType              imageType;
    VkFormat                 format;
    VkExtent3D               extent;
    uint32_t                 mipLevels;
    uint32_t                 arrayLayers;
    VkSampleCountFlagBits    samples;
    VkImageTiling            tiling;
    VkImageUsageFlags        usage;
    VkSharingMode            sharingMode;
    uint32_t                 queueFamilyIndexCount;
    const uint32_t*          pQueueFamilyIndices;
    VkImageLayout            initialLayout;
} VkImageCreateInfo;
           
  1. sType是此結構的類型,VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO
  2. pNext是NULL或指向擴充特定結構的指針
  3. flag是VkImageCreateFlagBits的位掩碼,用于描述圖像的其他參數
  4. imageType是VkImageType值,用于指定圖像的基本尺寸。就圖像類型而言,陣列紋理中的圖層不算作尺寸
    1. VK_IMAGE_TYPE_1D指定一維圖像
    2. VK_IMAGE_TYPE_2D指定二維圖像
    3. VK_IMAGE_TYPE_3D指定三維圖像
  5. format是一種VkFormat,它描述了将包含在圖像中的texel塊的格式和類型
  6. extent是一個VkExtent3D,它描述基本級别的每個次元中的資料元素數量
  7. mipLevels描述可用于圖像的最小采樣的細節級别的數量
  8. arrayLayers是圖像中的層數
  9. samples是VkSampleCountFlagBits,用于指定每個紋理像素的樣本數
  10. tiling是一個VkImageTiling值,它指定記憶體中紋理元素塊的平鋪模式
    1. VK_IMAGE_TILING_LINEAR: 以主要行順序排列像素
    2. VK_IMAGE_TILING_OPTIMAL: 指定最佳平鋪(紋理像素以實作相關的安排進行布局,以實作更好的記憶體通路)
    3. VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT: 表示圖檔的拼貼是由Linux DRM格式修飾符定義的
  11. usage是VkImageUsageFlagBits的位掩碼,用于描述圖像的預期用法
  12. SharingMode是VkSharingMode值,用于指定多個隊列系列将通路圖像時的圖像共享模式
  13. queueFamilyIndexCount是pQueueFamilyIndi​​ces數組中的條目數
  14. pQueueFamilyIndi​​ces是将通路此圖像的隊列系列的清單(如果sharedMode不是VK_SHARING_MODE_CONCURRENT,則将被忽略)
  15. initialLayout是一個VkImageLayout值,它指定圖像的所有圖像子資源的初始VkImageLayout。請參閱圖像布局
    1. VK_IMAGE_LAYOUT_UNDEFINED: GPU不可用,第一次轉換将丢棄紋理像素
    2. VK_IMAGE_LAYOUT_PREINITIALIZED:GPU無法使用,但第一個過渡将保留紋理像素

1.3.2 vkCreateImage

圖像表示多元(最多3個)資料數組,可用于各種目的(例如附件、紋理),通過描述符集将其綁定到圖形或計算管道,或直接将其指定為特定指令的參數。

VkResult vkCreateImage(
    VkDevice                                    device,
    const VkImageCreateInfo*                    pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkImage*                                    pImage);
           
  1. device是建立Image的邏輯裝置
  2. pCreateInfo是指向VkImageCreateInfo結構的指針,該結構包含用于建立圖像的參數
  3. pAllocator如“記憶體配置設定”一章中所述控制主機記憶體配置設定
  4. pImage是指向VkImage句柄的指針,在該句柄中傳回生成的圖像對象

1.3.3 createImage

現在我們重構下createTextureImage, 将建立VkImage的部分單獨做個函數:

void createImage(uint32_t width, uint32_t height, VkFormat format,
        VkImageTiling tiling, VkImageUsageFlags usage,
        VkMemoryPropertyFlags properties, VkImage& image,
        VkDeviceMemory& imageMemory) {

    VkImageCreateInfo imageInfo = {};
    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType = VK_IMAGE_TYPE_2D; //二維圖像
    imageInfo.extent.width = static_cast<uint32_t>(width);
    imageInfo.extent.height = static_cast<uint32_t>(height);
    imageInfo.extent.depth = 1;
    // 圖像的最小采樣的細節級别
    imageInfo.mipLevels = 1;
    // 圖像中的層數
    imageInfo.arrayLayers = 1;
    imageInfo.format = format;
    // 圖像平鋪模式,這裡指定圖像像素最佳記憶體拼接布局
    // 與圖像的布局不同,平鋪模式不能在以後更改。如果希望能夠直接通路圖像記憶體中的texel,則必須使用VK_IMAGE_TILING_OPTIMAL
    imageInfo.tiling = tiling;

    // 圖像的initialLayout隻有兩個可能的值:VK_IMAGE_LAYOUT_UNDEFINED || VK_IMAGE_LAYOUT_PREINITIALIZED
    imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

    imageInfo.usage = usage;
    // 圖像将僅由一個隊列族使用, 是以獨占模式
    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    // 圖像采樣
    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.flags = 0; // Optional
    // 建立圖像
    if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
        throw std::runtime_error("failed to create image!");
    }

    // 同樣的,需要給Image配置設定記憶體空間
    VkMemoryRequirements memRequirements;
    vkGetImageMemoryRequirements(device, image, &memRequirements);
    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex =
            findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate image memory!");
    }
    // 綁定圖像和記憶體
    vkBindImageMemory(device, image, imageMemory, 0);
}

void createTextureImage() {
    int texWidth, texHeight, texChannels;
    // 加載texture.jpg圖像
    stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
    // 每個像素4個位元組
    VkDeviceSize imageSize = texWidth * texHeight * 4;
    if (!pixels) {
        throw std::runtime_error("failed to load texture image!");
    }

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    // 緩沖區應該在主機可見記憶體中,以便我們可以映射它,并且它應該可用作傳輸源,以便我們以後可以複制
    createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
            VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer,
            stagingBufferMemory, VK_SHARING_MODE_EXCLUSIVE);
    // 記憶體映射
    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
    memcpy(data, pixels, static_cast<size_t>(imageSize));
    vkUnmapMemory(device, stagingBufferMemory);
    // 最後釋放原始像素資料
    stbi_image_free(pixels);

    createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL,
            VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
            VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);
}
           

1.4 布局轉換

我們需要再次記錄和執行一個指令緩沖區以完成布局轉換功能,是以最好是将執行指令緩沖區的部分邏輯抽離:

VkCommandBuffer beginSingleTimeCommands() {
    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);

    VkCommandBufferBeginInfo beginInfo = {};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
    vkBeginCommandBuffer(commandBuffer, &beginInfo);
    return commandBuffer;
}

void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
    vkEndCommandBuffer(commandBuffer);

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

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

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

現在有了beginSingleTimeCommands和endSingleTimeCommands函數,可以對執行單條指令緩沖區的函數進行優化:

void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
        VkCommandBuffer commandBuffer= beginSingleTimeCommands();
        // 緩沖拷貝指令
        VkBufferCopy copyRegion = {};
        copyRegion.srcOffset = 0; // Optional
        copyRegion.dstOffset = 0; // Optional
        copyRegion.size = size;
        // std::cout<<"copyBuffer vkCmdCopyBuffer"<<std::endl;
        // 緩沖區的内容使用vkCmdCopyBuffer指令傳輸。
        // 源和目标緩沖區以及要複制的區域數組作為參數。copyRegion由源緩沖區偏移量、目标緩沖區偏移量和大小組成
        vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

        endSingleTimeCommands(commandBuffer);
    }
           

如果我們仍然使用緩沖區,那麼我們現在可以編寫一個函數來記錄并執行vkCmdCopyBufferToImage,但是這個指令要求首先将Image置于正确的布局中。

建立一個新函數來處理布局轉換:

void transitionImageLayout(VkImage image, VkFormat format,
        VkImageLayout oldLayout, VkImageLayout newLayout) {

    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
    // 使用圖像記憶體屏障,用于同步資源通路
    VkImageMemoryBarrier barrier = {};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    // 指定布局轉換。如果不關心圖像的現有内容,可以将VK_IMAGE_LAYOUT_UNDEFINED用作oldLayout
    barrier.oldLayout = oldLayout;
    barrier.newLayout = newLayout;

    // 如果使用屏障來傳遞隊列族的所有權,那麼這兩個字段應該是隊列族的索引
    // 如果不這樣做,則必須将它們設定為VK_QUEUE_FAMILY_IGNORED
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    // image和subresourceRange指定受影響的圖像以及圖像的特定部分
    barrier.image = image;
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    // 我們的圖像不是數組,也沒有mipmapping級别,是以隻指定了一個級别和層
    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = 1;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 1;

    // 屏障主要用于同步目的,是以必須指定哪些涉及資源的操作類型必須在屏障之前發生,哪些涉及資源的操作必須在屏障上等待
    barrier.srcAccessMask = 0; // TODO
    barrier.dstAccessMask = 0; // TODO

    // 在管道上執行barrier指令, 所有類型的管道屏障都使用相同的函數送出
    vkCmdPipelineBarrier(commandBuffer,
        0 /* TODO */, 0 /* TODO */,
        0,
        0, nullptr,
        0, nullptr,
        1, &barrier
    );

    endSingleTimeCommands(commandBuffer);
}
           

執行布局轉換的最常見方法之一是使用圖像記憶體屏障。像這樣的管道屏障通常用于同步對資源的通路,例如確定在從緩沖區讀取之前完成對緩沖區的寫入,但是當使用VK_SHARING_MODE_EXCLUSIVE時,它也可以用于轉換映像布局和傳輸隊列族所有權。對于緩沖區,有一個等效的緩沖存儲器屏障來實作這一點。

1.4.1 VkImageMemoryBarrier

圖像存儲器屏障僅适用于涉及特定圖像子資源範圍的存儲器通路。也就是說,從圖像存儲器屏障形成的存儲器依賴被限定為通過指定的圖像子資源範圍進行通路。圖像記憶體屏障還可用于定義指定圖像子資源範圍的圖像布局轉換或隊列族所有權轉移。

typedef struct VkImageMemoryBarrier {
    VkStructureType            sType;
    const void*                pNext;
    VkAccessFlags              srcAccessMask;
    VkAccessFlags              dstAccessMask;
    VkImageLayout              oldLayout;
    VkImageLayout              newLayout;
    uint32_t                   srcQueueFamilyIndex;
    uint32_t                   dstQueueFamilyIndex;
    VkImage                    image;
    VkImageSubresourceRange    subresourceRange;
} VkImageMemoryBarrier;
           
  1. sType就是這種結構的類型, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER
  2. pNext為NULL或指向特定于擴充的結構的指針
  3. srccessmask是指定源通路掩碼的VkAccessFlagBits的位掩碼, 指定在哪個管道階段發生操作,這些操作應該在屏障之前發生
  4. dstAccessMask是指定目标通路掩碼的VkAccessFlagBits位掩碼, 指定操作将在其中等待屏障的管道階段
  5. oldLayout是圖像布局轉換中的舊布局
  6. newLayout是圖像布局轉換中的新布局
  7. srcQueueFamilyIndex是隊列系列所有權轉移的源隊列系列
  8. dstQueueFamilyIndex是隊列系列所有權轉移的目标隊列系列
  9. image是受此屏障影響的圖像
  10. subresourceRange描述圖像中受此屏障影響的圖像子資源範圍

1.4.1.1 VkImageSubresourceRange

指定圖像子資源範圍使用結構體VkImageSubresourceRange

typedef struct VkImageSubresourceRange {
    VkImageAspectFlags    aspectMask;
    uint32_t              baseMipLevel;
    uint32_t              levelCount;
    uint32_t              baseArrayLayer;
    uint32_t              layerCount;
} VkImageSubresourceRange;
           
  1. aspectMask是VkImageAspectFlagBits的位掩碼,用于指定視圖中包含圖像的哪些方面
  2. baseMipLevel是該視圖可通路的第一個mipmap級别
  3. levelCount是視圖可通路的mipmap級别數(從baseMipLevel開始)
  4. baseArrayLayer是視圖可通路的第一個數組層
  5. layerCount是視圖可通路的數組層數(從baseArrayLayer開始)

1.4.1.2 VkImageAspectFlags

辨別子資源的目的,指定視圖中包含圖像的哪些方面:

typedef enum VkImageAspectFlagBits {
    VK_IMAGE_ASPECT_COLOR_BIT = 0x00000001,
    VK_IMAGE_ASPECT_DEPTH_BIT = 0x00000002,
    VK_IMAGE_ASPECT_STENCIL_BIT = 0x00000004,
    VK_IMAGE_ASPECT_METADATA_BIT = 0x00000008,
    VK_IMAGE_ASPECT_PLANE_0_BIT = 0x00000010,
    VK_IMAGE_ASPECT_PLANE_1_BIT = 0x00000020,
    VK_IMAGE_ASPECT_PLANE_2_BIT = 0x00000040,
    VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT = 0x00000080,
    VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT = 0x00000100,
    VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT = 0x00000200,
    VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT = 0x00000400,
    VK_IMAGE_ASPECT_PLANE_0_BIT_KHR = VK_IMAGE_ASPECT_PLANE_0_BIT,
    VK_IMAGE_ASPECT_PLANE_1_BIT_KHR = VK_IMAGE_ASPECT_PLANE_1_BIT,
    VK_IMAGE_ASPECT_PLANE_2_BIT_KHR = VK_IMAGE_ASPECT_PLANE_2_BIT,
    VK_IMAGE_ASPECT_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkImageAspectFlagBits;
           
  1. VK_IMAGE_ASPECT_COLOR_BIT指定顔色方面
  2. VK_IMAGE_ASPECT_DEPTH_BIT指定深度方面
  3. VK_IMAGE_ASPECT_STENCIL_BIT指定模具外觀
  4. VK_IMAGE_ASPECT_METADATA_BIT指定用于稀疏稀疏資源操作的中繼資料方面
  5. VK_IMAGE_ASPECT_PLANE_0_BIT指定多平面圖像格式的平面0
  6. VK_IMAGE_ASPECT_PLANE_1_BIT指定多平面圖像格式的平面1
  7. VK_IMAGE_ASPECT_PLANE_2_BIT指定多平面圖像格式的平面2
  8. VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT指定記憶體平面0
  9. VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT指定記憶體平面1
  10. VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT指定記憶體平面2
  11. VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT指定記憶體平面3

1.4.2 vkCmdPipelineBarrier

vkCmdPipelineBarrier是一個同步指令,它在送出到同一隊列的指令之間或同一子類中的指令之間插入依賴關系。

void vkCmdPipelineBarrier(
    VkCommandBuffer                             commandBuffer,
    VkPipelineStageFlags                        srcStageMask,
    VkPipelineStageFlags                        dstStageMask,
    VkDependencyFlags                           dependencyFlags,
    uint32_t                                    memoryBarrierCount,
    const VkMemoryBarrier*                      pMemoryBarriers,
    uint32_t                                    bufferMemoryBarrierCount,
    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,
    uint32_t                                    imageMemoryBarrierCount,
    const VkImageMemoryBarrier*                 pImageMemoryBarriers);
           
  1. commandBuffer是将指令記錄到的指令緩沖區
  2. srcStageMask是一個指定源級掩碼的VkPipelineStageFlagBits的位掩碼
  3. dstStageMask是指定目标階段掩碼的VkPipelineStageFlagBits的位掩碼
  4. dependencyFlags是VkdePendencyFlags的位掩碼,指定如何形成執行和記憶體依賴關系
  5. memoryBarrierCount是pMemoryBarriers數組的長度
  6. pMemoryBarriers是指向VKMemorySbarrier結構數組的指針
  7. bufferMemoryBarrierCount是pBufferMemoryBarriers數組的長度
  8. pBufferMemoryBarriers是指向VkBufferMemoryBarrier結構數組的指針
  9. imageMemoryBarrierCount是pImageMemoryBarriers數組的長度
  10. pImageMemoryBarriers是指向VkimAgemoryBarrier結構數組的指針

當vkCmdPipelineBarrier送出到隊列時,它定義了在它之前送出的指令和在它之後送出的指令之間的記憶體依賴關系。

如果vkCmdPipelineBarrier是在渲染過程執行個體外部錄制的,則第一個同步作用域将包括按送出順序較早出現的所有指令。如果vkCmdPipelineBarrier記錄在渲染過程執行個體中,則第一個同步作用域僅包括在同一子過程中以送出順序較早出現的指令。在這兩種情況下,第一個同步作用域僅限于由srcStageMask指定的源階段掩碼确定的管道階段上的操作。

如果vkCmdPipelineBarrier是在渲染過程執行個體外部錄制的,則第二個同步作用域将包括以後按送出順序執行的所有指令。如果vkCmdPipelineBarrier記錄在渲染過程執行個體中,則第二個同步作用域僅包括稍後在同一子過程中按送出順序出現的指令。在任何一種情況下,第二同步作用域都限于由dstStageMask指定的目的級掩碼确定的管道級上的操作。

第一個通路範圍被限制為在由srcStageMask指定的源階段掩碼确定的管道階段中進行通路。其中,第一通路作用域僅包括由pMemoryBarriers、pBufferMemoryBarriers和pImageMemoryBarriers數組的元素定義的第一通路作用域,每個元素定義一組記憶體屏障。如果未指定記憶體屏障,則第一個通路作用域不包括任何通路。

第二通路範圍被限制為在由dstStageMask指定的目标階段掩碼确定的管道階段中的通路。其中,第二通路作用域僅包括由pMemoryBarriers、pBufferMemoryBarriers和pImageMemoryBarriers數組的元素定義的第二通路作用域,它們各自定義了一組記憶體屏障。如果未指定記憶體屏障,則第二通路作用域不包括任何通路。

1.5 拷貝緩存資料至Image

就像緩沖區複制一樣,需要指定緩沖區的哪個部分将被複制到圖像的哪個部分:

void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width,
        uint32_t height) {

    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
    // 使用VkBufferImageCopy指定緩沖區複制行為
    VkBufferImageCopy region = {};
    // 指定緩沖區中像素值開始的位元組偏移量
    region.bufferOffset = 0;
    // 指定像素在記憶體中的布局方式, 指定0表示像素緊密打包
    region.bufferRowLength = 0;
    region.bufferImageHeight = 0;

    // 訓示要将像素複制到圖像的哪個部分,這裡我們指定顔色(還有諸如深度、模闆、中繼資料等)
    region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    region.imageSubresource.mipLevel = 0;
    region.imageSubresource.baseArrayLayer = 0;
    region.imageSubresource.layerCount = 1;
    region.imageOffset = {0, 0, 0};
    region.imageExtent = {width, height, 1};

    // 使用vkCmdCopyBufferToImage函數将緩沖區到圖像的複制操作排隊
    // 第四個參數訓示圖像目前使用的布局
    vkCmdCopyBufferToImage(commandBuffer, buffer, image,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            1, &region);

    endSingleTimeCommands(commandBuffer);
}
           

1.5.1 VkBufferImageCopy

typedef struct VkBufferImageCopy {
    VkDeviceSize                bufferOffset;
    uint32_t                    bufferRowLength;
    uint32_t                    bufferImageHeight;
    VkImageSubresourceLayers    imageSubresource;
    VkOffset3D                  imageOffset;
    VkExtent3D                  imageExtent;
} VkBufferImageCopy;
           
  1. bufferOffset是從複制圖像資料的緩沖區對象的起始處開始的以位元組為機關的偏移量
  2. bufferRowLength和bufferImageHeight以texel為機關指定緩沖存儲器中較大的二維或三維圖像的子區域,并控制尋址計算。如果這些值中的任何一個為零,則根據imageExtent,緩沖存儲器的這一方面被認為是緊密壓縮的
  3. imageSubresource是一個VkImageSubresourceLayers,用于指定用于源或目标圖像資料的圖像的特定圖像子資源
  4. imageOffset選擇源或目标圖像資料子區域的初始x、y、z偏移(以texel為機關)
  5. imageExtent是要在寬度、高度和深度上複制的圖像的大小(以texel為機關)

當複制到或從深度或模具方面時,緩沖區記憶體中的資料使用的布局是深度或模具資料的(大部分)緊密封裝的表示形式。具體地說:

  1. 複制到或從任何深度/模闆格式的模闆方面的資料都用每個texel的VK_FORMAT_S8_UINT值緊密打包
  2. 複制到或從VK_FORMAT_D16_UNORM或VK_FORMAT_D16_UNORM_S8_UINT格式的深度方面的資料使用每個texel的VK_FORMAT_D16_UNORM值緊密打包
  3. 複制到或從VK_FORMAT_D32_SFLOAT或VK_FORMAT_D32_SFLOAT_S8_UINT格式的深度方面的資料使用每個texel的一個VK_FORMAT_D32_SFLOAT值緊密打包
  4. 複制到或從VK_FORMAT_X8_D24_UNORM_PACK32或VK_FORMAT_D24_UNORM_S8_UINT格式的深度方面的資料被打包為每個texel一個32位單詞,每個單詞的lsb中有D24值,8個msb中有未定義的值

由于圖像副本的深度或模闆方面緩沖區在某些實作上可能需要格式轉換,是以不支援圖形的隊列不支援格式轉換。

當複制到深度方面時,并且沒有啟用VK_EXT_depth_range_unrestricted擴充名,緩沖區記憶體中的資料必須在[0,1]範圍内,否則結果值是未定義的。

複制從imageSubresource的圖像圖層baseArrayLayer成員開始一層一層地進行。layerCount層從源圖像或目标圖像複制。

1.5.2 vkCmdCopyBufferToImage

在緩沖區和圖像之間複制資料, 從buffer對象複制資料到image對象, 調用vkCmdCopyBufferToImage:

void vkCmdCopyBufferToImage(
    VkCommandBuffer                             commandBuffer,
    VkBuffer                                    srcBuffer,
    VkImage                                     dstImage,
    VkImageLayout                               dstImageLayout,
    uint32_t                                    regionCount,
    const VkBufferImageCopy*                    pRegions);
           
  1. commandBuffer是指令将被記錄到的指令緩沖區
  2. srcBuffer是源緩沖區
  3. dstImage是目标圖像
  4. dstImageLayout是複制的目标圖像子資源的布局
  5. regionCount是要複制的區域數
  6. pRegions是一個指向VkBufferImageCopy結構數組的指針,該結構數組指定要複制的區域

區域中的每個區域從源緩沖區的指定區域複制到目标圖像的指定區域。

如果dstImage的格式是一個多平面的圖像格式),必須使用VkBufferImageCopy結構的pRegions成員單獨指定作為拷貝目标的每個平面的區域。在本例中,imageSubresource的aspectMask必須為VK_IMAGE_ASPECT_PLANE_0_BIT、VK_IMAGE_ASPECT_PLANE_1_BIT或VK_IMAGE_ASPECT_PLANE_2_BIT。對于vkCmdCopyBufferToImage來說,多平面圖像的每個平面都被視為具有由相應子資源的aspectMask辨別的平面的多平面格式的相容平面格式中列出的格式。這既适用于VkFormat,也适用于複制中使用的坐标,它對應于平面中的texel,而不是這些texel如何映射到整個圖像中的坐标。

1.6 準備紋理圖像

回到createTextureImage函數。我們在那裡做的最後一件事是建立紋理圖像。下一步是将暫存緩沖區複制到紋理圖像。這包括兩個步驟:

  1. 轉換紋理圖像到VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
  2. 執行緩沖區到圖像複制操作
// 該圖像是使用VK_IMAGE_LAYOUT_UNDEFINED布局建立的,是以在轉換textureImage時應将oldLayout指定為VK_IMAGE_LAYOUT_UNDEFINED
// 在執行複制操作之前,不關心圖像内容,是以可以這樣做
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM,
        VK_IMAGE_LAYOUT_UNDEFINED,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);

// 拷貝stagingBuffer中緩存的圖像資料至Image(GPU可見記憶體)
copyBufferToImage(stagingBuffer, textureImage,
        static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));

// 為了能夠從着色器中的紋理圖像開始采樣,我們需要最後一個過渡來準備着色器通路(用于同步對資源的通路):
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM,
        VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

           

1.7 轉換屏障的含義 VkAccessFlags

現在在啟用驗證層的情況下運作應用程式,那麼将看到transitionImageLayout中的通路掩碼和管道階段無效。

我們需要根據過渡中的布局來設定它們,拷貝前後的兩種轉換都需要設定:

  1. VK_IMAGE_LAYOUT_UNDEFINED->VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 不需要等待任何内容的傳輸寫入
  2. VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL-> VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: shader reads應該等待Transfer writes,特别是shader在片段着色器中讀取,因為這就是我們要使用紋理的地方
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;

if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED
        && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
    barrier.srcAccessMask = 0;
    // Image或緩沖區在清除或複制操作中的寫通路
    barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    // 指定隊列最初接收到任何指令的管道階段
    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    // 指定所有複制指令和清除指令管道階段
    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
        && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
    // Image或緩沖區在清除或複制操作中的寫通路
    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    // 指定對存儲緩沖區、實體存儲緩沖區、統一texel緩沖區、存儲texel緩沖區、采樣圖像或存儲圖像的讀通路
    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    // 指定所有複制指令和清除指令管道階段
    sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    // 指定片段着色器階段
    destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else {
    throw std::invalid_argument("unsupported layout transition!");
}

vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage,
        0,
        0, nullptr,
        0, nullptr,
        1, &barrier);
           

傳輸寫入必須在管道傳輸階段進行。因為寫操作不需要等待任何東西,是以您可以為預barrier操作指定一個空的通路掩碼和盡可能早的管道階段VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT。需要注意的是,VK_PIPELINE_STAGE_TRANSFER_BIT并不是圖形和計算管道中的一個真正的階段。它更多的是一個發生轉移的僞階段。

圖像将在相同的管道階段被寫入,然後被片段着色器讀取,這就是為什麼我們在片段着色器管道階段指定着色器讀取通路。需要注意的一點是,指令緩沖區送出在開始時會導緻隐式的VK_ACCESS_HOST_WRITE_BIT同步。由于transitionImageLayout函數隻使用一個指令來執行一個指令緩沖區,是以如果在布局轉換中需要VK_ACCESS_HOST_WRITE_BIT依賴項,您可以使用這個隐式同步并将srcAccessMask設定為0。

實際上,有一種特殊的圖像布局類型可以支援所有操作–VK_IMAGE_LAYOUT_GENERAL。當然,它的問題在于,它不一定能為任何操作提供最佳性能。在某些特殊情況下,例如使用圖像作為輸入和輸出,或者在離開預初始化的布局後讀取圖像。到目前為止,所有送出指令的幫助程式功能都已設定為通過等待隊列變為空閑狀态而同步執行。對于實際應用,建議将這些操作組合在單個指令緩沖區中,并異步執行它們以提高吞吐量,尤其是createTextureImage函數中的過渡和複制。通過建立一個helper函數将指令記錄到其中的setupCommandBuffer并嘗試添加一個flushSetupCommands來執行到目前為止已記錄的指令,來嘗試進行此操作。最好在紋理貼圖工作後執行此操作,以檢查紋理資源是否仍正确設定。

1.7.1 VkAccessFlagBits

Vulkan中的記憶體可以通過shader調用和管道中的一些固定函數來通路。通路類型是所使用的描述符類型的函數,或者固定函數階段如何通路記憶體。每個通路類型對應于VkAccessFlagBits中的一個位标志。

一些同步指令以通路類型集作為參數來定義記憶體依賴項的通路範圍。如果同步指令包含源通路掩碼,則其第一個通路作用域僅包括通過該掩碼中指定的通路類型進行的通路。類似地,如果同步指令包含目标通路掩碼,則其第二個通路作用域僅包括通過該掩碼中指定的通路類型進行的通路。

typedef enum VkAccessFlagBits {
    VK_ACCESS_INDIRECT_COMMAND_READ_BIT = 0x00000001,
    VK_ACCESS_INDEX_READ_BIT = 0x00000002,
    VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT = 0x00000004,
    VK_ACCESS_UNIFORM_READ_BIT = 0x00000008,
    VK_ACCESS_INPUT_ATTACHMENT_READ_BIT = 0x00000010,
    VK_ACCESS_SHADER_READ_BIT = 0x00000020,
    VK_ACCESS_SHADER_WRITE_BIT = 0x00000040,
    VK_ACCESS_COLOR_ATTACHMENT_READ_BIT = 0x00000080,
    VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT = 0x00000100,
    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT = 0x00000200,
    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT = 0x00000400,
    VK_ACCESS_TRANSFER_READ_BIT = 0x00000800,
    VK_ACCESS_TRANSFER_WRITE_BIT = 0x00001000,
    VK_ACCESS_HOST_READ_BIT = 0x00002000,
    VK_ACCESS_HOST_WRITE_BIT = 0x00004000,
    VK_ACCESS_MEMORY_READ_BIT = 0x00008000,
    VK_ACCESS_MEMORY_WRITE_BIT = 0x00010000,
    VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT = 0x02000000,
    VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT = 0x04000000,
    VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT = 0x08000000,
    VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT = 0x00100000,
    VK_ACCESS_COMMAND_PROCESS_READ_BIT_NVX = 0x00020000,
    VK_ACCESS_COMMAND_PROCESS_WRITE_BIT_NVX = 0x00040000,
    VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT = 0x00080000,
    VK_ACCESS_SHADING_RATE_IMAGE_READ_BIT_NV = 0x00800000,
    VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_NV = 0x00200000,
    VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_NV = 0x00400000,
    VK_ACCESS_FRAGMENT_DENSITY_MAP_READ_BIT_EXT = 0x01000000,
    VK_ACCESS_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkAccessFlagBits;
           
  1. VK_ACCESS_INDIRECT_COMMAND_READ_BIT指定對作為間接繪圖或排程指令一部分的間接指令資料的讀通路
  2. VK_ACCESS_INDEX_READ_BIT指定對索引緩沖區的讀通路,作為索引繪圖指令的一部分,由vkCmdBindIndexBuffer綁定
  3. VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT指定對頂點緩沖區的讀通路,作為繪圖指令的一部分,由vkCmdBindVertexBuffers綁定
  4. VK_ACCESS_UNIFORM_READ_BIT統一緩沖區讀通路權限
  5. VK_ACCESS_INPUT_ATTACHMENT_READ_BIT指定在片段着色期間渲染通道内對輸入附件的讀通路
  6. VK_ACCESS_SHADER_READ_BIT指定對存儲緩沖區、實體存儲緩沖區、統一texel緩沖區、存儲texel緩沖區、采樣圖像或存儲圖像的讀通路
  7. VK_ACCESS_SHADER_WRITE_BIT存儲緩沖區、實體存儲緩沖區、存儲texel緩沖區或存儲映像的寫通路
  8. VK_ACCESS_COLOR_ATTACHMENT_READ_BIT指定對顔色附件的讀通路,例如通過混合、邏輯操作或通過某些subpass加載操作。它不包括進階混合操作
  9. VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT指定在渲染通道期間或通過某些子通道加載和存儲操作對顔色、解析或深度/模闆解析附件的寫通路
  10. VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT指定對深度/模闆附件的讀通路,通過深度或模闆操作,或通過某些子傳遞加載操作
  11. VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT指定對深度/模闆附件的寫通路,通過深度或模闆操作,或者通過某些子傳遞加載和存儲操作
  12. VK_ACCESS_TRANSFER_READ_BIT拷貝操作中對鏡像或緩沖區的讀通路
  13. VK_ACCESS_TRANSFER_WRITE_BIT映像或緩沖區在清除或複制操作中的寫通路
  14. VK_ACCESS_HOST_READ_BIT主機操作讀通路。這種類型的通路不是通過資源執行的,而是直接在記憶體上執行的
  15. VK_ACCESS_HOST_WRITE_BIT主機操作寫通路。這種類型的通路不是通過資源執行的,而是直接在記憶體上執行的
  16. VK_ACCESS_MEMORY_READ_BIT所有讀通路。它在任何通路掩碼中都是有效的,并被視為等同于設定所有在使用它時有效的讀通路标志
  17. VK_ACCESS_MEMORY_WRITE_BIT所有寫通路。它在任何通路掩碼中都是有效的,并被視為等同于設定所有在使用它時有效的寫通路标志
  18. VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT指定對謂詞的讀通路,作為條件呈現的一部分
  19. VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT指定在轉換回報激活時對轉換回報緩沖區的寫通路
  20. VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT指定對轉換回報計數器緩沖區的讀通路,當vkCmdBeginTransformFeedbackEXT執行時讀取該緩沖區
  21. VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT指定對轉換回報計數器緩沖區的寫通路,該緩沖區在vkCmdEndTransformFeedbackEXT執行時寫入
  22. VK_ACCESS_COMMAND_PROCESS_READ_BIT_NVX指定從VkBuffer輸入讀取vkCmdProcessCommandsNVX
  23. VK_ACCESS_COMMAND_PROCESS_WRITE_BIT_NVX指定寫到vkCmdProcessCommandsNVX的目标指令緩沖區
  24. VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT類似于VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,但是也包括進階的混合操作
  25. VK_ACCESS_SHADING_RATE_IMAGE_READ_BIT_NV指定對着色率圖像的讀取通路,作為繪圖指令的一部分,由vkcmdbindshadingraemimagenv綁定
  26. VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_NV指定對加速結構的讀通路,作為跟蹤或建構指令的一部分
  27. VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_NV指定對加速結構的寫通路,作為建構指令的一部分
  28. VK_ACCESS_FRAGMENT_DENSITY_MAP_READ_BIT_EXT動态碎片密度圖操作時對碎片密度圖附件的讀通路

1.7.2 VkPipelineStageFlags 管道階段

操作或同步指令執行的工作由多個操作組成,這些操作作為邏輯上獨立的步驟序列執行,稱為管道階段。執行的确切管道階段取決于所使用的特定指令,以及記錄指令時的目前指令緩沖區狀态。繪制指令、分派指令、複制指令、清除指令和同步指令都在管道階段的不同集合中執行。同步指令不會在已定義的管道中執行,但會執行VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT和VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT。

注意同步指令執行的操作(例如可用性和可見性操作)不是由定義的管道階段執行的。但是,其他指令仍然可以通過VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT和VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT管道階段與它們同步。

跨管道階段執行操作必須遵循隐式排序保證,特别是包括管道階段順序。否則,與其他階段相比,跨管道階段的執行可能會重疊或無序執行,除非執行依賴項強制執行。

一些同步指令包括管道階段參數,将該指令的同步範圍限制在這些階段。這允許對精确的執行依賴關系和操作指令執行的通路進行細粒度的控制。實作應該使用這些管道階段來避免不必要的停頓或緩存重新整理。

可以設定指定管道階段通過VkPipelineStageFlags:

typedef enum VkPipelineStageFlagBits {
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT = 0x00000001,
    VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT = 0x00000002,
    VK_PIPELINE_STAGE_VERTEX_INPUT_BIT = 0x00000004,
    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT = 0x00000008,
    VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT = 0x00000010,
    VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT = 0x00000020,
    VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT = 0x00000040,
    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT = 0x00000080,
    VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT = 0x00000100,
    VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT = 0x00000200,
    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT = 0x00000400,
    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT = 0x00000800,
    VK_PIPELINE_STAGE_TRANSFER_BIT = 0x00001000,
    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT = 0x00002000,
    VK_PIPELINE_STAGE_HOST_BIT = 0x00004000,
    VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT = 0x00008000,
    VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000,
    VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT = 0x01000000,
    VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT = 0x00040000,
    VK_PIPELINE_STAGE_COMMAND_PROCESS_BIT_NVX = 0x00020000,
    VK_PIPELINE_STAGE_SHADING_RATE_IMAGE_BIT_NV = 0x00400000,
    VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_NV = 0x00200000,
    VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_NV = 0x02000000,
    VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV = 0x00080000,
    VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV = 0x00100000,
    VK_PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT_EXT = 0x00800000,
    VK_PIPELINE_STAGE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkPipelineStageFlagBits;
           
  1. VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT指定隊列最初接收到任何指令的管道階段
  2. VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT指定使用Draw/DispatchIndirect資料結構的管道階段。這個階段還包括讀取vkCmdProcessCommandsNVX寫的指令
  3. VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV指定任務着色器階段
  4. VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV指定網格着色器階段
  5. VK_PIPELINE_STAGE_VERTEX_INPUT_BIT指定消耗頂點和索引緩沖區的流水線階段
  6. VK_PIPELINE_STAGE_VERTEX_SHADER_BIT指定頂點着色器階段
  7. VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT指定鑲嵌控制着色器階段
  8. VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT指定鑲嵌評估着色器階段
  9. VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT指定幾何着色器階段
  10. VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT指定片段着色器階段
  11. VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT指定執行早期片段測試(片段着色之前的深度和模闆測試)的管道階段。此階段還包括針對具有深度/模闆格式的幀緩沖區附件的子傳遞加載操作
  12. VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT指定執行後期片段測試(片段着色後的深度和模闆測試)的管道階段。此階段還包括用于具有深度/模闆格式的幀緩沖區附件的子傳遞存儲操作
  13. VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT指定混合後管道的階段,從管道輸出最終顔色值。此階段還包括子通道加載和存儲操作以及具有顔色或深度/模闆格式的幀緩沖區附件的多樣本解析操作
  14. VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT指定執行計算着色器
  15. VK_PIPELINE_STAGE_TRANSFER_BIT指定以下指令:
    1. 所有複制指令,包括vkCmdCopyQueryPoolResults,vkCmdBlitImage,vkCmdResolveImage
    2. 所有清除指令,但vkCmdClearAttachments除外
  16. VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT指定管道中由所有指令生成的操作完成執行的最後階段
  17. VK_PIPELINE_STAGE_HOST_BIT指定一個僞階段,訓示在主機上執行裝置存儲器的讀/寫操作。記錄在指令緩沖區中的任何指令都不會調用此階段
  18. VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_NV指定光線跟蹤着色器階段的執行
  19. VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_NV指定vkCmdBuildAccelerationStructureNV,vkCmdCopyAccelerationStructureNV和vkCmdWriteAccelerationStructuresPropertiesNV的執行
  20. VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT指定所有圖形管線階段的執行,并且等效于:
    1. VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
    2. VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT
    3. VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV
    4. VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV
    5. VK_PIPELINE_STAGE_VERTEX_INPUT_BIT
    6. VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
    7. VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT
    8. VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT
    9. VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT
    10. VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
    11. VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
    12. VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
    13. VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
    14. VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT
    15. VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT
    16. VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT
    17. VK_PIPELINE_STAGE_SHADING_RATE_IMAGE_BIT_NV
    18. VK_PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT_EXT
  21. VK_PIPELINE_STAGE_ALL_COMMANDS_BIT等效于與其一起使用的隊列上支援的所有其他管道階段标志的邏輯或
  22. VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT指定使用條件渲染謂詞的管道階段
  23. VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT指定将頂點屬性輸出值寫入轉換回報緩沖區的管線階段
  24. VK_PIPELINE_STAGE_COMMAND_PROCESS_BIT_NVX指定了處理通過vkCmdProcessCommandsNVX在裝置端生成指令的管道階段
  25. VK_PIPELINE_STAGE_SHADING_RATE_IMAGE_BIT_NV指定管道的階段,在該階段中讀取陰影率圖像,以确定栅格化圖元各部分的陰影率
  26. VK_PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT_EXT指定讀取片段密度圖以生成片段區域的管線階段

1.8 清理

建立紋理貼圖後,不能忘記在必要的時候将記憶體釋放出來:

void createTextureImage() {
    ...
    // 通過清除過渡緩沖區及其末尾的記憶體來完成createTextureImage函數:
    transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

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

void cleanup() {
    cleanupSwapChain();

    vkDestroyImage(device, textureImage, nullptr);
    vkFreeMemory(device, textureImageMemory, nullptr);
    ...
}
           

1.9 總結

到目前為止,我們從裝置實體存儲上讀取了圖檔内容,将其轉成臨時緩存後又将其存儲在對應GPU可見的記憶體中以及生成對應VkImage紋理貼圖對象,接下來需要将其顯示在螢幕上還需要把這個對象放入圖形管道中。

在回顧下本章中的讀取圖像的步驟:

  1. 首先利用stb-image庫讀取圖檔,将其内容存儲在臨時緩存區VkBuffer中,注意需要開辟GPU可見記憶體
  2. 通過VkImageCreateInfo結構指明圖像格式并通過vkCreateImage建立VkImage對象
  3. 用VkBuffer圖像檔案中的像素填充建立的VkImage圖像對象
    1. 填充圖像對象需要使用VkImageMemoryBarrier
    2. 使用vkCmdPipelineBarrier使得圖像填充Barrier生效
    3. 通過vkCmdCopyBufferToImage完成圖像像素從VkBuffer到VkImage的拷貝(填充)
    4. 再通過VkImageMemoryBarrier指定圖像是能夠從着色器中的紋理圖像開始采樣
  4. 建立圖像視圖和圖像采樣器(後續下一章開始處理)
  5. 添加一個組合的圖像采樣器描述符來從紋理中采樣顔色

上面步驟中,4和5是下一章的内容。

1.10 Windows上的CMakefileLists.txt寫法

windows平台上編譯目前項目,可以使用cmake, CMakefileLists.txt檔案如下(注意先安裝Vulkan sdk):

cmake_minimum_required (VERSION 3.7) #最低要求的CMake版本
project(MyVulkan) # 項目名稱
set(VERSION 0.0.1)
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g -Wall -Wno-unused-variable -pthread")

message(STATUS "This is " ${PROJECT_NAME} " version " ${VERSION})
message(STATUS "This is for windows platform")
message("Build Type:" ${CMAKE_BUILD_TYPE} ${CMAKE_CXX_FLAGS})

# Use FindVulkan module added with CMAKE 3.7
if (NOT CMAKE_VERSION VERSION_LESS 3.7.0)
    message(STATUS "Using module to find Vulkan")
    find_package(Vulkan)
endif()

find_library(Vulkan_LIBRARY NAMES vulkan-1 vulkan PATHS ${CMAKE_SOURCE_DIR}/libs/vulkan)
IF (Vulkan_LIBRARY)
    set(Vulkan_FOUND ON)
    MESSAGE("Using bundled Vulkan library version")
ENDIF()

message(STATUS "Using Vulkan lib: " ${Vulkan_LIBRARY})

# CMAKE_SOURCE_DIR 代表工程根目錄CMakeLists.txt檔案所在目錄
set(ROOT_DIR ${CMAKE_SOURCE_DIR})

### GLFW3
set(GLFW_LIB_DIR ${ROOT_DIR}/lib/glfw3)
set(GLFW_LIBS ${GLFW_LIB_DIR}/glfw3dll.lib)
### GLM
set(GLM_INCLUDE_DIRS  ${ROOT_DIR}/include/glm)
### stb-image
set(STB_IMAGE_DIRS  ${ROOT_DIR}/include/stb-image)

message(STATUS "Lib path: ")
message(STATUS "  GLFW3: " ${GLFW_LIBS})
message(STATUS "  GLM  : " ${GLM_INCLUDE_DIRS})
message(STATUS "  STB_IMAGE: " ${STB_IMAGE_DIRS})

# 定義頭檔案搜尋路徑
include_directories(${ROOT_DIR}/inlcude
                    ${GLM_INCLUDE_DIRS})

#aux_source_directory(./ SOURCE_DIR)
aux_source_directory(${ROOT_DIR}/inlcude SOURCE_DIR)
aux_source_directory(${ROOT_DIR}/src SOURCE_DIR)

# Target
add_executable(MyVulkan ${SOURCE_DIR})

####Vulkan
find_package(Vulkan REQUIRED)
# GLFW3 is dynamic link
target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan ${GLFW_LIBS})
           

項目檔案目錄:

Vulkan入門(14)-VkImage圖像的建立.md參考資料簡述一. 紋理貼圖

項目位址: https://gitee.com/lunarswallow/MyVulkan