Magnum::Vk::Memory class new in Git master

Device memory.

Wraps a VkDeviceMemory and handles its allocation and mapping. Device memory is backing Vulkan buffers, images and other objects.

Memory allocation

By default, the memory will get allocated for you during the creation of Buffer, Image and other objects. In case you want to handle the allocation yourself instead (which you indicate by passing the NoAllocate tag to constructors of these objects), it consists of these steps:

  1. Querying memory requirements of a particular object, for example using Buffer::memoryRequirements() or Image::memoryRequirements()
  2. Picking a memory type satisfying requirements of the object it's being allocated for (such as allowed memory types) and user requirements (whether it should be device-local, host-mappable etc.) using DeviceProperties::pickMemory()
  3. Allocating a new Memory or taking a (correctly aligned) sub-range of an existing allocation from given memory type
  4. Binding the memory (sub-range) to the object, using Buffer::bindMemory(), Image::bindMemory() and others

The following example allocates a single block memory for two buffers, one containing vertex and the other index data:

#include <Magnum/Vk/MemoryAllocateInfo.h>



/* Create buffers without allocating them */
Vk::Buffer vertices{device,
    Vk::BufferCreateInfo{Vk::BufferUsage::VertexBuffer, vertexData.size()},
    NoAllocate};
Vk::Buffer indices{device,
    Vk::BufferCreateInfo{Vk::BufferUsage::IndexBuffer, vertexData.size()},
    NoAllocate};

/* Query memory requirements of both buffers, calculate max alignment */
Vk::MemoryRequirements verticesRequirements = vertices.memoryRequirements();
Vk::MemoryRequirements indicesRequirements = indices.memoryRequirements();
const UnsignedLong alignment = Math::max(verticesRequirements.alignment(),
                                         indicesRequirements.alignment());

/* Allocate memory that's large enough to contain both buffers including
   the strictest alignment, and is of a type satisfying requirements of both */
Vk::Memory memory{device, Vk::MemoryAllocateInfo{
    verticesRequirements.alignedSize(alignment) +
        indicesRequirements.alignedSize(alignment),
    device.properties().pickMemory(Vk::MemoryFlag::HostVisible,
        verticesRequirements.memories() & indicesRequirements.memories())
}};

const UnsignedLong indicesOffset = verticesRequirements.alignedSize(alignment);

/* Bind the respective sub-ranges to the buffers */
vertices.bindMemory(memory, 0);
indices.bindMemory(memory, indicesOffset);

Memory mapping

If the memory is created with the MemoryFlag::HostVisible flag, it can be mapped on the host via map(). The unmapping is then taken care of by a custom deleter in the returned Corrade::Containers::Array. It's possible to map either the whole range or a sub-range, however note that one Memory object can't be mapped twice at the same time — in the code snippet above, it means that in order to upload vertex and index data, there are two options:

  • One is to first map the vertex buffer sub-range, upload the data, unmap it, and then do the same process for the index buffer sub-range. This way is more encapsulated without having to worry if there's already a mapping and who owns it, but means more work for the driver.
  • Another option is to map the whole memory at once and then upload data of particular buffers to correct subranges. Here the mapping has to be owned by some external entity which ensures it's valid for as long as any buffer wants to map its memory sub-range.

The following example maps the memory allocated above and copies index and vertex data to it:

/* The memory gets unmapped again at the end of scope */
{
    Containers::Array<char, Vk::MemoryMapDeleter> mapped = memory.map();
    Utility::copy(vertexData, mapped.prefix(vertexData.size()));
    Utility::copy(indexData,
        mapped.slice(indicesOffset, indicesOffset + indexData.size()));
}

Public static functions

static auto wrap(Device& device, VkDeviceMemory handle, UnsignedLong size, HandleFlags flags = {}) -> Memory
Wrap existing Vulkan handle.

Constructors, destructors, conversion operators

Memory(Device& device, const MemoryAllocateInfo& info) explicit
Constructor.
Memory(NoCreateT) explicit
Construct without allocating the memory.
Memory(const Memory&) deleted
Copying is not allowed.
Memory(Memory&& other) noexcept
Move constructor.
~Memory()
Destructor.
operator VkDeviceMemory()

Public functions

auto operator=(const Memory&) -> Memory& deleted
Copying is not allowed.
auto operator=(Memory&& other) -> Memory& noexcept
Move assignment.
auto handle() -> VkDeviceMemory
Underlying VkDeviceMemory handle.
auto handleFlags() const -> HandleFlags
Handle flags.
auto size() const -> UnsignedLong
Memory allocation size.
auto map(UnsignedLong offset, UnsignedLong size) -> Containers::Array<char, MemoryMapDeleter>
Map a memory range.
auto map() -> Containers::Array<char, MemoryMapDeleter>
Map the whole memory.
auto mapRead(UnsignedLong offset, UnsignedLong size) -> Containers::Array<const char, MemoryMapDeleter>
Map a memory range read-only.
auto mapRead() -> Containers::Array<const char, MemoryMapDeleter>
Map the whole memory read-only.
auto release() -> VkDeviceMemory
Release the underlying Vulkan memory.

Function documentation

static Memory Magnum::Vk::Memory::wrap(Device& device, VkDeviceMemory handle, UnsignedLong size, HandleFlags flags = {})

Wrap existing Vulkan handle.

Parameters
device Vulkan device the memory is allocated on
handle The VkDeviceMemory handle
size Memory size
flags Handle flags

The handle is expected to be originating from device. The size parameter will be used to properly size the output array coming from map(). If a concrete size is unknown, use a zero — you will then be able to only use the map(UnsignedLong, UnsignedLong) overload.

Unlike a memory allocated using a constructor, the Vulkan memory is by default not freed on destruction, use flags for different behavior.

Magnum::Vk::Memory::Memory(Device& device, const MemoryAllocateInfo& info) explicit

Constructor.

Parameters
device Vulkan device to allocate the memory on
info Memory allocation info

Magnum::Vk::Memory::Memory(NoCreateT) explicit

Construct without allocating the memory.

The constructed instance is equivalent to moved-from state. Useful in cases where you will overwrite the instance later anyway. Move another object over it to make it useful.

Magnum::Vk::Memory::~Memory()

Destructor.

Frees associated VkDeviceMemory handle, unless the instance was created using wrap() without HandleFlag::DestroyOnDestruction specified.

Magnum::Vk::Memory::operator VkDeviceMemory()

This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts.

Containers::Array<char, MemoryMapDeleter> Magnum::Vk::Memory::map(UnsignedLong offset, UnsignedLong size)

Map a memory range.

Parameters
offset Byte offset
size Memory size

The returned array size is size and the deleter performs an unmap. For this operation to work, the memory has to be allocated with MemoryFlag::HostVisible and the offset and size be in bounds for size().

Containers::Array<char, MemoryMapDeleter> Magnum::Vk::Memory::map()

Map the whole memory.

Equivalent to calling map(UnsignedLong, UnsignedLong) with 0 and size().

Containers::Array<const char, MemoryMapDeleter> Magnum::Vk::Memory::mapRead(UnsignedLong offset, UnsignedLong size)

Map a memory range read-only.

Like map(UnsignedLong, UnsignedLong) but returning a const array. Currently Vulkan doesn't have any flags to control read/write access, so apart from a different return type the behavior is equivalent.

Containers::Array<const char, MemoryMapDeleter> Magnum::Vk::Memory::mapRead()

Map the whole memory read-only.

Equivalent to calling mapRead(UnsignedLong, UnsignedLong) with 0 and size().

VkDeviceMemory Magnum::Vk::Memory::release()

Release the underlying Vulkan memory.

Releases ownership of the Vulkan memory and returns its handle so vkFreeMemory() is not called on destruction. The internal state is then equivalent to moved-from state.