Ch3-2 图像与缓冲区

Device Memory

设备内存(VkDeviceMemory)是物理设备可以访问的内存。
根据你需要的内存属性,设备内存可以是计算机主存的一部分,也可以是显卡的显存。

分配设备内存

vkAllocateMemory分配设备内存:

VkResult VKAPI_CALL vkAllocateMemory(...) 的参数说明

VkDevice device

逻辑设备的handle

const VkMemoryAllocateInfo* pAllocateInfo

指向VkDeviceMemory的分配信息

const VkAllocationCallbacks* pAllocator

一个指向描述自定义内存分配方式的结构体的指针

VkDeviceMemory* pMemory

若执行成功,将设备内存的handle写入*pMemory

struct VkMemoryAllocateInfo 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkDeviceSize allocationSize

所需分配内存区的大小

uint32_t memoryTypeIndex

内存类型索引

  • 所需分配内存区的大小和内存类型索引需要根据与之对应的图像或缓冲区确定。

将设备内存绑定给缓冲区或图像

通常会把连续的设备内存区域绑定给缓冲区/图像,一次性分配的大量设备内存可以拆分成几块进行绑定。
将多块不连续的内存区绑定给单一资源的做法称为“稀疏绑定”,本套教程中不会就其做讲解。

一块设备内存可以被绑定给多个资源/缓冲区,换言之可以混叠,这在一定的限制下是安全的。
比如,将相同的设备内存,同时绑定给缓冲区和线性排列的图像:

Buffers, and linear image subresources in either the VK_IMAGE_LAYOUT_PREINITIALIZED or VK_IMAGE_LAYOUT_GENERAL layouts, are host-accessible subresources. That is, the host has a well-defined addressing scheme to interpret the contents, and thus the layout of the data in memory can be consistently interpreted across aliases if each of those aliases is a host-accessible subresource. Non-linear images, and linear image subresources in other layouts, are not host-accessible.
If two aliases are both host-accessible, then they interpret the contents of the memory in consistent ways, and data written to one alias can be read by the other alias.

官方文档出处见此

这在某些情况下可以帮你省事,比方说,要将某个图像格式Fa的图像Ia,转成另一格式Fb的图像Ib时,一些入门教程的做法可能是:
CPU可写的缓冲区 → 最优排列、格式为Fa的Ia → 最优排列、格式为Fb的Ib。
但其实可以用更快的方式,减少一次数据传送:
CPU可写的缓冲区混叠线性排列、格式为Fa的Ia → 最优排列、格式为Fb的Ib。

混叠时,绑定设备内存到图像和缓冲区的顺序没有先后。绑定到图像与数据的写入顺序之间也没有先后,先写入缓冲区再绑图像,和先绑图像再写入缓冲区,皆是安全的。
需要注意的是,即便是线性排列的数据,也有可能会有填充字节,详见Ch5-1 各种缓冲区一节中为staging创建混叠图像的部分。

关于绑定缓冲区或图像的具体函数,见其各自的段落。

映射内存区

对于具有VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT内存属性的设备内存区(关于取得内存属性的方法见后文的封装代码),在对其进行映射(map)后,可以由CPU侧对其进行直接读写。

vkMapMemory(...)映射内存区:

VkResult VKAPI_CALL vkMapMemory(...) 的参数说明

VkDevice device

逻辑设备的handle

VkDeviceMemory memory

设备内存的handle

VkDeviceSize offset

要被映射的内存块距离memory所指代内存区域起始位置的字节数

VkDeviceSize size

要被映射的内存块的大小,单位是字节

VkMemoryMapFlags flags

void** ppData

若执行成功,将用于访问内存区域的指针写入*ppData

注意,如果内存属性中不具有VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,那么你需要做两件事:
1.调整被映射内存区的offset和size,使offset为VkPhysicalDeviceProperties::limits::nonCoherentAtomSize的整数倍,size同为VkPhysicalDeviceProperties::limits::nonCoherentAtomSize的整数倍或offset到内存块末尾的距离,这可以用如下函数计算:

VkDeviceSize AdjustNonCoherentMemoryRange(VkDeviceSize& size, VkDeviceSize& offset) const {
    const VkDeviceSize& nonCoherentAtomSize = graphicsBase::Base().PhysicalDeviceProperties().limits.nonCoherentAtomSize;
    //记录offset的初值备用
    VkDeviceSize _offset = offset;
    //记录映射范围的尾部位置
    VkDeviceSize rangeEnd = size + offset;
    //将offset向下凑整到nonCoherentAtomSize的整数倍
    offset = offset / nonCoherentAtomSize * nonCoherentAtomSize;
    //将映射范围向上凑整到nonCoherentAtomSize的整数倍
    rangeEnd = (rangeEnd + nonCoherentAtomSize - 1) / nonCoherentAtomSize * nonCoherentAtomSize;
    //钳制映射范围
    rangeEnd = std::min(rangeEnd, allocationSize);
    //既然rangeEnd是nonCoherentAtomSize的整数倍或内存区尾端位置,offset是nonCoherentAtomSize的整数倍,两者作差即可求得满足条件的size
    size = rangeEnd - offset;
    //将offset的变化量(确切来说是变化量的相反数,我这里写的是初值减末值)返回出去
    return _offset - offset;
}

上述代码确保调整后的映射范围涵盖原先的范围。
由于映射时所指定的offset变了,需要对映射得到的指针的值进行修正:

VkDeviceSize inverseDeltaOffset = AdjustNonCoherentMemoryRange(size, offset);
vkMapMemory(graphicsBase::Base().Device(), handle, offset, size, 0, &pData);
pData = static_cast<uint8_t*>(pData) + inverseDeltaOffset; //void*类型指针不能进行加减,须将其转型

2.使用vkInvalidateMappedMemoryRanges(...)确保物理设备对该片内存的写入结果可以被CPU侧正确读取:

VkResult VKAPI_CALL vkInvalidateMappedMemoryRanges(...) 的参数说明

VkDevice device

逻辑设备的handle

uint32_t memoryRangeCount

要被invalidate的设备内存区域的数量

const VkMappedMemoryRange* pMemoryRanges

指向VkMappedMemoryRange的数组,用于指定要被invalidate的设备内存区域

struct VkMappedMemoryRange 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkDeviceMemory memory

设备内存的handle

VkDeviceSize offset

该内存区距离memory所指代内存区域起始位置的字节数

VkDeviceSize size

该内存区的大小,单位是字节

取消映射内存区

vkUnmapMemory(...)取消对内存区的映射:

VkResult VKAPI_CALL vkUnmapMemoryvkMapMemory(...) 的参数说明

VkDevice device

逻辑设备的handle

VkDeviceMemory memory

设备内存的handle

如果内存属性中不具有VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,使用vkFlushMappedMemoryRanges(...)确保CPU侧对该片内存的写入结果可以被物理设备正确读取

VkResult VKAPI_CALL vkFlushMappedMemoryRanges(...) 的参数说明

VkDevice device

逻辑设备的handle

uint32_t memoryRangeCount

要被flush的设备内存区域的数量

const VkMappedMemoryRange* pMemoryRanges

指向VkMappedMemoryRange的数组,用于指定要被flush的设备内存区域

Note

对于具有VK_MEMORY_PROPERTY_HOST_COHERENT_BIT属性的内存,CPU侧对其写入后,不必取消映射,vkQueueSubmit(...)会确保写入结果在相应的队列族间可见。
不过因为映射后,直到取消映射前不能再次映射。为防止重复映射,本套教程中总是会在写入结束后取消映射。

封装为deviceMemory类

VKBase.h,vulkan命名空间中添加以下代码:

