上一篇讲了Vulkan设备的初始化,这一篇将讲述Vulkan的Image和Buffer以及内存分配。
原文出处:Vulkan in 30 Minutes Github
既然我们有了VkDevice,我们可以开始创建任意类型的资源,比如VkImage和VkBuffer。
相对GL来说,使用Vulkan时,你必须在创建Image之前声明Image的用法。你可以设定bit表示Image的使用类型-Color Attachment、Sampled Image、或者Image load/store。
You also specify the tiling for the image - LINEAR or OPTIMAL. This specifies the tiling/swizzling layout for the image data in memory. OPTIMAL tiled images are opaquely tiled, LINEAR are laid out just as you expect. This affects whether the image data is directly readable/writable, as well as format support - drivers report image support in terms of ‘what image types are supported in OPTIMAL tiling, and what image types are supported in LINEAR’. Be prepared for very limited LINEAR support.
你也可以指定Image的Tiling模式-Linear或者Optimal。这设置了Image在内存中的布局。这影响Image数据是否可读可写。
Buffer类似并更加直接,你指定了大小和用处。
Image并不是直接使用的,所以你将要创建VkImageView-这和D3D11类似。不像GLTextureView,Vulkan的ImageView是强制性的,但同样的想法-用于描述将数组切片或MIPLevel暴露给ImageView使用的地方,以及可选的不同(但兼容)格式(如将UNORM格式的纹理作为UINT使用)。
Buffer通常直接使用,因为它仅仅是一块内存,但如果你想将他们在Shader中作为TextureBuffer使用,你需要提供一个VkBufferView。
刚创建的Buffer和Image并不能立即使用,因为我们并没有为他们分配内存。
我们可以通过调用vkGetPhysicalDeviceMemoryProperties
查询应用可使用的内存。它会返回请求大小的一个或多个内存堆,或者请求属性的一种或多种内存类型。每种内存类型来自于一个内存堆 - 因此,一个典例就是PC上的一个独立显卡将会有两个堆 - 一个是系统内存,另一个是GPU内存,并且他们各自拥有多种内存类型。
The memory types have different properties. Some will be CPU visible or not, coherent between GPU and CPU access, cached or uncached, etc. You can find out all of these properties by querying from the physical device. This allows you to choose the memory type you want. E.g. staging resources will need to be in host visible memory, but your images you render to will want to be in device local memory for optimal use. However there is an additional restriction on memory selection that we’ll get to in the next section.
内存类型有不同属性。一些内存可以被CPU访问或者不行、GPU和CPU访问一致、有缓存或者无缓存等等。
To allocate memory you call vkAllocateMemory() which requires your VkDevice handle and a description structure. The structure dictates which type of memory to allocate from which heap and how much to allocate, and returns a VkDeviceMemory handle.
通过调用vkAllocateMemory()
可以分配内存,但它需要VkDevice句柄和描述结构体。
Host visible memory can be mapped for update - vkMapMemory()/vkUnmapMemory() are familiar functions. All maps are by definition persistent, and as long as you synchronise it’s legal to have memory mapped while in use by the GPU.
HostVisibleMemory是可以通过Map方式来完成数据的更新的(vkMapMemory()
/vkUnmapMemory()
)。
GL people will be familiar with the concept, but to explain for D3D11 people - the pointers returned by vkMapMemory() can be held and even written to by the CPU while the GPU is using them. These ‘persistent’ maps are perfectly valid as long as you obey the rules and make sure to synchronise access so that the CPU isn’t writing to parts of the memory allocation that the GPU is using (see later).
GL使用者应该熟悉这个概念,但解释给D3D11的用户,vkMapMemory返回的指针可以被hold住被CPU写入当GPU正在使用它们。这些持久化的映射是完全正确的只要你遵守规则并且确定同步了内存访问。
This is a little outside the scope of this guide but I’m going to mention it any chance I get - for the purposes of debugging, persistent maps of non-coherent memory with explicit region flushes will be much more efficient/fast than coherent memory. The reason being that for coherent memory the debugger must jump through hoops to detect and track changes, but the explicit flushes of non-coherent memory provide nice markup of modifications.
In RenderDoc to help out with this, if you flush a memory region then the tool assumes you will flush for every write, and turns off the expensive hoop-jumping to track coherent memory. That way even if the only memory available is coherent, then you can get efficient debugging.
这有点出了这个教程,但是我一旦有机会就会提及它。
Each VkBuffer or VkImage, depending on its properties like usage flags and tiling mode (remember that one?) will report their memory requirements to you via vkGetBufferMemoryRequirements or vkGetImageMemoryRequirements.
通过调用vkGetBufferMemoryRequirements/vkGetImageMemoryRequirements
,你可以知道VkBuffer/VkImage
的内存需求和类型。
The reported size requirement will account for padding for alignment between mips, hidden meta-data, and anything else needed for the total allocation. The requirements also include a bitmask of the memory types that are compatible with this particular resource. The obvious restrictions kick in here: that OPTIMAL tiling color attachment image will report that only DEVICE_LOCAL memory types are compatible, and it will be invalid to try to bind some HOST_VISIBLE memory.
获取到的内存大小是包括了Mips,隐藏的元数据以及其他需要的对象之间内存对齐、或者铺垫。要求的东西也包含兼容该资源内存类型的BitmapMask。这里有个明显的限制就是Optimal的ColorAttach Image将只能使用DeviceLocal的内存,如果尝试绑定HostVisible内存将是不正确的。
The memory type requirements generally won’t vary if you have the same kind of image or buffer. For example if you know that optimally tiled images can go in memory type 3, you can allocate all of them from the same place. You will only have to check the size and alignment requirements per-image. Read the spec for the exact guarantee here!
如果你有相同类型的Image或者Buffer,内存类型的要求通常不会太不一样。例如,如果你知道最优平铺Image可以使用内存类型3,你可以从相同的地方分配它们。你只需要对每个Image检查大小和对齐要求。为了准确地保证去读规范吧。
Note the memory allocation is by no means 1:1. You can allocate a large amount of memory and as long as you obey the above restrictions you can place several images or buffers in it at different offsets. The requirements include an alignment if you are placing the resource at a non-zero offset. In fact you will definitely want to do this in any real application, as there are limits on the total number of allocations allowed.
There is an additional alignment requirement bufferImageGranularity - a minimum separation required between memory used for a VkImage and memory used for a VkBuffer in the same VkDeviceMemory. Read the spec for more details, but this mostly boils down to an effective page size, and requirement that each page is only used for one type of resource.
注意,内存分配不是1:1的。
Once you have the right memory type and size and alignment, you can bind it with vkBindBufferMemory or vkBindImageMemory. This binding is immutable, and must happen before you start using the buffer or image.
一旦你有了正确的内存类型、大小和对齐,你可以通过vkBindBufferMemory或者vkBindImageMemory来绑定内存。这个绑定是固定的,并且发生在你开始使用Buffer或者Image之前。