欢迎光临
我们一直在努力

Using Vulkan -- Memory Allocation -- Sparse Resources

Vulkan 稀疏资源是一种创建 VkBuffer 和 VkImage 对象的方式,这些对象可以非连续地绑定到一个或多个 VkDeviceMemory 分配上。稀疏资源包含诸多特性与细节,规范中已有详尽说明。正如实现指南所指出,大多数实现会利用稀疏资源向应用暴露一段线性虚拟地址范围,并在绑定时将每个稀疏块映射到物理页。


绑定稀疏内存

与普通资源调用 vkBindBufferMemory() 或 vkBindImageMemory() 不同,稀疏内存通过队列操作 vkQueueBindSparse() 进行绑定。其主要优势在于:应用可在稀疏资源的整个生命周期内重新绑定内存。

需要注意,这要求应用做出额外考量:

  • 应用必须使用同步原语,确保其他队列不会在绑定变更时并发访问内存范围。
  • 调用 vkFreeMemory() 释放 VkDeviceMemory 对象不会自动解除绑定到该内存的资源(或资源区域)。
  • 应用严禁访问已绑定到已释放内存的资源。

稀疏缓冲

以下示例直观展示稀疏 VkBuffer 在内存中的布局。注意:并非强制,但大多数实现对 VkBuffer 使用 64 KB 的稀疏块大小(实际大小由 VkMemoryRequirements::alignment 返回)。

假设一个 256 KB 的 VkBuffer,应用希望分别更新 3 个部分:

  • 区域 A:64 KB
  • 区域 B:128 KB
  • 区域 C:64 KB

下图展示应用视角下的 VkBuffer:


稀疏图像

Mip 尾区域

稀疏图像可用于分别更新各 mip 层级,从而形成 mip tail region。规范中配有图示描述各种可能场景。

基础稀疏资源示例

以下示例演示稀疏图像的基础创建,并将其绑定到物理内存。该基础示例创建普通 VkImage 对象,但使用细粒度内存分配,以多段内存范围支撑该资源。

VkDevice device;
VkQueue queue;
VkImage sparseImage;
VkAllocationCallbacks* pAllocator = NULL;
VkMemoryRequirements memoryRequirements = {};
VkDeviceSize offset = 0;
VkSparseMemoryBind binds[MAX_CHUNKS] = {}; // MAX_CHUNKS 并非 Vulkan 定义
uint32_t bindCount = 0;

// …

// 创建图像对象
const VkImageCreateInfo sparseImageInfo =
{
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType
NULL, // pNext
VK_IMAGE_CREATE_SPARSE_BINDING_BIT | …, // flags

};
vkCreateImage(device, &sparseImageInfo, pAllocator, &sparseImage);

// 获取内存需求
vkGetImageMemoryRequirements(
device,
sparseImage,
&memoryRequirements);

// 细粒度绑定内存,从可能的多个 VkDeviceMemory 池中获取可用内存范围
// 仅作示例,可按性能优化
while (memoryRequirements.size && bindCount < MAX_CHUNKS)
{
VkSparseMemoryBind* pBind = &binds[bindCount];
pBind->resourceOffset = offset;

AllocateOrGetMemoryRange(
device,
&memoryRequirements,
&pBind->memory,
&pBind->memoryOffset,
&pBind->size);

// 内存范围大小必须是对齐值的倍数
assert(IsMultiple(pBind->size, memoryRequirements.alignment));
assert(IsMultiple(pBind->memoryOffset, memoryRequirements.alignment));

memoryRequirements.size -= pBind->size;
offset += pBind->size;
bindCount++;
}

// 确保整张图像都有物理内存支撑
if (memoryRequirements.size)
{
// 错误:块数量过多
}

const VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo =
{
sparseImage, // image
bindCount, // bindCount
binds // pBinds
};

const VkBindSparseInfo bindSparseInfo =
{
VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, // sType
NULL, // pNext

1, // imageOpaqueBindCount
&opaqueBindInfo, // pImageOpaqueBinds

};

// vkQueueBindSparse 按队列对象外部同步
AcquireQueueOwnership(queue);

// 执行内存绑定
vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE);

ReleaseQueueOwnership(queue);

高级稀疏资源

该进阶示例创建数组式颜色附件 / 纹理图像,并仅将 LOD 0 与所需元数据绑定到物理内存。

VkDevice device;
VkQueue queue;
VkImage sparseImage;
VkAllocationCallbacks* pAllocator = NULL;
VkMemoryRequirements memoryRequirements = {};
uint32_t sparseRequirementsCount = 0;
VkSparseImageMemoryRequirements* pSparseReqs = NULL;
VkSparseMemoryBind binds[MY_IMAGE_ARRAY_SIZE] = {};
VkSparseImageMemoryBind imageBinds[MY_IMAGE_ARRAY_SIZE] = {};
uint32_t bindCount = 0;

