From e81f4d52246399833063d7ca008310dbe9a2a730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 23 Nov 2020 19:17:26 +0100 Subject: [PATCH] Vk: overview docs for Memory, Buffer and Image. --- doc/snippets/MagnumVk.cpp | 106 ++++++++++++++++++++++++++++++++++++++ src/Magnum/Vk/Buffer.h | 31 ++++++++++- src/Magnum/Vk/Image.h | 32 +++++++++++- src/Magnum/Vk/Memory.h | 60 ++++++++++++++++++++- 4 files changed, 226 insertions(+), 3 deletions(-) diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index 5ee24d059..c703c85ed 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -25,10 +25,12 @@ #include #include +#include #include #include "Magnum/Magnum.h" #include "Magnum/Math/Color.h" +#include "Magnum/Vk/Buffer.h" #include "Magnum/Vk/CommandBuffer.h" #include "Magnum/Vk/CommandPool.h" #include "Magnum/Vk/Device.h" @@ -37,6 +39,7 @@ #include "Magnum/Vk/ExtensionProperties.h" #include "Magnum/Vk/Instance.h" #include "Magnum/Vk/Integration.h" +#include "Magnum/Vk/Image.h" #include "Magnum/Vk/LayerProperties.h" #include "Magnum/Vk/Queue.h" #include "Magnum/Vk/Shader.h" @@ -115,6 +118,35 @@ Vk::Device device{instance, std::move(info)}; /* [wrapping-optimizing-properties-device-move] */ } +{ +Vk::Device device{NoCreate}; +/* [Buffer-usage] */ +Vk::Buffer buffer{device, + Vk::BufferCreateInfo{Vk::BufferUsage::VertexBuffer, 1024*1024}, + Vk::MemoryFlag::DeviceLocal +}; +/* [Buffer-usage] */ +} + +{ +Vk::Device device{NoCreate}; +/* [Buffer-usage-custom-allocation] */ +Vk::Buffer buffer{device, + Vk::BufferCreateInfo{Vk::BufferUsage::VertexBuffer, 1024*1024}, + NoAllocate +}; + +Vk::MemoryRequirements requirements = buffer.memoryRequirements(); +Vk::Memory memory{device, Vk::MemoryAllocateInfo{ + requirements.size(), + device.properties().pickMemory(Vk::MemoryFlag::DeviceLocal, + requirements.memories()) +}}; + +buffer.bindMemory(memory, 0); +/* [Buffer-usage-custom-allocation] */ +} + { /* [CommandPool-usage] */ Vk::Device device{DOXYGEN_IGNORE(NoCreate)}; @@ -211,6 +243,35 @@ if(device.isExtensionEnabled()) { /* [Device-isExtensionEnabled] */ } +{ +Vk::Device device{NoCreate}; +/* [Image-usage] */ +Vk::Image image{device, Vk::ImageCreateInfo2D{ + Vk::ImageUsage::Sampled, VK_FORMAT_R8G8B8A8_SRGB, {1024, 1024}, 1 + }, Vk::MemoryFlag::DeviceLocal +}; +/* [Image-usage] */ +} + +{ +Vk::Device device{NoCreate}; +/* [Image-usage-custom-allocation] */ +Vk::Image image{device, Vk::ImageCreateInfo2D{ + Vk::ImageUsage::Sampled, VK_FORMAT_R8G8B8A8_SRGB, {1024, 1024}, 1 + }, NoAllocate +}; + +Vk::MemoryRequirements requirements = image.memoryRequirements(); +Vk::Memory memory{device, Vk::MemoryAllocateInfo{ + requirements.size(), + device.properties().pickMemory(Vk::MemoryFlag::DeviceLocal, + requirements.memories()) +}}; + +image.bindMemory(memory, 0); +/* [Image-usage-custom-allocation] */ +} + { int argc{}; const char** argv{}; @@ -310,6 +371,51 @@ if(instance.isExtensionEnabled()) { /* [Instance-isExtensionEnabled] */ } +{ +Vk::Device device{NoCreate}; +Containers::ArrayView vertexData, indexData; +/* [Memory-usage] */ +/* 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-usage] */ + +/* [Memory-mapping] */ +/* The memory gets unmapped again at the end of scope */ +{ + Containers::Array mapped = memory.map(); + Utility::copy(vertexData, mapped.prefix(vertexData.size())); + Utility::copy(indexData, + mapped.slice(indicesOffset, indicesOffset + indexData.size())); +} +/* [Memory-mapping] */ +} + { Vk::Device device{DOXYGEN_IGNORE(NoCreate)}; /* [Shader-usage] */ diff --git a/src/Magnum/Vk/Buffer.h b/src/Magnum/Vk/Buffer.h index f13edcb17..85db17d20 100644 --- a/src/Magnum/Vk/Buffer.h +++ b/src/Magnum/Vk/Buffer.h @@ -159,7 +159,36 @@ CORRADE_ENUMSET_OPERATORS(BufferCreateInfo::Flags) @brief Buffer @m_since_latest -Wraps a @type_vk_keyword{Buffer}. +Wraps a @type_vk_keyword{Buffer} and its memory. + +@section Vk-Buffer-usage Basic usage + +Pass a @ref BufferCreateInfo with desired usage and size to the @ref Buffer +constructor together with specifying @ref MemoryFlags for the allocation. + +@snippet MagnumVk.cpp Buffer-usage + +@attention At this point, a dedicated allocation is used, subsequently + accessible through @ref dedicatedMemory(). This behavior may change in the + future. + +@section Vk-Buffer-usage-custom-allocation Custom memory allocation + +Using @ref Buffer(Device&, const BufferCreateInfo&, NoAllocateT), the buffer +will be created without any memory bound. Buffer memory requirements can be +then queried using @ref memoryRequirements() and an allocated memory bound with +@ref bindMemory(). See @ref Memory for further details about memory allocation. + +@snippet MagnumVk.cpp Buffer-usage-custom-allocation + +Using @ref bindDedicatedMemory() instead of @ref bindMemory() will transfer +ownership of the @ref Memory to the buffer instance, making it subsequently +available through @ref dedicatedMemory(). This matches current behavior of the +@ref Buffer(Device&, const BufferCreateInfo&, MemoryFlags) constructor shown +above, except that you have more control over choosing and allocating the +memory. + +@see @ref Image */ class MAGNUM_VK_EXPORT Buffer { public: diff --git a/src/Magnum/Vk/Image.h b/src/Magnum/Vk/Image.h index 804e736f8..9ac256f24 100644 --- a/src/Magnum/Vk/Image.h +++ b/src/Magnum/Vk/Image.h @@ -333,7 +333,37 @@ class ImageCreateInfoCubeMapArray: public ImageCreateInfo { @brief Image @m_since_latest -Wraps a @type_vk_keyword{Image}. +Wraps a @type_vk_keyword{Image} and its memory. + +@section Vk-Image-usage Basic usage + +Pass one of the @ref ImageCreateInfo subclasses depending on desired image type +with desired usage, format, size and other propoerties to the @ref Image +constructor together with specifying @ref MemoryFlags for memory allocation. + +@snippet MagnumVk.cpp Image-usage + +@attention At this point, a dedicated allocation is used, subsequently + accessible through @ref dedicatedMemory(). This behavior may change in the + future. + +@section Vk-Image-usage-custom-allocation Custom memory allocation + +Using @ref Image(Device&, const ImageCreateInfo&, NoAllocateT), the image will +be created without any memory attached. Image memory requirements can be +subsequently queried using @ref memoryRequirements() and an allocated memory +bound with @ref bindMemory(). See @ref Memory for further details about memory allocation. + +@snippet MagnumVk.cpp Image-usage-custom-allocation + +Using @ref bindDedicatedMemory() instead of @ref bindMemory() will transfer +ownership of the @ref Memory to the image instance, making it subsequently +available through @ref dedicatedMemory(). This matches current behavior of the +@ref Image(Device&, const ImageCreateInfo&, MemoryFlags) constructor shown +above, except that you have more control over choosing and allocating the +memory. + +@see @ref Buffer */ class MAGNUM_VK_EXPORT Image { public: diff --git a/src/Magnum/Vk/Memory.h b/src/Magnum/Vk/Memory.h index ae82909cd..e8f2263a2 100644 --- a/src/Magnum/Vk/Memory.h +++ b/src/Magnum/Vk/Memory.h @@ -267,7 +267,65 @@ class MAGNUM_VK_EXPORT MemoryAllocateInfo { @brief Device memory @m_since_latest -Wraps a @type_vk_keyword{DeviceMemory}. +Wraps a @type_vk_keyword{DeviceMemory} and handles its allocation and mapping. + +@section Vk-Memory-usage Usage + +By default, the memory will get allocated for you during the creation of +@ref Buffer, @ref Image and other objects. In case you want to handle the +allocation yourself instead (which you indicate by passing the @ref NoAllocate +tag to constructors of these objects), it consists of these steps: + +1. Querying memory requirements of a particular object, for example using + @ref Buffer::memoryRequirements() or @ref 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 + @ref DeviceProperties::pickMemory() +3. Allocating a new @ref 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 + @ref Buffer::bindMemory(), @ref Image::bindMemory() and others + +The following example allocates a single block memory for two buffers, one +containing vertex and the other index data: + +@snippet MagnumVk.cpp Memory-usage + +@section Vk-Memory-mapping Memory mapping + +If the memory is created with the @ref MemoryFlag::HostVisible flag, it can be +mapped on the host via @ref map(). The unmapping is then taken care of by a +custom deleter in the returned @ref Corrade::Containers::Array. It's possible +to map either the whole range or a sub-range, however note that one @ref 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: + +@snippet MagnumVk.cpp Memory-mapping + + + +@m_class{m-note m-success} + +@par Map temporarily or forever? + Mapping smaller ranges and unmapping again after makes sense on 32-bit + systems where the amount of virtual memory is limited --- otherwise it may + happen that the system won't be able to find a sufficiently large block of + virtual memory, causing the next mapping to fail. On 64-bit systems the + virtual address space is sufficiently large for most use cases and it's + common to just map the whole memory block for its whole lifetime. */ class MAGNUM_VK_EXPORT Memory { public: