Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
VulkanBuffer.cpp
Go to the documentation of this file.
6
8
9namespace EngineCore
10{
11
13{
15 if (engine == nullptr) throw std::runtime_error("engine is null");
16 ApplicationContext* context = engine->getContext();
17 device_ = context->getVkDevice();
19 allocator_ = context->getVmaAllocator();
20}
21
22VulkanBuffer::VulkanBuffer( VkDevice device, VkPhysicalDevice physicalDevice )
23 : device_( device )
24 , physicalDevice_( physicalDevice )
25 , allocator_( VK_NULL_HANDLE ) // Direct Vulkan mode - no VMA
26{
27}
28
30 : device_( context->getVkDevice() )
31 , physicalDevice_( context->getVkPhysicalDevice() )
32 , allocator_( context->getVmaAllocator() )
33{
34}
35
39
40void VulkanBuffer::setDebugName( const std::string & name )
41{
42 debugName = name;
43 if ( device_ != VK_NULL_HANDLE && buffer != VK_NULL_HANDLE )
44 {
46 }
47 // Set VMA allocation name for debugging
48 if ( allocator_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE )
49 {
50 vmaSetAllocationName( allocator_, allocation_, name.c_str() );
51 }
52}
53
54std::string VulkanBuffer::getDebugName() const
55{
56 return debugName;
57}
58
59void VulkanBuffer::create(size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
60{
61 // Use VMA if allocator is available, otherwise fall back to VulkanHelper
62 if (allocator_ != VK_NULL_HANDLE)
63 {
64 VkBufferCreateInfo bufferCI{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
65 bufferCI.size = size;
66 bufferCI.usage = usage;
67 bufferCI.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
68
69 VmaAllocationCreateInfo allocCI{};
70 allocCI.usage = VMA_MEMORY_USAGE_AUTO;
71
72 // Configure VMA based on requested memory properties
73 if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
74 {
75 allocCI.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
76 }
77 if (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)
78 {
79 allocCI.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
80 }
81 // For device-local memory, prefer device memory
82 if ((properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
83 !(properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT))
84 {
85 allocCI.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
86 }
87
88 VmaAllocationInfo allocInfo{};
89 if (vmaCreateBuffer(allocator_, &bufferCI, &allocCI, &buffer, &allocation_, &allocInfo) != VK_SUCCESS)
90 {
91 throw std::runtime_error("Failed to create VMA buffer: " + debugName);
92 }
93
94 this->size = size;
95
96 // If mapped bit was set, VMA returns persistent mapping
97 if (allocCI.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT)
98 {
99 mappedMemory = allocInfo.pMappedData;
101 }
102
103 isHostCoherent_ = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
104 }
105 else
106 {
107 // Fallback for direct Vulkan handle usage (tests without VMA)
108 VkDeviceMemory tempMemory;
109 VulkanHelper::createBuffer(device_, physicalDevice_, size, usage, properties, buffer, tempMemory);
110 this->size = size;
111 isHostCoherent_ = (properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
112 // Note: tempMemory is leaked in this case - this path should only be used in tests
113 // that manage memory directly
114 }
115}
116
118{
119 if (allocator_ != VK_NULL_HANDLE && buffer != VK_NULL_HANDLE)
120 {
121 // VMA handles both buffer and memory destruction together
122 // Note: VMA automatically unmaps if memory was mapped
123 vmaDestroyBuffer(allocator_, buffer, allocation_);
124 invalidate();
125 }
126 else if (device_ != VK_NULL_HANDLE && buffer != VK_NULL_HANDLE)
127 {
128 // Fallback for non-VMA path (should not be used in production)
129 if (isMapped()) unmap();
130 vkDestroyBuffer(device_, buffer, nullptr);
131 invalidate();
132 PLOGW << "VulkanBuffer::destroy() called without VMA - memory may be leaked";
133 }
134 else if (buffer != VK_NULL_HANDLE || allocation_ != VK_NULL_HANDLE)
135 {
136 PLOGW << "Called VulkanBuffer::destroy() with valid resources but no allocator handle. Resources could not be destroyed.";
137 }
138}
139
141{
142 if (isMapped()) return mappedMemory;
143
144 if (allocator_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE)
145 {
146 if (vmaMapMemory(allocator_, allocation_, &mappedMemory) != VK_SUCCESS)
147 {
148 PLOGE << "Failed to map VMA buffer: " << debugName;
149 return nullptr;
150 }
151 }
152 else
153 {
154 PLOGW << "VulkanBuffer::map() called without VMA allocation";
155 return nullptr;
156 }
157 return mappedMemory;
158}
159
161{
162 if (isMapped())
163 {
164 // Only call vmaUnmapMemory for explicit mappings, not persistent ones
165 // Persistent mappings (VMA_ALLOCATION_CREATE_MAPPED_BIT) should never be unmapped
166 if (!isPersistentlyMapped_ && allocator_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE)
167 {
168 vmaUnmapMemory(allocator_, allocation_);
169 mappedMemory = VK_NULL_HANDLE;
170 }
171 // For persistent mappings, keep mappedMemory valid - the memory stays mapped
172 }
173}
174
175void VulkanBuffer::flush(VkDeviceSize mappedOffset, VkDeviceSize mappedSize)
176{
177 if (!isHostCoherent_ && allocator_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE)
178 {
179 vmaFlushAllocation(allocator_, allocation_, mappedOffset, mappedSize);
180 }
181}
182
183void VulkanBuffer::uploadData(const void* data, VkDeviceSize dataSize)
184{
185 TRACY_ZONE_SCOPED_NAMED("Uploading buffer to the GPU");
186 if(dataSize > size)
187 {
188 throw BufferWriteException("Data size is too large for buffer");
189 }
190
191 void* memory = map();
192 memcpy(memory, data, dataSize);
193 // Only flush if not host-coherent (coherent memory is automatically visible)
194 if (!isHostCoherent_)
195 {
196 flush();
197 }
198 unmap();
199}
200
201VkDeviceMemory VulkanBuffer::getBufferMemory() const
202{
203 if (allocator_ != VK_NULL_HANDLE && allocation_ != VK_NULL_HANDLE)
204 {
205 VmaAllocationInfo info{};
206 vmaGetAllocationInfo(allocator_, allocation_, &info);
207 return info.deviceMemory;
208 }
209 return VK_NULL_HANDLE;
210}
211
212bool VulkanBuffer::ensureSize(size_t requiredSize, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
213{
214 // Minimum size to avoid zero-sized buffers
215 constexpr size_t MIN_BUFFER_SIZE = 64;
216 if (requiredSize < MIN_BUFFER_SIZE) {
217 requiredSize = MIN_BUFFER_SIZE;
218 }
219
220 // Check if current buffer is sufficient
221 if (size >= requiredSize) {
222 return false;
223 }
224
225 // Need to recreate - save debug name before destroying
226 std::string savedDebugName = debugName;
227
228 // Destroy existing buffer
229 destroy();
230
231 // Create new buffer with required size
232 create(requiredSize, usage, properties);
233
234 // Restore debug name
235 if (!savedDebugName.empty()) {
236 setDebugName(savedDebugName);
237 }
238
239 return true;
240}
241
242} // namespace EngineCore
#define TRACY_ZONE_SCOPED_NAMED(name)
The application context is the core class which stores the basic openxr and vulkan objects.
const VkDevice getVkDevice() const
Gets the vulkan device.
const VkPhysicalDevice & getVkPhysicalDevice() const
Gets vulkan physical device.
VmaAllocator getVmaAllocator() const
Gets the VMA allocator for memory management.
A buffer write exception is typically thrown when trying to write to a buffer which does not have eno...
Definition Exceptions.h:29
EngineCore::Engine * getEngineModule()
gets the pointer to the engine object
Definition Engine.cpp:1140
static EngineManager & getInstance()
gets a reference to the engine manager
Definition Engine.cpp:1135
ApplicationContext * getContext() const
Gets the application context which stores the vulkan instance the device and queues.
Definition Engine.h:90
void setDebugName(const std::string &name)
Sets the name shown in vulkan validation layer.
void flush(VkDeviceSize mappedOffset=0, VkDeviceSize mappedSize=VK_WHOLE_SIZE)
Flushes a mapped memory range to the GPU to make CPU writes visible.
void * map()
Maps the buffer to be writable by the cpu. The mapped memory range is also stored in the object.
VmaAllocator allocator_
Borrowed reference to VMA allocator.
void unmap()
Unmaps the memory if it is mapped.
void uploadData(const void *data, VkDeviceSize dataSize)
Helper function to map, copy the data and unmap the buffer. Best used for infrequent updates.
VulkanBuffer()
Default constructor - uses EngineManager singleton to get Vulkan handles.
VmaAllocation allocation_
VMA allocation handle.
bool ensureSize(size_t requiredSize, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
Ensures the buffer has at least the required size, recreating if necessary.
bool isMapped() const
Checks whether the buffer is already mapped to a memory range.
std::string getDebugName() const
void destroy()
Destroys all vulkan resources of the buffer.
VkDeviceMemory getBufferMemory() const
Gets the underlying VkDeviceMemory handle (for backward compatibility)
bool isHostCoherent_
True if buffer uses HOST_COHERENT memory (no flush needed)
void create(size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
Creates a buffer with a certain size, usage flags and memory properties.
VkPhysicalDevice physicalDevice_
static void setObjectName(VkDevice device, VulkanObjectType objectHandle, const std::string &name)
static void createBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
Log category system implementation.
constexpr size_t MIN_BUFFER_SIZE