// 创建图像对象(可渲染且可采样)
const VkImageCreateInfo sparseImageInfo =
{
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, // sType
NULL, // pNext
VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT | …, // flags

VK_FORMAT_R8G8B8A8_UNORM, // format

MY_IMAGE_ARRAY_SIZE, // arrayLayers

VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
VK_IMAGE_USAGE_SAMPLED_BIT, // usage

};
vkCreateImage(device, &sparseImageInfo, pAllocator, &sparseImage);

// 获取内存需求
vkGetImageMemoryRequirements(
device,
sparseImage,
&memoryRequirements);

// 获取稀疏图像切面属性
vkGetImageSparseMemoryRequirements(
device,
sparseImage,
&sparseRequirementsCount,
NULL);

pSparseReqs = (VkSparseImageMemoryRequirements*)
malloc(sparseRequirementsCount * sizeof(VkSparseImageMemoryRequirements));

vkGetImageSparseMemoryRequirements(
device,
sparseImage,
&sparseRequirementsCount,
pSparseReqs);

// 绑定 LOD 0 与所需元数据到内存
for (uint32_t i = 0; i < sparseRequirementsCount; ++i)
{
if (pSparseReqs[i].formatProperties.aspectMask &
VK_IMAGE_ASPECT_METADATA_BIT)
{
// 元数据不可与其他切面合并
assert(pSparseReqs[i].formatProperties.aspectMask ==
VK_IMAGE_ASPECT_METADATA_BIT);

if (pSparseReqs[i].formatProperties.flags &
VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT)
{
VkSparseMemoryBind* pBind = &binds[bindCount];
pBind->memorySize = pSparseReqs[i].imageMipTailSize;
bindCount++;

// … 分配内存范围

pBind->resourceOffset = pSparseReqs[i].imageMipTailOffset;
pBind->memoryOffset = /* 分配的 memoryOffset */;
pBind->memory = /* 分配的 memory */;
pBind->flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT;

}
else
{
// 每个数组层需要一个 mip tail 区域
for (uint32_t a = 0; a < sparseImageInfo.arrayLayers; ++a)
{
VkSparseMemoryBind* pBind = &binds[bindCount];
pBind->memorySize = pSparseReqs[i].imageMipTailSize;
bindCount++;

// … 分配内存范围

pBind->resourceOffset = pSparseReqs[i].imageMipTailOffset +
(a * pSparseReqs[i].imageMipTailStride);

pBind->memoryOffset = /* 分配的 memoryOffset */;
pBind->memory = /* 分配的 memory */
pBind->flags = VK_SPARSE_MEMORY_BIND_METADATA_BIT;
}
}
}
else
{
// 资源数据
VkExtent3D lod0BlockSize =
{
AlignedDivide(
sparseImageInfo.extent.width,
pSparseReqs[i].formatProperties.imageGranularity.width),
AlignedDivide(
sparseImageInfo.extent.height,
pSparseReqs[i].formatProperties.imageGranularity.height),
AlignedDivide(
sparseImageInfo.extent.depth,
pSparseReqs[i].formatProperties.imageGranularity.depth)
};
size_t totalBlocks =
lod0BlockSize.width *
lod0BlockSize.height *
lod0BlockSize.depth;

// 每个块大小与对齐要求相同,计算 0 级总内存大小
VkDeviceSize lod0MemSize = totalBlocks * memoryRequirements.alignment;

// 为每个数组层分配内存
for (uint32_t a = 0; a < sparseImageInfo.arrayLayers; ++a)
{
// … 分配内存范围

VkSparseImageMemoryBind* pBind = &imageBinds[a];
pBind->subresource.aspectMask = pSparseReqs[i].formatProperties.aspectMask;
pBind->subresource.mipLevel = 0;
pBind->subresource.arrayLayer = a;

pBind->offset = (VkOffset3D){0, 0, 0};
pBind->extent = sparseImageInfo.extent;
pBind->memoryOffset = /* 分配的 memoryOffset */;
pBind->memory = /* 分配的 memory */;
pBind->flags = 0;
}
}

free(pSparseReqs);
}

const VkSparseImageOpaqueMemoryBindInfo opaqueBindInfo =
{
sparseImage, // image
bindCount, // bindCount
binds // pBinds
};

const VkSparseImageMemoryBindInfo imageBindInfo =
{
sparseImage, // image
sparseImageInfo.arrayLayers, // bindCount
imageBinds // pBinds
};

const VkBindSparseInfo bindSparseInfo =
{
VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, // sType
NULL, // pNext

1, // imageOpaqueBindCount
&opaqueBindInfo, // pImageOpaqueBinds
1, // imageBindCount
&imageBindInfo, // pImageBinds

};

// vkQueueBindSparse 按队列对象外部同步
AcquireQueueOwnership(queue);

// 执行内存绑定
vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE);

ReleaseQueueOwnership(queue);

赞(0)
未经允许不得转载:171主机测评 » Using Vulkan -- Memory Allocation -- Sparse Resources
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址