ngscopeclient v0.2.2
Loading...
Searching...
No Matches
AcceleratorBuffer.h
Go to the documentation of this file.
1/***********************************************************************************************************************
2* *
3* libscopehal *
4* *
5* Copyright (c) 2012-2026 Andrew D. Zonenberg and contributors *
6* All rights reserved. *
7* *
8* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the *
9* following conditions are met: *
10* *
11* * Redistributions of source code must retain the above copyright notice, this list of conditions, and the *
12* following disclaimer. *
13* *
14* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the *
15* following disclaimer in the documentation and/or other materials provided with the distribution. *
16* *
17* * Neither the name of the author nor the names of any contributors may be used to endorse or promote products *
18* derived from this software without specific prior written permission. *
19* *
20* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
21* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
22* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
23* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR *
24* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
25* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
26* POSSIBILITY OF SUCH DAMAGE. *
27* *
28***********************************************************************************************************************/
29
35#ifndef AcceleratorBuffer_h
36#define AcceleratorBuffer_h
37
38#include "AlignedAllocator.h"
39#include "QueueManager.h"
40
41#ifdef _WIN32
42#undef MemoryBarrier
43#endif
44
45#ifndef _WIN32
46#include <sys/mman.h>
47#include <unistd.h>
48#endif
49
50#ifdef __GNUC__
51#include <cxxabi.h>
52#endif
53
54#include <type_traits>
55
56extern uint32_t g_vkPinnedMemoryType;
57extern uint32_t g_vkLocalMemoryType;
58extern std::shared_ptr<vk::raii::Device> g_vkComputeDevice;
59extern std::unique_ptr<vk::raii::CommandBuffer> g_vkTransferCommandBuffer;
60extern std::shared_ptr<QueueHandle> g_vkTransferQueue;
61extern std::mutex g_vkTransferMutex;
62
63extern bool g_hasDebugUtils;
65
70{
71public:
72 static void Reset()
73 {
77
81
85 }
86
88 // Helpers for logging specific interactions
89
90 static void LogHostDeviceCopyBlocking()
92
93 static void LogHostDeviceCopyNonBlocking()
95
96 static void LogHostDeviceCopySkipped()
98
99 //---
100
101 static void LogDeviceHostCopyBlocking()
103
104 static void LogDeviceHostCopyNonBlocking()
106
107 static void LogDeviceHostCopySkipped()
109
110 //---
111
112 static void LogDeviceDeviceCopyBlocking()
114
115 static void LogDeviceDeviceCopyNonBlocking()
117
118 static void LogDeviceDeviceCopySkipped()
120
121
123 // Actual counters
124
126 static std::atomic<int64_t> m_hostDeviceCopiesBlocking;
127
129 static std::atomic<int64_t> m_hostDeviceCopiesNonBlocking;
130
132 static std::atomic<int64_t> m_hostDeviceCopiesSkipped;
133
134 //---
135
137 static std::atomic<int64_t> m_deviceHostCopiesBlocking;
138
140 static std::atomic<int64_t> m_deviceHostCopiesNonBlocking;
141
143 static std::atomic<int64_t> m_deviceHostCopiesSkipped;
144
145 //---
146
148 static std::atomic<int64_t> m_deviceDeviceCopiesBlocking;
149
151 static std::atomic<int64_t> m_deviceDeviceCopiesNonBlocking;
152
154 static std::atomic<int64_t> m_deviceDeviceCopiesSkipped;
155
156 //---
157
158 /*
160 static std::atomic<int64_t> m_resizeRequests;
161
163 static std::atomic<int64_t> m_gpuAllocations;
164 */
165};
166
167template<class T>
169
172{
174 Hard,
175
184 Soft
185};
186
189{
191 Host,
192
194 Device
195};
196
198typedef bool (*MemoryPressureHandler)(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize);
199
200bool OnMemoryPressure(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize);
201
202template<class T>
204{
205public:
206 using value_type = T;
207 using iterator_category = std::forward_iterator_tag;
208 using difference_type = std::ptrdiff_t;
209 using pointer = T*;
210 using reference = T&;
211
213 : m_index(i)
214 , m_buf(buf)
215 {}
216
217 T& operator*()
218 { return m_buf[m_index]; }
219
220 size_t GetIndex() const
221 { return m_index; }
222
223 bool operator!=(AcceleratorBufferIterator<T>& it)
224 {
225 //TODO: should we check m_buf equality too?
226 //Will slow things down, but be more semantically correct. Does anything care?
227 return (m_index != it.m_index);
228 }
229
230 AcceleratorBufferIterator<T>& operator++()
231 {
232 m_index ++;
233 return *this;
234 }
235
236protected:
237 size_t m_index;
239};
240
241template<class T>
242std::ptrdiff_t operator-(const AcceleratorBufferIterator<T>& a, const AcceleratorBufferIterator<T>& b)
243{ return a.GetIndex() - b.GetIndex(); }
244
249{
250public:
251
252 AcceleratorBufferBase(const std::string& name = "")
253 : m_capacity(0)
254 , m_size(0)
255 , m_name(name)
256 {
257 std::lock_guard<std::recursive_mutex> lock(m_objectListMutex);
258 m_objectList.emplace(this);
259 }
260
261 virtual ~AcceleratorBufferBase()
262 {
263 std::lock_guard<std::recursive_mutex> lock(m_objectListMutex);
264 m_objectList.erase(this);
265 }
266
268 // Sizes of buffers
269
272
274 size_t m_size;
275
276protected:
277
279 std::string m_name;
280
282 // General accessors
283public:
284
286 size_t size() const
287 { return m_size; }
288
290 size_t capacity() const
291 { return m_capacity; }
292
294 const std::string& GetName() const
295 { return m_name; }
296
298 virtual std::string GetType() const =0;
299
301 virtual size_t GetElementSize() const =0;
302
304 // Object enumeration
305
306protected:
307
309 static std::recursive_mutex m_objectListMutex;
310
312 static std::set<AcceleratorBufferBase*> m_objectList;
313
314public:
315
317 static std::recursive_mutex& GetMutex()
318 { return m_objectListMutex; }
319
321 static const std::set<AcceleratorBufferBase*>& GetObjects()
322 { return m_objectList; }
323};
324
341template<class T>
343{
344public:
345
346 virtual std::string GetType() const
347 {
348 //Get the data type
349 auto& etype = typeid(T);
350
351 //Check common stdint data types and return them rather than the underlying C type
352 if(etype == typeid(int64_t))
353 return "int64_t";
354 else if(etype == typeid(uint64_t))
355 return "uint64_t";
356 else if(etype == typeid(int32_t))
357 return "int32_t";
358 else if(etype == typeid(uint32_t))
359 return "uint32_t";
360 else if(etype == typeid(int16_t))
361 return "int16_t";
362 else if(etype == typeid(uint16_t))
363 return "uint16_t";
364 else if(etype == typeid(int8_t))
365 return "int8_t";
366 else if(etype == typeid(uint8_t))
367 return "uint8_t";
368
369 else
370 {
371 //separate path here needed since GCC returns mangled name
372 #ifdef __GNUC__
373 int status;
374 auto pname = etype.name();
375 auto tmp = abi::__cxa_demangle(pname, nullptr, nullptr, &status);
376
377 std::string ret = std::string(tmp);
378 free(tmp);
379 return ret;
380 #else
381 return std::string(etype.name());
382 #endif
383 }
384 }
385
386 virtual size_t GetElementSize() const
387 { return sizeof(T); }
388
389protected:
390
392 // Allocator for CPU-only memory
393
394 AlignedAllocator<T, 32> m_cpuAllocator;
395
396public:
397
399 // Buffer types
400
405 {
406 //Location of the memory
407 MEM_ATTRIB_CPU_SIDE = 0x1,
408 MEM_ATTRIB_GPU_SIDE = 0x2,
409
410 //Reachability
411 MEM_ATTRIB_CPU_REACHABLE = 0x4,
412 MEM_ATTRIB_GPU_REACHABLE = 0x8,
413
414 //Speed
415 MEM_ATTRIB_CPU_FAST = 0x10,
416 MEM_ATTRIB_GPU_FAST = 0x20
417 };
418
423 {
424 //Pointer is invalid
425 MEM_TYPE_NULL = 0,
426
427 //Memory is located on the CPU but backed by a file and may get paged out
428 MEM_TYPE_CPU_PAGED =
429 MEM_ATTRIB_CPU_SIDE | MEM_ATTRIB_CPU_REACHABLE,
430
431 //Memory is located on the CPU but not pinned, or otherwise accessible to the GPU
432 MEM_TYPE_CPU_ONLY =
433 MEM_ATTRIB_CPU_SIDE | MEM_ATTRIB_CPU_REACHABLE | MEM_ATTRIB_CPU_FAST,
434
435 //Memory is located on the CPU, but can be accessed by the GPU.
436 //Fast to access from the CPU, but accesses from the GPU require PCIe DMA and is slow
437 //(unless platform uses unified memory, in which case g_vulkanDeviceHasUnifiedMemory will be true)
438 MEM_TYPE_CPU_DMA_CAPABLE =
439 MEM_ATTRIB_CPU_SIDE | MEM_ATTRIB_CPU_REACHABLE | MEM_ATTRIB_CPU_FAST | MEM_ATTRIB_GPU_REACHABLE,
440
441 //Memory is located on the GPU and cannot be directly accessed by the CPU
442 MEM_TYPE_GPU_ONLY =
443 MEM_ATTRIB_GPU_SIDE | MEM_ATTRIB_GPU_REACHABLE | MEM_ATTRIB_GPU_FAST,
444
445 //Memory is located on the GPU, but can be accessed by the CPU.
446 //Fast to access from the GPU, but accesses from the CPU require PCIe DMA and is slow
447 //(should not be used if platform uses unified memory, in which case g_vulkanDeviceHasUnifiedMemory will be true)
448 MEM_TYPE_GPU_DMA_CAPABLE =
449 MEM_ATTRIB_GPU_SIDE | MEM_ATTRIB_GPU_REACHABLE | MEM_ATTRIB_GPU_FAST | MEM_ATTRIB_CPU_REACHABLE
450 };
451
452protected:
453
458 { return (mt & MEM_ATTRIB_CPU_REACHABLE) != 0; }
459
464 { return (mt & MEM_ATTRIB_GPU_REACHABLE) != 0; }
465
470 { return (mt & MEM_ATTRIB_CPU_FAST) != 0; }
471
476 { return (mt & MEM_ATTRIB_GPU_FAST) != 0; }
477
480
483
485 // The actual memory buffers
486
489
491 std::unique_ptr<vk::raii::DeviceMemory> m_cpuPhysMem;
492
494 std::unique_ptr<vk::raii::DeviceMemory> m_gpuPhysMem;
495
497 std::unique_ptr<vk::raii::Buffer> m_cpuBuffer;
498
500 std::unique_ptr<vk::raii::Buffer> m_gpuBuffer;
501
504
507
510
512#ifndef _WIN32
514#endif
515
517 // Hint configuration
518public:
519 enum UsageHint
520 {
521 HINT_NEVER,
522 HINT_UNLIKELY,
523 HINT_LIKELY
524 };
525
526protected:
529
532
534 // Construction / destruction
535public:
536
540 __attribute__((noinline))
541 AcceleratorBuffer(const std::string& name = "")
543 , m_cpuMemoryType(MEM_TYPE_NULL)
544 , m_gpuMemoryType(MEM_TYPE_NULL)
545 , m_cpuPtr(nullptr)
546 , m_gpuPhysMem(nullptr)
547 , m_buffersAreSame(false)
548 , m_cpuPhysMemIsStale(false)
549 , m_gpuPhysMemIsStale(false)
550 #ifndef _WIN32
552 #endif
553 , m_cpuAccessHint(HINT_LIKELY) //default access hint: CPU-side pinned memory
554 , m_gpuAccessHint(HINT_UNLIKELY)
555 {
556 //non-trivially-copyable types can't be copied to GPU except on unified memory platforms
557 if(!std::is_trivially_copyable<T>::value && !g_vulkanDeviceHasUnifiedMemory)
558 m_gpuAccessHint = HINT_NEVER;
559
560 //Create synchronization events
561 //TODO: timeline semaphores if available
562 vk::EventCreateInfo eventCreateInfo;
563 m_deviceHostTransferEvent = std::make_unique<vk::raii::Event>(*g_vkComputeDevice, eventCreateInfo);
564 m_hostDeviceTransferEvent = std::make_unique<vk::raii::Event>(*g_vkComputeDevice, eventCreateInfo);
565
566 ClearTransferFlags();
567 }
568
569 virtual ~AcceleratorBuffer()
570 {
571 FreeCpuBuffer(true);
572 FreeGpuBuffer(true);
573 }
574
576 // General accessors
577public:
578
582 size_t GetMemoryBytes() const
583 { return m_capacity * sizeof(T); }
584
588 size_t GetCpuMemoryBytes() const
589 {
590 if(m_cpuMemoryType == MEM_TYPE_NULL)
591 return 0;
592 else
593 return m_capacity * sizeof(T);
594 }
595
599 size_t GetGpuMemoryBytes() const
600 {
601 if(m_gpuMemoryType == MEM_TYPE_NULL)
602 return 0;
603 else
604 return m_capacity * sizeof(T);
605 }
606
610 bool empty() const
611 { return (m_size == 0); }
612
616 bool IsCpuBufferStale() const
617 { return m_cpuPhysMemIsStale; }
618
622 bool IsGpuBufferStale() const
623 { return m_gpuPhysMemIsStale; }
624
628 bool HasCpuBuffer() const
629 { return (m_cpuPtr != nullptr); }
630
634 bool HasGpuBuffer() const
635 { return (m_gpuPhysMem != nullptr); }
636
641 { return m_buffersAreSame; }
642
648 vk::Buffer GetBuffer()
649 {
650 if(m_gpuBuffer != nullptr)
651 return **m_gpuBuffer;
652 else
653 return **m_cpuBuffer;
654 }
655
660 { return m_cpuPtr; }
661
665 vk::DescriptorBufferInfo GetBufferInfo()
666 {
667 return vk::DescriptorBufferInfo(
668 GetBuffer(),
669 0,
670 m_capacity * sizeof(T));
671 }
672
679 void resize(size_t size, bool exactSize = false)
680 {
681 //Need to grow?
682 if(size > m_capacity)
683 {
684 if(exactSize)
685 reserve(size);
686
687 //Default to doubling in size each time to avoid excessive copying.
688 else if(m_capacity == 0)
689 reserve(size);
690 else if(size > m_capacity*2)
691 reserve(size);
692 else
693 reserve(m_capacity * 2);
694 }
695
696 //Update our size
697 m_size = size;
698 }
699
703 void clear()
704 { resize(0); }
705
709 void reserve(size_t size)
710 {
711 if(size > m_capacity)
712 Reallocate(size);
713 }
714
719 {
720 if(m_size != m_capacity)
721 Reallocate(m_size);
722 }
723
727 __attribute__((noinline))
728 void CopyFrom(const std::vector<T>& rhs)
729 {
730 assert(std::is_trivially_copyable<T>::value);
731
732 PrepareForCpuAccess();
733 resize(rhs.size());
734
735 //This function should never be used if T isn't trivially copyable but cppcheck doesn't realize that
736 //cppcheck-suppress memsetClass
737 memcpy(m_cpuPtr, &rhs[0], m_size * sizeof(T));
738
739 MarkModifiedFromCpu();
740 }
741
747 __attribute__((noinline))
748 void CopyFrom(const AcceleratorBuffer<T>& rhs, bool reallocateToMatch = true)
749 {
750 //Copy placement hints from the other instance, then resize to match
751 SetCpuAccessHint(rhs.m_cpuAccessHint);
752 SetGpuAccessHint(rhs.m_gpuAccessHint, reallocateToMatch);
753 resize(rhs.m_size);
754
755 //Valid data CPU side? Copy it to here
756 if(rhs.HasCpuBuffer() && !rhs.m_cpuPhysMemIsStale)
757 {
758 //non-trivially-copyable types have to be copied one at a time
759 if(!std::is_trivially_copyable<T>::value)
760 {
761 for(size_t i=0; i<m_size; i++)
762 m_cpuPtr[i] = rhs.m_cpuPtr[i];
763 }
764
765 //Trivially copyable types can be done more efficiently in a block
766 //cppcheck doesn't realize this path is unreachable so suppress it
767 else
768 {
769 //cppcheck-suppress memsetClass
770 memcpy(m_cpuPtr, rhs.m_cpuPtr, m_size * sizeof(T));
771 }
772 }
774
775 //Valid data GPU side? Copy it to here
776 if(rhs.HasGpuBuffer() && !rhs.m_gpuPhysMemIsStale)
777 {
778 std::lock_guard<std::mutex> lock(g_vkTransferMutex);
779
780 AcceleratorBufferPerformanceCounters::LogDeviceDeviceCopyBlocking();
781
782 //Make the transfer request
783 g_vkTransferCommandBuffer->begin({});
784 vk::BufferCopy region(0, 0, m_size * sizeof(T));
785 g_vkTransferCommandBuffer->copyBuffer(**rhs.m_gpuBuffer, **m_gpuBuffer, {region});
787
788 //Submit the request and block until it completes
790 }
791 else if(rhs.HasGpuBuffer())
792 AcceleratorBufferPerformanceCounters::LogDeviceDeviceCopySkipped();
794 }
795
801 __attribute__((noinline))
802 void CopyFromNonblocking(
803 vk::raii::CommandBuffer& cmdBuf,
804 const AcceleratorBuffer<T>& rhs,
805 bool reallocateToMatch = true)
806 {
807 //Copy placement hints from the other instance, then resize to match
808 SetCpuAccessHint(rhs.m_cpuAccessHint);
809 SetGpuAccessHint(rhs.m_gpuAccessHint, reallocateToMatch);
810 resize(rhs.m_size);
811
812 //Valid data CPU side? Copy it to here
813 if(rhs.HasCpuBuffer() && !rhs.m_cpuPhysMemIsStale)
814 {
815 //non-trivially-copyable types have to be copied one at a time
816 if(!std::is_trivially_copyable<T>::value)
817 {
818 for(size_t i=0; i<m_size; i++)
819 m_cpuPtr[i] = rhs.m_cpuPtr[i];
820 }
821
822 //Trivially copyable types can be done more efficiently in a block
823 //cppcheck doesn't realize this path is unreachable so suppress it
824 else
825 {
826 //cppcheck-suppress memsetClass
827 memcpy(m_cpuPtr, rhs.m_cpuPtr, m_size * sizeof(T));
828 }
829 }
831
832 //Valid data GPU side? Copy it to here
833 if(rhs.HasGpuBuffer() && !rhs.m_gpuPhysMemIsStale)
834 {
835 AcceleratorBufferPerformanceCounters::LogDeviceDeviceCopyNonBlocking();
836
837 //Add a barrier
838 cmdBuf.pipelineBarrier(
839 vk::PipelineStageFlagBits::eComputeShader,
840 vk::PipelineStageFlagBits::eTransfer,
841 {},
842 vk::MemoryBarrier(
843 vk::AccessFlagBits::eShaderWrite,
844 vk::AccessFlagBits::eTransferRead),
845 {},
846 {});
847
848 //Make the transfer request
849 vk::BufferCopy region(0, 0, m_size * sizeof(T));
850 cmdBuf.copyBuffer(**rhs.m_gpuBuffer, **m_gpuBuffer, {region});
851 }
852 else if(rhs.HasGpuBuffer())
853 AcceleratorBufferPerformanceCounters::LogDeviceDeviceCopySkipped();
855
856 //Illegal to modify the buffer if a transfer is in progress. So mark any previous one as done
857 ClearTransferFlags();
858 }
859
860protected:
861
865 __attribute__((noinline))
866 void Reallocate(size_t size)
867 {
868 if(size == 0)
869 return;
870
871 //We can't have a transfer in progress when we reallocate
872 ClearTransferFlags();
873
874 /*
875 If we are a bool[], uint16_t[], or similar small type, we are likely going to be accessed from the GPU via
876 a uint32 descriptor for at least some shaders (such as rendering).
877
878 Round our actual allocated size to the next multiple of 4 bytes. The padding values are unimportant as the
879 bytes are never written, and the data read from the high bytes in the uint32 is discarded by the GPU.
880 We just need to ensure the memory is allocated so the 32-bit read is legal to perform.
881 */
882 if( (sizeof(T) < 4) && (m_gpuAccessHint != HINT_NEVER) )
883 {
884 if(size & 3)
885 size = (size | 3) + 1;
886 }
887
888 //If we do not anticipate using the data on the CPU, we shouldn't waste RAM.
889 //Allocate a GPU-local buffer, copy data to it, then free the CPU-side buffer
890 //Don't do this if the platform has unified memory
891 if( (m_cpuAccessHint == HINT_NEVER) && !g_vulkanDeviceHasUnifiedMemory)
892 {
893 PrepareForGpuAccess();
894 FreeCpuBuffer();
895 }
896
897 else
898 {
899 //Resize CPU memory
900 //TODO: optimization, when expanding a MEM_TYPE_CPU_PAGED we can just enlarge the file
901 //and not have to make a new temp file and copy the content
902 if(m_cpuPtr != nullptr)
903 {
904 //Save the old pointer
905 auto pOld = m_cpuPtr;
906 auto pOldPin = std::move(m_cpuPhysMem);
907 auto type = m_cpuMemoryType;
908
909 //Allocate the new buffer
910 AllocateCpuBuffer(size);
911
912 //If CPU-side data is valid, copy existing data over.
913 //New pointer is still valid in this case.
915 {
916 //non-trivially-copyable types have to be copied one at a time
917 if(!std::is_trivially_copyable<T>::value)
918 {
919 for(size_t i=0; i<m_size; i++)
920 m_cpuPtr[i] = std::move(pOld[i]);
921 }
922
923 //Trivially copyable types can be done more efficiently in a block
924 //gcc warns about this even though we only call this code if the type is trivially copyable,
925 //so disable the warning. Ditto for cppcheck.
926 else
927 {
928 #pragma GCC diagnostic push
929 #pragma GCC diagnostic ignored "-Wclass-memaccess"
930
931 //cppcheck-suppress memsetClass
932 memcpy(m_cpuPtr, pOld, m_size * sizeof(T));
933
934 #pragma GCC diagnostic pop
935 }
936 }
937
938 //If CPU-side data is stale, just allocate the new buffer but leave it as stale
939 //(don't do a potentially unnecessary copy from the GPU)
940
941 //Now we're done with the old pointer so get rid of it
942 FreeCpuPointer(pOld, pOldPin, type, m_capacity);
943 }
944
945 //Allocate new CPU memory, replacing our current (null) pointer
946 else
947 {
948 AllocateCpuBuffer(size);
949
950 //If we already had GPU-side memory containing data, then the new CPU-side buffer is stale
951 //until we copy stuff over to it
952 if(m_gpuPhysMem != nullptr)
953 m_cpuPhysMemIsStale = true;
954 }
955 }
956
957 //We're expecting to use data on the GPU, so prepare to do stuff with it
958 if(m_gpuAccessHint != HINT_NEVER)
959 {
960 //If GPU access is unlikely, we probably want to just use pinned memory.
961 //If available, mark buffers as the same, and free any existing GPU buffer we might have
962 //Always use pinned memory if the platform has unified memory
963 if( ((m_gpuAccessHint == HINT_UNLIKELY) && (m_cpuMemoryType == MEM_TYPE_CPU_DMA_CAPABLE)) || g_vulkanDeviceHasUnifiedMemory )
964 FreeGpuBuffer();
965
966 //Nope, we need to allocate dedicated GPU memory
967 else
968 {
969 //If we have an existing buffer with valid content, save it and copy content over
970 if( (m_gpuPhysMem != nullptr) && !m_gpuPhysMemIsStale && (m_size != 0))
971 {
972 auto pOld = std::move(m_gpuPhysMem);
973 //auto type = m_gpuMemoryType;
974 auto bOld = std::move(m_gpuBuffer);
975
976 //Allocation successful!
977 if(AllocateGpuBuffer(size))
978 {
979 std::lock_guard<std::mutex> lock(g_vkTransferMutex);
980
981 AcceleratorBufferPerformanceCounters::LogDeviceDeviceCopyBlocking();
982
983 //Make the transfer request
984 //TODO perf counters
985 g_vkTransferCommandBuffer->begin({});
986 vk::BufferCopy region(0, 0, m_size * sizeof(T));
987 g_vkTransferCommandBuffer->copyBuffer(**bOld, **m_gpuBuffer, {region});
989
990 //Submit the request and block until it completes
992
993 //make sure buffer is freed before underlying physical memory (pOld) goes out of scope
994 bOld = nullptr;
995 }
996
997 //Allocation failed!
998 else
999 {
1000 //Revert to the old buffer. We're now in a consistent state again
1001 m_gpuPhysMem = std::move(pOld);
1002 m_gpuBuffer = std::move(bOld);
1003
1004 //Make sure we have a CPU side buffer that's DMA capable
1005 if(m_cpuMemoryType != MEM_TYPE_CPU_DMA_CAPABLE)
1006 {
1007 SetCpuAccessHint(HINT_LIKELY);
1008 SetGpuAccessHint(HINT_LIKELY);
1009 AllocateCpuBuffer(size);
1010 }
1011
1012 //Free the GPU buffer, moving its contents to the CPU
1013 FreeGpuBuffer();
1014 }
1015 }
1016
1017 //Nope, just allocate a new buffer
1018 else
1019 {
1020 //Allocation successful? We now have the buffer
1021 if(AllocateGpuBuffer(size))
1022 {
1023 //If we already had CPU-side memory containing data, then the new GPU-side buffer is stale
1024 //until we copy stuff over to it.
1025 //Special case: if m_size is 0 (newly allocated buffer) we're not stale yet
1026 if( (m_cpuPhysMem != nullptr) && (m_size != 0) )
1027 m_gpuPhysMemIsStale = true;
1028 }
1029
1030 //Allocation failed? No change, we already had the CPU buffer and don't have to touch anything
1031 //But did the CPU buffer exist? if not, allocate *something*
1032 else if(m_cpuPhysMem == nullptr)
1033 {
1034 SetCpuAccessHint(HINT_LIKELY);
1035 SetGpuAccessHint(HINT_LIKELY);
1036 AllocateCpuBuffer(size);
1037 }
1038 }
1039 }
1040 }
1041
1042 //Existing GPU buffer we never expect to use again - needs to be freed
1043 else if(m_gpuPhysMem != nullptr)
1044 FreeGpuBuffer();
1045
1046 //We are never going to use the buffer on the GPU, but don't have any existing GPU memory
1047 //so no action required
1048 else
1049 {
1050 }
1051
1052 //Update our capacity
1053 m_capacity = size;
1054
1055 //If we have a pinned buffer and nothing on the other side, there's a single shared physical memory region
1057 ( (m_cpuMemoryType == MEM_TYPE_CPU_DMA_CAPABLE) && (m_gpuMemoryType == MEM_TYPE_NULL) ) ||
1058 ( (m_cpuMemoryType == MEM_TYPE_NULL) && (m_gpuMemoryType == MEM_TYPE_GPU_DMA_CAPABLE) );
1059 }
1060
1062 // CPU-side STL-esque container API
1063
1064 //PrepareForCpuAccess() *must* be called prior to calling any of these methods.
1065public:
1066
1067 //Reject static analysis error here
1068 //Compile time sanitizer checks don't understand Vulkan allocations and think the buffer is always 0 bytes
1069 #pragma GCC diagnostic push
1070 #pragma GCC diagnostic ignored "-Warray-bounds"
1071
1073 const T& operator[](size_t i) const
1074 { return m_cpuPtr[i]; }
1075
1077 T& operator[](size_t i)
1078 { return m_cpuPtr[i]; }
1079
1080 #pragma GCC diagnostic pop
1081
1085 void push_back(const T& value)
1086 {
1087 size_t cursize = m_size;
1088 resize(m_size + 1);
1089 m_cpuPtr[cursize] = value;
1090
1091 MarkModifiedFromCpu();
1092 }
1093
1097 void push_back_nomarkmod(const T& value)
1098 {
1099 size_t cursize = m_size;
1100 resize(m_size + 1);
1101 m_cpuPtr[cursize] = value;
1102 }
1103
1107 void pop_back()
1108 {
1109 if(!empty())
1110 resize(m_size - 1);
1111 }
1112
1118 void push_front(const T& value)
1119 {
1120 size_t cursize = m_size;
1121 resize(m_size + 1);
1122
1123 PrepareForCpuAccess();
1124
1125 //non-trivially-copyable types have to be copied one at a time
1126 if(!std::is_trivially_copyable<T>::value)
1127 {
1128 for(size_t i=0; i<cursize; i++)
1129 m_cpuPtr[i+1] = std::move(m_cpuPtr[i]);
1130 }
1131
1132 //Trivially copyable types can be done more efficiently in a block
1133 //cppcheck doesn't realize this path is unreachable so suppress it
1134 else
1135 {
1136 //cppcheck-suppress memsetClass
1137 memmove(m_cpuPtr+1, m_cpuPtr, sizeof(T) * (cursize));
1138 }
1139
1140 //Insert the new first element
1141 m_cpuPtr[0] = value;
1142
1143 MarkModifiedFromCpu();
1144 }
1145
1151 void pop_front()
1152 {
1153 //No need to move data if popping last element
1154 if(m_size == 1)
1155 {
1156 clear();
1157 return;
1158 }
1159
1160 //Don't touch GPU side buffer
1161
1162 PrepareForCpuAccess();
1163
1164 //non-trivially-copyable types have to be copied one at a time
1165 if(!std::is_trivially_copyable<T>::value)
1166 {
1167 for(size_t i=0; i<m_size-1; i++)
1168 m_cpuPtr[i] = std::move(m_cpuPtr[i+1]);
1169 }
1170
1171 //Trivially copyable types can be done more efficiently in a block
1172 else
1173 {
1174 //this path is unreachable if not trivially copyable, but cppcheck complains about it anyway
1175 //cppcheck-suppress memsetClass
1176 memmove(m_cpuPtr, m_cpuPtr+1, sizeof(T) * (m_size-1));
1177 }
1178
1179 resize(m_size - 1);
1180
1181 MarkModifiedFromCpu();
1182 }
1183
1185 { return AcceleratorBufferIterator<T>(*this, 0); }
1186
1188 { return AcceleratorBufferIterator<T>(*this, m_size); }
1189
1191 // Hints about near-future usage patterns
1192
1193public:
1194
1201 void SetCpuAccessHint(UsageHint hint, bool reallocateImmediately = false)
1202 {
1204
1205 if(reallocateImmediately && (m_size != 0))
1207 }
1208
1215 void SetGpuAccessHint(UsageHint hint, bool reallocateImmediately = false)
1216 {
1217 //Only trivially copyable datatypes are allowed on the GPU
1218 if(!std::is_trivially_copyable<T>::value)
1219 hint = HINT_NEVER;
1220
1222
1223 if(reallocateImmediately && (m_size != 0))
1225 }
1226
1228 // Cache invalidation
1229
1235 void MarkModifiedFromCpu()
1236 {
1238 {
1239 //Illegal to modify the buffer if a transfer is in progress. So mark any previous one as done
1240 ClearTransferFlags();
1241
1242 m_gpuPhysMemIsStale = true;
1243 }
1244 }
1245
1251 void MarkModifiedFromGpu()
1252 {
1254 {
1255 //Illegal to modify the buffer if a transfer is in progress. So mark any previous one as done
1256 ClearTransferFlags();
1257
1258 m_cpuPhysMemIsStale = true;
1259 }
1260 }
1261
1263 // Preparation for access
1264
1270 void PrepareForCpuAccess()
1271 {
1272 //Early out if no content
1273 if(m_size == 0)
1274 return;
1275
1276 //If there's no buffer at all on the CPU, allocate one
1277 if(!HasCpuBuffer() && (m_gpuMemoryType != MEM_TYPE_GPU_DMA_CAPABLE))
1279
1281 CopyToCpu();
1282 else
1283 AcceleratorBufferPerformanceCounters::LogDeviceHostCopySkipped();
1284 }
1285
1293 {
1294 //Early out if no content
1295 if(m_size == 0)
1296 return;
1297
1298 //If there's no buffer at all on the CPU, allocate one
1299 if(!HasCpuBuffer() && (m_gpuMemoryType != MEM_TYPE_GPU_DMA_CAPABLE))
1301
1303 {
1304 //If an existing transfer is active, wait
1305 //This should not race the filter graph because we have the waveform data mutex held
1307 {
1308 while( (m_deviceHostTransferEvent->getStatus() != vk::Result::eEventSet) ||
1309 (m_deviceHostTransferActive.load() == 0) )
1310 {}
1311 }
1312
1313 //otherwise copy the samples
1314 else
1316 }
1317 }
1318
1326 {
1327 //Early out if no content
1328 if(m_size == 0)
1329 return;
1330
1331 //If there's no buffer at all on the CPU, allocate one
1332 if(!HasCpuBuffer() && (m_gpuMemoryType != MEM_TYPE_GPU_DMA_CAPABLE))
1334
1335 m_gpuPhysMemIsStale = true;
1336 m_cpuPhysMemIsStale = false;
1337 }
1338
1346 void PrepareForCpuAccessNonblocking(vk::raii::CommandBuffer& cmdBuf, bool skipBarrier = false)
1347 {
1348 //Early out if no content
1349 if(m_size == 0)
1350 return;
1351
1352 //If there's no buffer at all on the CPU, allocate one
1353 if(!HasCpuBuffer() && (m_gpuMemoryType != MEM_TYPE_GPU_DMA_CAPABLE))
1355
1358 else
1359 AcceleratorBufferPerformanceCounters::LogDeviceHostCopySkipped();
1360 }
1361
1370 void PrepareForGpuAccess(bool outputOnly = false)
1371 {
1372 //Early out if no content or if unified memory
1374 return;
1375
1376 //If our current hint has no GPU access at all, update to say "unlikely" and reallocate
1377 if(m_gpuAccessHint == HINT_NEVER)
1378 SetGpuAccessHint(HINT_UNLIKELY, true);
1379
1380 //If we don't have a buffer, allocate one unless our CPU buffer is pinned and GPU-readable
1381 if(!HasGpuBuffer() && (m_cpuMemoryType != MEM_TYPE_CPU_DMA_CAPABLE) )
1382 {
1384 return;
1385 }
1386
1387 //Make sure the GPU-side buffer is up to date
1389 CopyToGpu();
1390 else
1391 AcceleratorBufferPerformanceCounters::LogHostDeviceCopySkipped();
1392 }
1393
1402 void PrepareForGpuAccessNonblocking(bool outputOnly, vk::raii::CommandBuffer& cmdBuf)
1403 {
1404 //Early out if no content or if unified memory
1406 return;
1407
1408 //If our current hint has no GPU access at all, update to say "unlikely" and reallocate
1409 if(m_gpuAccessHint == HINT_NEVER)
1410 SetGpuAccessHint(HINT_UNLIKELY, true);
1411
1412 //If we don't have a buffer, allocate one unless our CPU buffer is pinned and GPU-readable
1413 if(!HasGpuBuffer() && (m_cpuMemoryType != MEM_TYPE_CPU_DMA_CAPABLE) )
1414 {
1416 return;
1417 }
1418
1419 //Make sure the GPU-side buffer is up to date
1421 CopyToGpuNonblocking(cmdBuf);
1422 else
1423 AcceleratorBufferPerformanceCounters::LogHostDeviceCopySkipped();
1424 }
1425
1426protected:
1427
1429 // Copying of buffer content
1430
1434 void CopyToCpu()
1435 {
1436 assert(std::is_trivially_copyable<T>::value);
1437
1438 AcceleratorBufferPerformanceCounters::LogDeviceHostCopyBlocking();
1439
1440 std::lock_guard<std::mutex> lock(g_vkTransferMutex);
1441
1442 //Make the transfer request
1443 g_vkTransferCommandBuffer->begin({});
1444 g_vkTransferCommandBuffer->pipelineBarrier(
1445 vk::PipelineStageFlagBits::eComputeShader,
1446 vk::PipelineStageFlagBits::eTransfer,
1447 {},
1448 vk::MemoryBarrier(
1449 vk::AccessFlagBits::eShaderWrite,
1450 vk::AccessFlagBits::eTransferRead),
1451 {},
1452 {});
1453 vk::BufferCopy region(0, 0, m_size * sizeof(T));
1455
1456 //TODO: timeline semaphores if available
1457 //for now use events
1458 g_vkTransferCommandBuffer->setEvent(**m_deviceHostTransferEvent, vk::PipelineStageFlagBits::eTransfer);
1459
1461
1462 //Submit the request and block until it completes
1464
1465 m_cpuPhysMemIsStale = false;
1466 }
1467
1472 {
1473 assert(std::is_trivially_copyable<T>::value);
1474
1475 AcceleratorBufferPerformanceCounters::LogDeviceHostCopyBlocking();
1476
1477 std::lock_guard<std::mutex> lock(g_vkTransferMutex);
1478
1479 //Make the transfer request
1480 g_vkTransferCommandBuffer->begin({});
1481
1482 g_vkTransferCommandBuffer->pipelineBarrier(
1483 vk::PipelineStageFlagBits::eComputeShader,
1484 vk::PipelineStageFlagBits::eTransfer,
1485 {},
1486 vk::MemoryBarrier(
1487 vk::AccessFlagBits::eShaderWrite,
1488 vk::AccessFlagBits::eTransferRead),
1489 {},
1490 {});
1491
1492 vk::BufferCopy startregion(0, 0, sizeof(T));
1493 size_t endOffset = (m_size - 1) * sizeof(T);
1494 vk::BufferCopy endregion(endOffset, endOffset, sizeof(T));
1497
1499
1500 //Submit the request and block until it completes
1502
1503 //do NOT modify m_cpuPhysMemIsStale, since the rest of the buffer is still stale
1504 }
1505
1509 void CopyToCpuNonblocking(vk::raii::CommandBuffer& cmdBuf, bool skipBarrier = false)
1510 {
1511 assert(std::is_trivially_copyable<T>::value);
1512
1513 AcceleratorBufferPerformanceCounters::LogDeviceHostCopyNonBlocking();
1514
1515 //Add a barrier just in case a shader is still writing to it
1516 if(!skipBarrier)
1517 {
1518 cmdBuf.pipelineBarrier(
1519 vk::PipelineStageFlagBits::eComputeShader,
1520 vk::PipelineStageFlagBits::eTransfer,
1521 {},
1522 vk::MemoryBarrier(
1523 vk::AccessFlagBits::eShaderWrite,
1524 vk::AccessFlagBits::eTransferRead
1525 ),
1526 {},
1527 {});
1528 }
1529
1530 //Make the transfer request
1531 vk::BufferCopy region(0, 0, m_size * sizeof(T));
1532 cmdBuf.copyBuffer(**m_gpuBuffer, **m_cpuBuffer, {region});
1533
1534 //TODO: timeline semaphores if available
1535 //for now use events
1536 cmdBuf.setEvent(**m_deviceHostTransferEvent, vk::PipelineStageFlagBits::eTransfer);
1537
1538 m_cpuPhysMemIsStale = false;
1539 }
1540
1544 void CopyToGpu()
1545 {
1546 assert(std::is_trivially_copyable<T>::value);
1547
1548 AcceleratorBufferPerformanceCounters::LogHostDeviceCopyBlocking();
1549
1550 std::lock_guard<std::mutex> lock(g_vkTransferMutex);
1551
1552 //Make the transfer request
1553 g_vkTransferCommandBuffer->begin({});
1554 vk::BufferCopy region(0, 0, m_size * sizeof(T));
1556
1557 //TODO: timeline semaphores if available
1558 //for now use events
1559 g_vkTransferCommandBuffer->setEvent(**m_hostDeviceTransferEvent, vk::PipelineStageFlagBits::eTransfer);
1560
1562
1563 //Submit the request and block until it completes
1565
1566 m_gpuPhysMemIsStale = false;
1567 }
1568
1569
1575 void CopyToGpuNonblocking(vk::raii::CommandBuffer& cmdBuf)
1576 {
1577 assert(std::is_trivially_copyable<T>::value);
1578
1579 AcceleratorBufferPerformanceCounters::LogHostDeviceCopyNonBlocking();
1580
1581 //Make the transfer request
1582 vk::BufferCopy region(0, 0, m_size * sizeof(T));
1583 cmdBuf.copyBuffer(**m_cpuBuffer, **m_gpuBuffer, {region});
1584
1585 //TODO: timeline semaphores if available
1586 //for now use events
1587 cmdBuf.setEvent(**m_hostDeviceTransferEvent, vk::PipelineStageFlagBits::eTransfer);
1588
1589 //Add the barrier
1590 cmdBuf.pipelineBarrier(
1591 vk::PipelineStageFlagBits::eTransfer,
1592 vk::PipelineStageFlagBits::eComputeShader,
1593 {},
1594 vk::MemoryBarrier(
1595 vk::AccessFlagBits::eTransferWrite,
1596 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite),
1597 {},
1598 {});
1599
1600 m_gpuPhysMemIsStale = false;
1601 }
1602public:
1606 static void HostToDeviceTransferMemoryBarrier(vk::raii::CommandBuffer& cmdBuf)
1607 {
1608 cmdBuf.pipelineBarrier(
1609 vk::PipelineStageFlagBits::eTransfer,
1610 vk::PipelineStageFlagBits::eComputeShader,
1611 {},
1612 vk::MemoryBarrier(
1613 vk::AccessFlagBits::eTransferWrite,
1614 vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite),
1615 {},
1616 {});
1617 }
1618
1619protected:
1620
1622 // Cleanup
1623
1630 void FreeCpuBuffer(bool dataLossOK = false)
1631 {
1632 //Early out if buffer is already null
1633 if(m_cpuPtr == nullptr)
1634 return;
1635
1636 //We have a buffer on the GPU.
1637 //If it's stale, need to push our updated content there before freeing the CPU-side copy
1638 if( (m_gpuMemoryType != MEM_TYPE_NULL) && m_gpuPhysMemIsStale && !empty() && !dataLossOK)
1639 CopyToGpu();
1640
1641 //Free the Vulkan buffer object
1642 m_cpuBuffer = nullptr;
1643
1644 //Free the buffer and unmap any memory
1646
1647 //Mark CPU-side buffer as empty
1648 m_cpuPtr = nullptr;
1649 m_cpuPhysMem = nullptr;
1650 m_cpuMemoryType = MEM_TYPE_NULL;
1651 m_buffersAreSame = false;
1652
1653 //If we have no GPU-side buffer either, we're empty
1654 if(m_gpuMemoryType == MEM_TYPE_NULL)
1655 {
1656 m_size = 0;
1657 m_capacity = 0;
1658 }
1659 }
1660
1661public:
1662
1669 void FreeGpuBuffer(bool dataLossOK = false)
1670 {
1671 //Early out if buffer is already null
1672 if(m_gpuPhysMem == nullptr)
1673 return;
1674
1675 //If we do NOT have a CPU-side buffer, we're deleting all of our data! Warn for now
1676 if( (m_cpuMemoryType == MEM_TYPE_NULL) && m_gpuPhysMemIsStale && !empty() && !dataLossOK)
1677 {
1678 LogWarning("Freeing a GPU buffer without any CPU backing, may cause data loss\n");
1679 }
1680
1681 //If we have a CPU-side buffer, and it's stale, move our about-to-be-deleted content over before we free it
1682 if( (m_cpuMemoryType != MEM_TYPE_NULL) && m_cpuPhysMemIsStale && !empty() )
1683 CopyToCpu();
1684
1685 m_gpuBuffer = nullptr;
1686 m_gpuPhysMem = nullptr;
1687 m_gpuMemoryType = MEM_TYPE_NULL;
1688 }
1689
1690protected:
1691
1693 // Allocation
1694
1699 void AllocateCpuBuffer(size_t size)
1700 {
1701 if(size == 0)
1702 LogFatal("AllocateCpuBuffer with size zero (invalid)\n");
1703
1704 //If any GPU access is expected, use pinned memory so we don't have to move things around
1705 if(m_gpuAccessHint != HINT_NEVER)
1706 {
1707 //Make a Vulkan buffer first
1708 vk::BufferCreateInfo bufinfo(
1709 {},
1710 size * sizeof(T),
1711 vk::BufferUsageFlagBits::eTransferSrc |
1712 vk::BufferUsageFlagBits::eTransferDst |
1713 vk::BufferUsageFlagBits::eStorageBuffer);
1714 m_cpuBuffer = std::make_unique<vk::raii::Buffer>(*g_vkComputeDevice, bufinfo);
1715
1716 //Figure out actual memory requirements of the buffer
1717 //(may be rounded up from what we asked for)
1718 auto req = m_cpuBuffer->getMemoryRequirements();
1719
1720 //Allocate the physical memory to back the buffer
1721 vk::MemoryAllocateInfo info(req.size, g_vkPinnedMemoryType);
1722 m_cpuPhysMem = std::make_unique<vk::raii::DeviceMemory>(*g_vkComputeDevice, info);
1723
1724 //Map it and bind to the buffer
1725 m_cpuPtr = reinterpret_cast<T*>(m_cpuPhysMem->mapMemory(0, req.size));
1726 m_cpuBuffer->bindMemory(**m_cpuPhysMem, 0);
1727
1728 //We now have pinned memory
1729 m_cpuMemoryType = MEM_TYPE_CPU_DMA_CAPABLE;
1730
1731 if(g_hasDebugUtils)
1733 }
1734
1735 //If frequent CPU access is expected, use normal host memory
1736 else if(m_cpuAccessHint == HINT_LIKELY)
1737 {
1738 m_cpuBuffer = nullptr;
1739 m_cpuMemoryType = MEM_TYPE_CPU_ONLY;
1740 m_cpuPtr = m_cpuAllocator.allocate(size);
1741 }
1742
1743 //If infrequent CPU access is expected, use a memory mapped temporary file so it can be paged out to disk
1744 else
1745 {
1746 #ifdef _WIN32
1747
1748 //On Windows, use normal memory for now
1749 //until we figure out how to do this there
1750 m_cpuBuffer = nullptr;
1751 m_cpuMemoryType = MEM_TYPE_CPU_ONLY;
1752 m_cpuPtr = m_cpuAllocator.allocate(size);
1753
1754 #else
1755
1756 m_cpuBuffer = nullptr;
1757 m_cpuMemoryType = MEM_TYPE_CPU_PAGED;
1758
1759 //Make the temp file
1760 char fname[] = "/tmp/ngscopeclient-tmpXXXXXX";
1762 if(m_tempFileHandle < 0)
1763 {
1764 LogError("Failed to create temporary file %s\n", fname);
1765 abort();
1766 }
1767
1768 //Resize it to our desired file size
1769 size_t bytesize = size * sizeof(T);
1771 {
1772 LogError("Failed to resize temporary file %s\n", fname);
1773 abort();
1774 }
1775
1776 //Map it
1777 m_cpuPtr = reinterpret_cast<T*>(mmap(
1778 nullptr,
1779 bytesize,
1781 MAP_SHARED/* | MAP_UNINITIALIZED*/,
1783 0));
1784 if(m_cpuPtr == MAP_FAILED)
1785 {
1786 LogError("Failed to map temporary file %s\n", fname);
1787 perror("mmap failed: ");
1788 abort();
1789 }
1790 m_cpuMemoryType = MEM_TYPE_CPU_PAGED;
1791
1792 //Delete it (file will be removed by the OS after our active handle is closed)
1793 if(0 != unlink(fname))
1794 LogWarning("Failed to unlink temporary file %s, file will remain after application terminates\n", fname);
1795
1796 #endif
1797 }
1798
1799 //Memory has been allocated. Call constructors iff type is not trivially copyable
1800 //(This is not exactly 1:1 with having a constructor, but hopefully good enough?)
1801 if(!std::is_trivially_copyable<T>::value)
1802 {
1803 for(size_t i=0; i<size; i++)
1804 new(m_cpuPtr +i) T;
1805 }
1806 }
1807
1816 void FreeCpuPointer(T* ptr, MemoryType type, size_t size)
1817 {
1818 //Call destructors iff type is not trivially copyable
1819 if(!std::is_trivially_copyable<T>::value)
1820 {
1821 for(size_t i=0; i<size; i++)
1822 ptr[i].~T();
1823 }
1824
1825 switch(type)
1826 {
1827 case MEM_TYPE_NULL:
1828 //legal no-op
1829 break;
1830
1831 case MEM_TYPE_CPU_DMA_CAPABLE:
1832 LogFatal("FreeCpuPointer for MEM_TYPE_CPU_DMA_CAPABLE requires the vk::raii::DeviceMemory\n");
1833 break;
1834
1835 case MEM_TYPE_CPU_PAGED:
1836 #ifndef _WIN32
1837 munmap(ptr, size * sizeof(T));
1839 m_tempFileHandle = -1;
1840 #endif
1841 break;
1842
1843 case MEM_TYPE_CPU_ONLY:
1844 m_cpuAllocator.deallocate(ptr, size);
1845 break;
1846
1847 default:
1848 LogFatal("FreeCpuPointer: invalid type %x\n", type);
1849 }
1850 }
1851
1860 void FreeCpuPointer(T* ptr, std::unique_ptr<vk::raii::DeviceMemory>& buf, MemoryType type, size_t size)
1861 {
1862 switch(type)
1863 {
1864 case MEM_TYPE_CPU_DMA_CAPABLE:
1865 buf->unmapMemory();
1866 break;
1867
1868 default:
1869 FreeCpuPointer(ptr, type, size);
1870 }
1871 }
1872
1879 bool AllocateGpuBuffer(size_t size)
1880 {
1881 assert(std::is_trivially_copyable<T>::value);
1882
1883 //Make a Vulkan buffer first
1884 vk::BufferCreateInfo bufinfo(
1885 {},
1886 size * sizeof(T),
1887 vk::BufferUsageFlagBits::eTransferSrc |
1888 vk::BufferUsageFlagBits::eTransferDst |
1889 vk::BufferUsageFlagBits::eStorageBuffer);
1890 m_gpuBuffer = std::make_unique<vk::raii::Buffer>(*g_vkComputeDevice, bufinfo);
1891
1892 //Figure out actual memory requirements of the buffer
1893 //(may be rounded up from what we asked for)
1894 auto req = m_gpuBuffer->getMemoryRequirements();
1895
1896 //Try to allocate the memory
1897 vk::MemoryAllocateInfo info(req.size, g_vkLocalMemoryType);
1898 try
1899 {
1900 //For now, always use local memory
1901 m_gpuPhysMem = std::make_unique<vk::raii::DeviceMemory>(*g_vkComputeDevice, info);
1902 }
1903
1904 //Fallback path in case of low memory
1905 catch(vk::OutOfDeviceMemoryError& ex)
1906 {
1907 bool ok = false;
1908 while(!ok)
1909 {
1910 //Attempt to free memory and stop if we couldn't free more
1912 break;
1913
1915 try
1916 {
1917 m_gpuPhysMem = std::make_unique<vk::raii::DeviceMemory>(*g_vkComputeDevice, info);
1918 ok = true;
1919 }
1920 catch(vk::OutOfDeviceMemoryError& ex2)
1921 {
1922 LogDebug("Allocation failed again\n");
1923 }
1924 }
1925
1926 //Retry one more time.
1927 //If we OOM simultaneously in two threads, it's possible to have the second OnMemoryPressure call
1928 //return false because the first one already freed all it could. But we might have enough free to continue.
1929 if(!ok)
1930 {
1931 LogDebug("Final retry\n");
1932 try
1933 {
1934 m_gpuPhysMem = std::make_unique<vk::raii::DeviceMemory>(*g_vkComputeDevice, info);
1935 ok = true;
1936 }
1937 catch(vk::OutOfDeviceMemoryError& ex2)
1938 {
1939 LogDebug("Allocation failed again\n");
1940 }
1941 }
1942
1943 //If we get here, we couldn't allocate no matter what
1944 //Fall back to a CPU-side allocation
1945 if(!ok)
1946 {
1947 LogError(
1948 "Failed to allocate %s of GPU memory despite our best efforts to reclaim space, falling back to CPU-side pinned allocation\n",
1949 Unit(Unit::UNIT_BYTES).PrettyPrint(req.size, 4).c_str());
1950 m_gpuMemoryType = MEM_TYPE_NULL;
1951 m_gpuPhysMem = nullptr;
1952 m_gpuBuffer = nullptr;
1953 return false;
1954 }
1955 }
1956 m_gpuMemoryType = MEM_TYPE_GPU_ONLY;
1957
1958 m_gpuBuffer->bindMemory(**m_gpuPhysMem, 0);
1959
1960 if(g_hasDebugUtils)
1962
1963 return true;
1964 }
1965
1966protected:
1967
1972 void UpdateGpuNames()
1973 {
1974 std::string sname = m_name;
1975 if(sname.empty())
1976 sname = "unnamed";
1977 std::string prefix = std::string("AcceleratorBuffer.") + sname + ".";
1978
1979 std::string gpuBufName = prefix + "m_gpuBuffer";
1980 std::string gpuPhysName = prefix + "m_gpuPhysMem";
1981
1982 g_vkComputeDevice->setDebugUtilsObjectNameEXT(
1983 vk::DebugUtilsObjectNameInfoEXT(
1984 vk::ObjectType::eBuffer,
1985 reinterpret_cast<uint64_t>(static_cast<VkBuffer>(**m_gpuBuffer)),
1986 gpuBufName.c_str()));
1987
1988 g_vkComputeDevice->setDebugUtilsObjectNameEXT(
1989 vk::DebugUtilsObjectNameInfoEXT(
1990 vk::ObjectType::eDeviceMemory,
1991 reinterpret_cast<uint64_t>(static_cast<VkDeviceMemory>(**m_gpuPhysMem)),
1992 gpuPhysName.c_str()));
1993 }
1994
1999 void UpdateCpuNames()
2000 {
2001 std::string sname = m_name;
2002 if(sname.empty())
2003 sname = "unnamed";
2004 std::string prefix = std::string("AcceleratorBuffer.") + sname + ".";
2005
2006 std::string cpuBufName = prefix + "m_cpuBuffer";
2007 std::string cpuPhysName = prefix + "m_cpuPhysMem";
2008
2009 g_vkComputeDevice->setDebugUtilsObjectNameEXT(
2010 vk::DebugUtilsObjectNameInfoEXT(
2011 vk::ObjectType::eBuffer,
2012 reinterpret_cast<uint64_t>(static_cast<VkBuffer>(**m_cpuBuffer)),
2013 cpuBufName.c_str()));
2014
2015 g_vkComputeDevice->setDebugUtilsObjectNameEXT(
2016 vk::DebugUtilsObjectNameInfoEXT(
2017 vk::ObjectType::eDeviceMemory,
2018 reinterpret_cast<uint64_t>(static_cast<VkDeviceMemory>(**m_cpuPhysMem)),
2019 cpuPhysName.c_str()));
2020 }
2021
2022public:
2023
2032 void SetName(const std::string& name)
2033 {
2034 //Early out if name hasn't actually changed
2035 if(m_name == name)
2036 return;
2037
2038 m_name = name;
2039 if(g_hasDebugUtils)
2040 {
2041 if(m_gpuBuffer != nullptr)
2043 if(m_cpuBuffer != nullptr)
2045 }
2046 }
2047
2052 void DebugDumpToFile(const std::string& fname)
2053 {
2054 FILE* fp = fopen(fname.c_str(), "wb");
2055 if(!fp)
2056 LogFatal("failed to open debug dump %s\n", fname.c_str());
2057
2058 PrepareForCpuAccess();
2059
2060 //Actually write the data (only the valid part of the buffer for now... TODO extra tail stuff?)
2061 fwrite(GetCpuPointer(), size(), sizeof(T), fp);
2062
2063 fclose(fp);
2064 }
2065
2066public:
2067
2069 // Device-host transfer synchronization
2070
2071 /*
2072 KEY CONCEPTS
2073 Filters run in parallel, multiple PrepareFor*Access calls can be concurrent on the same object
2074 Modifying an AcceleratorBuffer can only be done from the filter/driver that creates it
2075 Nobody will use it until that block has finished executing
2076 Which means... we do NOT need to worry about a buffer becoming stale unexpectedly from another thread
2077 modifying it underneath us.
2078 */
2079
2086 {
2087 //CPU copy is up to date
2089 return false;
2090
2091 //Set transfer-active flag to 1
2092 //If it already was 1, somebody else started a transfer already! Wait until it finishes
2093 if(m_deviceHostTransferActive.exchange(true))
2094 {
2095 #ifdef HAVE_NVTX
2096 nvtx3::scoped_range nrange("Dev/host busy wait");
2097 #endif
2098
2099 //Block until the other transfer finishes
2100 while( (m_deviceHostTransferEvent->getStatus() != vk::Result::eEventSet) ||
2101 (m_deviceHostTransferActive.load() == 0) )
2102 {}
2103
2104 //Transfer is no longer in progress
2106
2107 return false;
2108 }
2109
2110 //If we get here, we need to actually do the transfer
2111 return true;
2112 }
2113
2115 std::atomic<bool> m_deviceHostTransferActive;
2116
2118 std::unique_ptr<vk::raii::Event> m_deviceHostTransferEvent;
2119
2121 // Host-device transfer synchronization
2122
2123 /*
2124 KEY CONCEPTS
2125 Filters run in parallel, multiple PrepareFor*Access calls can be concurrent on the same object
2126 Modifying an AcceleratorBuffer can only be done from the filter/driver that creates it
2127 Nobody will use it until that block has finished executing
2128 Which means... we do NOT need to worry about a buffer becoming stale unexpectedly from another thread
2129 modifying it underneath us.
2130 */
2131
2138 {
2139 //GPU copy is up to date
2141 return false;
2142
2143 //Set transfer-active flag to 1
2144 //If it already was 1, somebody else started a transfer already! Wait until it finishes
2145 if(m_hostDeviceTransferActive.exchange(true))
2146 {
2147 #ifdef HAVE_NVTX
2148 nvtx3::scoped_range nrange("Host/dev busy wait");
2149 #endif
2150
2151 //Block until the other transfer finishes
2152 while( (m_hostDeviceTransferEvent->getStatus() != vk::Result::eEventSet) ||
2153 (m_hostDeviceTransferActive.load() == 0) )
2154 {}
2155
2156 //Transfer is no longer in progress
2158
2159 return false;
2160 }
2161
2162 //If we get here, we need to actually do the transfer
2163 return true;
2164 }
2165
2168
2170 std::unique_ptr<vk::raii::Event> m_hostDeviceTransferEvent;
2171
2172 void ClearTransferFlags()
2173 {
2178 }
2179};
2180
2181extern std::set<MemoryPressureHandler> g_memoryPressureHandlers;
2182
2183#endif
bool(* MemoryPressureHandler)(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize)
Memory pressure handler type, called when free memory reaches a warning level or a Vulkan allocation ...
Definition AcceleratorBuffer.h:198
MemoryPressureLevel
Levels of memory pressure.
Definition AcceleratorBuffer.h:172
@ Hard
A memory allocation has failed and we need to free memory immediately to continue execution.
@ Soft
Free memory has reached a warning threshold.
bool OnMemoryPressure(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize)
Called when we run low on memory.
Definition scopehal.cpp:1129
MemoryPressureType
Types of memory pressure.
Definition AcceleratorBuffer.h:189
@ Host
Pinned CPU-side memory.
@ Device
GPU-side memory.
std::set< MemoryPressureHandler > g_memoryPressureHandlers
List of handlers for low memory registered by various subsystems.
Definition scopehal.cpp:212
Declaration of AlignedAllocator.
Declaration of QueueManager and QueueHandle.
Base class for AcceleratorBuffer storing common metadata used by all derived types.
Definition AcceleratorBuffer.h:249
static std::recursive_mutex m_objectListMutex
Mutex controlling access to m_objectList.
Definition AcceleratorBuffer.h:309
size_t capacity() const
Returns the allocated size of the container.
Definition AcceleratorBuffer.h:290
const std::string & GetName() const
Returns the debug name of the buffer, if any.
Definition AcceleratorBuffer.h:294
static std::recursive_mutex & GetMutex()
Get the mutex for m_objectList.
Definition AcceleratorBuffer.h:317
std::string m_name
Friendly name of the buffer (for debug tools)
Definition AcceleratorBuffer.h:279
size_t size() const
Returns the actual size of the container (may be smaller than what was allocated)
Definition AcceleratorBuffer.h:286
size_t m_size
Size of the memory actually being used.
Definition AcceleratorBuffer.h:274
static std::set< AcceleratorBufferBase * > m_objectList
Set of all existing AcceleratorBufferBase objects.
Definition AcceleratorBuffer.h:312
virtual size_t GetElementSize() const =0
Gets the size of each entry in the container.
static const std::set< AcceleratorBufferBase * > & GetObjects()
Get the set of all objects. You must hold a lock on m_objectListMutex while working with it.
Definition AcceleratorBuffer.h:321
virtual std::string GetType() const =0
Gets the underlying C++ object type.
size_t m_capacity
Size of the allocated memory space (may be larger than m_size)
Definition AcceleratorBuffer.h:271
Definition AcceleratorBuffer.h:204
Performance counters shared by all AcceleratorBuffer instances.
Definition AcceleratorBuffer.h:70
static std::atomic< int64_t > m_hostDeviceCopiesBlocking
Number of blocking copies from the CPU to GPU made with the global transfer queue.
Definition AcceleratorBuffer.h:126
static std::atomic< int64_t > m_deviceDeviceCopiesBlocking
Number of blocking copies from the GPU to GPU made with the global transfer queue.
Definition AcceleratorBuffer.h:148
static std::atomic< int64_t > m_deviceDeviceCopiesNonBlocking
Number of nonblocking copies from the GPU to GPU made as part of a larger command buffer.
Definition AcceleratorBuffer.h:151
static std::atomic< int64_t > m_hostDeviceCopiesNonBlocking
Number of nonblocking copies from the CPU to GPU made as part of a larger command buffer.
Definition AcceleratorBuffer.h:129
static std::atomic< int64_t > m_deviceHostCopiesNonBlocking
Number of nonblocking copies from the GPU to CPU made as part of a larger command buffer.
Definition AcceleratorBuffer.h:140
static std::atomic< int64_t > m_hostDeviceCopiesSkipped
Number of copies from the CPU to GPU avoided because the data was already resident.
Definition AcceleratorBuffer.h:132
static std::atomic< int64_t > m_deviceDeviceCopiesSkipped
Number of copies from the GPU to GPU avoided because the data was already resident.
Definition AcceleratorBuffer.h:154
static std::atomic< int64_t > m_deviceHostCopiesSkipped
Number of copies from the CPU to GPU avoided because the data was already resident.
Definition AcceleratorBuffer.h:143
static std::atomic< int64_t > m_deviceHostCopiesBlocking
Number of blocking copies from the GPU to CPU made with the global transfer queue.
Definition AcceleratorBuffer.h:137
A buffer of memory which may be used by GPU acceleration.
Definition AcceleratorBuffer.h:343
std::unique_ptr< vk::raii::Event > m_deviceHostTransferEvent
Event signaled upon completion of a device-host transfer.
Definition AcceleratorBuffer.h:2118
std::unique_ptr< vk::raii::Buffer > m_gpuBuffer
Buffer object for GPU-side memory.
Definition AcceleratorBuffer.h:500
bool empty() const
Returns true if the container is empty.
Definition AcceleratorBuffer.h:610
T * m_cpuPtr
CPU-side mapped pointer.
Definition AcceleratorBuffer.h:488
__attribute__((noinline)) void CopyFrom(const std
Copies our content from a std::vector.
Definition AcceleratorBuffer.h:727
bool IsCpuBufferStale() const
Returns true if the CPU-side buffer is stale.
Definition AcceleratorBuffer.h:616
vk::DescriptorBufferInfo GetBufferInfo()
Returns a vk::DescriptorBufferInfo suitable for binding this object to.
Definition AcceleratorBuffer.h:665
std::unique_ptr< vk::raii::DeviceMemory > m_cpuPhysMem
CPU-side physical memory.
Definition AcceleratorBuffer.h:491
bool IsReachableFromCpu(MemoryType mt)
Returns true if the given buffer type can be reached from the CPU.
Definition AcceleratorBuffer.h:457
bool IsSingleSharedBuffer() const
Returns true if the object contains only a single buffer.
Definition AcceleratorBuffer.h:640
bool HasCpuBuffer() const
Returns true if there is currently a CPU-side buffer.
Definition AcceleratorBuffer.h:628
std::unique_ptr< vk::raii::DeviceMemory > m_gpuPhysMem
GPU-side physical memory.
Definition AcceleratorBuffer.h:494
bool m_gpuPhysMemIsStale
True if m_gpuPhysMem contains stale data (m_cpuPtr has been modified and they point to different memo...
Definition AcceleratorBuffer.h:509
bool IsReachableFromGpu(MemoryType mt)
Returns true if the given buffer type can be reached from the GPU.
Definition AcceleratorBuffer.h:463
vk::Buffer GetBuffer()
Returns the preferred buffer for GPU-side access.
Definition AcceleratorBuffer.h:648
std::atomic< bool > m_hostDeviceTransferActive
True if a host-device transfer has been submitted.
Definition AcceleratorBuffer.h:2167
size_t GetCpuMemoryBytes() const
Returns the total reserved CPU memory, in bytes.
Definition AcceleratorBuffer.h:588
size_t GetMemoryBytes() const
Returns the total size of the buffer (wherever it is), in bytes.
Definition AcceleratorBuffer.h:582
bool m_buffersAreSame
True if we have only one piece of physical memory accessible from both sides.
Definition AcceleratorBuffer.h:503
__attribute__((noinline)) AcceleratorBuffer(const std
Creates a new AcceleratorBuffer with no content.
Definition AcceleratorBuffer.h:540
void reserve(size_t size)
Reallocates buffers so that at least size elements of storage are available.
Definition AcceleratorBuffer.h:709
UsageHint m_gpuAccessHint
Hint about how likely future GPU access is.
Definition AcceleratorBuffer.h:531
MemoryType m_cpuMemoryType
Type of the CPU-side buffer.
Definition AcceleratorBuffer.h:479
bool IsFastFromGpu(MemoryType mt)
Returns true if the given buffer type is fast to access from the GPU.
Definition AcceleratorBuffer.h:475
std::unique_ptr< vk::raii::Event > m_hostDeviceTransferEvent
Event signaled upon completion of a host-device transfer.
Definition AcceleratorBuffer.h:2170
std::unique_ptr< vk::raii::Buffer > m_cpuBuffer
Buffer object for CPU-side memory.
Definition AcceleratorBuffer.h:497
MemoryType m_gpuMemoryType
Type of the GPU-side buffer.
Definition AcceleratorBuffer.h:482
bool HasGpuBuffer() const
Returns true if there is currently a GPU-side buffer.
Definition AcceleratorBuffer.h:634
size_t GetGpuMemoryBytes() const
Returns the total reserved GPU memory, in bytes.
Definition AcceleratorBuffer.h:599
int m_tempFileHandle
File handle used for MEM_TYPE_CPU_PAGED.
Definition AcceleratorBuffer.h:513
virtual std::string GetType() const
Gets the underlying C++ object type.
Definition AcceleratorBuffer.h:346
void shrink_to_fit()
Frees unused memory so that m_size == m_capacity.
Definition AcceleratorBuffer.h:718
bool IsFastFromCpu(MemoryType mt)
Returns true if the given buffer type is fast to access from the CPU.
Definition AcceleratorBuffer.h:469
bool BeginHostDeviceTransferIfNeeded()
Starts a host-to-device transfer if we need to do one.
Definition AcceleratorBuffer.h:2137
MemoryAttributes
Attributes that a memory buffer can have.
Definition AcceleratorBuffer.h:405
bool m_cpuPhysMemIsStale
True if m_cpuPtr contains stale data (m_gpuPhysMem has been modified and they point to different memo...
Definition AcceleratorBuffer.h:506
virtual size_t GetElementSize() const
Gets the size of each entry in the container.
Definition AcceleratorBuffer.h:386
__attribute__((noinline)) void CopyFrom(const AcceleratorBuffer< T > &rhs
Copies our content from another AcceleratorBuffer.
UsageHint m_cpuAccessHint
Hint about how likely future CPU access is.
Definition AcceleratorBuffer.h:528
MemoryType
Types of memory buffer.
Definition AcceleratorBuffer.h:423
T * GetCpuPointer()
Gets a pointer to the CPU-side buffer.
Definition AcceleratorBuffer.h:659
void clear()
Resize the container to be empty (but don't free memory)
Definition AcceleratorBuffer.h:703
bool IsGpuBufferStale() const
Returns true if the GPU-side buffer is stale.
Definition AcceleratorBuffer.h:622
void resize(size_t size, bool exactSize=false)
Change the usable size of the container.
Definition AcceleratorBuffer.h:679
Aligned memory allocator for STL containers.
Definition AlignedAllocator.h:53
void deallocate(T *const p, const size_t unused) const
Free a block of memory.
Definition AlignedAllocator.h:194
T * allocate(size_t n) const
Allocate a block of memory.
Definition AlignedAllocator.h:159
A unit of measurement, plus conversion to pretty-printed output.
Definition Unit.h:59
uint32_t g_vkPinnedMemoryType
Vulkan memory type for CPU-based memory that is also GPU-readable.
Definition VulkanInit.cpp:118
std::mutex g_vkTransferMutex
Mutex for interlocking access to g_vkTransferCommandBuffer and g_vkTransferCommandPool.
Definition VulkanInit.cpp:112
bool g_hasDebugUtils
Indicates whether the VK_EXT_debug_utils extension is available.
Definition VulkanInit.cpp:201
std::shared_ptr< vk::raii::Device > g_vkComputeDevice
The Vulkan device selected for compute operations (may or may not be same device as rendering)
Definition VulkanInit.cpp:71
std::unique_ptr< vk::raii::CommandBuffer > g_vkTransferCommandBuffer
Command buffer for AcceleratorBuffer transfers.
Definition VulkanInit.cpp:89
std::shared_ptr< QueueHandle > g_vkTransferQueue
Queue for AcceleratorBuffer transfers.
Definition VulkanInit.cpp:98
uint32_t g_vkLocalMemoryType
Vulkan memory type for GPU-based memory (generally not CPU-readable, except on unified memory systems...
Definition VulkanInit.cpp:124
bool g_vulkanDeviceHasUnifiedMemory
Indicates whether the Vulkan device is unified memory.
Definition VulkanInit.cpp:226