class deviceMemory {
    VkDeviceMemory handle = VK_NULL_HANDLE;
    VkDeviceSize allocationSize = 0; //实际分配的内存大小
    VkMemoryPropertyFlags memoryProperties = 0; //内存属性
    //--------------------
    //该函数用于在映射内存区时,调整非host coherent的内存区域的范围
    VkDeviceSize AdjustNonCoherentMemoryRange(VkDeviceSize& size, VkDeviceSize& offset) const {
        const VkDeviceSize& nonCoherentAtomSize = graphicsBase::Base().PhysicalDeviceProperties().limits.nonCoherentAtomSize;
        VkDeviceSize _offset = offset;
        offset = offset / nonCoherentAtomSize * nonCoherentAtomSize;
        size = std::min((size + _offset + nonCoherentAtomSize - 1) / nonCoherentAtomSize * nonCoherentAtomSize, allocationSize) - offset;
        return _offset - offset;
    }
protected:
    //用于bufferMemory或imageMemory,定义于此以节省8个字节
    class {
        friend class bufferMemory;
        friend class imageMemory;
        bool value = false;
        operator bool() const { return value; }
        auto& operator=(bool value) { this->value = value; return *this; }
    } areBound;
public:
    deviceMemory() = default;
    deviceMemory(VkMemoryAllocateInfo& allocateInfo) {
        Allocate(allocateInfo);
    }
    deviceMemory(deviceMemory&& other) noexcept {
        MoveHandle;
        allocationSize = other.allocationSize;
        memoryProperties = other.memoryProperties;
        other.allocationSize = 0;
        other.memoryProperties = 0;
    }
    ~deviceMemory() { DestroyHandleBy(vkFreeMemory); allocationSize = 0; memoryProperties = 0; }
    //Getter
    DefineHandleTypeOperator;
    DefineAddressFunction;
    VkDeviceSize AllocationSize() const { return allocationSize; }
    VkMemoryPropertyFlags MemoryProperties() const { return memoryProperties; }
    //Const Function
    //映射host visible的内存区
    result_t MapMemory(void*& pData, VkDeviceSize size, VkDeviceSize offset = 0) const {
        VkDeviceSize inverseDeltaOffset;
        if (!(memoryProperties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
            inverseDeltaOffset = AdjustNonCoherentMemoryRange(size, offset);
        if (VkResult result = vkMapMemory(graphicsBase::Base().Device(), handle, offset, size, 0, &pData)) {
            outStream << std::format("[ deviceMemory ] ERROR\nFailed to map the memory!\nError code: {}\n", int32_t(result));
            return result;
        }
        if (!(memoryProperties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
            pData = static_cast<uint8_t*>(pData) + inverseDeltaOffset;
            VkMappedMemoryRange mappedMemoryRange = {
                .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
                .memory = handle,
                .offset = offset,
                .size = size
            };
            if (VkResult result = vkInvalidateMappedMemoryRanges(graphicsBase::Base().Device(), 1, &mappedMemoryRange)) {
                outStream << std::format("[ deviceMemory ] ERROR\nFailed to flush the memory!\nError code: {}\n", int32_t(result));
                return result;
            }
        }
        return VK_SUCCESS;
    }
    //取消映射host visible的内存区
    result_t UnmapMemory(VkDeviceSize size, VkDeviceSize offset = 0) const {
        if (!(memoryProperties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
            AdjustNonCoherentMemoryRange(size, offset);
            VkMappedMemoryRange mappedMemoryRange = {
                .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
                .memory = handle,
                .offset = offset,
                .size = size
            };
            if (VkResult result = vkFlushMappedMemoryRanges(graphicsBase::Base().Device(), 1, &mappedMemoryRange)) {
                outStream << std::format("[ deviceMemory ] ERROR\nFailed to flush the memory!\nError code: {}\n", int32_t(result));
                return result;
            }
        }
        vkUnmapMemory(graphicsBase::Base().Device(), handle);
        return VK_SUCCESS;
    }
    //BufferData(...)用于方便地更新设备内存区,适用于用memcpy(...)向内存区写入数据后立刻取消映射的情况
    result_t BufferData(const void* pData_src, VkDeviceSize size, VkDeviceSize offset = 0) const {
        void* pData_dst;
        if (VkResult result = MapMemory(pData_dst, size, offset))
            return result;
        memcpy(pData_dst, pData_src, size_t(size));
        return UnmapMemory(size, offset);
    }
    result_t BufferData(const auto& data_src) const {
        return BufferData(&data_src, sizeof data_src);
    }
    //RetrieveData(...)用于方便地从设备内存区取回数据,适用于用memcpy(...)从内存区取得数据后立刻取消映射的情况
    result_t RetrieveData(void* pData_dst, VkDeviceSize size, VkDeviceSize offset = 0) const {
        void* pData_src;
        if (VkResult result = MapMemory(pData_src, size, offset))
            return result;
        memcpy(pData_dst, pData_src, size_t(size));
        return UnmapMemory(size, offset);
    }
    //Non-const Function
    result_t Allocate(VkMemoryAllocateInfo& allocateInfo) {
        if (allocateInfo.memoryTypeIndex >= graphicsBase::Base().PhysicalDeviceMemoryProperties().memoryTypeCount) {
            outStream << std::format("[ deviceMemory ] ERROR\nInvalid memory type index!\n");
            return VK_RESULT_MAX_ENUM; //没有合适的错误代码,别用VK_ERROR_UNKNOWN
        }
        allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
        if (VkResult result = vkAllocateMemory(graphicsBase::Base().Device(), &allocateInfo, nullptr, &handle)) {
            outStream << std::format("[ deviceMemory ] ERROR\nFailed to allocate memory!\nError code: {}\n", int32_t(result));
            return result;
        }
        //记录实际分配的内存大小
        allocationSize = allocateInfo.allocationSize;
        //取得内存属性
        memoryProperties = graphicsBase::Base().PhysicalDeviceMemoryProperties().memoryTypes[allocateInfo.memoryTypeIndex].propertyFlags;
        return VK_SUCCESS;
    }
};
  • areBound用于记录设备内存是否被完整绑定到缓冲区或图像,用在后文的bufferMemoryimageMemory类中,定义在这里是因为成员变量memoryProperties后有4个字节的空档(考虑到VkDeviceMemoryVkDeviceSize都是8字节大小,对齐当然也是8字节)。

Buffer

缓冲区(VkBuffer)引用设备内存,指代缓冲区数据。缓冲区中的数据是线性排列的。

创建缓冲区

vkCreateBuffer(...)创建缓冲区:

VkResult VKAPI_CALL vkCreateBuffer(...) 的参数说明

VkDevice device

逻辑设备的handle

const VkBufferCreateInfo* pCreateInfo

指向VkBuffer的创建信息

const VkAllocationCallbacks* pAllocator

一个指向描述自定义内存分配方式的结构体的指针

VkBuffer* pBuffer

若执行成功,将缓冲区的handle写入*pBuffer

struct VkBufferCreateInfo 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkBufferCreateFlags flags

VkDeviceSize size

缓冲区的大小

VkBufferUsageFlags usage

缓冲区的用途

VkSharingMode sharingMode

分享访问模式,默认为单一队列族独占访问,若填入VK_SHARING_MODE_CONCURRENT,则允许不同队列族的队列同时访问

uint32_t queueFamilyIndexCount

若sharingMode为VK_SHARING_MODE_CONCURRENT,则在此填入同时访问本缓冲区的队列族的个数

uint32_t pQueueFamilyIndices

若sharingMode为VK_SHARING_MODE_CONCURRENT,则指向同时访问本缓冲区的队列族的索引

  • sharingMode为VK_SHARING_MODE_CONCURRENT可能降低访问效率,其好处是可以免去资源的队列族所有权转移。

  • 若sharingMode为默认的VK_SHARING_MODE_EXCLUSIVE,则pQueueFamilyIndices被无视。

版本要求

VkBufferCreateFlagBits 的枚举项

1.0

VK_BUFFER_CREATE_SPARSE_BINDING_BIT 表示缓冲区会被稀疏绑定

1.0

VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT 表示会被稀疏绑定的缓冲区能被部分绑定

1.0

VK_BUFFER_CREATE_SPARSE_ALIASED_BIT 表示会被稀疏绑定的缓冲区对应的内存会与其他缓冲区的内存混叠

1.1

VK_BUFFER_CREATE_PROTECTED_BIT 表示创建受保护的缓冲区

1.2

VK_BUFFER_CREATE_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT 表示缓冲区的在物理设备内存中的地址会被保存并用于下一次程序运行

  • 上述bit所涉及内容在本套教程中皆不做解说。

版本要求

VkBufferUsageFlagBits 的枚举项

1.0

VK_BUFFER_USAGE_TRANSFER_SRC_BIT 表示缓冲区会被用作数据传送命令的来源

1.0

VK_BUFFER_USAGE_TRANSFER_DST_BIT 表示缓冲区会被用作数据传送命令的目标

1.0

VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT 表示缓冲区会被用作uniform纹素缓冲区(着色器中只读)

1.0

VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT 表示缓冲区会被用作storage纹素缓冲区(任意着色器可读,可在计算着色器中进行写入)

1.0

VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT 表示缓冲区会被用作uniform缓冲区(在着色器中只读)

1.0

VK_BUFFER_USAGE_STORAGE_BUFFER_BIT 表示缓冲区会被用作storage缓冲区(可在任意着色器中指定其可读写性)

1.0

VK_BUFFER_USAGE_INDEX_BUFFER_BIT 表示缓冲区会被用作索引缓冲区

1.0

VK_BUFFER_USAGE_VERTEX_BUFFER_BIT 表示缓冲区会被用作顶点缓冲区

1.0

VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT 表示缓冲区会被用作间接绘制命令的参数缓冲区

1.2

VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT 表示缓冲区的在物理设备内存中的地址会被保存并用于下一次程序运行

  • 纹素指贴图纹理中的单个像素。纹素缓冲区可以被指定格式,如同1D图像,以及可按索引对纹素进行访问。

  • 只读的storage缓冲区的优势是最大容量比uniform缓冲区大,甚至无上限,劣势是访问速度可能比uniform缓冲区慢。

获取缓冲区的内存分配要求

vkGetBufferMemoryRequirements(...)获取缓冲区的内存分配信息:

VkResult void vkGetBufferMemoryRequirements(...) 的参数说明

VkDevice device

逻辑设备的handle

VkBuffer buffer

缓冲区的handle

VkMemoryRequirements* pMemoryRequirements

若执行成功,将内存分配要求写入到*pMemoryRequirements

struct VkMemoryRequirements 的成员说明

VkDeviceSize size

所需设备内存的大小

VkDeviceSize alignment

内存的对齐要求,一定是2的乘方,除了dynamic uniform缓冲区等情况外,通常不必理会这个成员

uint32_t memoryTypeBits

各个bit分别对应VkPhysicalDeviceMemoryProperties::memoryTypes数组各成员表示的内存类型是否支持该缓冲区/图像

可用以下代码确定VkMemoryAllocateInfo::memoryTypeIndex,其中desiredMemoryProperties是你所需的物理设备内存属性:

VkMemoryAllocateInfo memoryAllocateInfo = {
    .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
};
VkMemoryRequirements memoryRequirements;
vkGetBufferMemoryRequirements(graphicsBase::Base().Device(), handle, &memoryRequirements);
memoryAllocateInfo.allocationSize = memoryRequirements.size; //取得所需的内存分配大小
memoryAllocateInfo.memoryTypeIndex = UINT32_MAX;
auto& physicalDeviceMemoryProperties = graphicsBase::Base().PhysicalDeviceMemoryProperties();
for (size_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++) //遍历所有设备内存类型
    if ( //如果相应设备内存类型支持该缓冲区/图像,则继续执行后续判断,否则短路
        memoryRequirements.memoryTypeBits & 1 << i &&
        //如果相应设备内存类型支持所需的内存属性,覆写memoryAllocateInfo.memoryTypeIndex
        (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & desiredMemoryProperties) == desiredMemoryProperties) {
        memoryAllocateInfo.memoryTypeIndex = i;
        break;
    }
  • memoryRequirements.memoryTypeBits & 1 << i可能很不好懂,1 << i得到的是左数第i个比特位为1的数值,若其与memoryRequirements.memoryTypeBits位与的结果非零,则说明memoryRequirements.memoryTypeBitsi位被置位,也就意味着,VkPhysicalDeviceMemoryProperties::memoryTypes数组中的第i个成员所代表的内存类型,可以被用于该缓冲区/图像。注意一定要理解VkMemoryAllocateInfo::memoryTypeBits的意义。

将设备内存绑定到缓冲区

vkBindBufferMemory(...)将一块连续的设备内存绑定到缓冲区:

VkResult void vkBindBufferMemory(...) 的参数说明

VkDevice device

逻辑设备的handle

VkBuffer buffer

缓冲区的handle

VkDeviceMemory deviceMemory

设备内存的handle

VkDeviceSize memoryOffset

要绑定的内存块距离deviceMemory所指代内存区域起始位置的字节数

封装为buffer类

VKBase.h,vulkan命名空间中,添加以下代码:

class buffer {
    VkBuffer handle = VK_NULL_HANDLE;
public:
    buffer() = default;
    buffer(VkBufferCreateInfo& createInfo) {
        Create(createInfo);
    }
    buffer(buffer&& other) noexcept { MoveHandle; }
    ~buffer() { DestroyHandleBy(vkDestroyBuffer); }
    //Getter
    DefineHandleTypeOperator;
    DefineAddressFunction;
    //Const Function
    VkMemoryAllocateInfo MemoryAllocateInfo(VkMemoryPropertyFlags desiredMemoryProperties) const {
        VkMemoryAllocateInfo memoryAllocateInfo = {
            .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
        };
        VkMemoryRequirements memoryRequirements;
        vkGetBufferMemoryRequirements(graphicsBase::Base().Device(), handle, &memoryRequirements);
        memoryAllocateInfo.allocationSize = memoryRequirements.size;
        memoryAllocateInfo.memoryTypeIndex = UINT32_MAX;
        auto& physicalDeviceMemoryProperties = graphicsBase::Base().PhysicalDeviceMemoryProperties();
        for (size_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++)
            if (memoryRequirements.memoryTypeBits & 1 << i &&
                (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & desiredMemoryProperties) == desiredMemoryProperties) {
                memoryAllocateInfo.memoryTypeIndex = i;
                break;
            }
        //不在此检查是否成功取得内存类型索引,因为会把memoryAllocateInfo返回出去,交由外部检查
        //if (memoryAllocateInfo.memoryTypeIndex == UINT32_MAX)
        //    outStream << std::format("[ buffer ] ERROR\nFailed to find any memory type satisfies all desired memory properties!\n");
        return memoryAllocateInfo;
    }
    result_t BindMemory(VkDeviceMemory deviceMemory, VkDeviceSize memoryOffset = 0) const {
        VkResult result = vkBindBufferMemory(graphicsBase::Base().Device(), handle, deviceMemory, memoryOffset);
        if (result)
            outStream << std::format("[ buffer ] ERROR\nFailed to attach the memory!\nError code: {}\n", int32_t(result));
        return result;
    }
    //Non-const Function
    result_t Create(VkBufferCreateInfo& createInfo) {
        createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
        VkResult result = vkCreateBuffer(graphicsBase::Base().Device(), &createInfo, nullptr, &handle);
        if (result)
            outStream << std::format("[ buffer ] ERROR\nFailed to create a buffer!\nError code: {}\n", int32_t(result));
        return result;
    }
};

同设备内存一起封装为bufferMemory类

VKBase.h,vulkan命名空间中,buffer的定义后添加以下代码:

class bufferMemory :buffer, deviceMemory {
public:
    bufferMemory() = default;
    bufferMemory(VkBufferCreateInfo& createInfo, VkMemoryPropertyFlags desiredMemoryProperties) {
        Create(createInfo, desiredMemoryProperties);
    }
    bufferMemory(bufferMemory&& other) noexcept :
        buffer(std::move(other)), deviceMemory(std::move(other)) {
        areBound = other.areBound;
        other.areBound = false;
    }
    ~bufferMemory() { areBound = false; }
    //Getter
    //不定义到VkBuffer和VkDeviceMemory的转换函数,因为32位下这俩类型都是uint64_t的别名,会造成冲突(虽然,谁他妈还用32位PC!)
    VkBuffer Buffer() const { return static_cast<const buffer&>(*this); }
    const VkBuffer* AddressOfBuffer() const { return buffer::Address(); }
    VkDeviceMemory Memory() const { return static_cast<const deviceMemory&>(*this); }
    const VkDeviceMemory* AddressOfMemory() const { return deviceMemory::Address(); }
    //若areBond为true,则成功分配了设备内存、创建了缓冲区,且成功绑定在一起
    bool AreBound() const { return areBound; }
    using deviceMemory::AllocationSize;
    using deviceMemory::MemoryProperties;
    //Const Function
    using deviceMemory::MapMemory;
    using deviceMemory::UnmapMemory;
    using deviceMemory::BufferData;
    using deviceMemory::RetrieveData;
    //Non-const Function
    //以下三个函数仅用于Create(...)可能执行失败的情况
    result_t CreateBuffer(VkBufferCreateInfo& createInfo) {
        return buffer::Create(createInfo);
    }
    result_t AllocateMemory(VkMemoryPropertyFlags desiredMemoryProperties) {
        VkMemoryAllocateInfo allocateInfo = MemoryAllocateInfo(desiredMemoryProperties);
        if (allocateInfo.memoryTypeIndex >= graphicsBase::Base().PhysicalDeviceMemoryProperties().memoryTypeCount)
            return VK_RESULT_MAX_ENUM; //没有合适的错误代码,别用VK_ERROR_UNKNOWN
        return Allocate(allocateInfo);
    }
    result_t BindMemory() {
        if (VkResult result = buffer::BindMemory(Memory()))
            return result;
        areBound = true;
        return VK_SUCCESS;
    }
    //分配设备内存、创建缓冲、绑定
    result_t Create(VkBufferCreateInfo& createInfo, VkMemoryPropertyFlags desiredMemoryProperties) {
        VkResult result;
        false || //这行用来应对Visual Studio中代码的对齐
            (result = CreateBuffer(createInfo)) || //用||短路执行
            (result = AllocateMemory(desiredMemoryProperties)) ||
            (result = BindMemory());
        return result;
    }
};

Buffer View

缓冲区视图(VkBufferView)定义了将纹素缓冲区作为1D图像使用的方式。

创建缓冲区视图

vkCreateBufferView(...)创建缓冲区视图:

VkResult VKAPI_CALL vkCreateBufferView(...) 的参数说明

VkDevice device

逻辑设备的handle

const VkBufferViewCreateInfo* pCreateInfo

指向VkBufferView的创建信息

const VkAllocationCallbacks* pAllocator

一个指向描述自定义内存分配方式的结构体的指针

VkBufferView* pBufferView

若执行成功,将缓冲区视图的handle写入*pBufferView

struct VkBufferViewCreateInfo 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkBufferViewCreateFlags flags

VkBuffer buffer

缓冲区的handle

VkFormat format

被用作的图像格式,关于图像格式见后文

VkDeviceSize offset

要被用作图像的内存块距离绑定到缓冲区的内存区域起始位置的字节数

VkDeviceSize range

要被用作图像的内存块的大小,单位为字节,用特殊值VK_WHOLE_SIZE表示使用从offset到缓冲区末尾的所有字节

封装为bufferView类

VKBase.h,vulkan命名空间中添加以下代码:

class bufferView {
    VkBufferView handle = VK_NULL_HANDLE;
public:
    bufferView() = default;
    bufferView(VkBufferViewCreateInfo& createInfo) {
        Create(createInfo);
    }
    bufferView(VkBuffer buffer, VkFormat format, VkDeviceSize offset = 0, VkDeviceSize range = 0 /*VkBufferViewCreateFlags flags*/) {
        Create(buffer, format, offset, range);
    }
    bufferView(bufferView&& other) noexcept { MoveHandle; }
    ~bufferView() { DestroyHandleBy(vkDestroyBufferView); }
    //Getter
    DefineHandleTypeOperator;
    DefineAddressFunction;
    //Non-const Function
    result_t Create(VkBufferViewCreateInfo& createInfo) {
        createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
        VkResult result = vkCreateBufferView(graphicsBase::Base().Device(), &createInfo, nullptr, &handle);
        if (result)
            outStream << std::format("[ bufferView ] ERROR\nFailed to create a buffer view!\nError code: {}\n", int32_t(result));
        return result;
    }
    result_t Create(VkBuffer buffer, VkFormat format, VkDeviceSize offset = 0, VkDeviceSize range = 0 /*VkBufferViewCreateFlags flags*/) {
        VkBufferViewCreateInfo createInfo = {
            .buffer = buffer,
            .format = format,
            .offset = offset,
            .range = range
        };
        return Create(createInfo);
    }
};

Image

图像(VkImage)引用设备内存,指代图像数据。图像同缓冲区的不同之处是,图像具有格式和内存布局,也就意味着其中的数据未必是线性排列的。

创建图像

vkCreateImage(...)创建图像:

VkResult VKAPI_CALL vkCreateImage(...) 的参数说明

VkDevice device

逻辑设备的handle

const VkImageCreateInfo* pCreateInfo

指向VkImage的创建信息

const VkAllocationCallbacks* pAllocator

一个指向描述自定义内存分配方式的结构体的指针

VkImage* pImage

若执行成功,将图像的handle写入*pImage

struct VkImageCreateInfo 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkImageCreateFlags flags

VkImageType imageType

图像类型,可为VK_IMAGE_TYPE_1DVK_IMAGE_TYPE_2DVK_IMAGE_TYPE_3D

VkFormat format

图像的格式,关于图像格式见后文

VkExtent3D extent

图像的尺寸

uint32_t mipLevels

图像的mip等级数,应用于需要生成mipmap的图像

uint32_t arrayLayers

图像的图层数,应用于图像数组

VkSampleCountFlagBits samples

图像的采样点个数,必须是2的次数,可用值从VK_SAMPLE_COUNT_1_BITVK_SAMPLE_COUNT_64_BIT

VkImageTiling tiling

图像数据的排列方式,不涉及扩展的话,可以是VK_IMAGE_TILING_OPTIMAL(最优排列),VK_IMAGE_TILING_LINEAR(线性排列,可能影响读写效率)

VkImageUsageFlags usage

图像的用途

VkSharingMode sharingMode

分享访问模式,默认为单一队列族独占访问,若填入VK_SHARING_MODE_CONCURRENT,则允许不同队列族的队列同时访问

uint32_t queueFamilyIndexCount

若sharingMode为VK_SHARING_MODE_CONCURRENT,则在此填入同时访问本缓冲区的队列族的个数

uint32_t pQueueFamilyIndices

若sharingMode为VK_SHARING_MODE_CONCURRENT,则指向同时访问本缓冲区的队列族的索引

VkImageLayout layout

图像的初始内存布局

版本要求

VkImageCreateFlagBits 的枚举项

1.0

VK_IMAGE_CREATE_SPARSE_BINDING_BIT 表示图像会被稀疏绑定

1.0

VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT 表示会被稀疏绑定的图像能被部分绑定

1.0

VK_IMAGE_CREATE_SPARSE_ALIASED_BIT 表示会被稀疏绑定的图像对应的内存会与其他图像的内存混叠

1.0

VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT 表示为此图像创建的图像视图能具有与此图像不同的格式

1.0

VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT 表示为此2D图像创建的图像视图能被用作立方体贴图或立方体贴图数组

1.1

VK_IMAGE_CREATE_ALIAS_BIT 见后文

1.1

VK_IMAGE_CREATE_SPLIT_INSTANCE_BIND_REGIONS_BIT 只知道与device group相关,我不知道这具体是干啥的

1.1

VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT 表示为此3D图像创建的图像视图能被用作2D贴图或2D贴图数组

1.1

VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT 表示此图像的格式为压缩格式,但能用于创建非压缩格式的图像视图

1.1

VK_IMAGE_CREATE_EXTENDED_USAGE_BIT 表示usageFlags中能包含此图像的格式不支持,但此图像的图像视图的格式支持的用途

1.1

VK_IMAGE_CREATE_PROTECTED_BIT 表示创建受保护的图像

1.1

VK_IMAGE_CREATE_DISJOINT_BIT 表示多层面(multi-planar)图像格式的各个层面必须被分别绑定(通过向vkBindImageMemory2(...)提供多个绑定信息)

  • VK_IMAGE_CREATE_ALIAS_BIT表示存在与此图像以完全相同的参数创建并绑定到相同设备内存的另一图像,通过任一图像进行的读写会被连贯地反映到另一图像上。创建时注明了VK_IMAGE_CREATE_DISJOINT_BIT的多层面图像的一个层面,和同样注明了该bit的单层面图像也能混叠,这里不做进一步解释。

版本要求

VkImageUsageFlagBits 的枚举项

1.0

VK_IMAGE_USAGE_TRANSFER_SRC_BIT 表示图像会被用作数据传送命令的来源

1.0

VK_IMAGE_USAGE_TRANSFER_DST_BIT 表示图像会被用作数据传送命令的目标

1.0

VK_IMAGE_USAGE_SAMPLED_BIT 表示图像会被用于采样

1.0

VK_IMAGE_USAGE_STORAGE_BIT 表示图像会被用作storage图像(任意着色器可读,可在计算着色器中进行写入)

1.0

VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT 表示图像会被用作颜色附件

1.1

VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT 表示图像会被用作深度模板附件

1.1

VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT 表示绑定到图像的设备内存会是惰性分配的(见本节末尾)

1.1

VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT 表示图像会被用作输入附件

  • VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT不得与VK_IMAGE_USAGE_COLOR_ATTACHMENT_BITVK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BITVK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT以外的bit一同使用。

关于图像的内存布局,参见图像内存屏障

获取图像的内存分配要求

vkGetImageMemoryRequirements(...)获取图像的内存分配信息:

VkResult void vkGetImageMemoryRequirements(...) 的参数说明

VkDevice device

逻辑设备的handle

VkImage image

图像的handle

VkMemoryRequirements* pMemoryRequirements

若执行成功,将内存分配要求写入到*pMemoryRequirements

关于VkMemoryRequirements和确定VkMemoryAllocateInfo::memoryTypeIndex的方式与前文获取缓冲区的内存分配要求中一致。
不同于缓冲区的是,图像内存可以被惰性分配(见后文),但驱动未必支持惰性分配内存,因此相比缓冲区的情形,还得多做一步:

VkMemoryAllocateInfo memoryAllocateInfo = {
    .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
};
VkMemoryRequirements memoryRequirements;
vkGetImageMemoryRequirements(graphicsBase::Base().Device(), handle, &memoryRequirements);
memoryAllocateInfo.allocationSize = memoryRequirements.size;
auto GetMemoryTypeIndex = [](uint32_t memoryTypeBits, VkMemoryPropertyFlags desiredMemoryProperties) {
    auto& physicalDeviceMemoryProperties = graphicsBase::Base().PhysicalDeviceMemoryProperties();
    for (size_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++)
        if (memoryTypeBits & 1 << i &&
            (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & desiredMemoryProperties) == desiredMemoryProperties)
            return i;
    return UINT32_MAX;
};
memoryAllocateInfo.memoryTypeIndex = GetMemoryTypeIndex(memoryRequirements.memoryTypeBits, desiredMemoryProperties);
//如果没能获取有效的memoryTypeIndex,试着从desiredMemoryProperties中除去VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT后再次调用GetMemoryTypeIndex(...)
if (memoryAllocateInfo.memoryTypeIndex == UINT32_MAX &&
    desiredMemoryProperties & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)
    memoryAllocateInfo.memoryTypeIndex = GetMemoryTypeIndex(memoryRequirements.memoryTypeBits, desiredMemoryProperties & ~VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);

将设备内存绑定到图像

vkBindImageMemory(...)将一块连续的设备内存绑定到图像:

VkResult void vkBindImageMemory(...) 的参数说明

VkDevice device

逻辑设备的handle

VkImage image

图像的handle

VkDeviceMemory deviceMemory

设备内存的handle

VkDeviceSize memoryOffset

要绑定的内存块距离deviceMemory所指代内存区域起始位置的字节数

封装为image类

VKBase.h,vulkan命名空间中,添加以下代码:

class image {
    VkImage handle = VK_NULL_HANDLE;
public:
    image() = default;
    image(VkImageCreateInfo& createInfo) {
        Create(createInfo);
    }
    image(image&& other) noexcept { MoveHandle; }
    ~image() { DestroyHandleBy(vkDestroyImage); }
    //Getter
    DefineHandleTypeOperator;
    DefineAddressFunction;
    //Const Function
    VkMemoryAllocateInfo MemoryAllocateInfo(VkMemoryPropertyFlags desiredMemoryProperties) const {
        VkMemoryAllocateInfo memoryAllocateInfo = {
            .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
        };
        VkMemoryRequirements memoryRequirements;
        vkGetImageMemoryRequirements(graphicsBase::Base().Device(), handle, &memoryRequirements);
        memoryAllocateInfo.allocationSize = memoryRequirements.size;
        auto GetMemoryTypeIndex = [](uint32_t memoryTypeBits, VkMemoryPropertyFlags desiredMemoryProperties) {
            auto& physicalDeviceMemoryProperties = graphicsBase::Base().PhysicalDeviceMemoryProperties();
            for (size_t i = 0; i < physicalDeviceMemoryProperties.memoryTypeCount; i++)
                if (memoryTypeBits & 1 << i &&
                    (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & desiredMemoryProperties) == desiredMemoryProperties)
                    return i;
            return UINT32_MAX;
        };
        memoryAllocateInfo.memoryTypeIndex = GetMemoryTypeIndex(memoryRequirements.memoryTypeBits, desiredMemoryProperties);
        if (memoryAllocateInfo.memoryTypeIndex == UINT32_MAX &&
            desiredMemoryProperties & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT)
            memoryAllocateInfo.memoryTypeIndex = GetMemoryTypeIndex(memoryRequirements.memoryTypeBits, desiredMemoryProperties & ~VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);
        //不在此检查是否成功取得内存类型索引,因为会把memoryAllocateInfo返回出去,交由外部检查
        //if (memoryAllocateInfo.memoryTypeIndex == -1)
        //    outStream << std::format("[ image ] ERROR\nFailed to find any memory type satisfies all desired memory properties!\n");
        return memoryAllocateInfo;
    }
    result_t BindMemory(VkDeviceMemory deviceMemory, VkDeviceSize memoryOffset = 0) const {
        VkResult result = vkBindImageMemory(graphicsBase::Base().Device(), handle, deviceMemory, memoryOffset);
        if (result)
            outStream << std::format("[ image ] ERROR\nFailed to attach the memory!\nError code: {}\n", int32_t(result));
        return result;
    }
    //Non-const Function
    result_t Create(VkImageCreateInfo& createInfo) {
        createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
        VkResult result = vkCreateImage(graphicsBase::Base().Device(), &createInfo, nullptr, &handle);
        if (result)
            outStream << std::format("[ image ] ERROR\nFailed to create an image!\nError code: {}\n", int32_t(result));
        return result;
    }
};

同设备内存一起封装为imageMemory类

VKBase.h,vulkan命名空间中,image的定义后添加以下代码:

class imageMemory :image, deviceMemory {
public:
    imageMemory() = default;
    imageMemory(VkImageCreateInfo& createInfo, VkMemoryPropertyFlags desiredMemoryProperties) {
        Create(createInfo, desiredMemoryProperties);
    }
    imageMemory(imageMemory&& other) noexcept :
        image(std::move(other)), deviceMemory(std::move(other)) {
        areBound = other.areBound;
        other.areBound = false;
    }
    ~imageMemory() { areBound = false; }
    //Getter
    VkImage Image() const { return static_cast<const image&>(*this); }
    const VkImage* AddressOfImage() const { return image::Address(); }
    VkDeviceMemory Memory() const { return static_cast<const deviceMemory&>(*this); }
    const VkDeviceMemory* AddressOfMemory() const { return deviceMemory::Address(); }
    bool AreBound() const { return areBound; }
    using deviceMemory::AllocationSize;
    using deviceMemory::MemoryProperties;
    //Non-const Function
    //以下三个函数仅用于Create(...)可能执行失败的情况
    result_t CreateImage(VkImageCreateInfo& createInfo) {
        return image::Create(createInfo);
    }
    result_t AllocateMemory(VkMemoryPropertyFlags desiredMemoryProperties) {
        VkMemoryAllocateInfo allocateInfo = MemoryAllocateInfo(desiredMemoryProperties);
        if (allocateInfo.memoryTypeIndex >= graphicsBase::Base().PhysicalDeviceMemoryProperties().memoryTypeCount)
            return VK_RESULT_MAX_ENUM; //没有合适的错误代码,别用VK_ERROR_UNKNOWN
        return Allocate(allocateInfo);
    }
    result_t BindMemory() {
        if (VkResult result = image::BindMemory(Memory()))
            return result;
        areBound = true;
        return VK_SUCCESS;
    }
    //分配设备内存、创建图像、绑定
    result_t Create(VkImageCreateInfo& createInfo, VkMemoryPropertyFlags desiredMemoryProperties) {
        VkResult result;
        false || //这行用来应对Visual Studio中代码的对齐
            (result = CreateImage(createInfo)) || //用||短路执行
            (result = AllocateMemory(desiredMemoryProperties)) ||
            (result = BindMemory());
        return result;
    }
};

Image View

图像视图(VkImageView)定义了图像的使用方式。

创建图像视图

vkCreateImageView(...)创建图像视图,参数如下:

VkResult VKAPI_CALL vkCreateImageView(...) 的参数说明

VkDevice device

逻辑设备的handle

const VkImageViewCreateInfo* pCreateInfo

指向VkImageView的创建信息

const VkAllocationCallbacks* pAllocator

一个指向描述自定义内存分配方式的结构体的指针

VkImageView* pView

若执行成功,将图像视图的handle写入*pView

struct VkImageViewCreateInfo 的成员说明

VkStructureType sType

结构体的类型,本处必须是VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO

const void* pNext

如有必要,指向一个用于扩展该结构体的结构体

VkImageViewCreateFlags flags

VkImage image

图像的handle

VkImageViewType viewType

图像视图的类型

VkFormat format

图像视图的格式,通常与图像的格式一致

VkComponentMapping components

指定各通道的映射关系

VkImageSubresourceRange subresourceRange

子资源范围

版本要求

VkImageViewType 的枚举项

1.0

VK_IMAGE_VIEW_TYPE_1D 表示将图像用作1D图像

1.0

VK_IMAGE_VIEW_TYPE_2D 表示将图像用作2D图像

1.0

VK_IMAGE_VIEW_TYPE_3D 表示将图像用作3D图像

1.0

VK_IMAGE_VIEW_TYPE_CUBE 表示将图像用作立方体图像

1.0

VK_IMAGE_VIEW_TYPE_1D_ARRAY 表示将图像用作1D图像数组

1.0

VK_IMAGE_VIEW_TYPE_2D_ARRAY 表示将图像用作2D图像数组

1.0

VK_IMAGE_VIEW_TYPE_CUBE_ARRAY 表示将图像用作立方体图像数组

VkComponentMapping由四个VkComponentSwizzle构成。

版本要求

VkComponentSwizzle 的枚举项

1.0

VK_COMPONENT_SWIZZLE_IDENTITY 表示使用该通道原本对应的通道,即不改变映射关系

1.0

VK_COMPONENT_SWIZZLE_ZERO 表示使用该通道的数值一概归0

1.0

VK_COMPONENT_SWIZZLE_ONE 表示使用该通道的数值一概归1

1.0

VK_COMPONENT_SWIZZLE_R 表示将R通道映射到该通道

1.0

VK_COMPONENT_SWIZZLE_G 表示将G通道映射到该通道

1.0

VK_COMPONENT_SWIZZLE_B 表示将B通道映射到该通道

1.0

VK_COMPONENT_SWIZZLE_A 表示将A通道映射到该通道

VkImageSubresourceRange指定能通过view访问image的哪部分资源,先来看其成员:

struct VkImageSubresourceRange 的成员说明

VkImageAspectFlags aspectMask

所使用图像的层面(即aspect,我想不到更好的翻译了)

uint32_t baseMipLevel

初始mip等级

uint32_t levelCount

mip等级总数

uint32_t baseArrayLayer

初始图层

uint32_t layerCount

图层总数

版本要求

VkImageAspectFlagBits 的枚举项

1.3

VK_IMAGE_ASPECT_NONE 表示不使用任何层面

1.0

VK_IMAGE_ASPECT_COLOR_BIT 表示颜色层面

1.0

VK_IMAGE_ASPECT_DEPTH_BIT 表示深度层面

1.0

VK_IMAGE_ASPECT_STENCIL_BIT 表示模板层面

1.0

VK_IMAGE_ASPECT_METADATA_BIT 表示元数据层面(用于稀疏绑定)

1.1

VK_IMAGE_ASPECT_PLANE_0_BIT 表示多层面(multi-planar)图像格式的0号平面

1.1

VK_IMAGE_ASPECT_PLANE_1_BIT 表示多层面(multi-planar)图像格式的1号平面

1.1

VK_IMAGE_ASPECT_PLANE_2_BIT 表示多层面(multi-planar)图像格式的2号平面

  • 为什么要指定aspectMask?举例而言,图像格式中存在同时存储深度值和模板值的格式,你不能同时对深度值和模板值进行采样,那么当你要实现阴影贴图时(这是通过采样深度值实现的),应当将aspectMask设置为VK_IMAGE_ASPECT_DEPTH_BIT

baseMipLevel和levelCount应用于有mipmap(见生成Mipmap)的图像。在着色器中以0为LOD(level of detail)进行采样时,采样得到的是图像中mip等级为baseMipLevel的mipmap,有效的采样范围对应图像中mip范围在闭区间 [baseMipLevel, baseMipLevel + levelCount - 1] 内的mipmap。

baseArrayLayer和layerCount应用于贴图数组。此处图层的概念并非是类似Photoshop中的图层,一个图层指代数组中的一张图像。在着色器中以0为w坐标(w是采样坐标的第三个分量)进行采样时,采样得到的是图像中索引为baseArrayLayer的图层,有效的采样范围对应原image中索引范围在闭区间 [baseArrayLayer, baseArrayLayer + layerCount - 1] 内的图层。

封装为imageView类

VKBase.h,vulkan命名空间中,添加以下代码:

class imageView {
    VkImageView handle = VK_NULL_HANDLE;
public:
    imageView() = default;
    imageView(VkImageViewCreateInfo& createInfo) {
        Create(createInfo);
    }
    imageView(VkImage image, VkImageViewType viewType, VkFormat format, const VkImageSubresourceRange& subresourceRange, VkImageViewCreateFlags flags = 0) {
        Create(image, viewType, format, subresourceRange, flags);
    }
    imageView(imageView&& other) noexcept { MoveHandle; }
    ~imageView() { DestroyHandleBy(vkDestroyImageView); }
    //Getter
    DefineHandleTypeOperator;
    DefineAddressFunction;
    //Non-const Function
    result_t Create(VkImageViewCreateInfo& createInfo) {
        createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        VkResult result = vkCreateImageView(graphicsBase::Base().Device(), &createInfo, nullptr, &handle);
        if (result)
            outStream << std::format("[ imageView ] ERROR\nFailed to create an image view!\nError code: {}\n", int32_t(result));
        return result;
    }
    result_t Create(VkImage image, VkImageViewType viewType, VkFormat format, const VkImageSubresourceRange& subresourceRange, VkImageViewCreateFlags flags = 0) {
        VkImageViewCreateInfo createInfo = {
            .flags = flags,
            .image = image,
            .viewType = viewType,
            .format = format,
            .subresourceRange = subresourceRange
        };
        return Create(createInfo);
    }
};

图像及数据的格式

Vulkan中的VkFormat即适用于图像格式,也适用于描述数据格式(比如顶点属性)。
由于VkFormat的枚举项过多,这里不一一解释,仅对非压缩颜色格式和深度格式做简单提要。

非压缩颜色格式

非压缩颜色格式有两个部分,以VK_FORMAT_R8G8B8A8_UNORM为例,是R8G8B8A8和UNORM,前者是作为图像格式的各个通道名称与该通道所占据的比特数的组合,后者叫做数字格式(numeric format)。

数字格式

对应的C++底层类型

在着色器中读取为

说明(记任意通道占据的比特数为N)

UNORM

无符号整型

float

在着色器中被标准化到[ 0, 1]的范围

SNORM

无符号整型

float

在着色器中被标准化到[-1, 1]的范围

USCALED

无符号整型

float

在着色器中数值不变,但被转为浮点数,数值范围为[0, 2^N-1]

SSCALED

有符号整型

float

在着色器中数值不变,但被转为浮点数,数值范围为[-2^(N-1), 2^(N-1)-1]

UINT

无符号整型

uint

在着色器中数值不变,数值范围为[0, 2^N-1]

SINT

有符号整型

int

在着色器中数值不变,数值范围为[-2^(N-1), 2^(N-1)-1]

UFLOAT

无符号浮点数(C++里没这东西)

float

在着色器中数值不变

SFLOAT

有符号浮点数

float

在着色器中数值不变

SRGB

无符号整型

float

在着色器中被标准化到[ 0, 1]的范围,通过渲染等方式写入该格式的图像时,对RGB通道应用sRGB EOTF

  • 用作顶点属性时,RGBA各通道对应xyzw各分量。

  • 数值不变的前提是没超出相应的有效数值范围,着色器中的float是32位浮点数。

关于标准化,举例而言:
VK_FORMAT_R8G8B8A8_UNORM的任一通道,若数值为0,则着色器中读取为0.f,若数值为255,则着色器中读取为1.f。
VK_FORMAT_R16G16B16A16_SNORM的任一通道,若数值为32767,则着色器中读取为1.f,若数值为-32768,则着色器中读取为-1.f。

深度模板格式

深度模板格式有以下几种:

格式名称

说明

VK_FORMAT_D16_UNORM

只有深度值,16位无符号整型,被标准化到[ 0, 1]的范围

VK_FORMAT_X8_D24_UNORM_PACK32

共32位,8个比特未使用,只有深度值,24位无符号整型,被标准化到[ 0, 1]的范围

VK_FORMAT_D32_SFLOAT

只有深度值,32位有符号浮点数

VK_FORMAT_S8_UINT

只有模板值,8位无符号整型

VK_FORMAT_D16_UNORM_S8_UINT

16位无符号整型作为深度值,被标准化到[ 0, 1]的范围,8位无符号整型作为模板值

VK_FORMAT_D24_UNORM_S8_UINT

24位无符号整型作为深度值,被标准化到[ 0, 1]的范围,8位无符号整型作为模板值

VK_FORMAT_D32_SFLOAT_S8_UINT

32位有符号浮点数作为深度值,8位无符号整型作为模板值

  • 出于对齐的考虑,VK_FORMAT_D32_SFLOAT_S8_UINT格式下每个像素很可能是8字节大小(尤其是,当数据线性排列时),即有24位不被使用。

VKFormat.h

为了在从文件读取/存入文件时正确计算图像的大小,这里给出我的VKFormat.h文件,其中记录了各种格式(仅包含Vulkan1.0且不算扩展提供的格式)对应的通道数、每个通道的大小、单个像素的大小,以及数据类型。

24.03.23更新:你可以直接使用Vulkan官方提供的vk_format_utils.h,你可以在以下安装目录找到它:VulkanSDK\版本号\Include\vulkan\utility,较早期的SDK可能没有这个文件,相应地有VulkanSDK\版本号\Include\vulkan\vulkan_format_traits.hpp

注意我的VKFormat.hvulkan_format_traits.hpp有所不同,在vulkan_format_traits.hppVK_FORMAT_D32_SFLOAT_S8_UINT格式的blockSize是5,在我的VKFormat.hVK_FORMAT_D32_SFLOAT_S8_UINT格式的单个像素大小是8(其实有些格式不应适用“单个像素”这个概念),这种差别是由于我的VKFormat.h的用处是计算线性排列的数据大小。

#pragma once
#include "EasyVKStart.h"

struct formatInfo {
    enum rawDataType :uint8_t {
        other,        //0,没数据或各个通道不同
        integer,      //1,数据类型为整型
        floatingPoint //1,数据类型为浮点数
    };
    uint8_t componentCount;   //通道数
    uint8_t sizePerComponent; //每个通道的大小,0意味着压缩,或不均等,或少于1
    uint8_t sizePerPixel;     //每个像素的大小,0意味着压缩
    uint8_t rawDataType;      //底层数据类型
};
constexpr formatInfo formatInfos_v1_0[] = {
    { 0, 0, 0, 0 },//VK_FORMAT_UNDEFINED = 0,

    { 2, 0, 1, 1 },//VK_FORMAT_R4G4_UNORM_PACK8 = 1,

    { 4, 0, 2, 1 },//VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2,
    { 4, 0, 2, 1 },//VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3,

    { 3, 0, 2, 1 },//VK_FORMAT_R5G6B5_UNORM_PACK16 = 4,
    { 3, 0, 2, 1 },//VK_FORMAT_B5G6R5_UNORM_PACK16 = 5,

    { 4, 0, 2, 1 },//VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6,
    { 4, 0, 2, 1 },//VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7,
    { 4, 0, 2, 1 },//VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8,

    { 1, 1, 1, 1 },//VK_FORMAT_R8_UNORM = 9,
    { 1, 1, 1, 1 },//VK_FORMAT_R8_SNORM = 10,
    { 1, 1, 1, 1 },//VK_FORMAT_R8_USCALED = 11,
    { 1, 1, 1, 1 },//VK_FORMAT_R8_SSCALED = 12,
    { 1, 1, 1, 1 },//VK_FORMAT_R8_UINT = 13,
    { 1, 1, 1, 1 },//VK_FORMAT_R8_SINT = 14,
    { 1, 1, 1, 1 },//VK_FORMAT_R8_SRGB = 15,

    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_UNORM = 16,
    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_SNORM = 17,
    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_USCALED = 18,
    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_SSCALED = 19,
    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_UINT = 20,
    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_SINT = 21,
    { 2, 1, 2, 1 },//VK_FORMAT_R8G8_SRGB = 22,

    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_UNORM = 23,
    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_SNORM = 24,
    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_USCALED = 25,
    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_SSCALED = 26,
    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_UINT = 27,
    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_SINT = 28,
    { 3, 1, 3, 1 },//VK_FORMAT_R8G8B8_SRGB = 29,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_UNORM = 30,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_SNORM = 31,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_USCALED = 32,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_SSCALED = 33,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_UINT = 34,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_SINT = 35,
    { 3, 1, 3, 1 },//VK_FORMAT_B8G8R8_SRGB = 36,

    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_UNORM = 37,
    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_SNORM = 38,
    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_USCALED = 39,
    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_SSCALED = 40,
    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_UINT = 41,
    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_SINT = 42,
    { 4, 1, 4, 1 },//VK_FORMAT_R8G8B8A8_SRGB = 43,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_UNORM = 44,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_SNORM = 45,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_USCALED = 46,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_SSCALED = 47,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_UINT = 48,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_SINT = 49,
    { 4, 1, 4, 1 },//VK_FORMAT_B8G8R8A8_SRGB = 50,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56,
    { 4, 1, 4, 1 },//VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57,

    { 4, 0, 4, 1 },//VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58,
    { 4, 0, 4, 1 },//VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59,
    { 4, 0, 4, 1 },//VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60,
    { 4, 0, 4, 1 },//VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61,
    { 4, 0, 4, 1 },//VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62,
    { 4, 0, 4, 1 },//VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63,
    { 4, 0, 4, 1 },//VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64,
    { 4, 0, 4, 1 },//VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65,
    { 4, 0, 4, 1 },//VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66,
    { 4, 0, 4, 1 },//VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67,
    { 4, 0, 4, 1 },//VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68,
    { 4, 0, 4, 1 },//VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69,

    { 1, 2, 2, 1 },//VK_FORMAT_R16_UNORM = 70,
    { 1, 2, 2, 1 },//VK_FORMAT_R16_SNORM = 71,
    { 1, 2, 2, 1 },//VK_FORMAT_R16_USCALED = 72,
    { 1, 2, 2, 1 },//VK_FORMAT_R16_SSCALED = 73,
    { 1, 2, 2, 1 },//VK_FORMAT_R16_UINT = 74,
    { 1, 2, 2, 1 },//VK_FORMAT_R16_SINT = 75,
    { 1, 2, 2, 2 },//VK_FORMAT_R16_SFLOAT = 76,

    { 2, 2, 4, 1 },//VK_FORMAT_R16G16_UNORM = 77,
    { 2, 2, 4, 1 },//VK_FORMAT_R16G16_SNORM = 78,
    { 2, 2, 4, 1 },//VK_FORMAT_R16G16_USCALED = 79,
    { 2, 2, 4, 1 },//VK_FORMAT_R16G16_SSCALED = 80,
    { 2, 2, 4, 1 },//VK_FORMAT_R16G16_UINT = 81,
    { 2, 2, 4, 1 },//VK_FORMAT_R16G16_SINT = 82,
    { 2, 2, 4, 2 },//VK_FORMAT_R16G16_SFLOAT = 83,

    { 3, 2, 6, 1 },//VK_FORMAT_R16G16B16_UNORM = 84,
    { 3, 2, 6, 1 },//VK_FORMAT_R16G16B16_SNORM = 85,
    { 3, 2, 6, 1 },//VK_FORMAT_R16G16B16_USCALED = 86,
    { 3, 2, 6, 1 },//VK_FORMAT_R16G16B16_SSCALED = 87,
    { 3, 2, 6, 1 },//VK_FORMAT_R16G16B16_UINT = 88,
    { 3, 2, 6, 1 },//VK_FORMAT_R16G16B16_SINT = 89,
    { 3, 2, 6, 2 },//VK_FORMAT_R16G16B16_SFLOAT = 90,

    { 4, 2, 8, 1 },//VK_FORMAT_R16G16B16A16_UNORM = 91,
    { 4, 2, 8, 1 },//VK_FORMAT_R16G16B16A16_SNORM = 92,
    { 4, 2, 8, 1 },//VK_FORMAT_R16G16B16A16_USCALED = 93,
    { 4, 2, 8, 1 },//VK_FORMAT_R16G16B16A16_SSCALED = 94,
    { 4, 2, 8, 1 },//VK_FORMAT_R16G16B16A16_UINT = 95,
    { 4, 2, 8, 1 },//VK_FORMAT_R16G16B16A16_SINT = 96,
    { 4, 2, 8, 2 },//VK_FORMAT_R16G16B16A16_SFLOAT = 97,

    { 1, 4, 4, 1 },//VK_FORMAT_R32_UINT = 98,
    { 1, 4, 4, 1 },//VK_FORMAT_R32_SINT = 99,
    { 1, 4, 4, 2 },//VK_FORMAT_R32_SFLOAT = 100,

    { 2, 4, 8, 1 },//VK_FORMAT_R32G32_UINT = 101,
    { 2, 4, 8, 1 },//VK_FORMAT_R32G32_SINT = 102,
    { 2, 4, 8, 2 },//VK_FORMAT_R32G32_SFLOAT = 103,

    { 3, 4, 12, 1 },//VK_FORMAT_R32G32B32_UINT = 104,
    { 3, 4, 12, 1 },//VK_FORMAT_R32G32B32_SINT = 105,
    { 3, 4, 12, 2 },//VK_FORMAT_R32G32B32_SFLOAT = 106,

    { 4, 4, 16, 1 },//VK_FORMAT_R32G32B32A32_UINT = 107,
    { 4, 4, 16, 1 },//VK_FORMAT_R32G32B32A32_SINT = 108,
    { 4, 4, 16, 2 },//VK_FORMAT_R32G32B32A32_SFLOAT = 109,

    { 1, 8, 8, 1 },//VK_FORMAT_R64_UINT = 110,
    { 1, 8, 8, 1 },//VK_FORMAT_R64_SINT = 111,
    { 1, 8, 8, 2 },//VK_FORMAT_R64_SFLOAT = 112,

    { 2, 8, 16, 1 },//VK_FORMAT_R64G64_UINT = 113,
    { 2, 8, 16, 1 },//VK_FORMAT_R64G64_SINT = 114,
    { 2, 8, 16, 2 },//VK_FORMAT_R64G64_SFLOAT = 115,

    { 3, 8, 24, 1 },//VK_FORMAT_R64G64B64_UINT = 116,
    { 3, 8, 24, 1 },//VK_FORMAT_R64G64B64_SINT = 117,
    { 3, 8, 24, 2 },//VK_FORMAT_R64G64B64_SFLOAT = 118,

    { 4, 8, 32, 1 },//VK_FORMAT_R64G64B64A64_UINT = 119,
    { 4, 8, 32, 1 },//VK_FORMAT_R64G64B64A64_SINT = 120,
    { 4, 8, 32, 2 },//VK_FORMAT_R64G64B64A64_SFLOAT = 121,

    { 3, 0, 4, 2 },//VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122,
    { 3, 0, 4, 2 },//VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123,//'E' is a 5-bit shared exponent

    { 1, 2, 2, 1 },//VK_FORMAT_D16_UNORM = 124,
    { 1, 3, 4, 1 },//VK_FORMAT_X8_D24_UNORM_PACK32 = 125,//8 bits are unused therefore componentCount is 1, sizePerComponent is 3
    { 1, 4, 4, 2 },//VK_FORMAT_D32_SFLOAT = 126,
    { 1, 1, 1, 1 },//VK_FORMAT_S8_UINT = 127,
    { 2, 0, 3, 1 },//VK_FORMAT_D16_UNORM_S8_UINT = 128,
    { 2, 0, 4, 1 },//VK_FORMAT_D24_UNORM_S8_UINT = 129,
    { 2, 0, 8, 0 },//VK_FORMAT_D32_SFLOAT_S8_UINT = 130,//24 bits are unused if data is of linear tiling therefore sizePerPixel is 8

    //压缩格式
    { 3, 0, 0, 1 },//VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131,
    { 3, 0, 0, 1 },//VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132,
    { 4, 0, 0, 1 },//VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133,
    { 4, 0, 0, 1 },//VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134,

    { 4, 0, 0, 1 },//VK_FORMAT_BC2_UNORM_BLOCK = 135,
    { 4, 0, 0, 1 },//VK_FORMAT_BC2_SRGB_BLOCK = 136,
    { 4, 0, 0, 1 },//VK_FORMAT_BC3_UNORM_BLOCK = 137,
    { 4, 0, 0, 1 },//VK_FORMAT_BC3_SRGB_BLOCK = 138,

    { 1, 0, 0, 1 },//VK_FORMAT_BC4_UNORM_BLOCK = 139,
    { 1, 0, 0, 1 },//VK_FORMAT_BC4_SNORM_BLOCK = 140,
    { 2, 0, 0, 1 },//VK_FORMAT_BC5_UNORM_BLOCK = 141,
    { 2, 0, 0, 1 },//VK_FORMAT_BC5_SNORM_BLOCK = 142,
    { 3, 0, 0, 2 },//VK_FORMAT_BC6H_UFLOAT_BLOCK = 143,
    { 3, 0, 0, 2 },//VK_FORMAT_BC6H_SFLOAT_BLOCK = 144,
    { 4, 0, 0, 1 },//VK_FORMAT_BC7_UNORM_BLOCK = 145,
    { 4, 0, 0, 1 },//VK_FORMAT_BC7_SRGB_BLOCK = 146,

    { 3, 0, 0, 1 },//VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147,
    { 3, 0, 0, 1 },//VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148,
    { 4, 0, 0, 1 },//VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149,
    { 4, 0, 0, 1 },//VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150,
    { 4, 0, 0, 1 },//VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151,
    { 4, 0, 0, 1 },//VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152,

    { 1, 0, 0, 1 },//VK_FORMAT_EAC_R11_UNORM_BLOCK = 153,
    { 1, 0, 0, 1 },//VK_FORMAT_EAC_R11_SNORM_BLOCK = 154,
    { 2, 0, 0, 1 },//VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155,
    { 2, 0, 0, 1 },//VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156,

    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183,
    { 4, 0, 0, 1 },//VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184,
};
  • 压缩格式写在这里是凑数的。

惰性初始化的设备内存

若分配的设备内存具有VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT内存属性,那么它是可以被惰性分配的。
所谓惰性分配,就是指若暂时用不着这部分内存,那么就先不对其进行分配,或只分配很小的容量。
惰性分配的设备内存配合VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,可能可以(取决于显卡驱动)使得,一旦渲染通道结束后就没用的图像不需要实际占用设备内存。比如先被用作图像附件,然后在下一个子通道中被用作输入附件的图像。
Vulkan官方标准11.2.15. Lazily Allocated Memory的Note中的原文:

Using lazily allocated memory objects for framebuffer attachments that are not needed once a render pass instance has completed may allow some implementations to never allocate memory for such attachments.

注意,惰性分配的设备内存只能被绑定给创建时注明了VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT的图像,不得被用于缓冲区。且分配可被惰性初始化的设备内存时,不得注明VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT(即惰性分配的设备内存无法被CPU侧读写)。