使用暂存(staging)缓冲区更新设备本地内存绑定的图像
译者注:示例代码点击此处
暂存缓冲区不仅可用于在缓冲区之间传输数据,还可用于在图像之间传输数据,在这里,我们将展示如何映射缓冲区的内存并将其内容复制到所需的图像。
怎么做...
1.创建一个足够大的暂存缓冲区来保存要传输的整个数据。为缓冲区指定VK_BUFFER_USAGE_TRANSFER_SRC_BIT用法,并将其句柄存储在名为staging_buffer的VkBuffer类型变量中。分配一个支持VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT属性的内存对象,并将其绑定到暂存缓冲区。将内存对象的句柄存储在名为memory_object的VkDeviceMemory类型变量中。映射内存并使用要传输到图像的数据更新其内容。取消映射内存。暂存缓冲区更详细的描述在使用暂存缓冲区更新设备本地内存绑定的缓冲区内容。
2.获取主命令缓冲区的句柄并使用它来初始化名为command_buffer的VkCommandBuffer类型变量。
3.开始记录command_buffer。提供VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT标志(请参阅第三章缓冲区和同步中的开始命令缓冲区记录操作内容)。
4.获取要传输数据的图像句柄,并确保使用指定的VK_IMAGE_USAGE_TRANSFER_DST_BIT创建它。使用句柄初始化名为destination_image的VkImage类型变量。
5.在command_buffer中记录图像内存屏障。指定到目前为止使用图像的阶段,并为消费阶段使用VK_PIPELINE_STAGE_TRANSFER_BIT阶段。使用destination_image变量,提供图像的当前访问类型,并为新访问使用VK_ACCESS_TRANSFER_WRITE_BIT值。指定图像的当前布局,并为新布局使用VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL值。提供图像的应用方面,但是忽略队列索引,为两者使用VK_QUEUE_FAMILY_IGNORED值(请参阅设置图像内存屏障内容)。
6.在command_buffer中,记录从staging_buffer到destination_image的数据传输操作,提供VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL值作为图像布局,缓冲区偏移量0,缓冲区行长度为0,缓冲区图像高度为0。通过提供所需的mipmap级别,基本数组层索引和要更新的层数,指定要复制数据的图像的内存区域。提供图像的应用方面。指定图像的x、y和z坐标(以纹素为单位)和图像大小的偏移量(请参阅将将数据从缓冲区复制到图像内容)。
7.在command_buffer中记录另一个图像内存屏障。这次指定VK_PIPELINE_STAGE_TRANSFER_BIT值给生产阶段并设置数据传输之后将使用目标图像的适当阶段。在屏障中,将图像的布局从VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL更改为适合新用法的值。为两个队列族设置VK_QUEUE_FAMILY_IGNORED值,并提供图像的应用方面(请参阅置图像内存屏障内容)。
8.结束命令缓冲区记录操作,创建一个未发出信号的围栏并在将命令缓冲区提交到队列期间使用它以及应该发信号的信号量。等待创建的fence信号,销毁暂存缓冲区,并释放其内存对象,如使用暂存缓冲区更新设备本地内存绑定的缓冲区中所述。
这个怎么运作...
本节内容非常类似于使用暂存缓冲区更新设备本地内存绑定的缓冲区,这就是为什么只更详细的描述差异的原因。
首先,我们创建一个暂存缓冲区,为其分配一个内存对象,将其绑定在缓冲区,并将其映射到要从应用程序上传到GPU的数据上。
VkBuffer staging_buffer;
if( !CreateBuffer( logical_device, data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, *staging_buffer ) ) {
return false;
}
VkDeviceMemory memory_object;
if( !AllocateAndBindMemoryObjectToBuffer( physical_device, logical_device, *staging_buffer, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, *memory_object ) ) {
return false;
}
if( !MapUpdateAndUnmapHostVisibleMemory( logical_device, *memory_object, 0, data_size, data, true, nullptr ) ) {
return false;
}
接下来,我们开始命令缓冲区记录,并为目标图像设置一个屏障,以便它可以用作数据数据的目标。我们还记录数据传输操作:
if( !BeginCommandBufferRecordingOperation( command_buffer, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr ) ) {
return false;
}
SetImageMemoryBarrier( command_buffer, destination_image_generating_stages, VK_PIPELINE_STAGE_TRANSFER_BIT,
{
{
destination_image, // VkImage Image
destination_image_current_access, // VkAccessFlags CurrentAccess
VK_ACCESS_TRANSFER_WRITE_BIT, // VkAccessFlags NewAccess
destination_image_current_layout, // VkImageLayout CurrentLayout
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // VkImageLayout NewLayout
VK_QUEUE_FAMILY_IGNORED, // uint32_t CurrentQueueFamily
VK_QUEUE_FAMILY_IGNORED, // uint32_t NewQueueFamily
destination_image_aspect // VkImageAspectFlags Aspect
} } );
CopyDataFromBufferToImage( command_buffer, *staging_buffer, destination_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
{
{
0, // VkDeviceSize bufferOffset
0, // uint32_t bufferRowLength
0, // uint32_t bufferImageHeight
destination_image_subresource, // VkImageSubresourceLayers imageSubresource
destination_image_offset, // VkOffset3D imageOffset
destination_image_size, // VkExtent3D imageExtent
} } );
接下来,我们记录另一个屏障,将图像的使用从复制操作的目标更改为接下来将使用图像的目的的屏障。我们还结束了命令缓冲区记录操作:
SetImageMemoryBarrier( command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, destination_image_consuming_stages,
{
{
destination_image, // VkImage Image
VK_ACCESS_TRANSFER_WRITE_BIT, // VkAccessFlags CurrentAccess
destination_image_new_access, // VkAccessFlags NewAccess
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, // VkImageLayout CurrentLayout
destination_image_new_layout, // VkImageLayout NewLayout
VK_QUEUE_FAMILY_IGNORED, // uint32_t CurrentQueueFamily
VK_QUEUE_FAMILY_IGNORED, // uint32_t NewQueueFamily
destination_image_aspect // VkImageAspectFlags Aspect
} } );
if( !EndCommandBufferRecordingOperation( command_buffer ) ) {
return false;
}
之后,我们创建一个围栏(fence)并向队列提交一个命令缓冲区。然后我们等待围栏通知我们可以安全地删除缓冲区以及内存对象的那一刻:
VkFence fence;
if( !CreateFence( logical_device, false, fence ) ) {
return false;
}
if( !SubmitCommandBuffersToQueue( queue, {}, { command_buffer }, signal_semaphores, fence ) ) {
return false;
}
if( !WaitForFences( logical_device, { fence }, VK_FALSE, 500000000 ) ) {
return false;
}
DestroyBuffer( logical_device, staging_buffer );
FreeMemoryObject( logical_device, memory_object );
return true;
如果将现有缓冲区重用为暂存资源,我们不需要围栏,因为缓冲区将存活更长时间,可能在应用程序的整个生命周期中。这样我们可以避免频繁和不必要的创建和删除,以及内存对象的分配和释放。