1#if defined(CONF_BACKEND_VULKAN)
2
3#include <base/dbg.h>
4#include <base/log.h>
5#include <base/math.h>
6#include <base/mem.h>
7#include <base/str.h>
8#include <base/time.h>
9
10#include <engine/client/backend/backend_base.h>
11#include <engine/client/backend/vulkan/backend_vulkan.h>
12#include <engine/client/backend_sdl.h>
13#include <engine/client/graphics_threaded.h>
14#include <engine/gfx/image_manipulation.h>
15#include <engine/graphics.h>
16#include <engine/shared/config.h>
17#include <engine/shared/localization.h>
18#include <engine/storage.h>
19
20#include <SDL_video.h>
21#include <SDL_vulkan.h>
22#include <vulkan/vk_platform.h>
23#include <vulkan/vulkan_core.h>
24
25#include <algorithm>
26#include <array>
27#include <condition_variable>
28#include <cstddef>
29#include <cstdlib>
30#include <functional>
31#include <limits>
32#include <map>
33#include <memory>
34#include <mutex>
35#include <optional>
36#include <set>
37#include <string>
38#include <thread>
39#include <unordered_map>
40#include <utility>
41#include <vector>
42
43#ifndef VK_API_VERSION_MAJOR
44#define VK_API_VERSION_MAJOR VK_VERSION_MAJOR
45#define VK_API_VERSION_MINOR VK_VERSION_MINOR
46#define VK_API_VERSION_PATCH VK_VERSION_PATCH
47#endif
48
49using namespace std::chrono_literals;
50
51class CCommandProcessorFragment_Vulkan : public CCommandProcessorFragment_GLBase
52{
53 enum EMemoryBlockUsage
54 {
55 MEMORY_BLOCK_USAGE_TEXTURE = 0,
56 MEMORY_BLOCK_USAGE_BUFFER,
57 MEMORY_BLOCK_USAGE_STREAM,
58 MEMORY_BLOCK_USAGE_STAGING,
59
60 // whenever dummy is used, make sure to deallocate all memory
61 MEMORY_BLOCK_USAGE_DUMMY,
62 };
63
64 [[nodiscard]] bool IsVerbose()
65 {
66 return g_Config.m_DbgGfx == DEBUG_GFX_MODE_VERBOSE || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL;
67 }
68
69 static const char *MemoryUsageName(EMemoryBlockUsage MemUsage)
70 {
71 switch(MemUsage)
72 {
73 case MEMORY_BLOCK_USAGE_TEXTURE:
74 return "texture";
75 case MEMORY_BLOCK_USAGE_BUFFER:
76 return "buffer";
77 case MEMORY_BLOCK_USAGE_STREAM:
78 return "stream";
79 case MEMORY_BLOCK_USAGE_STAGING:
80 return "staging buffer";
81 default:
82 dbg_assert_failed("Invalid MemUsage: %d", (int)MemUsage);
83 }
84 }
85
86 void VerboseAllocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) const
87 {
88 log_debug("gfx/vulkan", "Allocated chunk of memory with size %" PRIzu " for frame %" PRIzu " (%s).",
89 (size_t)Size, (size_t)m_CurImageIndex, MemoryUsageName(MemUsage));
90 }
91
92 void VerboseDeallocatedMemory(VkDeviceSize Size, size_t FrameImageIndex, EMemoryBlockUsage MemUsage) const
93 {
94 log_debug("gfx/vulkan", "Deallocated chunk of memory with size %" PRIzu " for frame %" PRIzu " (%s).",
95 (size_t)Size, (size_t)m_CurImageIndex, MemoryUsageName(MemUsage));
96 }
97
98 /************************
99 * STRUCT DEFINITIONS
100 ************************/
101
102 static constexpr size_t STAGING_BUFFER_CACHE_ID = 0;
103 static constexpr size_t STAGING_BUFFER_IMAGE_CACHE_ID = 1;
104 static constexpr size_t VERTEX_BUFFER_CACHE_ID = 2;
105 static constexpr size_t IMAGE_BUFFER_CACHE_ID = 3;
106
107 struct SDeviceMemoryBlock
108 {
109 VkDeviceMemory m_Mem = VK_NULL_HANDLE;
110 VkDeviceSize m_Size = 0;
111 EMemoryBlockUsage m_UsageType;
112 };
113
114 struct SDeviceDescriptorPools;
115
116 struct SDeviceDescriptorSet
117 {
118 VkDescriptorSet m_Descriptor = VK_NULL_HANDLE;
119 SDeviceDescriptorPools *m_pPools = nullptr;
120 size_t m_PoolIndex = std::numeric_limits<size_t>::max();
121 };
122
123 struct SDeviceDescriptorPool
124 {
125 VkDescriptorPool m_Pool;
126 VkDeviceSize m_Size = 0;
127 VkDeviceSize m_CurSize = 0;
128 };
129
130 struct SDeviceDescriptorPools
131 {
132 std::vector<SDeviceDescriptorPool> m_vPools;
133 VkDeviceSize m_DefaultAllocSize = 0;
134 bool m_IsUniformPool = false;
135 };
136
137 // some mix of queue and binary tree
138 struct SMemoryHeap
139 {
140 struct SMemoryHeapElement;
141 struct SMemoryHeapQueueElement
142 {
143 size_t m_AllocationSize;
144 // only useful information for the heap
145 size_t m_OffsetInHeap;
146 // useful for the user of this element
147 size_t m_OffsetToAlign;
148 SMemoryHeapElement *m_pElementInHeap;
149 [[nodiscard]] bool operator>(const SMemoryHeapQueueElement &Other) const { return m_AllocationSize > Other.m_AllocationSize; }
150 // respects alignment requirements
151 constexpr bool CanFitAllocation(size_t AllocSize, size_t AllocAlignment) const
152 {
153 size_t ExtraSizeAlign = m_OffsetInHeap % AllocAlignment;
154 if(ExtraSizeAlign != 0)
155 ExtraSizeAlign = AllocAlignment - ExtraSizeAlign;
156 size_t RealAllocSize = AllocSize + ExtraSizeAlign;
157 return m_AllocationSize >= RealAllocSize;
158 }
159 };
160
161 typedef std::multiset<SMemoryHeapQueueElement, std::greater<>> TMemoryHeapQueue;
162
163 struct SMemoryHeapElement
164 {
165 size_t m_AllocationSize;
166 size_t m_Offset;
167 SMemoryHeapElement *m_pParent;
168 std::unique_ptr<SMemoryHeapElement> m_pLeft;
169 std::unique_ptr<SMemoryHeapElement> m_pRight;
170
171 bool m_InUse;
172 TMemoryHeapQueue::iterator m_InQueue;
173 };
174
175 SMemoryHeapElement m_Root;
176 TMemoryHeapQueue m_Elements;
177
178 void Init(size_t Size, size_t Offset)
179 {
180 m_Root.m_AllocationSize = Size;
181 m_Root.m_Offset = Offset;
182 m_Root.m_pParent = nullptr;
183 m_Root.m_InUse = false;
184
185 SMemoryHeapQueueElement QueueEl;
186 QueueEl.m_AllocationSize = Size;
187 QueueEl.m_OffsetInHeap = Offset;
188 QueueEl.m_OffsetToAlign = Offset;
189 QueueEl.m_pElementInHeap = &m_Root;
190 m_Root.m_InQueue = m_Elements.insert(x: QueueEl);
191 }
192
193 [[nodiscard]] bool Allocate(size_t AllocSize, size_t AllocAlignment, SMemoryHeapQueueElement &AllocatedMemory)
194 {
195 if(m_Elements.empty())
196 {
197 return false;
198 }
199 else
200 {
201 // check if there is enough space in this instance
202 if(!m_Elements.begin()->CanFitAllocation(AllocSize, AllocAlignment))
203 {
204 return false;
205 }
206 else
207 {
208 // see SMemoryHeapQueueElement::operator>
209 SMemoryHeapQueueElement FindAllocSize;
210 FindAllocSize.m_AllocationSize = AllocSize;
211 // find upper bound for a allocation size
212 auto Upper = m_Elements.upper_bound(x: FindAllocSize);
213 // then find the first entry that respects alignment, this is a linear search!
214 auto FoundEl = m_Elements.rend();
215 for(auto AllocIterator = std::make_reverse_iterator(i: Upper); AllocIterator != m_Elements.rend(); ++AllocIterator)
216 {
217 if(AllocIterator->CanFitAllocation(AllocSize, AllocAlignment))
218 {
219 FoundEl = AllocIterator;
220 break;
221 }
222 }
223
224 auto TopEl = *FoundEl;
225 m_Elements.erase(position: TopEl.m_pElementInHeap->m_InQueue);
226
227 TopEl.m_pElementInHeap->m_InUse = true;
228
229 // calculate the real alloc size + alignment offset
230 size_t ExtraSizeAlign = TopEl.m_OffsetInHeap % AllocAlignment;
231 if(ExtraSizeAlign != 0)
232 ExtraSizeAlign = AllocAlignment - ExtraSizeAlign;
233 size_t RealAllocSize = AllocSize + ExtraSizeAlign;
234
235 // the heap element gets children
236 TopEl.m_pElementInHeap->m_pLeft = std::make_unique<SMemoryHeapElement>();
237 TopEl.m_pElementInHeap->m_pLeft->m_AllocationSize = RealAllocSize;
238 TopEl.m_pElementInHeap->m_pLeft->m_Offset = TopEl.m_OffsetInHeap;
239 TopEl.m_pElementInHeap->m_pLeft->m_pParent = TopEl.m_pElementInHeap;
240 TopEl.m_pElementInHeap->m_pLeft->m_InUse = true;
241
242 if(RealAllocSize < TopEl.m_AllocationSize)
243 {
244 SMemoryHeapQueueElement RemainingEl;
245 RemainingEl.m_OffsetInHeap = TopEl.m_OffsetInHeap + RealAllocSize;
246 RemainingEl.m_AllocationSize = TopEl.m_AllocationSize - RealAllocSize;
247
248 TopEl.m_pElementInHeap->m_pRight = std::make_unique<SMemoryHeapElement>();
249 TopEl.m_pElementInHeap->m_pRight->m_AllocationSize = RemainingEl.m_AllocationSize;
250 TopEl.m_pElementInHeap->m_pRight->m_Offset = RemainingEl.m_OffsetInHeap;
251 TopEl.m_pElementInHeap->m_pRight->m_pParent = TopEl.m_pElementInHeap;
252 TopEl.m_pElementInHeap->m_pRight->m_InUse = false;
253
254 RemainingEl.m_pElementInHeap = TopEl.m_pElementInHeap->m_pRight.get();
255 RemainingEl.m_pElementInHeap->m_InQueue = m_Elements.insert(x: RemainingEl);
256 }
257
258 AllocatedMemory.m_pElementInHeap = TopEl.m_pElementInHeap->m_pLeft.get();
259 AllocatedMemory.m_AllocationSize = RealAllocSize;
260 AllocatedMemory.m_OffsetInHeap = TopEl.m_OffsetInHeap;
261 AllocatedMemory.m_OffsetToAlign = TopEl.m_OffsetInHeap + ExtraSizeAlign;
262 return true;
263 }
264 }
265 }
266
267 void Free(const SMemoryHeapQueueElement &AllocatedMemory)
268 {
269 bool ContinueFree = true;
270 SMemoryHeapQueueElement ThisEl = AllocatedMemory;
271 while(ContinueFree)
272 {
273 // first check if the other block is in use, if not merge them again
274 SMemoryHeapElement *pThisHeapObj = ThisEl.m_pElementInHeap;
275 SMemoryHeapElement *pThisParent = pThisHeapObj->m_pParent;
276 pThisHeapObj->m_InUse = false;
277 SMemoryHeapElement *pOtherHeapObj = nullptr;
278 if(pThisParent != nullptr && pThisHeapObj == pThisParent->m_pLeft.get())
279 pOtherHeapObj = pThisHeapObj->m_pParent->m_pRight.get();
280 else if(pThisParent != nullptr)
281 pOtherHeapObj = pThisHeapObj->m_pParent->m_pLeft.get();
282
283 if((pThisParent != nullptr && pOtherHeapObj == nullptr) || (pOtherHeapObj != nullptr && !pOtherHeapObj->m_InUse))
284 {
285 // merge them
286 if(pOtherHeapObj != nullptr)
287 {
288 m_Elements.erase(position: pOtherHeapObj->m_InQueue);
289 pOtherHeapObj->m_InUse = false;
290 }
291
292 SMemoryHeapQueueElement ParentEl;
293 ParentEl.m_OffsetInHeap = pThisParent->m_Offset;
294 ParentEl.m_AllocationSize = pThisParent->m_AllocationSize;
295 ParentEl.m_pElementInHeap = pThisParent;
296
297 pThisParent->m_pLeft = nullptr;
298 pThisParent->m_pRight = nullptr;
299
300 ThisEl = ParentEl;
301 }
302 else
303 {
304 // else just put this back into queue
305 ThisEl.m_pElementInHeap->m_InQueue = m_Elements.insert(x: ThisEl);
306 ContinueFree = false;
307 }
308 }
309 }
310
311 [[nodiscard]] bool IsUnused() const
312 {
313 return !m_Root.m_InUse;
314 }
315 };
316
317 template<size_t Id>
318 struct SMemoryBlock
319 {
320 SMemoryHeap::SMemoryHeapQueueElement m_HeapData;
321
322 VkDeviceSize m_UsedSize;
323
324 // optional
325 VkBuffer m_Buffer;
326
327 SDeviceMemoryBlock m_BufferMem;
328 void *m_pMappedBuffer;
329
330 bool m_IsCached;
331 SMemoryHeap *m_pHeap;
332 };
333
334 template<size_t Id>
335 struct SMemoryImageBlock : public SMemoryBlock<Id>
336 {
337 uint32_t m_ImageMemoryBits;
338 };
339
340 template<size_t Id>
341 struct SMemoryBlockCache
342 {
343 struct SMemoryCacheType
344 {
345 struct SMemoryCacheHeap
346 {
347 SMemoryHeap m_Heap;
348 VkBuffer m_Buffer;
349
350 SDeviceMemoryBlock m_BufferMem;
351 void *m_pMappedBuffer;
352 };
353 std::vector<SMemoryCacheHeap *> m_vpMemoryHeaps;
354 };
355 SMemoryCacheType m_MemoryCaches;
356 std::vector<std::vector<SMemoryBlock<Id>>> m_vvFrameDelayedCachedBufferCleanup;
357
358 bool m_CanShrink = false;
359
360 void Init(size_t SwapChainImageCount)
361 {
362 m_vvFrameDelayedCachedBufferCleanup.resize(SwapChainImageCount);
363 }
364
365 void DestroyFrameData(size_t ImageCount)
366 {
367 for(size_t i = 0; i < ImageCount; ++i)
368 Cleanup(ImgIndex: i);
369 m_vvFrameDelayedCachedBufferCleanup.clear();
370 }
371
372 void Destroy(VkDevice &Device)
373 {
374 for(auto HeapIterator = m_MemoryCaches.m_vpMemoryHeaps.begin(); HeapIterator != m_MemoryCaches.m_vpMemoryHeaps.end();)
375 {
376 auto *pHeap = *HeapIterator;
377 if(pHeap->m_pMappedBuffer != nullptr)
378 vkUnmapMemory(Device, pHeap->m_BufferMem.m_Mem);
379 if(pHeap->m_Buffer != VK_NULL_HANDLE)
380 vkDestroyBuffer(Device, pHeap->m_Buffer, nullptr);
381 vkFreeMemory(Device, pHeap->m_BufferMem.m_Mem, nullptr);
382
383 delete pHeap;
384 HeapIterator = m_MemoryCaches.m_vpMemoryHeaps.erase(HeapIterator);
385 }
386
387 m_MemoryCaches.m_vpMemoryHeaps.clear();
388 m_vvFrameDelayedCachedBufferCleanup.clear();
389 }
390
391 void Cleanup(size_t ImgIndex)
392 {
393 for(auto &MemBlock : m_vvFrameDelayedCachedBufferCleanup[ImgIndex])
394 {
395 MemBlock.m_UsedSize = 0;
396 MemBlock.m_pHeap->Free(MemBlock.m_HeapData);
397
398 m_CanShrink = true;
399 }
400 m_vvFrameDelayedCachedBufferCleanup[ImgIndex].clear();
401 }
402
403 void FreeMemBlock(SMemoryBlock<Id> &Block, size_t ImgIndex)
404 {
405 m_vvFrameDelayedCachedBufferCleanup[ImgIndex].push_back(Block);
406 }
407
408 // returns the total free'd memory
409 size_t Shrink(VkDevice &Device)
410 {
411 size_t FreedMemory = 0;
412 if(m_CanShrink)
413 {
414 m_CanShrink = false;
415 if(m_MemoryCaches.m_vpMemoryHeaps.size() > 1)
416 {
417 for(auto HeapIterator = m_MemoryCaches.m_vpMemoryHeaps.begin(); HeapIterator != m_MemoryCaches.m_vpMemoryHeaps.end();)
418 {
419 auto *pHeap = *HeapIterator;
420 if(pHeap->m_Heap.IsUnused())
421 {
422 if(pHeap->m_pMappedBuffer != nullptr)
423 vkUnmapMemory(Device, pHeap->m_BufferMem.m_Mem);
424 if(pHeap->m_Buffer != VK_NULL_HANDLE)
425 vkDestroyBuffer(Device, pHeap->m_Buffer, nullptr);
426 vkFreeMemory(Device, pHeap->m_BufferMem.m_Mem, nullptr);
427 FreedMemory += pHeap->m_BufferMem.m_Size;
428
429 delete pHeap;
430 HeapIterator = m_MemoryCaches.m_vpMemoryHeaps.erase(HeapIterator);
431 if(m_MemoryCaches.m_vpMemoryHeaps.size() == 1)
432 break;
433 }
434 else
435 ++HeapIterator;
436 }
437 }
438 }
439
440 return FreedMemory;
441 }
442 };
443
444 struct CTexture
445 {
446 VkImage m_Img = VK_NULL_HANDLE;
447 SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> m_ImgMem;
448 VkImageView m_ImgView = VK_NULL_HANDLE;
449 VkSampler m_aSamplers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
450
451 VkImage m_Img3D = VK_NULL_HANDLE;
452 SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> m_Img3DMem;
453 VkImageView m_Img3DView = VK_NULL_HANDLE;
454 VkSampler m_Sampler3D = VK_NULL_HANDLE;
455
456 uint32_t m_Width = 0;
457 uint32_t m_Height = 0;
458 uint32_t m_RescaleCount = 0;
459
460 uint32_t m_MipMapCount = 1;
461
462 std::array<SDeviceDescriptorSet, 2> m_aVKStandardTexturedDescrSets;
463 SDeviceDescriptorSet m_VKStandard3DTexturedDescrSet;
464 SDeviceDescriptorSet m_VKTextDescrSet;
465 };
466
467 struct SBufferObject
468 {
469 SMemoryBlock<VERTEX_BUFFER_CACHE_ID> m_Mem;
470 };
471
472 struct SBufferObjectFrame
473 {
474 SBufferObject m_BufferObject;
475
476 // since stream buffers can be used the cur buffer should always be used for rendering
477 bool m_IsStreamedBuffer = false;
478 VkBuffer m_CurBuffer = VK_NULL_HANDLE;
479 size_t m_CurBufferOffset = 0;
480 };
481
482 struct SBufferContainer
483 {
484 int m_BufferObjectIndex;
485 };
486
487 struct SFrameBuffers
488 {
489 VkBuffer m_Buffer;
490 SDeviceMemoryBlock m_BufferMem;
491 size_t m_OffsetInBuffer = 0;
492 size_t m_Size;
493 size_t m_UsedSize;
494 uint8_t *m_pMappedBufferData;
495
496 SFrameBuffers(VkBuffer Buffer, SDeviceMemoryBlock BufferMem, size_t OffsetInBuffer, size_t Size, size_t UsedSize, uint8_t *pMappedBufferData) :
497 m_Buffer(Buffer), m_BufferMem(BufferMem), m_OffsetInBuffer(OffsetInBuffer), m_Size(Size), m_UsedSize(UsedSize), m_pMappedBufferData(pMappedBufferData)
498 {
499 }
500 };
501
502 struct SFrameUniformBuffers : public SFrameBuffers
503 {
504 std::array<SDeviceDescriptorSet, 2> m_aUniformSets;
505
506 SFrameUniformBuffers(VkBuffer Buffer, SDeviceMemoryBlock BufferMem, size_t OffsetInBuffer, size_t Size, size_t UsedSize, uint8_t *pMappedBufferData) :
507 SFrameBuffers(Buffer, BufferMem, OffsetInBuffer, Size, UsedSize, pMappedBufferData) {}
508 };
509
510 template<typename TName>
511 struct SStreamMemory
512 {
513 typedef std::vector<std::vector<TName>> TBufferObjectsOfFrame;
514 typedef std::vector<std::vector<VkMappedMemoryRange>> TMemoryMapRangesOfFrame;
515 typedef std::vector<size_t> TStreamUseCount;
516 TBufferObjectsOfFrame m_vvBufferObjectsOfFrame;
517 TMemoryMapRangesOfFrame m_vvBufferObjectsOfFrameRangeData;
518 TStreamUseCount m_vCurrentUsedCount;
519
520 std::vector<TName> &GetBuffers(size_t FrameImageIndex)
521 {
522 return m_vvBufferObjectsOfFrame[FrameImageIndex];
523 }
524
525 std::vector<VkMappedMemoryRange> &GetRanges(size_t FrameImageIndex)
526 {
527 return m_vvBufferObjectsOfFrameRangeData[FrameImageIndex];
528 }
529
530 size_t GetUsedCount(size_t FrameImageIndex)
531 {
532 return m_vCurrentUsedCount[FrameImageIndex];
533 }
534
535 void IncreaseUsedCount(size_t FrameImageIndex)
536 {
537 ++m_vCurrentUsedCount[FrameImageIndex];
538 }
539
540 [[nodiscard]] bool IsUsed(size_t FrameImageIndex)
541 {
542 return GetUsedCount(FrameImageIndex) > 0;
543 }
544
545 void ResetFrame(size_t FrameImageIndex)
546 {
547 m_vCurrentUsedCount[FrameImageIndex] = 0;
548 }
549
550 void Init(size_t FrameImageCount)
551 {
552 m_vvBufferObjectsOfFrame.resize(FrameImageCount);
553 m_vvBufferObjectsOfFrameRangeData.resize(sz: FrameImageCount);
554 m_vCurrentUsedCount.resize(sz: FrameImageCount);
555 }
556
557 typedef std::function<void(size_t, TName &)> TDestroyBufferFunc;
558
559 void Destroy(TDestroyBufferFunc &&DestroyBuffer)
560 {
561 size_t ImageIndex = 0;
562 for(auto &vBuffersOfFrame : m_vvBufferObjectsOfFrame)
563 {
564 for(auto &BufferOfFrame : vBuffersOfFrame)
565 {
566 VkDeviceMemory BufferMem = BufferOfFrame.m_BufferMem.m_Mem;
567 DestroyBuffer(ImageIndex, BufferOfFrame);
568
569 // delete similar buffers
570 for(auto &BufferOfFrameDel : vBuffersOfFrame)
571 {
572 if(BufferOfFrameDel.m_BufferMem.m_Mem == BufferMem)
573 {
574 BufferOfFrameDel.m_Buffer = VK_NULL_HANDLE;
575 BufferOfFrameDel.m_BufferMem.m_Mem = VK_NULL_HANDLE;
576 }
577 }
578 }
579 ++ImageIndex;
580 }
581 m_vvBufferObjectsOfFrame.clear();
582 m_vvBufferObjectsOfFrameRangeData.clear();
583 m_vCurrentUsedCount.clear();
584 }
585 };
586
587 struct SShaderModule
588 {
589 VkShaderModule m_VertShaderModule = VK_NULL_HANDLE;
590 VkShaderModule m_FragShaderModule = VK_NULL_HANDLE;
591
592 VkDevice m_VKDevice = VK_NULL_HANDLE;
593
594 ~SShaderModule()
595 {
596 if(m_VKDevice != VK_NULL_HANDLE)
597 {
598 if(m_VertShaderModule != VK_NULL_HANDLE)
599 vkDestroyShaderModule(device: m_VKDevice, shaderModule: m_VertShaderModule, pAllocator: nullptr);
600
601 if(m_FragShaderModule != VK_NULL_HANDLE)
602 vkDestroyShaderModule(device: m_VKDevice, shaderModule: m_FragShaderModule, pAllocator: nullptr);
603 }
604 }
605 };
606
607 enum EVulkanBackendAddressModes
608 {
609 VULKAN_BACKEND_ADDRESS_MODE_REPEAT = 0,
610 VULKAN_BACKEND_ADDRESS_MODE_CLAMP_EDGES,
611
612 VULKAN_BACKEND_ADDRESS_MODE_COUNT,
613 };
614
615 enum EVulkanBackendBlendModes
616 {
617 VULKAN_BACKEND_BLEND_MODE_ALPHA = 0,
618 VULKAN_BACKEND_BLEND_MODE_NONE,
619 VULKAN_BACKEND_BLEND_MODE_ADDITATIVE,
620
621 VULKAN_BACKEND_BLEND_MODE_COUNT,
622 };
623
624 enum EVulkanBackendClipModes
625 {
626 VULKAN_BACKEND_CLIP_MODE_NONE = 0,
627 VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT,
628
629 VULKAN_BACKEND_CLIP_MODE_COUNT,
630 };
631
632 enum EVulkanBackendTextureModes
633 {
634 VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED = 0,
635 VULKAN_BACKEND_TEXTURE_MODE_TEXTURED,
636
637 VULKAN_BACKEND_TEXTURE_MODE_COUNT,
638 };
639
640 struct SPipelineContainer
641 {
642 // 3 blend modes - 2 viewport & scissor modes - 2 texture modes
643 std::array<std::array<std::array<VkPipelineLayout, VULKAN_BACKEND_TEXTURE_MODE_COUNT>, VULKAN_BACKEND_CLIP_MODE_COUNT>, VULKAN_BACKEND_BLEND_MODE_COUNT> m_aaaPipelineLayouts;
644 std::array<std::array<std::array<VkPipeline, VULKAN_BACKEND_TEXTURE_MODE_COUNT>, VULKAN_BACKEND_CLIP_MODE_COUNT>, VULKAN_BACKEND_BLEND_MODE_COUNT> m_aaaPipelines;
645
646 SPipelineContainer()
647 {
648 for(auto &aaPipeLayouts : m_aaaPipelineLayouts)
649 {
650 for(auto &aPipeLayouts : aaPipeLayouts)
651 {
652 for(auto &PipeLayout : aPipeLayouts)
653 {
654 PipeLayout = VK_NULL_HANDLE;
655 }
656 }
657 }
658 for(auto &aaPipe : m_aaaPipelines)
659 {
660 for(auto &aPipe : aaPipe)
661 {
662 for(auto &Pipe : aPipe)
663 {
664 Pipe = VK_NULL_HANDLE;
665 }
666 }
667 }
668 }
669
670 void Destroy(VkDevice &Device)
671 {
672 for(auto &aaPipeLayouts : m_aaaPipelineLayouts)
673 {
674 for(auto &aPipeLayouts : aaPipeLayouts)
675 {
676 for(auto &PipeLayout : aPipeLayouts)
677 {
678 if(PipeLayout != VK_NULL_HANDLE)
679 vkDestroyPipelineLayout(device: Device, pipelineLayout: PipeLayout, pAllocator: nullptr);
680 PipeLayout = VK_NULL_HANDLE;
681 }
682 }
683 }
684 for(auto &aaPipe : m_aaaPipelines)
685 {
686 for(auto &aPipe : aaPipe)
687 {
688 for(auto &Pipe : aPipe)
689 {
690 if(Pipe != VK_NULL_HANDLE)
691 vkDestroyPipeline(device: Device, pipeline: Pipe, pAllocator: nullptr);
692 Pipe = VK_NULL_HANDLE;
693 }
694 }
695 }
696 }
697 };
698
699 /*******************************
700 * UNIFORM PUSH CONSTANT LAYOUTS
701 ********************************/
702
703 struct SUniformGPos
704 {
705 float m_aPos[4 * 2];
706 };
707
708 struct SUniformGTextPos
709 {
710 float m_aPos[4 * 2];
711 float m_TextureSize;
712 };
713
714 typedef vec3 SUniformTextGFragmentOffset;
715
716 struct SUniformTextGFragmentConstants
717 {
718 ColorRGBA m_TextColor;
719 ColorRGBA m_TextOutlineColor;
720 };
721
722 struct SUniformTextFragment
723 {
724 SUniformTextGFragmentConstants m_Constants;
725 };
726
727 struct SUniformTileGPos
728 {
729 float m_aPos[4 * 2];
730 };
731
732 struct SUniformTileGPosBorder : public SUniformTileGPos
733 {
734 vec2 m_Offset;
735 vec2 m_Scale;
736 };
737
738 typedef ColorRGBA SUniformTileGVertColor;
739
740 struct SUniformTileGVertColorAlign
741 {
742 float m_aPad[(64 - 48) / 4];
743 };
744
745 struct SUniformPrimExGPosRotationless
746 {
747 float m_aPos[4 * 2];
748 };
749
750 struct SUniformPrimExGPos : public SUniformPrimExGPosRotationless
751 {
752 vec2 m_Center;
753 float m_Rotation;
754 };
755
756 typedef ColorRGBA SUniformPrimExGVertColor;
757
758 struct SUniformPrimExGVertColorAlign
759 {
760 float m_aPad[(48 - 44) / 4];
761 };
762
763 struct SUniformSpriteMultiGPos
764 {
765 float m_aPos[4 * 2];
766 vec2 m_Center;
767 };
768
769 typedef ColorRGBA SUniformSpriteMultiGVertColor;
770
771 struct SUniformSpriteMultiGVertColorAlign
772 {
773 float m_aPad[(48 - 40) / 4];
774 };
775
776 struct SUniformSpriteMultiPushGPosBase
777 {
778 float m_aPos[4 * 2];
779 vec2 m_Center;
780 vec2 m_Padding;
781 };
782
783 struct SUniformSpriteMultiPushGPos : public SUniformSpriteMultiPushGPosBase
784 {
785 vec4 m_aPSR[1];
786 };
787
788 typedef ColorRGBA SUniformSpriteMultiPushGVertColor;
789
790 struct SUniformQuadGPosBase
791 {
792 float m_aPos[4 * 2];
793 int32_t m_QuadOffset;
794 };
795
796 struct SUniformQuadPushGBufferObject
797 {
798 ColorRGBA m_VertColor;
799 vec2 m_Offset;
800 float m_Rotation;
801 float m_Padding;
802 };
803
804 struct SUniformQuadGroupedGPos
805 {
806 float m_aPos[4 * 2];
807 SUniformQuadPushGBufferObject m_BOPush;
808 };
809
810 struct SUniformQuadGPos
811 {
812 float m_aPos[4 * 2];
813 int32_t m_QuadOffset;
814 };
815
816 enum ESupportedSamplerTypes
817 {
818 SUPPORTED_SAMPLER_TYPE_REPEAT = 0,
819 SUPPORTED_SAMPLER_TYPE_CLAMP_TO_EDGE,
820 SUPPORTED_SAMPLER_TYPE_2D_TEXTURE_ARRAY,
821
822 SUPPORTED_SAMPLER_TYPE_COUNT,
823 };
824
825 struct SShaderFileCache
826 {
827 std::vector<uint8_t> m_vBinary;
828 };
829
830 struct SSwapImgViewportExtent
831 {
832 VkExtent2D m_SwapImageViewport;
833 bool m_HasForcedViewport = false;
834 VkExtent2D m_ForcedViewport;
835
836 // the viewport of the resulting presented image on the screen
837 // if there is a forced viewport the resulting image is smaller
838 // than the full swap image size
839 VkExtent2D GetPresentedImageViewport() const
840 {
841 uint32_t ViewportWidth = m_SwapImageViewport.width;
842 uint32_t ViewportHeight = m_SwapImageViewport.height;
843 if(m_HasForcedViewport)
844 {
845 ViewportWidth = m_ForcedViewport.width;
846 ViewportHeight = m_ForcedViewport.height;
847 }
848
849 return {.width: ViewportWidth, .height: ViewportHeight};
850 }
851 };
852
853 struct SSwapChainMultiSampleImage
854 {
855 VkImage m_Image = VK_NULL_HANDLE;
856 SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> m_ImgMem;
857 VkImageView m_ImgView = VK_NULL_HANDLE;
858 };
859
860 /************************
861 * MEMBER VARIABLES
862 ************************/
863
864 std::unordered_map<std::string, SShaderFileCache> m_ShaderFiles;
865
866 SMemoryBlockCache<STAGING_BUFFER_CACHE_ID> m_StagingBufferCache;
867 SMemoryBlockCache<STAGING_BUFFER_IMAGE_CACHE_ID> m_StagingBufferCacheImage;
868 SMemoryBlockCache<VERTEX_BUFFER_CACHE_ID> m_VertexBufferCache;
869 std::map<uint32_t, SMemoryBlockCache<IMAGE_BUFFER_CACHE_ID>> m_ImageBufferCaches;
870
871 std::vector<VkMappedMemoryRange> m_vNonFlushedStagingBufferRange;
872
873 std::vector<CTexture> m_vTextures;
874
875 std::atomic<uint64_t> *m_pTextureMemoryUsage;
876 std::atomic<uint64_t> *m_pBufferMemoryUsage;
877 std::atomic<uint64_t> *m_pStreamMemoryUsage;
878 std::atomic<uint64_t> *m_pStagingMemoryUsage;
879
880 TTwGraphicsGpuList *m_pGpuList;
881
882 int m_GlobalTextureLodBIAS;
883 uint32_t m_MultiSamplingCount = 1;
884
885 uint32_t m_NextMultiSamplingCount = std::numeric_limits<uint32_t>::max();
886
887 bool m_RecreateSwapChain = false;
888 bool m_SwapchainCreated = false;
889 bool m_RenderingPaused = false;
890 bool m_HasDynamicViewport = false;
891 VkOffset2D m_DynamicViewportOffset;
892 VkExtent2D m_DynamicViewportSize;
893
894 bool m_AllowsLinearBlitting = false;
895 bool m_OptimalSwapChainImageBlitting = false;
896 bool m_OptimalRGBAImageBlitting = false;
897 bool m_LinearRGBAImageBlitting = false;
898
899 VkBuffer m_IndexBuffer;
900 SDeviceMemoryBlock m_IndexBufferMemory;
901
902 VkBuffer m_RenderIndexBuffer;
903 SDeviceMemoryBlock m_RenderIndexBufferMemory;
904 size_t m_CurRenderIndexPrimitiveCount;
905
906 VkDeviceSize m_NonCoherentMemAlignment;
907 VkDeviceSize m_OptimalImageCopyMemAlignment;
908 uint32_t m_MaxTextureSize;
909 uint32_t m_MaxSamplerAnisotropy;
910 VkSampleCountFlags m_MaxMultiSample;
911
912 uint32_t m_MinUniformAlign;
913
914 std::vector<uint8_t> m_vReadPixelHelper;
915 std::vector<uint8_t> m_vScreenshotHelper;
916
917 SDeviceMemoryBlock m_GetPresentedImgDataHelperMem;
918 VkImage m_GetPresentedImgDataHelperImage = VK_NULL_HANDLE;
919 uint8_t *m_pGetPresentedImgDataHelperMappedMemory = nullptr;
920 VkDeviceSize m_GetPresentedImgDataHelperMappedLayoutOffset = 0;
921 VkDeviceSize m_GetPresentedImgDataHelperMappedLayoutPitch = 0;
922 uint32_t m_GetPresentedImgDataHelperWidth = 0;
923 uint32_t m_GetPresentedImgDataHelperHeight = 0;
924 VkFence m_GetPresentedImgDataHelperFence = VK_NULL_HANDLE;
925
926 std::array<VkSampler, SUPPORTED_SAMPLER_TYPE_COUNT> m_aSamplers;
927
928 class IStorage *m_pStorage;
929
930 struct SDelayedBufferCleanupItem
931 {
932 VkBuffer m_Buffer;
933 SDeviceMemoryBlock m_Mem;
934 void *m_pMappedData = nullptr;
935 };
936
937 std::vector<std::vector<SDelayedBufferCleanupItem>> m_vvFrameDelayedBufferCleanup;
938 std::vector<std::vector<CTexture>> m_vvFrameDelayedTextureCleanup;
939 std::vector<std::vector<std::pair<CTexture, CTexture>>> m_vvFrameDelayedTextTexturesCleanup;
940
941 size_t m_ThreadCount = 1;
942 static constexpr size_t MAIN_THREAD_INDEX = 0;
943 size_t m_CurCommandInPipe = 0;
944 size_t m_CurRenderCallCountInPipe = 0;
945 size_t m_CommandsInPipe = 0;
946 size_t m_RenderCallsInPipe = 0;
947 size_t m_LastCommandsInPipeThreadIndex = 0;
948
949 struct SRenderThread
950 {
951 bool m_IsRendering = false;
952 std::thread m_Thread;
953 std::mutex m_Mutex;
954 std::condition_variable m_Cond;
955 bool m_Finished = false;
956 bool m_Started = false;
957 };
958 std::vector<std::unique_ptr<SRenderThread>> m_vpRenderThreads;
959
960private:
961 std::vector<VkImageView> m_vSwapChainImageViewList;
962 std::vector<SSwapChainMultiSampleImage> m_vSwapChainMultiSamplingImages;
963 std::vector<VkFramebuffer> m_vFramebufferList;
964 std::vector<VkCommandBuffer> m_vMainDrawCommandBuffers;
965
966 std::vector<std::vector<VkCommandBuffer>> m_vvThreadDrawCommandBuffers;
967 std::vector<VkCommandBuffer> m_vHelperThreadDrawCommandBuffers;
968 std::vector<std::vector<bool>> m_vvUsedThreadDrawCommandBuffer;
969
970 std::vector<VkCommandBuffer> m_vMemoryCommandBuffers;
971 std::vector<bool> m_vUsedMemoryCommandBuffer;
972
973 std::vector<VkSemaphore> m_vQueueSubmitSemaphores;
974 std::vector<VkSemaphore> m_vBusyAcquireImageSemaphores;
975 VkSemaphore m_AcquireImageSemaphore;
976
977 std::vector<VkFence> m_vQueueSubmitFences;
978
979 uint64_t m_CurFrame = 0;
980 std::vector<uint64_t> m_vImageLastFrameCheck;
981
982 uint32_t m_LastPresentedSwapChainImageIndex;
983
984 std::vector<SBufferObjectFrame> m_vBufferObjects;
985
986 std::vector<SBufferContainer> m_vBufferContainers;
987
988 VkInstance m_VKInstance;
989 VkPhysicalDevice m_VKGPU;
990 uint32_t m_VKGraphicsQueueIndex = std::numeric_limits<uint32_t>::max();
991 VkDevice m_VKDevice;
992 VkQueue m_VKGraphicsQueue, m_VKPresentQueue;
993 VkSurfaceKHR m_VKPresentSurface;
994 SSwapImgViewportExtent m_VKSwapImgAndViewportExtent;
995
996#ifdef VK_EXT_debug_utils
997 VkDebugUtilsMessengerEXT m_DebugMessenger;
998#endif
999
1000 VkDescriptorSetLayout m_StandardTexturedDescriptorSetLayout;
1001 VkDescriptorSetLayout m_Standard3DTexturedDescriptorSetLayout;
1002
1003 VkDescriptorSetLayout m_TextDescriptorSetLayout;
1004
1005 VkDescriptorSetLayout m_SpriteMultiUniformDescriptorSetLayout;
1006 VkDescriptorSetLayout m_QuadUniformDescriptorSetLayout;
1007
1008 SPipelineContainer m_StandardPipeline;
1009 SPipelineContainer m_StandardLinePipeline;
1010 SPipelineContainer m_Standard3DPipeline;
1011 SPipelineContainer m_TextPipeline;
1012 SPipelineContainer m_TilePipeline;
1013 SPipelineContainer m_TileBorderPipeline;
1014 SPipelineContainer m_PrimExPipeline;
1015 SPipelineContainer m_PrimExRotationlessPipeline;
1016 SPipelineContainer m_SpriteMultiPipeline;
1017 SPipelineContainer m_SpriteMultiPushPipeline;
1018 SPipelineContainer m_QuadPipeline;
1019 SPipelineContainer m_QuadGroupedPipeline;
1020
1021 std::vector<VkPipeline> m_vLastPipeline;
1022
1023 std::vector<VkCommandPool> m_vCommandPools;
1024
1025 VkRenderPass m_VKRenderPass;
1026
1027 VkSurfaceFormatKHR m_VKSurfFormat;
1028
1029 SDeviceDescriptorPools m_StandardTextureDescrPool;
1030 SDeviceDescriptorPools m_TextTextureDescrPool;
1031
1032 std::vector<SDeviceDescriptorPools> m_vUniformBufferDescrPools;
1033
1034 VkSwapchainKHR m_VKSwapChain = VK_NULL_HANDLE;
1035 std::vector<VkImage> m_vSwapChainImages;
1036 uint32_t m_SwapChainImageCount = 0;
1037
1038 std::vector<SStreamMemory<SFrameBuffers>> m_vStreamedVertexBuffers;
1039 std::vector<SStreamMemory<SFrameUniformBuffers>> m_vStreamedUniformBuffers;
1040
1041 uint32_t m_CurImageIndex = 0;
1042
1043 uint32_t m_CanvasWidth;
1044 uint32_t m_CanvasHeight;
1045
1046 SDL_Window *m_pWindow;
1047
1048 std::array<float, 4> m_aClearColor = {0, 0, 0, 0};
1049
1050 struct SRenderCommandExecuteBuffer
1051 {
1052 CCommandBuffer::ECommandBufferCMD m_Command;
1053 const CCommandBuffer::SCommand *m_pRawCommand;
1054 uint32_t m_ThreadIndex;
1055
1056 // must be calculated when the buffer gets filled
1057 size_t m_EstimatedRenderCallCount = 0;
1058
1059 // useful data
1060 VkBuffer m_Buffer;
1061 size_t m_BufferOff;
1062 std::array<SDeviceDescriptorSet, 2> m_aDescriptors;
1063
1064 VkBuffer m_IndexBuffer;
1065
1066 bool m_ClearColorInRenderThread = false;
1067
1068 bool m_HasDynamicState = false;
1069 VkViewport m_Viewport;
1070 VkRect2D m_Scissor;
1071 };
1072
1073 typedef std::vector<SRenderCommandExecuteBuffer> TCommandList;
1074 typedef std::vector<TCommandList> TThreadCommandList;
1075
1076 TThreadCommandList m_vvThreadCommandLists;
1077 std::vector<bool> m_vThreadHelperHadCommands;
1078
1079 typedef std::function<bool(const CCommandBuffer::SCommand *, SRenderCommandExecuteBuffer &)> TCommandBufferCommandCallback;
1080 typedef std::function<void(SRenderCommandExecuteBuffer &, const CCommandBuffer::SCommand *)> TCommandBufferFillExecuteBufferFunc;
1081
1082 struct SCommandCallback
1083 {
1084 bool m_IsRenderCommand;
1085 TCommandBufferFillExecuteBufferFunc m_FillExecuteBuffer;
1086 TCommandBufferCommandCallback m_CommandCB;
1087 // command should be considered handled after it executed
1088 bool m_CMDIsHandled = true;
1089 };
1090 std::array<SCommandCallback, static_cast<int>(CCommandBuffer::CMD_COUNT) - static_cast<int>(CCommandBuffer::CMD_FIRST)> m_aCommandCallbacks;
1091
1092protected:
1093 /************************
1094 * ERROR MANAGEMENT
1095 ************************/
1096 std::mutex m_ErrWarnMutex;
1097 std::string m_ErrorHelper;
1098
1099 bool m_HasError = false;
1100 bool m_CanAssert = false;
1101
1102 /**
1103 * After an error occurred, the rendering stop as soon as possible
1104 * Always stop the current code execution after a call to this function (e.g. return false)
1105 */
1106 void SetError(EGfxErrorType ErrType, const char *pErr, const char *pErrStrExtra = nullptr)
1107 {
1108 std::unique_lock<std::mutex> Lock(m_ErrWarnMutex);
1109 SGfxErrorContainer::SError Err = {.m_RequiresTranslation: false, .m_Err: pErr};
1110 if(std::find(first: m_Error.m_vErrors.begin(), last: m_Error.m_vErrors.end(), val: Err) == m_Error.m_vErrors.end())
1111 m_Error.m_vErrors.emplace_back(args&: Err);
1112 if(pErrStrExtra != nullptr)
1113 {
1114 SGfxErrorContainer::SError ErrExtra = {.m_RequiresTranslation: false, .m_Err: pErrStrExtra};
1115 if(std::find(first: m_Error.m_vErrors.begin(), last: m_Error.m_vErrors.end(), val: ErrExtra) == m_Error.m_vErrors.end())
1116 m_Error.m_vErrors.emplace_back(args&: ErrExtra);
1117 }
1118 if(m_CanAssert)
1119 {
1120 if(pErrStrExtra != nullptr)
1121 log_error("gfx/vulkan", "%s: %s", pErr, pErrStrExtra);
1122 else
1123 log_error("gfx/vulkan", "%s", pErr);
1124 m_HasError = true;
1125 m_Error.m_ErrorType = ErrType;
1126 }
1127 else
1128 {
1129 Lock.unlock();
1130 // during initialization vulkan should not throw any errors but warnings instead
1131 // since most code in the swapchain is shared with runtime code, add this extra code path
1132 SetWarning(WarningType: EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED, pWarning: pErr);
1133 }
1134 }
1135
1136 void SetWarningPreMsg(const char *pWarningPre)
1137 {
1138 std::unique_lock<std::mutex> Lock(m_ErrWarnMutex);
1139 if(std::find(first: m_Warning.m_vWarnings.begin(), last: m_Warning.m_vWarnings.end(), val: pWarningPre) == m_Warning.m_vWarnings.end())
1140 m_Warning.m_vWarnings.emplace(position: m_Warning.m_vWarnings.begin(), args&: pWarningPre);
1141 }
1142
1143 void SetWarning(EGfxWarningType WarningType, const char *pWarning)
1144 {
1145 std::unique_lock<std::mutex> Lock(m_ErrWarnMutex);
1146 log_warn("gfx/vulkan", "%s", pWarning);
1147 if(std::find(first: m_Warning.m_vWarnings.begin(), last: m_Warning.m_vWarnings.end(), val: pWarning) == m_Warning.m_vWarnings.end())
1148 m_Warning.m_vWarnings.emplace_back(args&: pWarning);
1149 m_Warning.m_WarningType = WarningType;
1150 }
1151
1152 const char *CheckVulkanCriticalError(VkResult CallResult)
1153 {
1154 const char *pCriticalError = nullptr;
1155 switch(CallResult)
1156 {
1157 case VK_ERROR_OUT_OF_HOST_MEMORY:
1158 pCriticalError = "Host ran out of memory.";
1159 log_error("gfx/vulkan", "%s", pCriticalError);
1160 break;
1161 case VK_ERROR_OUT_OF_DEVICE_MEMORY:
1162 pCriticalError = "Device ran out of memory.";
1163 log_error("gfx/vulkan", "%s", pCriticalError);
1164 break;
1165 case VK_ERROR_DEVICE_LOST:
1166 pCriticalError = "Device lost.";
1167 log_error("gfx/vulkan", "%s", pCriticalError);
1168 break;
1169 case VK_ERROR_OUT_OF_DATE_KHR:
1170 {
1171 if(IsVerbose())
1172 {
1173 log_debug("gfx/vulkan", "Queueing swap chain recreation because the current is out of date.");
1174 }
1175 m_RecreateSwapChain = true;
1176 break;
1177 }
1178 case VK_ERROR_SURFACE_LOST_KHR:
1179 log_error("gfx/vulkan", "Surface lost.");
1180 break;
1181 case VK_ERROR_INCOMPATIBLE_DRIVER:
1182 pCriticalError = "No compatible driver found. Vulkan 1.1 is required.";
1183 log_error("gfx/vulkan", "%s", pCriticalError);
1184 break;
1185 case VK_ERROR_INITIALIZATION_FAILED:
1186 pCriticalError = "Initialization failed for unknown reason.";
1187 log_error("gfx/vulkan", "%s", pCriticalError);
1188 break;
1189 case VK_ERROR_LAYER_NOT_PRESENT:
1190 SetWarning(WarningType: EGfxWarningType::GFX_WARNING_MISSING_EXTENSION, pWarning: "At least one Vulkan layer was not present. (Try to disable them.)");
1191 break;
1192 case VK_ERROR_EXTENSION_NOT_PRESENT:
1193 SetWarning(WarningType: EGfxWarningType::GFX_WARNING_MISSING_EXTENSION, pWarning: "At least one Vulkan extension was not present. (Try to disable them.)");
1194 break;
1195 case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
1196 log_error("gfx/vulkan", "Native window in use.");
1197 break;
1198 case VK_SUCCESS:
1199 break;
1200 case VK_SUBOPTIMAL_KHR:
1201 if(IsVerbose())
1202 {
1203 log_debug("gfx/vulkan", "Queueing swap chain recreation because the current is suboptimal.");
1204 }
1205 m_RecreateSwapChain = true;
1206 break;
1207 default:
1208 m_ErrorHelper = "Unknown error: ";
1209 m_ErrorHelper.append(str: std::to_string(val: CallResult));
1210 pCriticalError = m_ErrorHelper.c_str();
1211 log_error("gfx/vulkan", "%s", pCriticalError);
1212 break;
1213 }
1214
1215 return pCriticalError;
1216 }
1217
1218 void ErroneousCleanup() override
1219 {
1220 CleanupVulkanSDL();
1221 }
1222
1223 /************************
1224 * COMMAND CALLBACKS
1225 ************************/
1226
1227 size_t CommandBufferCMDOff(CCommandBuffer::ECommandBufferCMD CommandBufferCMD)
1228 {
1229 return (size_t)CommandBufferCMD - CCommandBuffer::CMD_FIRST;
1230 }
1231
1232 void RegisterCommands()
1233 {
1234 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TEXTURE_CREATE)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Texture_Create(pCommand: static_cast<const CCommandBuffer::SCommand_Texture_Create *>(pBaseCommand)); }};
1235 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TEXTURE_DESTROY)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Texture_Destroy(pCommand: static_cast<const CCommandBuffer::SCommand_Texture_Destroy *>(pBaseCommand)); }};
1236 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TEXT_TEXTURES_CREATE)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_TextTextures_Create(pCommand: static_cast<const CCommandBuffer::SCommand_TextTextures_Create *>(pBaseCommand)); }};
1237 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TEXT_TEXTURES_DESTROY)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_TextTextures_Destroy(pCommand: static_cast<const CCommandBuffer::SCommand_TextTextures_Destroy *>(pBaseCommand)); }};
1238 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TEXT_TEXTURE_UPDATE)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_TextTexture_Update(pCommand: static_cast<const CCommandBuffer::SCommand_TextTexture_Update *>(pBaseCommand)); }};
1239
1240 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_CLEAR)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_Clear_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_Clear *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Clear(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_Clear *>(pBaseCommand)); }};
1241 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_Render_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_Render *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Render(pCommand: static_cast<const CCommandBuffer::SCommand_Render *>(pBaseCommand), ExecBuffer); }};
1242 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_TEX3D)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderTex3D_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderTex3D *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderTex3D(pCommand: static_cast<const CCommandBuffer::SCommand_RenderTex3D *>(pBaseCommand), ExecBuffer); }};
1243
1244 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_CREATE_BUFFER_OBJECT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_CreateBufferObject(pCommand: static_cast<const CCommandBuffer::SCommand_CreateBufferObject *>(pBaseCommand)); }};
1245 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RECREATE_BUFFER_OBJECT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RecreateBufferObject(pCommand: static_cast<const CCommandBuffer::SCommand_RecreateBufferObject *>(pBaseCommand)); }};
1246 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_UPDATE_BUFFER_OBJECT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_UpdateBufferObject(pCommand: static_cast<const CCommandBuffer::SCommand_UpdateBufferObject *>(pBaseCommand)); }};
1247 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_COPY_BUFFER_OBJECT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_CopyBufferObject(pCommand: static_cast<const CCommandBuffer::SCommand_CopyBufferObject *>(pBaseCommand)); }};
1248 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_DELETE_BUFFER_OBJECT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_DeleteBufferObject(pCommand: static_cast<const CCommandBuffer::SCommand_DeleteBufferObject *>(pBaseCommand)); }};
1249
1250 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_CREATE_BUFFER_CONTAINER)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_CreateBufferContainer(pCommand: static_cast<const CCommandBuffer::SCommand_CreateBufferContainer *>(pBaseCommand)); }};
1251 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_DELETE_BUFFER_CONTAINER)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_DeleteBufferContainer(pCommand: static_cast<const CCommandBuffer::SCommand_DeleteBufferContainer *>(pBaseCommand)); }};
1252 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_UPDATE_BUFFER_CONTAINER)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_UpdateBufferContainer(pCommand: static_cast<const CCommandBuffer::SCommand_UpdateBufferContainer *>(pBaseCommand)); }};
1253
1254 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_INDICES_REQUIRED_NUM_NOTIFY)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_IndicesRequiredNumNotify(pCommand: static_cast<const CCommandBuffer::SCommand_IndicesRequiredNumNotify *>(pBaseCommand)); }};
1255
1256 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_TILE_LAYER)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderTileLayer_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderTileLayer *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderTileLayer(pCommand: static_cast<const CCommandBuffer::SCommand_RenderTileLayer *>(pBaseCommand), ExecBuffer); }};
1257 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_BORDER_TILE)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderBorderTile_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderBorderTile *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderBorderTile(pCommand: static_cast<const CCommandBuffer::SCommand_RenderBorderTile *>(pBaseCommand), ExecBuffer); }};
1258 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_QUAD_LAYER)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderQuadLayer_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadLayer *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderQuadLayer(pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadLayer *>(pBaseCommand), ExecBuffer, Grouped: false); }};
1259 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_QUAD_LAYER_GROUPED)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderQuadLayer_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadLayer *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderQuadLayer(pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadLayer *>(pBaseCommand), ExecBuffer, Grouped: true); }};
1260 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_TEXT)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderText_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderText *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderText(pCommand: static_cast<const CCommandBuffer::SCommand_RenderText *>(pBaseCommand), ExecBuffer); }};
1261 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_QUAD_CONTAINER)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderQuadContainer_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadContainer *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderQuadContainer(pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadContainer *>(pBaseCommand), ExecBuffer); }};
1262 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_EX)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderQuadContainerEx_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadContainerEx *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderQuadContainerEx(pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadContainerEx *>(pBaseCommand), ExecBuffer); }};
1263 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_SPRITE_MULTIPLE)] = {.m_IsRenderCommand: true, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_RenderQuadContainerAsSpriteMultiple_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_RenderQuadContainerAsSpriteMultiple(pCommand: static_cast<const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *>(pBaseCommand), ExecBuffer); }};
1264
1265 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_SWAP)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Swap(pCommand: static_cast<const CCommandBuffer::SCommand_Swap *>(pBaseCommand)); }};
1266
1267 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_VSYNC)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_VSync(pCommand: static_cast<const CCommandBuffer::SCommand_VSync *>(pBaseCommand)); }};
1268 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_MULTISAMPLING)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_MultiSampling(pCommand: static_cast<const CCommandBuffer::SCommand_MultiSampling *>(pBaseCommand)); }};
1269 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TRY_SWAP_AND_READ_PIXEL)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_ReadPixel(pCommand: static_cast<const CCommandBuffer::SCommand_TrySwapAndReadPixel *>(pBaseCommand)); }};
1270 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_TRY_SWAP_AND_SCREENSHOT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Screenshot(pCommand: static_cast<const CCommandBuffer::SCommand_TrySwapAndScreenshot *>(pBaseCommand)); }};
1271
1272 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_UPDATE_VIEWPORT)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [this](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) { Cmd_Update_Viewport_FillExecuteBuffer(ExecBuffer, pCommand: static_cast<const CCommandBuffer::SCommand_Update_Viewport *>(pBaseCommand)); }, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_Update_Viewport(pCommand: static_cast<const CCommandBuffer::SCommand_Update_Viewport *>(pBaseCommand)); }};
1273
1274 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_WINDOW_CREATE_NTF)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_WindowCreateNtf(pCommand: static_cast<const CCommandBuffer::SCommand_WindowCreateNtf *>(pBaseCommand)); }, .m_CMDIsHandled: false};
1275 m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::CMD_WINDOW_DESTROY_NTF)] = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [this](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return Cmd_WindowDestroyNtf(pCommand: static_cast<const CCommandBuffer::SCommand_WindowDestroyNtf *>(pBaseCommand)); }, .m_CMDIsHandled: false};
1276
1277 for(auto &Callback : m_aCommandCallbacks)
1278 {
1279 if(!(bool)Callback.m_CommandCB)
1280 Callback = {.m_IsRenderCommand: false, .m_FillExecuteBuffer: [](SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand *pBaseCommand) {}, .m_CommandCB: [](const CCommandBuffer::SCommand *pBaseCommand, SRenderCommandExecuteBuffer &ExecBuffer) { return true; }};
1281 }
1282 }
1283
1284 /*****************************
1285 * VIDEO AND SCREENSHOT HELPER
1286 ******************************/
1287
1288 [[nodiscard]] bool PreparePresentedImageDataImage(uint8_t *&pResImageData, uint32_t Width, uint32_t Height)
1289 {
1290 bool NeedsNewImg = Width != m_GetPresentedImgDataHelperWidth || Height != m_GetPresentedImgDataHelperHeight;
1291 if(m_GetPresentedImgDataHelperImage == VK_NULL_HANDLE || NeedsNewImg)
1292 {
1293 if(m_GetPresentedImgDataHelperImage != VK_NULL_HANDLE)
1294 {
1295 DeletePresentedImageDataImage();
1296 }
1297 m_GetPresentedImgDataHelperWidth = Width;
1298 m_GetPresentedImgDataHelperHeight = Height;
1299
1300 VkImageCreateInfo ImageInfo{};
1301 ImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1302 ImageInfo.imageType = VK_IMAGE_TYPE_2D;
1303 ImageInfo.extent.width = Width;
1304 ImageInfo.extent.height = Height;
1305 ImageInfo.extent.depth = 1;
1306 ImageInfo.mipLevels = 1;
1307 ImageInfo.arrayLayers = 1;
1308 ImageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
1309 ImageInfo.tiling = VK_IMAGE_TILING_LINEAR;
1310 ImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1311 ImageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
1312 ImageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1313 ImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1314
1315 vkCreateImage(device: m_VKDevice, pCreateInfo: &ImageInfo, pAllocator: nullptr, pImage: &m_GetPresentedImgDataHelperImage);
1316 // Create memory to back up the image
1317 VkMemoryRequirements MemRequirements;
1318 vkGetImageMemoryRequirements(device: m_VKDevice, image: m_GetPresentedImgDataHelperImage, pMemoryRequirements: &MemRequirements);
1319
1320 VkMemoryAllocateInfo MemAllocInfo{};
1321 MemAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1322 MemAllocInfo.allocationSize = MemRequirements.size;
1323 MemAllocInfo.memoryTypeIndex = FindMemoryType(PhyDevice: m_VKGPU, TypeFilter: MemRequirements.memoryTypeBits, Properties: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
1324
1325 vkAllocateMemory(device: m_VKDevice, pAllocateInfo: &MemAllocInfo, pAllocator: nullptr, pMemory: &m_GetPresentedImgDataHelperMem.m_Mem);
1326 vkBindImageMemory(device: m_VKDevice, image: m_GetPresentedImgDataHelperImage, memory: m_GetPresentedImgDataHelperMem.m_Mem, memoryOffset: 0);
1327
1328 if(!ImageBarrier(Image: m_GetPresentedImgDataHelperImage, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: 1, Format: VK_FORMAT_R8G8B8A8_UNORM, OldLayout: VK_IMAGE_LAYOUT_UNDEFINED, NewLayout: VK_IMAGE_LAYOUT_GENERAL))
1329 return false;
1330
1331 VkImageSubresource SubResource{.aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel: 0, .arrayLayer: 0};
1332 VkSubresourceLayout SubResourceLayout;
1333 vkGetImageSubresourceLayout(device: m_VKDevice, image: m_GetPresentedImgDataHelperImage, pSubresource: &SubResource, pLayout: &SubResourceLayout);
1334
1335 if(vkMapMemory(device: m_VKDevice, memory: m_GetPresentedImgDataHelperMem.m_Mem, offset: 0, VK_WHOLE_SIZE, flags: 0, ppData: (void **)&m_pGetPresentedImgDataHelperMappedMemory) != VK_SUCCESS)
1336 return false;
1337 m_GetPresentedImgDataHelperMappedLayoutOffset = SubResourceLayout.offset;
1338 m_GetPresentedImgDataHelperMappedLayoutPitch = SubResourceLayout.rowPitch;
1339 m_pGetPresentedImgDataHelperMappedMemory += m_GetPresentedImgDataHelperMappedLayoutOffset;
1340
1341 VkFenceCreateInfo FenceInfo{};
1342 FenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1343 FenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
1344 vkCreateFence(device: m_VKDevice, pCreateInfo: &FenceInfo, pAllocator: nullptr, pFence: &m_GetPresentedImgDataHelperFence);
1345 }
1346 pResImageData = m_pGetPresentedImgDataHelperMappedMemory;
1347 return true;
1348 }
1349
1350 void DeletePresentedImageDataImage()
1351 {
1352 if(m_GetPresentedImgDataHelperImage != VK_NULL_HANDLE)
1353 {
1354 vkDestroyFence(device: m_VKDevice, fence: m_GetPresentedImgDataHelperFence, pAllocator: nullptr);
1355
1356 m_GetPresentedImgDataHelperFence = VK_NULL_HANDLE;
1357
1358 vkDestroyImage(device: m_VKDevice, image: m_GetPresentedImgDataHelperImage, pAllocator: nullptr);
1359 vkUnmapMemory(device: m_VKDevice, memory: m_GetPresentedImgDataHelperMem.m_Mem);
1360 vkFreeMemory(device: m_VKDevice, memory: m_GetPresentedImgDataHelperMem.m_Mem, pAllocator: nullptr);
1361
1362 m_GetPresentedImgDataHelperImage = VK_NULL_HANDLE;
1363 m_GetPresentedImgDataHelperMem = {};
1364 m_pGetPresentedImgDataHelperMappedMemory = nullptr;
1365
1366 m_GetPresentedImgDataHelperWidth = 0;
1367 m_GetPresentedImgDataHelperHeight = 0;
1368 }
1369 }
1370
1371 [[nodiscard]] bool GetPresentedImageDataImpl(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData, bool ResetAlpha, std::optional<ivec2> PixelOffset)
1372 {
1373 bool IsB8G8R8A8 = m_VKSurfFormat.format == VK_FORMAT_B8G8R8A8_UNORM;
1374 bool UsesRGBALikeFormat = m_VKSurfFormat.format == VK_FORMAT_R8G8B8A8_UNORM || IsB8G8R8A8;
1375 if(UsesRGBALikeFormat && m_LastPresentedSwapChainImageIndex != std::numeric_limits<decltype(m_LastPresentedSwapChainImageIndex)>::max())
1376 {
1377 auto Viewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport();
1378 VkOffset3D SrcOffset;
1379 if(PixelOffset.has_value())
1380 {
1381 SrcOffset.x = PixelOffset.value().x;
1382 SrcOffset.y = PixelOffset.value().y;
1383 Width = 1;
1384 Height = 1;
1385 }
1386 else
1387 {
1388 SrcOffset.x = 0;
1389 SrcOffset.y = 0;
1390 Width = Viewport.width;
1391 Height = Viewport.height;
1392 }
1393 SrcOffset.z = 0;
1394 Format = CImageInfo::FORMAT_RGBA;
1395
1396 const size_t ImageTotalSize = (size_t)Width * Height * CImageInfo::PixelSize(Format);
1397
1398 uint8_t *pResImageData;
1399 if(!PreparePresentedImageDataImage(pResImageData, Width, Height))
1400 return false;
1401
1402 VkCommandBuffer *pCommandBuffer;
1403 if(!GetMemoryCommandBuffer(pMemCommandBuffer&: pCommandBuffer))
1404 return false;
1405 VkCommandBuffer &CommandBuffer = *pCommandBuffer;
1406
1407 auto &SwapImg = m_vSwapChainImages[m_LastPresentedSwapChainImageIndex];
1408
1409 if(!ImageBarrier(Image: m_GetPresentedImgDataHelperImage, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: 1, Format: VK_FORMAT_R8G8B8A8_UNORM, OldLayout: VK_IMAGE_LAYOUT_GENERAL, NewLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL))
1410 return false;
1411 if(!ImageBarrier(Image: SwapImg, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: 1, Format: m_VKSurfFormat.format, OldLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, NewLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL))
1412 return false;
1413
1414 // If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB)
1415 if(m_OptimalSwapChainImageBlitting && m_LinearRGBAImageBlitting)
1416 {
1417 VkOffset3D BlitSize;
1418 BlitSize.x = Width;
1419 BlitSize.y = Height;
1420 BlitSize.z = 1;
1421
1422 VkImageBlit ImageBlitRegion{};
1423 ImageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1424 ImageBlitRegion.srcSubresource.layerCount = 1;
1425 ImageBlitRegion.srcOffsets[0] = SrcOffset;
1426 ImageBlitRegion.srcOffsets[1] = {.x: SrcOffset.x + BlitSize.x, .y: SrcOffset.y + BlitSize.y, .z: SrcOffset.z + BlitSize.z};
1427 ImageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1428 ImageBlitRegion.dstSubresource.layerCount = 1;
1429 ImageBlitRegion.dstOffsets[1] = BlitSize;
1430
1431 // Issue the blit command
1432 vkCmdBlitImage(commandBuffer: CommandBuffer, srcImage: SwapImg, srcImageLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
1433 dstImage: m_GetPresentedImgDataHelperImage, dstImageLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1434 regionCount: 1, pRegions: &ImageBlitRegion, filter: VK_FILTER_NEAREST);
1435
1436 // transformed to RGBA
1437 IsB8G8R8A8 = false;
1438 }
1439 else
1440 {
1441 // Otherwise use image copy (requires us to manually flip components)
1442 VkImageCopy ImageCopyRegion{};
1443 ImageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1444 ImageCopyRegion.srcSubresource.layerCount = 1;
1445 ImageCopyRegion.srcOffset = SrcOffset;
1446 ImageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
1447 ImageCopyRegion.dstSubresource.layerCount = 1;
1448 ImageCopyRegion.extent.width = Width;
1449 ImageCopyRegion.extent.height = Height;
1450 ImageCopyRegion.extent.depth = 1;
1451
1452 // Issue the copy command
1453 vkCmdCopyImage(commandBuffer: CommandBuffer, srcImage: SwapImg, srcImageLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
1454 dstImage: m_GetPresentedImgDataHelperImage, dstImageLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1455 regionCount: 1, pRegions: &ImageCopyRegion);
1456 }
1457
1458 if(!ImageBarrier(Image: m_GetPresentedImgDataHelperImage, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: 1, Format: VK_FORMAT_R8G8B8A8_UNORM, OldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, NewLayout: VK_IMAGE_LAYOUT_GENERAL))
1459 return false;
1460 if(!ImageBarrier(Image: SwapImg, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: 1, Format: m_VKSurfFormat.format, OldLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, NewLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR))
1461 return false;
1462
1463 vkEndCommandBuffer(commandBuffer: CommandBuffer);
1464 m_vUsedMemoryCommandBuffer[m_CurImageIndex] = false;
1465
1466 VkSubmitInfo SubmitInfo{};
1467 SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1468 SubmitInfo.commandBufferCount = 1;
1469 SubmitInfo.pCommandBuffers = &CommandBuffer;
1470
1471 vkResetFences(device: m_VKDevice, fenceCount: 1, pFences: &m_GetPresentedImgDataHelperFence);
1472 vkQueueSubmit(queue: m_VKGraphicsQueue, submitCount: 1, pSubmits: &SubmitInfo, fence: m_GetPresentedImgDataHelperFence);
1473 vkWaitForFences(device: m_VKDevice, fenceCount: 1, pFences: &m_GetPresentedImgDataHelperFence, VK_TRUE, timeout: std::numeric_limits<uint64_t>::max());
1474
1475 VkMappedMemoryRange MemRange{};
1476 MemRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
1477 MemRange.memory = m_GetPresentedImgDataHelperMem.m_Mem;
1478 MemRange.offset = m_GetPresentedImgDataHelperMappedLayoutOffset;
1479 MemRange.size = VK_WHOLE_SIZE;
1480 vkInvalidateMappedMemoryRanges(device: m_VKDevice, memoryRangeCount: 1, pMemoryRanges: &MemRange);
1481
1482 size_t RealFullImageSize = maximum(a: ImageTotalSize, b: (size_t)(Height * m_GetPresentedImgDataHelperMappedLayoutPitch));
1483 size_t ExtraRowSize = Width * 4;
1484 if(vDstData.size() < RealFullImageSize + ExtraRowSize)
1485 vDstData.resize(sz: RealFullImageSize + ExtraRowSize);
1486
1487 mem_copy(dest: vDstData.data(), source: pResImageData, size: RealFullImageSize);
1488
1489 // pack image data together without any offset that the driver might require
1490 if(Width * 4 < m_GetPresentedImgDataHelperMappedLayoutPitch)
1491 {
1492 for(uint32_t Y = 0; Y < Height; ++Y)
1493 {
1494 size_t OffsetImagePacked = (Y * Width * 4);
1495 size_t OffsetImageUnpacked = (Y * m_GetPresentedImgDataHelperMappedLayoutPitch);
1496 mem_copy(dest: vDstData.data() + RealFullImageSize, source: vDstData.data() + OffsetImageUnpacked, size: Width * 4);
1497 mem_copy(dest: vDstData.data() + OffsetImagePacked, source: vDstData.data() + RealFullImageSize, size: Width * 4);
1498 }
1499 }
1500
1501 if(IsB8G8R8A8 || ResetAlpha)
1502 {
1503 // swizzle
1504 for(uint32_t Y = 0; Y < Height; ++Y)
1505 {
1506 for(uint32_t X = 0; X < Width; ++X)
1507 {
1508 size_t ImgOff = (Y * Width * 4) + (X * 4);
1509 if(IsB8G8R8A8)
1510 {
1511 std::swap(a&: vDstData[ImgOff], b&: vDstData[ImgOff + 2]);
1512 }
1513 vDstData[ImgOff + 3] = 255;
1514 }
1515 }
1516 }
1517
1518 return true;
1519 }
1520 else
1521 {
1522 if(!UsesRGBALikeFormat)
1523 {
1524 log_error("gfx/vulkan", "Swap chain image was not in an RGBA-like format.");
1525 }
1526 else
1527 {
1528 log_error("gfx/vulkan", "Swap chain image was not ready to be copied.");
1529 }
1530 return false;
1531 }
1532 }
1533
1534 [[nodiscard]] bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) override
1535 {
1536 return GetPresentedImageDataImpl(Width, Height, Format, vDstData, ResetAlpha: false, PixelOffset: {});
1537 }
1538
1539 /************************
1540 * MEMORY MANAGEMENT
1541 ************************/
1542
1543 [[nodiscard]] bool AllocateVulkanMemory(const VkMemoryAllocateInfo *pAllocateInfo, VkDeviceMemory *pMemory)
1544 {
1545 VkResult Res = vkAllocateMemory(device: m_VKDevice, pAllocateInfo, pAllocator: nullptr, pMemory);
1546 if(Res != VK_SUCCESS)
1547 {
1548 log_warn("gfx/vulkan", "Memory allocation failed, trying to recover.");
1549 if(Res == VK_ERROR_OUT_OF_HOST_MEMORY || Res == VK_ERROR_OUT_OF_DEVICE_MEMORY)
1550 {
1551 // aggressively try to get more memory
1552 vkDeviceWaitIdle(device: m_VKDevice);
1553 for(size_t i = 0; i < m_SwapChainImageCount + 1; ++i)
1554 {
1555 if(!NextFrame())
1556 return false;
1557 }
1558 Res = vkAllocateMemory(device: m_VKDevice, pAllocateInfo, pAllocator: nullptr, pMemory);
1559 }
1560 if(Res != VK_SUCCESS)
1561 {
1562 log_error("gfx/vulkan", "Memory allocation and recovery failed.");
1563 return false;
1564 }
1565 }
1566 return true;
1567 }
1568
1569 [[nodiscard]] bool GetBufferImpl(VkDeviceSize RequiredSize, EMemoryBlockUsage MemUsage, VkBuffer &Buffer, SDeviceMemoryBlock &BufferMemory, VkBufferUsageFlags BufferUsage, VkMemoryPropertyFlags BufferProperties)
1570 {
1571 return CreateBuffer(BufferSize: RequiredSize, MemUsage, BufferUsage, MemoryProperties: BufferProperties, VKBuffer&: Buffer, VKBufferMemory&: BufferMemory);
1572 }
1573
1574 template<size_t Id,
1575 int64_t MemoryBlockSize, size_t BlockCount,
1576 bool RequiresMapping>
1577 [[nodiscard]] bool GetBufferBlockImpl(SMemoryBlock<Id> &RetBlock, SMemoryBlockCache<Id> &MemoryCache, VkBufferUsageFlags BufferUsage, VkMemoryPropertyFlags BufferProperties, const void *pBufferData, VkDeviceSize RequiredSize, VkDeviceSize TargetAlignment)
1578 {
1579 bool Res = true;
1580
1581 auto &&CreateCacheBlock = [&]() -> bool {
1582 bool FoundAllocation = false;
1583 SMemoryHeap::SMemoryHeapQueueElement AllocatedMem;
1584 SDeviceMemoryBlock TmpBufferMemory;
1585 typename SMemoryBlockCache<Id>::SMemoryCacheType::SMemoryCacheHeap *pCacheHeap = nullptr;
1586 auto &Heaps = MemoryCache.m_MemoryCaches.m_vpMemoryHeaps;
1587 for(size_t i = 0; i < Heaps.size(); ++i)
1588 {
1589 auto *pHeap = Heaps[i];
1590 if(pHeap->m_Heap.Allocate(RequiredSize, TargetAlignment, AllocatedMem))
1591 {
1592 TmpBufferMemory = pHeap->m_BufferMem;
1593 FoundAllocation = true;
1594 pCacheHeap = pHeap;
1595 break;
1596 }
1597 }
1598 if(!FoundAllocation)
1599 {
1600 typename SMemoryBlockCache<Id>::SMemoryCacheType::SMemoryCacheHeap *pNewHeap = new typename SMemoryBlockCache<Id>::SMemoryCacheType::SMemoryCacheHeap();
1601
1602 VkBuffer TmpBuffer;
1603 if(!GetBufferImpl(RequiredSize: MemoryBlockSize * BlockCount, MemUsage: RequiresMapping ? MEMORY_BLOCK_USAGE_STAGING : MEMORY_BLOCK_USAGE_BUFFER, Buffer&: TmpBuffer, BufferMemory&: TmpBufferMemory, BufferUsage, BufferProperties))
1604 {
1605 delete pNewHeap;
1606 return false;
1607 }
1608
1609 void *pMapData = nullptr;
1610
1611 if(RequiresMapping)
1612 {
1613 if(vkMapMemory(device: m_VKDevice, memory: TmpBufferMemory.m_Mem, offset: 0, VK_WHOLE_SIZE, flags: 0, ppData: &pMapData) != VK_SUCCESS)
1614 {
1615 SetError(ErrType: RequiresMapping ? EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, pErr: "Failed to map buffer block memory.");
1616 delete pNewHeap;
1617 return false;
1618 }
1619 }
1620
1621 pNewHeap->m_Buffer = TmpBuffer;
1622
1623 pNewHeap->m_BufferMem = TmpBufferMemory;
1624 pNewHeap->m_pMappedBuffer = pMapData;
1625
1626 pCacheHeap = pNewHeap;
1627 Heaps.emplace_back(pNewHeap);
1628 Heaps.back()->m_Heap.Init(MemoryBlockSize * BlockCount, 0);
1629 if(!Heaps.back()->m_Heap.Allocate(RequiredSize, TargetAlignment, AllocatedMem))
1630 {
1631 SetError(ErrType: RequiresMapping ? EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING : EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, pErr: "Heap allocation failed directly after creating fresh heap.");
1632 return false;
1633 }
1634 }
1635
1636 RetBlock.m_Buffer = pCacheHeap->m_Buffer;
1637 RetBlock.m_BufferMem = TmpBufferMemory;
1638 if(RequiresMapping)
1639 RetBlock.m_pMappedBuffer = ((uint8_t *)pCacheHeap->m_pMappedBuffer) + AllocatedMem.m_OffsetToAlign;
1640 else
1641 RetBlock.m_pMappedBuffer = nullptr;
1642 RetBlock.m_IsCached = true;
1643 RetBlock.m_pHeap = &pCacheHeap->m_Heap;
1644 RetBlock.m_HeapData = AllocatedMem;
1645 RetBlock.m_UsedSize = RequiredSize;
1646
1647 if(RequiresMapping)
1648 mem_copy(RetBlock.m_pMappedBuffer, pBufferData, RequiredSize);
1649
1650 return true;
1651 };
1652
1653 if(RequiredSize < (VkDeviceSize)MemoryBlockSize)
1654 {
1655 Res = CreateCacheBlock();
1656 }
1657 else
1658 {
1659 VkBuffer TmpBuffer;
1660 SDeviceMemoryBlock TmpBufferMemory;
1661 if(!GetBufferImpl(RequiredSize, MemUsage: RequiresMapping ? MEMORY_BLOCK_USAGE_STAGING : MEMORY_BLOCK_USAGE_BUFFER, Buffer&: TmpBuffer, BufferMemory&: TmpBufferMemory, BufferUsage, BufferProperties))
1662 return false;
1663
1664 void *pMapData = nullptr;
1665 if(RequiresMapping)
1666 {
1667 if(vkMapMemory(device: m_VKDevice, memory: TmpBufferMemory.m_Mem, offset: 0, VK_WHOLE_SIZE, flags: 0, ppData: &pMapData) != VK_SUCCESS)
1668 return false;
1669 mem_copy(dest: pMapData, source: pBufferData, size: static_cast<size_t>(RequiredSize));
1670 }
1671
1672 RetBlock.m_Buffer = TmpBuffer;
1673 RetBlock.m_BufferMem = TmpBufferMemory;
1674 RetBlock.m_pMappedBuffer = pMapData;
1675 RetBlock.m_pHeap = nullptr;
1676 RetBlock.m_IsCached = false;
1677 RetBlock.m_HeapData.m_OffsetToAlign = 0;
1678 RetBlock.m_HeapData.m_AllocationSize = RequiredSize;
1679 RetBlock.m_UsedSize = RequiredSize;
1680 }
1681
1682 return Res;
1683 }
1684
1685 [[nodiscard]] bool GetStagingBuffer(SMemoryBlock<STAGING_BUFFER_CACHE_ID> &ResBlock, const void *pBufferData, VkDeviceSize RequiredSize)
1686 {
1687 return GetBufferBlockImpl<STAGING_BUFFER_CACHE_ID, 8 * 1024 * 1024, 3, true>(RetBlock&: ResBlock, MemoryCache&: m_StagingBufferCache, BufferUsage: VK_BUFFER_USAGE_TRANSFER_SRC_BIT, BufferProperties: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, pBufferData, RequiredSize, TargetAlignment: maximum<VkDeviceSize>(a: m_NonCoherentMemAlignment, b: 16));
1688 }
1689
1690 [[nodiscard]] bool GetStagingBufferImage(SMemoryBlock<STAGING_BUFFER_IMAGE_CACHE_ID> &ResBlock, const void *pBufferData, VkDeviceSize RequiredSize)
1691 {
1692 return GetBufferBlockImpl<STAGING_BUFFER_IMAGE_CACHE_ID, 8 * 1024 * 1024, 3, true>(RetBlock&: ResBlock, MemoryCache&: m_StagingBufferCacheImage, BufferUsage: VK_BUFFER_USAGE_TRANSFER_SRC_BIT, BufferProperties: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, pBufferData, RequiredSize, TargetAlignment: maximum<VkDeviceSize>(a: m_OptimalImageCopyMemAlignment, b: maximum<VkDeviceSize>(a: m_NonCoherentMemAlignment, b: 16)));
1693 }
1694
1695 template<size_t Id>
1696 void PrepareStagingMemRange(SMemoryBlock<Id> &Block)
1697 {
1698 VkMappedMemoryRange UploadRange{};
1699 UploadRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
1700 UploadRange.memory = Block.m_BufferMem.m_Mem;
1701 UploadRange.offset = Block.m_HeapData.m_OffsetToAlign;
1702
1703 auto AlignmentMod = ((VkDeviceSize)Block.m_HeapData.m_AllocationSize % m_NonCoherentMemAlignment);
1704 auto AlignmentReq = (m_NonCoherentMemAlignment - AlignmentMod);
1705 if(AlignmentMod == 0)
1706 AlignmentReq = 0;
1707 UploadRange.size = Block.m_HeapData.m_AllocationSize + AlignmentReq;
1708
1709 if(UploadRange.offset + UploadRange.size > Block.m_BufferMem.m_Size)
1710 UploadRange.size = VK_WHOLE_SIZE;
1711
1712 m_vNonFlushedStagingBufferRange.push_back(x: UploadRange);
1713 }
1714
1715 void UploadAndFreeStagingMemBlock(SMemoryBlock<STAGING_BUFFER_CACHE_ID> &Block)
1716 {
1717 PrepareStagingMemRange(Block);
1718 if(!Block.m_IsCached)
1719 {
1720 m_vvFrameDelayedBufferCleanup[m_CurImageIndex].push_back(x: {.m_Buffer: Block.m_Buffer, .m_Mem: Block.m_BufferMem, .m_pMappedData: Block.m_pMappedBuffer});
1721 }
1722 else
1723 {
1724 m_StagingBufferCache.FreeMemBlock(Block, ImgIndex: m_CurImageIndex);
1725 }
1726 }
1727
1728 void UploadAndFreeStagingImageMemBlock(SMemoryBlock<STAGING_BUFFER_IMAGE_CACHE_ID> &Block)
1729 {
1730 PrepareStagingMemRange(Block);
1731 if(!Block.m_IsCached)
1732 {
1733 m_vvFrameDelayedBufferCleanup[m_CurImageIndex].push_back(x: {.m_Buffer: Block.m_Buffer, .m_Mem: Block.m_BufferMem, .m_pMappedData: Block.m_pMappedBuffer});
1734 }
1735 else
1736 {
1737 m_StagingBufferCacheImage.FreeMemBlock(Block, ImgIndex: m_CurImageIndex);
1738 }
1739 }
1740
1741 [[nodiscard]] bool GetVertexBuffer(SMemoryBlock<VERTEX_BUFFER_CACHE_ID> &ResBlock, VkDeviceSize RequiredSize)
1742 {
1743 return GetBufferBlockImpl<VERTEX_BUFFER_CACHE_ID, 8 * 1024 * 1024, 3, false>(RetBlock&: ResBlock, MemoryCache&: m_VertexBufferCache, BufferUsage: VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, BufferProperties: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, pBufferData: nullptr, RequiredSize, TargetAlignment: 16);
1744 }
1745
1746 void FreeVertexMemBlock(SMemoryBlock<VERTEX_BUFFER_CACHE_ID> &Block)
1747 {
1748 if(!Block.m_IsCached)
1749 {
1750 m_vvFrameDelayedBufferCleanup[m_CurImageIndex].push_back(x: {.m_Buffer: Block.m_Buffer, .m_Mem: Block.m_BufferMem, .m_pMappedData: nullptr});
1751 }
1752 else
1753 {
1754 m_VertexBufferCache.FreeMemBlock(Block, ImgIndex: m_CurImageIndex);
1755 }
1756 }
1757
1758 static size_t ImageMipLevelCount(size_t Width, size_t Height, size_t Depth)
1759 {
1760 return std::floor(x: std::log2(x: maximum(a: Width, b: maximum(a: Height, b: Depth)))) + 1;
1761 }
1762
1763 static size_t ImageMipLevelCount(const VkExtent3D &ImgExtent)
1764 {
1765 return ImageMipLevelCount(Width: ImgExtent.width, Height: ImgExtent.height, Depth: ImgExtent.depth);
1766 }
1767
1768 // good approximation of 1024x1024 image with mipmaps
1769 static constexpr int64_t IMAGE_SIZE_1024X1024_APPROXIMATION = (1024 * 1024 * 4) * 2;
1770
1771 [[nodiscard]] bool GetImageMemoryImpl(VkDeviceSize RequiredSize, uint32_t RequiredMemoryTypeBits, SDeviceMemoryBlock &BufferMemory, VkMemoryPropertyFlags BufferProperties)
1772 {
1773 VkMemoryAllocateInfo MemAllocInfo{};
1774 MemAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
1775 MemAllocInfo.allocationSize = RequiredSize;
1776 MemAllocInfo.memoryTypeIndex = FindMemoryType(PhyDevice: m_VKGPU, TypeFilter: RequiredMemoryTypeBits, Properties: BufferProperties);
1777
1778 BufferMemory.m_Size = RequiredSize;
1779 m_pTextureMemoryUsage->store(i: m_pTextureMemoryUsage->load(m: std::memory_order_relaxed) + RequiredSize, m: std::memory_order_relaxed);
1780
1781 if(IsVerbose())
1782 {
1783 VerboseAllocatedMemory(Size: RequiredSize, FrameImageIndex: m_CurImageIndex, MemUsage: MEMORY_BLOCK_USAGE_TEXTURE);
1784 }
1785
1786 if(!AllocateVulkanMemory(pAllocateInfo: &MemAllocInfo, pMemory: &BufferMemory.m_Mem))
1787 {
1788 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE, pErr: "Allocation for image memory failed.");
1789 return false;
1790 }
1791
1792 BufferMemory.m_UsageType = MEMORY_BLOCK_USAGE_TEXTURE;
1793
1794 return true;
1795 }
1796
1797 template<size_t Id,
1798 int64_t MemoryBlockSize, size_t BlockCount>
1799 [[nodiscard]] bool GetImageMemoryBlockImpl(SMemoryImageBlock<Id> &RetBlock, SMemoryBlockCache<Id> &MemoryCache, VkMemoryPropertyFlags BufferProperties, VkDeviceSize RequiredSize, VkDeviceSize RequiredAlignment, uint32_t RequiredMemoryTypeBits)
1800 {
1801 auto &&CreateCacheBlock = [&]() -> bool {
1802 bool FoundAllocation = false;
1803 SMemoryHeap::SMemoryHeapQueueElement AllocatedMem;
1804 SDeviceMemoryBlock TmpBufferMemory;
1805 typename SMemoryBlockCache<Id>::SMemoryCacheType::SMemoryCacheHeap *pCacheHeap = nullptr;
1806 for(size_t i = 0; i < MemoryCache.m_MemoryCaches.m_vpMemoryHeaps.size(); ++i)
1807 {
1808 auto *pHeap = MemoryCache.m_MemoryCaches.m_vpMemoryHeaps[i];
1809 if(pHeap->m_Heap.Allocate(RequiredSize, RequiredAlignment, AllocatedMem))
1810 {
1811 TmpBufferMemory = pHeap->m_BufferMem;
1812 FoundAllocation = true;
1813 pCacheHeap = pHeap;
1814 break;
1815 }
1816 }
1817 if(!FoundAllocation)
1818 {
1819 typename SMemoryBlockCache<Id>::SMemoryCacheType::SMemoryCacheHeap *pNewHeap = new typename SMemoryBlockCache<Id>::SMemoryCacheType::SMemoryCacheHeap();
1820
1821 if(!GetImageMemoryImpl(RequiredSize: MemoryBlockSize * BlockCount, RequiredMemoryTypeBits, BufferMemory&: TmpBufferMemory, BufferProperties))
1822 {
1823 delete pNewHeap;
1824 return false;
1825 }
1826
1827 pNewHeap->m_Buffer = VK_NULL_HANDLE;
1828
1829 pNewHeap->m_BufferMem = TmpBufferMemory;
1830 pNewHeap->m_pMappedBuffer = nullptr;
1831
1832 auto &Heaps = MemoryCache.m_MemoryCaches.m_vpMemoryHeaps;
1833 pCacheHeap = pNewHeap;
1834 Heaps.emplace_back(pNewHeap);
1835 Heaps.back()->m_Heap.Init(MemoryBlockSize * BlockCount, 0);
1836 if(!Heaps.back()->m_Heap.Allocate(RequiredSize, RequiredAlignment, AllocatedMem))
1837 {
1838 dbg_assert_failed("Heap allocation failed directly after creating fresh heap for image");
1839 }
1840 }
1841
1842 RetBlock.m_Buffer = VK_NULL_HANDLE;
1843 RetBlock.m_BufferMem = TmpBufferMemory;
1844 RetBlock.m_pMappedBuffer = nullptr;
1845 RetBlock.m_IsCached = true;
1846 RetBlock.m_pHeap = &pCacheHeap->m_Heap;
1847 RetBlock.m_HeapData = AllocatedMem;
1848 RetBlock.m_UsedSize = RequiredSize;
1849
1850 return true;
1851 };
1852
1853 if(RequiredSize < (VkDeviceSize)MemoryBlockSize)
1854 {
1855 if(!CreateCacheBlock())
1856 return false;
1857 }
1858 else
1859 {
1860 SDeviceMemoryBlock TmpBufferMemory;
1861 if(!GetImageMemoryImpl(RequiredSize, RequiredMemoryTypeBits, BufferMemory&: TmpBufferMemory, BufferProperties))
1862 return false;
1863
1864 RetBlock.m_Buffer = VK_NULL_HANDLE;
1865 RetBlock.m_BufferMem = TmpBufferMemory;
1866 RetBlock.m_pMappedBuffer = nullptr;
1867 RetBlock.m_IsCached = false;
1868 RetBlock.m_pHeap = nullptr;
1869 RetBlock.m_HeapData.m_OffsetToAlign = 0;
1870 RetBlock.m_HeapData.m_AllocationSize = RequiredSize;
1871 RetBlock.m_UsedSize = RequiredSize;
1872 }
1873
1874 RetBlock.m_ImageMemoryBits = RequiredMemoryTypeBits;
1875
1876 return true;
1877 }
1878
1879 [[nodiscard]] bool GetImageMemory(SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> &RetBlock, VkDeviceSize RequiredSize, VkDeviceSize RequiredAlignment, uint32_t RequiredMemoryTypeBits)
1880 {
1881 auto BufferCacheIterator = m_ImageBufferCaches.find(x: RequiredMemoryTypeBits);
1882 if(BufferCacheIterator == m_ImageBufferCaches.end())
1883 {
1884 BufferCacheIterator = m_ImageBufferCaches.insert(x: {RequiredMemoryTypeBits, {}}).first;
1885
1886 BufferCacheIterator->second.Init(SwapChainImageCount: m_SwapChainImageCount);
1887 }
1888 return GetImageMemoryBlockImpl<IMAGE_BUFFER_CACHE_ID, IMAGE_SIZE_1024X1024_APPROXIMATION, 2>(RetBlock, MemoryCache&: BufferCacheIterator->second, BufferProperties: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, RequiredSize, RequiredAlignment, RequiredMemoryTypeBits);
1889 }
1890
1891 void FreeImageMemBlock(SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> &Block)
1892 {
1893 if(!Block.m_IsCached)
1894 {
1895 m_vvFrameDelayedBufferCleanup[m_CurImageIndex].push_back(x: {.m_Buffer: Block.m_Buffer, .m_Mem: Block.m_BufferMem, .m_pMappedData: nullptr});
1896 }
1897 else
1898 {
1899 m_ImageBufferCaches[Block.m_ImageMemoryBits].FreeMemBlock(Block, ImgIndex: m_CurImageIndex);
1900 }
1901 }
1902
1903 template<bool FlushForRendering, typename TName>
1904 void UploadStreamedBuffer(SStreamMemory<TName> &StreamedBuffer)
1905 {
1906 size_t RangeUpdateCount = 0;
1907 if(StreamedBuffer.IsUsed(m_CurImageIndex))
1908 {
1909 for(size_t i = 0; i < StreamedBuffer.GetUsedCount(m_CurImageIndex); ++i)
1910 {
1911 auto &BufferOfFrame = StreamedBuffer.GetBuffers(m_CurImageIndex)[i];
1912 auto &MemRange = StreamedBuffer.GetRanges(m_CurImageIndex)[RangeUpdateCount++];
1913 MemRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
1914 MemRange.memory = BufferOfFrame.m_BufferMem.m_Mem;
1915 MemRange.offset = BufferOfFrame.m_OffsetInBuffer;
1916 auto AlignmentMod = ((VkDeviceSize)BufferOfFrame.m_UsedSize % m_NonCoherentMemAlignment);
1917 auto AlignmentReq = (m_NonCoherentMemAlignment - AlignmentMod);
1918 if(AlignmentMod == 0)
1919 AlignmentReq = 0;
1920 MemRange.size = BufferOfFrame.m_UsedSize + AlignmentReq;
1921
1922 if(MemRange.offset + MemRange.size > BufferOfFrame.m_BufferMem.m_Size)
1923 MemRange.size = VK_WHOLE_SIZE;
1924
1925 BufferOfFrame.m_UsedSize = 0;
1926 }
1927 if(RangeUpdateCount > 0 && FlushForRendering)
1928 {
1929 vkFlushMappedMemoryRanges(m_VKDevice, RangeUpdateCount, StreamedBuffer.GetRanges(m_CurImageIndex).data());
1930 }
1931 }
1932 StreamedBuffer.ResetFrame(m_CurImageIndex);
1933 }
1934
1935 void CleanBufferPair(size_t ImageIndex, VkBuffer &Buffer, SDeviceMemoryBlock &BufferMem)
1936 {
1937 bool IsBuffer = Buffer != VK_NULL_HANDLE;
1938 if(IsBuffer)
1939 {
1940 vkDestroyBuffer(device: m_VKDevice, buffer: Buffer, pAllocator: nullptr);
1941
1942 Buffer = VK_NULL_HANDLE;
1943 }
1944 if(BufferMem.m_Mem != VK_NULL_HANDLE)
1945 {
1946 vkFreeMemory(device: m_VKDevice, memory: BufferMem.m_Mem, pAllocator: nullptr);
1947 if(BufferMem.m_UsageType == MEMORY_BLOCK_USAGE_BUFFER)
1948 m_pBufferMemoryUsage->store(i: m_pBufferMemoryUsage->load(m: std::memory_order_relaxed) - BufferMem.m_Size, m: std::memory_order_relaxed);
1949 else if(BufferMem.m_UsageType == MEMORY_BLOCK_USAGE_TEXTURE)
1950 m_pTextureMemoryUsage->store(i: m_pTextureMemoryUsage->load(m: std::memory_order_relaxed) - BufferMem.m_Size, m: std::memory_order_relaxed);
1951 else if(BufferMem.m_UsageType == MEMORY_BLOCK_USAGE_STREAM)
1952 m_pStreamMemoryUsage->store(i: m_pStreamMemoryUsage->load(m: std::memory_order_relaxed) - BufferMem.m_Size, m: std::memory_order_relaxed);
1953 else if(BufferMem.m_UsageType == MEMORY_BLOCK_USAGE_STAGING)
1954 m_pStagingMemoryUsage->store(i: m_pStagingMemoryUsage->load(m: std::memory_order_relaxed) - BufferMem.m_Size, m: std::memory_order_relaxed);
1955
1956 if(IsVerbose())
1957 {
1958 VerboseDeallocatedMemory(Size: BufferMem.m_Size, FrameImageIndex: ImageIndex, MemUsage: BufferMem.m_UsageType);
1959 }
1960
1961 BufferMem.m_Mem = VK_NULL_HANDLE;
1962 }
1963 }
1964
1965 void DestroyTexture(CTexture &Texture)
1966 {
1967 if(Texture.m_Img != VK_NULL_HANDLE)
1968 {
1969 FreeImageMemBlock(Block&: Texture.m_ImgMem);
1970 vkDestroyImage(device: m_VKDevice, image: Texture.m_Img, pAllocator: nullptr);
1971
1972 vkDestroyImageView(device: m_VKDevice, imageView: Texture.m_ImgView, pAllocator: nullptr);
1973 }
1974
1975 if(Texture.m_Img3D != VK_NULL_HANDLE)
1976 {
1977 FreeImageMemBlock(Block&: Texture.m_Img3DMem);
1978 vkDestroyImage(device: m_VKDevice, image: Texture.m_Img3D, pAllocator: nullptr);
1979
1980 vkDestroyImageView(device: m_VKDevice, imageView: Texture.m_Img3DView, pAllocator: nullptr);
1981 }
1982
1983 DestroyTexturedStandardDescriptorSets(Texture, DescrIndex: 0);
1984 DestroyTexturedStandardDescriptorSets(Texture, DescrIndex: 1);
1985
1986 DestroyTextured3DStandardDescriptorSets(Texture);
1987 }
1988
1989 void DestroyTextTexture(CTexture &Texture, CTexture &TextureOutline)
1990 {
1991 if(Texture.m_Img != VK_NULL_HANDLE)
1992 {
1993 FreeImageMemBlock(Block&: Texture.m_ImgMem);
1994 vkDestroyImage(device: m_VKDevice, image: Texture.m_Img, pAllocator: nullptr);
1995
1996 vkDestroyImageView(device: m_VKDevice, imageView: Texture.m_ImgView, pAllocator: nullptr);
1997 }
1998
1999 if(TextureOutline.m_Img != VK_NULL_HANDLE)
2000 {
2001 FreeImageMemBlock(Block&: TextureOutline.m_ImgMem);
2002 vkDestroyImage(device: m_VKDevice, image: TextureOutline.m_Img, pAllocator: nullptr);
2003
2004 vkDestroyImageView(device: m_VKDevice, imageView: TextureOutline.m_ImgView, pAllocator: nullptr);
2005 }
2006
2007 DestroyTextDescriptorSets(Texture, TextureOutline);
2008 }
2009
2010 void ClearFrameData(size_t FrameImageIndex)
2011 {
2012 UploadStagingBuffers();
2013
2014 // clear pending buffers, that require deletion
2015 for(auto &BufferPair : m_vvFrameDelayedBufferCleanup[FrameImageIndex])
2016 {
2017 if(BufferPair.m_pMappedData != nullptr)
2018 {
2019 vkUnmapMemory(device: m_VKDevice, memory: BufferPair.m_Mem.m_Mem);
2020 }
2021 CleanBufferPair(ImageIndex: FrameImageIndex, Buffer&: BufferPair.m_Buffer, BufferMem&: BufferPair.m_Mem);
2022 }
2023 m_vvFrameDelayedBufferCleanup[FrameImageIndex].clear();
2024
2025 // clear pending textures, that require deletion
2026 for(auto &Texture : m_vvFrameDelayedTextureCleanup[FrameImageIndex])
2027 {
2028 DestroyTexture(Texture);
2029 }
2030 m_vvFrameDelayedTextureCleanup[FrameImageIndex].clear();
2031
2032 for(auto &TexturePair : m_vvFrameDelayedTextTexturesCleanup[FrameImageIndex])
2033 {
2034 DestroyTextTexture(Texture&: TexturePair.first, TextureOutline&: TexturePair.second);
2035 }
2036 m_vvFrameDelayedTextTexturesCleanup[FrameImageIndex].clear();
2037
2038 m_StagingBufferCache.Cleanup(ImgIndex: FrameImageIndex);
2039 m_StagingBufferCacheImage.Cleanup(ImgIndex: FrameImageIndex);
2040 m_VertexBufferCache.Cleanup(ImgIndex: FrameImageIndex);
2041 for(auto &ImageBufferCache : m_ImageBufferCaches)
2042 ImageBufferCache.second.Cleanup(ImgIndex: FrameImageIndex);
2043 }
2044
2045 void ShrinkUnusedCaches()
2046 {
2047 size_t FreedMemory = 0;
2048 FreedMemory += m_StagingBufferCache.Shrink(Device&: m_VKDevice);
2049 FreedMemory += m_StagingBufferCacheImage.Shrink(Device&: m_VKDevice);
2050 if(FreedMemory > 0)
2051 {
2052 m_pStagingMemoryUsage->store(i: m_pStagingMemoryUsage->load(m: std::memory_order_relaxed) - FreedMemory, m: std::memory_order_relaxed);
2053 if(IsVerbose())
2054 {
2055 log_debug("gfx/vulkan", "Deallocated chunks of memory with size %" PRIzu " from all frames (staging buffer).", FreedMemory);
2056 }
2057 }
2058 FreedMemory = 0;
2059 FreedMemory += m_VertexBufferCache.Shrink(Device&: m_VKDevice);
2060 if(FreedMemory > 0)
2061 {
2062 m_pBufferMemoryUsage->store(i: m_pBufferMemoryUsage->load(m: std::memory_order_relaxed) - FreedMemory, m: std::memory_order_relaxed);
2063 if(IsVerbose())
2064 {
2065 log_debug("gfx/vulkan", "Deallocated chunks of memory with size %" PRIzu " from all frames (buffer).", FreedMemory);
2066 }
2067 }
2068 FreedMemory = 0;
2069 for(auto &ImageBufferCache : m_ImageBufferCaches)
2070 FreedMemory += ImageBufferCache.second.Shrink(Device&: m_VKDevice);
2071 if(FreedMemory > 0)
2072 {
2073 m_pTextureMemoryUsage->store(i: m_pTextureMemoryUsage->load(m: std::memory_order_relaxed) - FreedMemory, m: std::memory_order_relaxed);
2074 if(IsVerbose())
2075 {
2076 log_debug("gfx/vulkan", "Deallocated chunks of memory with size %" PRIzu " from all frames (texture).", FreedMemory);
2077 }
2078 }
2079 }
2080
2081 [[nodiscard]] bool MemoryBarrier(VkBuffer Buffer, VkDeviceSize Offset, VkDeviceSize Size, VkAccessFlags BufferAccessType, bool BeforeCommand)
2082 {
2083 VkCommandBuffer *pMemCommandBuffer;
2084 if(!GetMemoryCommandBuffer(pMemCommandBuffer))
2085 return false;
2086 auto &MemCommandBuffer = *pMemCommandBuffer;
2087
2088 VkBufferMemoryBarrier Barrier{};
2089 Barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
2090 Barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2091 Barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2092 Barrier.buffer = Buffer;
2093 Barrier.offset = Offset;
2094 Barrier.size = Size;
2095
2096 VkPipelineStageFlags SourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2097 VkPipelineStageFlags DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2098
2099 if(BeforeCommand)
2100 {
2101 Barrier.srcAccessMask = BufferAccessType;
2102 Barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2103
2104 SourceStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
2105 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2106 }
2107 else
2108 {
2109 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2110 Barrier.dstAccessMask = BufferAccessType;
2111
2112 SourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2113 DestinationStage = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
2114 }
2115
2116 vkCmdPipelineBarrier(
2117 commandBuffer: MemCommandBuffer,
2118 srcStageMask: SourceStage, dstStageMask: DestinationStage,
2119 dependencyFlags: 0,
2120 memoryBarrierCount: 0, pMemoryBarriers: nullptr,
2121 bufferMemoryBarrierCount: 1, pBufferMemoryBarriers: &Barrier,
2122 imageMemoryBarrierCount: 0, pImageMemoryBarriers: nullptr);
2123
2124 return true;
2125 }
2126
2127 /************************
2128 * SWAPPING MECHANISM
2129 ************************/
2130
2131 void StartRenderThread(size_t ThreadIndex)
2132 {
2133 auto &List = m_vvThreadCommandLists[ThreadIndex];
2134 if(!List.empty())
2135 {
2136 m_vThreadHelperHadCommands[ThreadIndex] = true;
2137 auto *pThread = m_vpRenderThreads[ThreadIndex].get();
2138 std::unique_lock<std::mutex> Lock(pThread->m_Mutex);
2139 pThread->m_IsRendering = true;
2140 pThread->m_Cond.notify_one();
2141 }
2142 }
2143
2144 void FinishRenderThreads()
2145 {
2146 if(m_ThreadCount > 1)
2147 {
2148 // execute threads
2149
2150 for(size_t ThreadIndex = 0; ThreadIndex < m_ThreadCount - 1; ++ThreadIndex)
2151 {
2152 if(!m_vThreadHelperHadCommands[ThreadIndex])
2153 {
2154 StartRenderThread(ThreadIndex);
2155 }
2156 }
2157
2158 for(size_t ThreadIndex = 0; ThreadIndex < m_ThreadCount - 1; ++ThreadIndex)
2159 {
2160 if(m_vThreadHelperHadCommands[ThreadIndex])
2161 {
2162 auto &pRenderThread = m_vpRenderThreads[ThreadIndex];
2163 m_vThreadHelperHadCommands[ThreadIndex] = false;
2164 std::unique_lock<std::mutex> Lock(pRenderThread->m_Mutex);
2165 pRenderThread->m_Cond.wait(lock&: Lock, p: [&pRenderThread] { return !pRenderThread->m_IsRendering; });
2166 m_vLastPipeline[ThreadIndex + 1] = VK_NULL_HANDLE;
2167 }
2168 }
2169 }
2170 }
2171
2172 void ExecuteMemoryCommandBuffer()
2173 {
2174 if(m_vUsedMemoryCommandBuffer[m_CurImageIndex])
2175 {
2176 auto &MemoryCommandBuffer = m_vMemoryCommandBuffers[m_CurImageIndex];
2177 vkEndCommandBuffer(commandBuffer: MemoryCommandBuffer);
2178
2179 VkSubmitInfo SubmitInfo{};
2180 SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2181
2182 SubmitInfo.commandBufferCount = 1;
2183 SubmitInfo.pCommandBuffers = &MemoryCommandBuffer;
2184 vkQueueSubmit(queue: m_VKGraphicsQueue, submitCount: 1, pSubmits: &SubmitInfo, VK_NULL_HANDLE);
2185 vkQueueWaitIdle(queue: m_VKGraphicsQueue);
2186
2187 m_vUsedMemoryCommandBuffer[m_CurImageIndex] = false;
2188 }
2189 }
2190
2191 void ClearFrameMemoryUsage()
2192 {
2193 ClearFrameData(FrameImageIndex: m_CurImageIndex);
2194 ShrinkUnusedCaches();
2195 }
2196
2197 [[nodiscard]] bool WaitFrame()
2198 {
2199 FinishRenderThreads();
2200 m_LastCommandsInPipeThreadIndex = 0;
2201
2202 UploadNonFlushedBuffers<true>();
2203
2204 auto &CommandBuffer = GetMainGraphicCommandBuffer();
2205
2206 // render threads
2207 if(m_ThreadCount > 1)
2208 {
2209 size_t ThreadedCommandsUsedCount = 0;
2210 size_t RenderThreadCount = m_ThreadCount - 1;
2211 for(size_t i = 0; i < RenderThreadCount; ++i)
2212 {
2213 if(m_vvUsedThreadDrawCommandBuffer[i + 1][m_CurImageIndex])
2214 {
2215 const auto &GraphicThreadCommandBuffer = m_vvThreadDrawCommandBuffers[i + 1][m_CurImageIndex];
2216 m_vHelperThreadDrawCommandBuffers[ThreadedCommandsUsedCount++] = GraphicThreadCommandBuffer;
2217
2218 m_vvUsedThreadDrawCommandBuffer[i + 1][m_CurImageIndex] = false;
2219 }
2220 }
2221 if(ThreadedCommandsUsedCount > 0)
2222 {
2223 vkCmdExecuteCommands(commandBuffer: CommandBuffer, commandBufferCount: ThreadedCommandsUsedCount, pCommandBuffers: m_vHelperThreadDrawCommandBuffers.data());
2224 }
2225
2226 // special case if swap chain was not completed in one runbuffer call
2227
2228 if(m_vvUsedThreadDrawCommandBuffer[0][m_CurImageIndex])
2229 {
2230 auto &GraphicThreadCommandBuffer = m_vvThreadDrawCommandBuffers[0][m_CurImageIndex];
2231 vkEndCommandBuffer(commandBuffer: GraphicThreadCommandBuffer);
2232
2233 vkCmdExecuteCommands(commandBuffer: CommandBuffer, commandBufferCount: 1, pCommandBuffers: &GraphicThreadCommandBuffer);
2234
2235 m_vvUsedThreadDrawCommandBuffer[0][m_CurImageIndex] = false;
2236 }
2237 }
2238
2239 vkCmdEndRenderPass(commandBuffer: CommandBuffer);
2240
2241 if(vkEndCommandBuffer(commandBuffer: CommandBuffer) != VK_SUCCESS)
2242 {
2243 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, pErr: "Command buffer cannot be ended anymore.");
2244 return false;
2245 }
2246
2247 VkSubmitInfo SubmitInfo{};
2248 SubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
2249
2250 SubmitInfo.commandBufferCount = 1;
2251 SubmitInfo.pCommandBuffers = &CommandBuffer;
2252
2253 std::array<VkCommandBuffer, 2> aCommandBuffers = {};
2254
2255 if(m_vUsedMemoryCommandBuffer[m_CurImageIndex])
2256 {
2257 auto &MemoryCommandBuffer = m_vMemoryCommandBuffers[m_CurImageIndex];
2258 vkEndCommandBuffer(commandBuffer: MemoryCommandBuffer);
2259
2260 aCommandBuffers[0] = MemoryCommandBuffer;
2261 aCommandBuffers[1] = CommandBuffer;
2262 SubmitInfo.commandBufferCount = 2;
2263 SubmitInfo.pCommandBuffers = aCommandBuffers.data();
2264
2265 m_vUsedMemoryCommandBuffer[m_CurImageIndex] = false;
2266 }
2267
2268 std::array<VkSemaphore, 1> aWaitSemaphores = {m_AcquireImageSemaphore};
2269 std::array<VkPipelineStageFlags, 1> aWaitStages = {(VkPipelineStageFlags)VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
2270 SubmitInfo.waitSemaphoreCount = aWaitSemaphores.size();
2271 SubmitInfo.pWaitSemaphores = aWaitSemaphores.data();
2272 SubmitInfo.pWaitDstStageMask = aWaitStages.data();
2273
2274 std::array<VkSemaphore, 1> aSignalSemaphores = {m_vQueueSubmitSemaphores[m_CurImageIndex]};
2275 SubmitInfo.signalSemaphoreCount = aSignalSemaphores.size();
2276 SubmitInfo.pSignalSemaphores = aSignalSemaphores.data();
2277
2278 vkResetFences(device: m_VKDevice, fenceCount: 1, pFences: &m_vQueueSubmitFences[m_CurImageIndex]);
2279
2280 VkResult QueueSubmitRes = vkQueueSubmit(queue: m_VKGraphicsQueue, submitCount: 1, pSubmits: &SubmitInfo, fence: m_vQueueSubmitFences[m_CurImageIndex]);
2281 if(QueueSubmitRes != VK_SUCCESS)
2282 {
2283 const char *pCritErrorMsg = CheckVulkanCriticalError(CallResult: QueueSubmitRes);
2284 if(pCritErrorMsg != nullptr)
2285 {
2286 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED, pErr: "Submitting to graphics queue failed.", pErrStrExtra: pCritErrorMsg);
2287 return false;
2288 }
2289 }
2290
2291 std::swap(a&: m_vBusyAcquireImageSemaphores[m_CurImageIndex], b&: m_AcquireImageSemaphore);
2292
2293 VkPresentInfoKHR PresentInfo{};
2294 PresentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
2295
2296 PresentInfo.waitSemaphoreCount = aSignalSemaphores.size();
2297 PresentInfo.pWaitSemaphores = aSignalSemaphores.data();
2298
2299 std::array<VkSwapchainKHR, 1> aSwapChains = {m_VKSwapChain};
2300 PresentInfo.swapchainCount = aSwapChains.size();
2301 PresentInfo.pSwapchains = aSwapChains.data();
2302
2303 PresentInfo.pImageIndices = &m_CurImageIndex;
2304
2305 m_LastPresentedSwapChainImageIndex = m_CurImageIndex;
2306
2307 VkResult QueuePresentRes = vkQueuePresentKHR(queue: m_VKPresentQueue, pPresentInfo: &PresentInfo);
2308 if(QueuePresentRes != VK_SUCCESS && QueuePresentRes != VK_SUBOPTIMAL_KHR)
2309 {
2310 const char *pCritErrorMsg = CheckVulkanCriticalError(CallResult: QueuePresentRes);
2311 if(pCritErrorMsg != nullptr)
2312 {
2313 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_SWAP_FAILED, pErr: "Presenting graphics queue failed.", pErrStrExtra: pCritErrorMsg);
2314 return false;
2315 }
2316 }
2317
2318 return true;
2319 }
2320
2321 [[nodiscard]] bool PrepareFrame()
2322 {
2323 if(m_RecreateSwapChain)
2324 {
2325 m_RecreateSwapChain = false;
2326 if(IsVerbose())
2327 {
2328 log_debug("gfx/vulkan", "Recreating swap chain requested by user (prepare frame).");
2329 }
2330 RecreateSwapChain();
2331 }
2332
2333 auto AcqResult = vkAcquireNextImageKHR(device: m_VKDevice, swapchain: m_VKSwapChain, timeout: std::numeric_limits<uint64_t>::max(), semaphore: m_AcquireImageSemaphore, VK_NULL_HANDLE, pImageIndex: &m_CurImageIndex);
2334 if(AcqResult != VK_SUCCESS)
2335 {
2336 if(AcqResult == VK_ERROR_OUT_OF_DATE_KHR || m_RecreateSwapChain)
2337 {
2338 m_RecreateSwapChain = false;
2339 if(IsVerbose())
2340 {
2341 log_debug("gfx/vulkan", "Recreating swap chain requested by acquire next image (prepare frame).");
2342 }
2343 RecreateSwapChain();
2344 return PrepareFrame();
2345 }
2346 else
2347 {
2348 const char *pCritErrorMsg = CheckVulkanCriticalError(CallResult: AcqResult);
2349 if(pCritErrorMsg != nullptr)
2350 {
2351 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_SWAP_FAILED, pErr: "Acquiring next image failed.", pErrStrExtra: pCritErrorMsg);
2352 return false;
2353 }
2354 else if(AcqResult == VK_ERROR_SURFACE_LOST_KHR)
2355 {
2356 m_RenderingPaused = true;
2357 return true;
2358 }
2359 }
2360 }
2361
2362 vkWaitForFences(device: m_VKDevice, fenceCount: 1, pFences: &m_vQueueSubmitFences[m_CurImageIndex], VK_TRUE, timeout: std::numeric_limits<uint64_t>::max());
2363
2364 // next frame
2365 m_CurFrame++;
2366 m_vImageLastFrameCheck[m_CurImageIndex] = m_CurFrame;
2367
2368 // check if older frames weren't used in a long time
2369 for(size_t FrameImageIndex = 0; FrameImageIndex < m_vImageLastFrameCheck.size(); ++FrameImageIndex)
2370 {
2371 auto LastFrame = m_vImageLastFrameCheck[FrameImageIndex];
2372 if(m_CurFrame - LastFrame > (uint64_t)m_SwapChainImageCount)
2373 {
2374 vkWaitForFences(device: m_VKDevice, fenceCount: 1, pFences: &m_vQueueSubmitFences[FrameImageIndex], VK_TRUE, timeout: std::numeric_limits<uint64_t>::max());
2375 ClearFrameData(FrameImageIndex);
2376 m_vImageLastFrameCheck[FrameImageIndex] = m_CurFrame;
2377 }
2378 }
2379
2380 // clear frame's memory data
2381 ClearFrameMemoryUsage();
2382
2383 // clear frame
2384 vkResetCommandBuffer(commandBuffer: GetMainGraphicCommandBuffer(), flags: VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
2385
2386 auto &CommandBuffer = GetMainGraphicCommandBuffer();
2387 VkCommandBufferBeginInfo BeginInfo{};
2388 BeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
2389 BeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
2390
2391 if(vkBeginCommandBuffer(commandBuffer: CommandBuffer, pBeginInfo: &BeginInfo) != VK_SUCCESS)
2392 {
2393 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, pErr: "Command buffer cannot be filled anymore.");
2394 return false;
2395 }
2396
2397 VkRenderPassBeginInfo RenderPassInfo{};
2398 RenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
2399 RenderPassInfo.renderPass = m_VKRenderPass;
2400 RenderPassInfo.framebuffer = m_vFramebufferList[m_CurImageIndex];
2401 RenderPassInfo.renderArea.offset = {.x: 0, .y: 0};
2402 RenderPassInfo.renderArea.extent = m_VKSwapImgAndViewportExtent.m_SwapImageViewport;
2403
2404 VkClearValue ClearColorVal = {.color: {.float32: {m_aClearColor[0], m_aClearColor[1], m_aClearColor[2], m_aClearColor[3]}}};
2405 RenderPassInfo.clearValueCount = 1;
2406 RenderPassInfo.pClearValues = &ClearColorVal;
2407
2408 vkCmdBeginRenderPass(commandBuffer: CommandBuffer, pRenderPassBegin: &RenderPassInfo, contents: m_ThreadCount > 1 ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS : VK_SUBPASS_CONTENTS_INLINE);
2409
2410 for(auto &LastPipe : m_vLastPipeline)
2411 LastPipe = VK_NULL_HANDLE;
2412
2413 return true;
2414 }
2415
2416 void UploadStagingBuffers()
2417 {
2418 if(!m_vNonFlushedStagingBufferRange.empty())
2419 {
2420 vkFlushMappedMemoryRanges(device: m_VKDevice, memoryRangeCount: m_vNonFlushedStagingBufferRange.size(), pMemoryRanges: m_vNonFlushedStagingBufferRange.data());
2421
2422 m_vNonFlushedStagingBufferRange.clear();
2423 }
2424 }
2425
2426 template<bool FlushForRendering>
2427 void UploadNonFlushedBuffers()
2428 {
2429 // streamed vertices
2430 for(auto &StreamVertexBuffer : m_vStreamedVertexBuffers)
2431 UploadStreamedBuffer<FlushForRendering>(StreamVertexBuffer);
2432 // now the buffer objects
2433 for(auto &StreamUniformBuffer : m_vStreamedUniformBuffers)
2434 UploadStreamedBuffer<FlushForRendering>(StreamUniformBuffer);
2435
2436 UploadStagingBuffers();
2437 }
2438
2439 [[nodiscard]] bool PureMemoryFrame()
2440 {
2441 ExecuteMemoryCommandBuffer();
2442
2443 // reset streamed data
2444 UploadNonFlushedBuffers<false>();
2445
2446 ClearFrameMemoryUsage();
2447
2448 return true;
2449 }
2450
2451 [[nodiscard]] bool NextFrame()
2452 {
2453 if(!m_RenderingPaused)
2454 {
2455 if(!WaitFrame())
2456 return false;
2457 if(!PrepareFrame())
2458 return false;
2459 }
2460 // else only execute the memory command buffer
2461 else
2462 {
2463 if(!PureMemoryFrame())
2464 return false;
2465 }
2466
2467 return true;
2468 }
2469
2470 /************************
2471 * TEXTURES
2472 ************************/
2473
2474 size_t VulkanFormatToPixelSize(VkFormat Format)
2475 {
2476 if(Format == VK_FORMAT_R8G8B8_UNORM)
2477 return 3;
2478 else if(Format == VK_FORMAT_R8G8B8A8_UNORM)
2479 return 4;
2480 else if(Format == VK_FORMAT_R8_UNORM)
2481 return 1;
2482 return 4;
2483 }
2484
2485 [[nodiscard]] bool UpdateTexture(size_t TextureSlot, VkFormat Format, uint8_t *&pData, int64_t XOff, int64_t YOff, size_t Width, size_t Height)
2486 {
2487 const size_t ImageSize = Width * Height * VulkanFormatToPixelSize(Format);
2488 SMemoryBlock<STAGING_BUFFER_IMAGE_CACHE_ID> StagingBuffer;
2489 if(!GetStagingBufferImage(ResBlock&: StagingBuffer, pBufferData: pData, RequiredSize: ImageSize))
2490 return false;
2491
2492 auto &Tex = m_vTextures[TextureSlot];
2493
2494 if(Tex.m_RescaleCount > 0)
2495 {
2496 for(uint32_t i = 0; i < Tex.m_RescaleCount; ++i)
2497 {
2498 Width >>= 1;
2499 Height >>= 1;
2500
2501 XOff /= 2;
2502 YOff /= 2;
2503 }
2504
2505 uint8_t *pTmpData = ResizeImage(pImageData: pData, Width, Height, NewWidth: Width, NewHeight: Height, BPP: VulkanFormatToPixelSize(Format));
2506 free(ptr: pData);
2507 pData = pTmpData;
2508 }
2509
2510 if(!ImageBarrier(Image: Tex.m_Img, MipMapBase: 0, MipMapCount: Tex.m_MipMapCount, LayerBase: 0, LayerCount: 1, Format, OldLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, NewLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL))
2511 return false;
2512 if(!CopyBufferToImage(Buffer: StagingBuffer.m_Buffer, BufferOffset: StagingBuffer.m_HeapData.m_OffsetToAlign, Image: Tex.m_Img, X: XOff, Y: YOff, Width, Height, Depth: 1))
2513 return false;
2514
2515 if(Tex.m_MipMapCount > 1)
2516 {
2517 if(!BuildMipmaps(Image: Tex.m_Img, ImageFormat: Format, Width, Height, Depth: 1, MipMapLevelCount: Tex.m_MipMapCount))
2518 return false;
2519 }
2520 else
2521 {
2522 if(!ImageBarrier(Image: Tex.m_Img, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: 1, Format, OldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, NewLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL))
2523 return false;
2524 }
2525
2526 UploadAndFreeStagingImageMemBlock(Block&: StagingBuffer);
2527
2528 return true;
2529 }
2530
2531 [[nodiscard]] bool CreateTextureCMD(
2532 int Slot,
2533 int Width,
2534 int Height,
2535 VkFormat Format,
2536 VkFormat StoreFormat,
2537 int Flags,
2538 uint8_t *&pData)
2539 {
2540 size_t ImageIndex = (size_t)Slot;
2541 const size_t PixelSize = VulkanFormatToPixelSize(Format);
2542
2543 while(ImageIndex >= m_vTextures.size())
2544 {
2545 m_vTextures.resize(sz: (m_vTextures.size() * 2) + 1);
2546 }
2547
2548 // resample if needed
2549 uint32_t RescaleCount = 0;
2550 if((size_t)Width > m_MaxTextureSize || (size_t)Height > m_MaxTextureSize)
2551 {
2552 do
2553 {
2554 Width >>= 1;
2555 Height >>= 1;
2556 ++RescaleCount;
2557 } while((size_t)Width > m_MaxTextureSize || (size_t)Height > m_MaxTextureSize);
2558
2559 uint8_t *pTmpData = ResizeImage(pImageData: pData, Width, Height, NewWidth: Width, NewHeight: Height, BPP: PixelSize);
2560 free(ptr: pData);
2561 pData = pTmpData;
2562 }
2563
2564 bool Requires2DTexture = (Flags & TextureFlag::NO_2D_TEXTURE) == 0;
2565 bool Requires2DTextureArray = (Flags & TextureFlag::TO_2D_ARRAY_TEXTURE) != 0;
2566 bool RequiresMipMaps = (Flags & TextureFlag::NO_MIPMAPS) == 0;
2567 size_t MipMapLevelCount = 1;
2568 if(RequiresMipMaps)
2569 {
2570 VkExtent3D ImgSize{.width: (uint32_t)Width, .height: (uint32_t)Height, .depth: 1};
2571 MipMapLevelCount = ImageMipLevelCount(ImgExtent: ImgSize);
2572 if(!m_OptimalRGBAImageBlitting)
2573 MipMapLevelCount = 1;
2574 }
2575
2576 CTexture &Texture = m_vTextures[ImageIndex];
2577
2578 Texture.m_Width = Width;
2579 Texture.m_Height = Height;
2580 Texture.m_RescaleCount = RescaleCount;
2581 Texture.m_MipMapCount = MipMapLevelCount;
2582
2583 if(Requires2DTexture)
2584 {
2585 if(!CreateTextureImage(ImageIndex, NewImage&: Texture.m_Img, NewImgMem&: Texture.m_ImgMem, pData, Format, Width, Height, Depth: 1, PixelSize, MipMapLevelCount))
2586 return false;
2587 VkFormat ImgFormat = Format;
2588 VkImageView ImgView = CreateTextureImageView(TexImage: Texture.m_Img, ImgFormat, ViewType: VK_IMAGE_VIEW_TYPE_2D, Depth: 1, MipMapLevelCount);
2589 Texture.m_ImgView = ImgView;
2590 VkSampler ImgSampler = GetTextureSampler(SamplerType: SUPPORTED_SAMPLER_TYPE_REPEAT);
2591 Texture.m_aSamplers[0] = ImgSampler;
2592 ImgSampler = GetTextureSampler(SamplerType: SUPPORTED_SAMPLER_TYPE_CLAMP_TO_EDGE);
2593 Texture.m_aSamplers[1] = ImgSampler;
2594
2595 if(!CreateNewTexturedStandardDescriptorSets(TextureSlot: ImageIndex, DescrIndex: 0))
2596 return false;
2597 if(!CreateNewTexturedStandardDescriptorSets(TextureSlot: ImageIndex, DescrIndex: 1))
2598 return false;
2599 }
2600
2601 if(Requires2DTextureArray)
2602 {
2603 int ConvertWidth = Width;
2604 int ConvertHeight = Height;
2605
2606 if(ConvertWidth == 0 || (ConvertWidth % 16) != 0 || ConvertHeight == 0 || (ConvertHeight % 16) != 0)
2607 {
2608 int NewWidth = maximum<int>(a: HighestBit(OfVar: ConvertWidth), b: 16);
2609 int NewHeight = maximum<int>(a: HighestBit(OfVar: ConvertHeight), b: 16);
2610 uint8_t *pNewTexData = ResizeImage(pImageData: pData, Width: ConvertWidth, Height: ConvertHeight, NewWidth, NewHeight, BPP: PixelSize);
2611 if(IsVerbose())
2612 {
2613 log_debug("gfx/vulkan", "3D/2D array texture was resized. Slot=%d Size=(%d, %d) Resized=(%d, %d)", Slot, ConvertWidth, ConvertHeight, NewWidth, NewHeight);
2614 }
2615
2616 ConvertWidth = NewWidth;
2617 ConvertHeight = NewHeight;
2618
2619 free(ptr: pData);
2620 pData = pNewTexData;
2621 }
2622
2623 int Image3DWidth, Image3DHeight;
2624 uint8_t *pTexData3D = static_cast<uint8_t *>(malloc(size: (size_t)PixelSize * ConvertWidth * ConvertHeight));
2625 Texture2DTo3D(pImageBuffer: pData, ImageWidth: ConvertWidth, ImageHeight: ConvertHeight, PixelSize, SplitCountWidth: 16, SplitCountHeight: 16, pTarget3DImageData: pTexData3D, Target3DImageWidth&: Image3DWidth, Target3DImageHeight&: Image3DHeight);
2626
2627 const size_t ImageDepth2DArray = (size_t)16 * 16;
2628 VkExtent3D ImgSize{.width: (uint32_t)Image3DWidth, .height: (uint32_t)Image3DHeight, .depth: 1};
2629 if(RequiresMipMaps)
2630 {
2631 MipMapLevelCount = ImageMipLevelCount(ImgExtent: ImgSize);
2632 if(!m_OptimalRGBAImageBlitting)
2633 MipMapLevelCount = 1;
2634 }
2635
2636 if(!CreateTextureImage(ImageIndex, NewImage&: Texture.m_Img3D, NewImgMem&: Texture.m_Img3DMem, pData: pTexData3D, Format, Width: Image3DWidth, Height: Image3DHeight, Depth: ImageDepth2DArray, PixelSize, MipMapLevelCount))
2637 return false;
2638 VkFormat ImgFormat = Format;
2639 VkImageView ImgView = CreateTextureImageView(TexImage: Texture.m_Img3D, ImgFormat, ViewType: VK_IMAGE_VIEW_TYPE_2D_ARRAY, Depth: ImageDepth2DArray, MipMapLevelCount);
2640 Texture.m_Img3DView = ImgView;
2641 VkSampler ImgSampler = GetTextureSampler(SamplerType: SUPPORTED_SAMPLER_TYPE_2D_TEXTURE_ARRAY);
2642 Texture.m_Sampler3D = ImgSampler;
2643
2644 if(!CreateNew3DTexturedStandardDescriptorSets(TextureSlot: ImageIndex))
2645 return false;
2646
2647 free(ptr: pTexData3D);
2648 }
2649 return true;
2650 }
2651
2652 [[nodiscard]] bool BuildMipmaps(VkImage Image, VkFormat ImageFormat, size_t Width, size_t Height, size_t Depth, size_t MipMapLevelCount)
2653 {
2654 VkCommandBuffer *pMemCommandBuffer;
2655 if(!GetMemoryCommandBuffer(pMemCommandBuffer))
2656 return false;
2657 auto &MemCommandBuffer = *pMemCommandBuffer;
2658
2659 VkImageMemoryBarrier Barrier{};
2660 Barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2661 Barrier.image = Image;
2662 Barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2663 Barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2664 Barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2665 Barrier.subresourceRange.levelCount = 1;
2666 Barrier.subresourceRange.baseArrayLayer = 0;
2667 Barrier.subresourceRange.layerCount = Depth;
2668
2669 int32_t TmpMipWidth = (int32_t)Width;
2670 int32_t TmpMipHeight = (int32_t)Height;
2671
2672 for(size_t i = 1; i < MipMapLevelCount; ++i)
2673 {
2674 Barrier.subresourceRange.baseMipLevel = i - 1;
2675 Barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2676 Barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
2677 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2678 Barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2679
2680 vkCmdPipelineBarrier(commandBuffer: MemCommandBuffer, srcStageMask: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask: VK_PIPELINE_STAGE_TRANSFER_BIT, dependencyFlags: 0, memoryBarrierCount: 0, pMemoryBarriers: nullptr, bufferMemoryBarrierCount: 0, pBufferMemoryBarriers: nullptr, imageMemoryBarrierCount: 1, pImageMemoryBarriers: &Barrier);
2681
2682 VkImageBlit Blit{};
2683 Blit.srcOffsets[0] = {.x: 0, .y: 0, .z: 0};
2684 Blit.srcOffsets[1] = {.x: TmpMipWidth, .y: TmpMipHeight, .z: 1};
2685 Blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2686 Blit.srcSubresource.mipLevel = i - 1;
2687 Blit.srcSubresource.baseArrayLayer = 0;
2688 Blit.srcSubresource.layerCount = Depth;
2689 Blit.dstOffsets[0] = {.x: 0, .y: 0, .z: 0};
2690 Blit.dstOffsets[1] = {.x: TmpMipWidth > 1 ? TmpMipWidth / 2 : 1, .y: TmpMipHeight > 1 ? TmpMipHeight / 2 : 1, .z: 1};
2691 Blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2692 Blit.dstSubresource.mipLevel = i;
2693 Blit.dstSubresource.baseArrayLayer = 0;
2694 Blit.dstSubresource.layerCount = Depth;
2695
2696 vkCmdBlitImage(commandBuffer: MemCommandBuffer,
2697 srcImage: Image, srcImageLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
2698 dstImage: Image, dstImageLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
2699 regionCount: 1, pRegions: &Blit,
2700 filter: m_AllowsLinearBlitting ? VK_FILTER_LINEAR : VK_FILTER_NEAREST);
2701
2702 Barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
2703 Barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2704 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2705 Barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2706
2707 vkCmdPipelineBarrier(commandBuffer: MemCommandBuffer,
2708 srcStageMask: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dependencyFlags: 0,
2709 memoryBarrierCount: 0, pMemoryBarriers: nullptr,
2710 bufferMemoryBarrierCount: 0, pBufferMemoryBarriers: nullptr,
2711 imageMemoryBarrierCount: 1, pImageMemoryBarriers: &Barrier);
2712
2713 if(TmpMipWidth > 1)
2714 TmpMipWidth /= 2;
2715 if(TmpMipHeight > 1)
2716 TmpMipHeight /= 2;
2717 }
2718
2719 Barrier.subresourceRange.baseMipLevel = MipMapLevelCount - 1;
2720 Barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
2721 Barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
2722 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2723 Barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2724
2725 vkCmdPipelineBarrier(commandBuffer: MemCommandBuffer,
2726 srcStageMask: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, dependencyFlags: 0,
2727 memoryBarrierCount: 0, pMemoryBarriers: nullptr,
2728 bufferMemoryBarrierCount: 0, pBufferMemoryBarriers: nullptr,
2729 imageMemoryBarrierCount: 1, pImageMemoryBarriers: &Barrier);
2730
2731 return true;
2732 }
2733
2734 [[nodiscard]] bool CreateTextureImage(size_t ImageIndex, VkImage &NewImage, SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> &NewImgMem, const uint8_t *pData, VkFormat Format, size_t Width, size_t Height, size_t Depth, size_t PixelSize, size_t MipMapLevelCount)
2735 {
2736 VkDeviceSize ImageSize = Width * Height * Depth * PixelSize;
2737
2738 SMemoryBlock<STAGING_BUFFER_IMAGE_CACHE_ID> StagingBuffer;
2739 if(!GetStagingBufferImage(ResBlock&: StagingBuffer, pBufferData: pData, RequiredSize: ImageSize))
2740 return false;
2741
2742 VkFormat ImgFormat = Format;
2743
2744 if(!CreateImage(Width, Height, Depth, MipMapLevelCount, Format: ImgFormat, Tiling: VK_IMAGE_TILING_OPTIMAL, Image&: NewImage, ImageMemory&: NewImgMem))
2745 return false;
2746
2747 if(!ImageBarrier(Image: NewImage, MipMapBase: 0, MipMapCount: MipMapLevelCount, LayerBase: 0, LayerCount: Depth, Format: ImgFormat, OldLayout: VK_IMAGE_LAYOUT_UNDEFINED, NewLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL))
2748 return false;
2749 if(!CopyBufferToImage(Buffer: StagingBuffer.m_Buffer, BufferOffset: StagingBuffer.m_HeapData.m_OffsetToAlign, Image: NewImage, X: 0, Y: 0, Width: static_cast<uint32_t>(Width), Height: static_cast<uint32_t>(Height), Depth))
2750 return false;
2751
2752 UploadAndFreeStagingImageMemBlock(Block&: StagingBuffer);
2753
2754 if(MipMapLevelCount > 1)
2755 {
2756 if(!BuildMipmaps(Image: NewImage, ImageFormat: ImgFormat, Width, Height, Depth, MipMapLevelCount))
2757 return false;
2758 }
2759 else
2760 {
2761 if(!ImageBarrier(Image: NewImage, MipMapBase: 0, MipMapCount: 1, LayerBase: 0, LayerCount: Depth, Format: ImgFormat, OldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, NewLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL))
2762 return false;
2763 }
2764
2765 return true;
2766 }
2767
2768 VkImageView CreateTextureImageView(VkImage TexImage, VkFormat ImgFormat, VkImageViewType ViewType, size_t Depth, size_t MipMapLevelCount)
2769 {
2770 return CreateImageView(Image: TexImage, Format: ImgFormat, ViewType, Depth, MipMapLevelCount);
2771 }
2772
2773 [[nodiscard]] bool CreateTextureSamplersImpl(VkSampler &CreatedSampler, VkSamplerAddressMode AddrModeU, VkSamplerAddressMode AddrModeV, VkSamplerAddressMode AddrModeW)
2774 {
2775 VkSamplerCreateInfo SamplerInfo{};
2776 SamplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
2777 SamplerInfo.magFilter = VK_FILTER_LINEAR;
2778 SamplerInfo.minFilter = VK_FILTER_LINEAR;
2779 SamplerInfo.addressModeU = AddrModeU;
2780 SamplerInfo.addressModeV = AddrModeV;
2781 SamplerInfo.addressModeW = AddrModeW;
2782 SamplerInfo.anisotropyEnable = VK_FALSE;
2783 SamplerInfo.maxAnisotropy = m_MaxSamplerAnisotropy;
2784 SamplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
2785 SamplerInfo.unnormalizedCoordinates = VK_FALSE;
2786 SamplerInfo.compareEnable = VK_FALSE;
2787 SamplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
2788 SamplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
2789 SamplerInfo.mipLodBias = (m_GlobalTextureLodBIAS / 1000.0f);
2790 SamplerInfo.minLod = -1000;
2791 SamplerInfo.maxLod = 1000;
2792
2793 if(vkCreateSampler(device: m_VKDevice, pCreateInfo: &SamplerInfo, pAllocator: nullptr, pSampler: &CreatedSampler) != VK_SUCCESS)
2794 {
2795 log_error("gfx/vulkan", "Failed to create texture sampler.");
2796 return false;
2797 }
2798 return true;
2799 }
2800
2801 [[nodiscard]] bool CreateTextureSamplers()
2802 {
2803 bool Ret = true;
2804 Ret &= CreateTextureSamplersImpl(CreatedSampler&: m_aSamplers[SUPPORTED_SAMPLER_TYPE_REPEAT], AddrModeU: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_REPEAT, AddrModeV: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_REPEAT, AddrModeW: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_REPEAT);
2805 Ret &= CreateTextureSamplersImpl(CreatedSampler&: m_aSamplers[SUPPORTED_SAMPLER_TYPE_CLAMP_TO_EDGE], AddrModeU: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, AddrModeV: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, AddrModeW: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
2806 Ret &= CreateTextureSamplersImpl(CreatedSampler&: m_aSamplers[SUPPORTED_SAMPLER_TYPE_2D_TEXTURE_ARRAY], AddrModeU: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, AddrModeV: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, AddrModeW: VkSamplerAddressMode::VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT);
2807 return Ret;
2808 }
2809
2810 void DestroyTextureSamplers()
2811 {
2812 vkDestroySampler(device: m_VKDevice, sampler: m_aSamplers[SUPPORTED_SAMPLER_TYPE_REPEAT], pAllocator: nullptr);
2813 vkDestroySampler(device: m_VKDevice, sampler: m_aSamplers[SUPPORTED_SAMPLER_TYPE_CLAMP_TO_EDGE], pAllocator: nullptr);
2814 vkDestroySampler(device: m_VKDevice, sampler: m_aSamplers[SUPPORTED_SAMPLER_TYPE_2D_TEXTURE_ARRAY], pAllocator: nullptr);
2815 }
2816
2817 VkSampler GetTextureSampler(ESupportedSamplerTypes SamplerType)
2818 {
2819 return m_aSamplers[SamplerType];
2820 }
2821
2822 VkImageView CreateImageView(VkImage Image, VkFormat Format, VkImageViewType ViewType, size_t Depth, size_t MipMapLevelCount)
2823 {
2824 VkImageViewCreateInfo ViewCreateInfo{};
2825 ViewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
2826 ViewCreateInfo.image = Image;
2827 ViewCreateInfo.viewType = ViewType;
2828 ViewCreateInfo.format = Format;
2829 ViewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2830 ViewCreateInfo.subresourceRange.baseMipLevel = 0;
2831 ViewCreateInfo.subresourceRange.levelCount = MipMapLevelCount;
2832 ViewCreateInfo.subresourceRange.baseArrayLayer = 0;
2833 ViewCreateInfo.subresourceRange.layerCount = Depth;
2834
2835 VkImageView ImageView;
2836 if(vkCreateImageView(device: m_VKDevice, pCreateInfo: &ViewCreateInfo, pAllocator: nullptr, pView: &ImageView) != VK_SUCCESS)
2837 {
2838 return VK_NULL_HANDLE;
2839 }
2840
2841 return ImageView;
2842 }
2843
2844 [[nodiscard]] bool CreateImage(uint32_t Width, uint32_t Height, uint32_t Depth, size_t MipMapLevelCount, VkFormat Format, VkImageTiling Tiling, VkImage &Image, SMemoryImageBlock<IMAGE_BUFFER_CACHE_ID> &ImageMemory, VkImageUsageFlags ImageUsage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)
2845 {
2846 VkImageCreateInfo ImageInfo{};
2847 ImageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
2848 ImageInfo.imageType = VK_IMAGE_TYPE_2D;
2849 ImageInfo.extent.width = Width;
2850 ImageInfo.extent.height = Height;
2851 ImageInfo.extent.depth = 1;
2852 ImageInfo.mipLevels = MipMapLevelCount;
2853 ImageInfo.arrayLayers = Depth;
2854 ImageInfo.format = Format;
2855 ImageInfo.tiling = Tiling;
2856 ImageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
2857 ImageInfo.usage = ImageUsage;
2858 ImageInfo.samples = (ImageUsage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) == 0 ? VK_SAMPLE_COUNT_1_BIT : GetSampleCount();
2859 ImageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2860
2861 if(vkCreateImage(device: m_VKDevice, pCreateInfo: &ImageInfo, pAllocator: nullptr, pImage: &Image) != VK_SUCCESS)
2862 {
2863 log_error("gfx/vulkan", "Failed to create image.");
2864 return false;
2865 }
2866
2867 VkMemoryRequirements MemRequirements;
2868 vkGetImageMemoryRequirements(device: m_VKDevice, image: Image, pMemoryRequirements: &MemRequirements);
2869
2870 if(!GetImageMemory(RetBlock&: ImageMemory, RequiredSize: MemRequirements.size, RequiredAlignment: MemRequirements.alignment, RequiredMemoryTypeBits: MemRequirements.memoryTypeBits))
2871 return false;
2872
2873 vkBindImageMemory(device: m_VKDevice, image: Image, memory: ImageMemory.m_BufferMem.m_Mem, memoryOffset: ImageMemory.m_HeapData.m_OffsetToAlign);
2874
2875 return true;
2876 }
2877
2878 [[nodiscard]] bool ImageBarrier(const VkImage &Image, size_t MipMapBase, size_t MipMapCount, size_t LayerBase, size_t LayerCount, VkFormat Format, VkImageLayout OldLayout, VkImageLayout NewLayout)
2879 {
2880 VkCommandBuffer *pMemCommandBuffer;
2881 if(!GetMemoryCommandBuffer(pMemCommandBuffer))
2882 return false;
2883 auto &MemCommandBuffer = *pMemCommandBuffer;
2884
2885 VkImageMemoryBarrier Barrier{};
2886 Barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2887 Barrier.oldLayout = OldLayout;
2888 Barrier.newLayout = NewLayout;
2889 Barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2890 Barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2891 Barrier.image = Image;
2892 Barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2893 Barrier.subresourceRange.baseMipLevel = MipMapBase;
2894 Barrier.subresourceRange.levelCount = MipMapCount;
2895 Barrier.subresourceRange.baseArrayLayer = LayerBase;
2896 Barrier.subresourceRange.layerCount = LayerCount;
2897
2898 VkPipelineStageFlags SourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2899 VkPipelineStageFlags DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2900
2901 if(OldLayout == VK_IMAGE_LAYOUT_UNDEFINED && NewLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
2902 {
2903 Barrier.srcAccessMask = 0;
2904 Barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2905
2906 SourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2907 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2908 }
2909 else if(OldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && NewLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
2910 {
2911 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2912 Barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
2913
2914 SourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2915 DestinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2916 }
2917 else if(OldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL && NewLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
2918 {
2919 Barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
2920 Barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2921
2922 SourceStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
2923 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2924 }
2925 else if(OldLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && NewLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR)
2926 {
2927 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2928 Barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2929
2930 SourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2931 DestinationStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
2932 }
2933 else if(OldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && NewLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
2934 {
2935 Barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2936 Barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
2937
2938 SourceStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
2939 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2940 }
2941 else if(OldLayout == VK_IMAGE_LAYOUT_UNDEFINED && NewLayout == VK_IMAGE_LAYOUT_GENERAL)
2942 {
2943 Barrier.srcAccessMask = 0;
2944 Barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2945
2946 SourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
2947 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2948 }
2949 else if(OldLayout == VK_IMAGE_LAYOUT_GENERAL && NewLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
2950 {
2951 Barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2952 Barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2953
2954 SourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2955 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2956 }
2957 else if(OldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && NewLayout == VK_IMAGE_LAYOUT_GENERAL)
2958 {
2959 Barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
2960 Barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
2961
2962 SourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2963 DestinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
2964 }
2965 else
2966 {
2967 dbg_assert_failed("Unsupported layout transition. OldLayout=%d NewLayout=%d", (int)OldLayout, (int)NewLayout);
2968 }
2969
2970 vkCmdPipelineBarrier(
2971 commandBuffer: MemCommandBuffer,
2972 srcStageMask: SourceStage, dstStageMask: DestinationStage,
2973 dependencyFlags: 0,
2974 memoryBarrierCount: 0, pMemoryBarriers: nullptr,
2975 bufferMemoryBarrierCount: 0, pBufferMemoryBarriers: nullptr,
2976 imageMemoryBarrierCount: 1, pImageMemoryBarriers: &Barrier);
2977
2978 return true;
2979 }
2980
2981 [[nodiscard]] bool CopyBufferToImage(VkBuffer Buffer, VkDeviceSize BufferOffset, VkImage Image, int32_t X, int32_t Y, uint32_t Width, uint32_t Height, size_t Depth)
2982 {
2983 VkCommandBuffer *pCommandBuffer;
2984 if(!GetMemoryCommandBuffer(pMemCommandBuffer&: pCommandBuffer))
2985 return false;
2986 auto &CommandBuffer = *pCommandBuffer;
2987
2988 VkBufferImageCopy Region{};
2989 Region.bufferOffset = BufferOffset;
2990 Region.bufferRowLength = 0;
2991 Region.bufferImageHeight = 0;
2992 Region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2993 Region.imageSubresource.mipLevel = 0;
2994 Region.imageSubresource.baseArrayLayer = 0;
2995 Region.imageSubresource.layerCount = Depth;
2996 Region.imageOffset = {.x: X, .y: Y, .z: 0};
2997 Region.imageExtent = {
2998 .width: Width,
2999 .height: Height,
3000 .depth: 1};
3001
3002 vkCmdCopyBufferToImage(commandBuffer: CommandBuffer, srcBuffer: Buffer, dstImage: Image, dstImageLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regionCount: 1, pRegions: &Region);
3003
3004 return true;
3005 }
3006
3007 /************************
3008 * BUFFERS
3009 ************************/
3010
3011 [[nodiscard]] bool CreateBufferObject(size_t BufferIndex, const void *pUploadData, VkDeviceSize BufferDataSize, bool IsOneFrameBuffer)
3012 {
3013 std::vector<uint8_t> UploadDataTmp;
3014 if(pUploadData == nullptr)
3015 {
3016 UploadDataTmp.resize(sz: BufferDataSize);
3017 pUploadData = UploadDataTmp.data();
3018 }
3019
3020 while(BufferIndex >= m_vBufferObjects.size())
3021 {
3022 m_vBufferObjects.resize(sz: (m_vBufferObjects.size() * 2) + 1);
3023 }
3024 auto &BufferObject = m_vBufferObjects[BufferIndex];
3025
3026 VkBuffer VertexBuffer;
3027 size_t BufferOffset = 0;
3028 if(!IsOneFrameBuffer)
3029 {
3030 SMemoryBlock<STAGING_BUFFER_CACHE_ID> StagingBuffer;
3031 if(!GetStagingBuffer(ResBlock&: StagingBuffer, pBufferData: pUploadData, RequiredSize: BufferDataSize))
3032 return false;
3033
3034 SMemoryBlock<VERTEX_BUFFER_CACHE_ID> Mem;
3035 if(!GetVertexBuffer(ResBlock&: Mem, RequiredSize: BufferDataSize))
3036 return false;
3037
3038 BufferObject.m_BufferObject.m_Mem = Mem;
3039 VertexBuffer = Mem.m_Buffer;
3040 BufferOffset = Mem.m_HeapData.m_OffsetToAlign;
3041
3042 if(!MemoryBarrier(Buffer: VertexBuffer, Offset: Mem.m_HeapData.m_OffsetToAlign, Size: BufferDataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: true))
3043 return false;
3044 if(!CopyBuffer(SrcBuffer: StagingBuffer.m_Buffer, DstBuffer: VertexBuffer, SrcOffset: StagingBuffer.m_HeapData.m_OffsetToAlign, DstOffset: Mem.m_HeapData.m_OffsetToAlign, CopySize: BufferDataSize))
3045 return false;
3046 if(!MemoryBarrier(Buffer: VertexBuffer, Offset: Mem.m_HeapData.m_OffsetToAlign, Size: BufferDataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: false))
3047 return false;
3048 UploadAndFreeStagingMemBlock(Block&: StagingBuffer);
3049 }
3050 else
3051 {
3052 SDeviceMemoryBlock VertexBufferMemory;
3053 if(!CreateStreamVertexBuffer(RenderThreadIndex: MAIN_THREAD_INDEX, NewBuffer&: VertexBuffer, NewBufferMem&: VertexBufferMemory, BufferOffset, pData: pUploadData, DataSize: BufferDataSize))
3054 return false;
3055 }
3056 BufferObject.m_IsStreamedBuffer = IsOneFrameBuffer;
3057 BufferObject.m_CurBuffer = VertexBuffer;
3058 BufferObject.m_CurBufferOffset = BufferOffset;
3059
3060 return true;
3061 }
3062
3063 void DeleteBufferObject(size_t BufferIndex)
3064 {
3065 auto &BufferObject = m_vBufferObjects[BufferIndex];
3066 if(!BufferObject.m_IsStreamedBuffer)
3067 {
3068 FreeVertexMemBlock(Block&: BufferObject.m_BufferObject.m_Mem);
3069 }
3070 BufferObject = {};
3071 }
3072
3073 [[nodiscard]] bool CopyBuffer(VkBuffer SrcBuffer, VkBuffer DstBuffer, VkDeviceSize SrcOffset, VkDeviceSize DstOffset, VkDeviceSize CopySize)
3074 {
3075 VkCommandBuffer *pCommandBuffer;
3076 if(!GetMemoryCommandBuffer(pMemCommandBuffer&: pCommandBuffer))
3077 return false;
3078 auto &CommandBuffer = *pCommandBuffer;
3079 VkBufferCopy CopyRegion{};
3080 CopyRegion.srcOffset = SrcOffset;
3081 CopyRegion.dstOffset = DstOffset;
3082 CopyRegion.size = CopySize;
3083 vkCmdCopyBuffer(commandBuffer: CommandBuffer, srcBuffer: SrcBuffer, dstBuffer: DstBuffer, regionCount: 1, pRegions: &CopyRegion);
3084
3085 return true;
3086 }
3087
3088 /************************
3089 * RENDER STATES
3090 ************************/
3091
3092 void GetStateMatrix(const CCommandBuffer::SState &State, std::array<float, (size_t)4 * 2> &Matrix)
3093 {
3094 Matrix = {
3095 // column 1
3096 2.f / (State.m_ScreenBR.x - State.m_ScreenTL.x),
3097 0,
3098 // column 2
3099 0,
3100 2.f / (State.m_ScreenBR.y - State.m_ScreenTL.y),
3101 // column 3
3102 0,
3103 0,
3104 // column 4
3105 -((State.m_ScreenTL.x + State.m_ScreenBR.x) / (State.m_ScreenBR.x - State.m_ScreenTL.x)),
3106 -((State.m_ScreenTL.y + State.m_ScreenBR.y) / (State.m_ScreenBR.y - State.m_ScreenTL.y)),
3107 };
3108 }
3109
3110 [[nodiscard]] bool GetIsTextured(const CCommandBuffer::SState &State)
3111 {
3112 return State.m_Texture != -1;
3113 }
3114
3115 size_t GetAddressModeIndex(const CCommandBuffer::SState &State)
3116 {
3117 switch(State.m_WrapMode)
3118 {
3119 case EWrapMode::REPEAT:
3120 return VULKAN_BACKEND_ADDRESS_MODE_REPEAT;
3121 case EWrapMode::CLAMP:
3122 return VULKAN_BACKEND_ADDRESS_MODE_CLAMP_EDGES;
3123 default:
3124 dbg_assert_failed("Invalid wrap mode: %d", (int)State.m_WrapMode);
3125 };
3126 }
3127
3128 size_t GetBlendModeIndex(const CCommandBuffer::SState &State)
3129 {
3130 switch(State.m_BlendMode)
3131 {
3132 case EBlendMode::NONE:
3133 return VULKAN_BACKEND_BLEND_MODE_NONE;
3134 case EBlendMode::ALPHA:
3135 return VULKAN_BACKEND_BLEND_MODE_ALPHA;
3136 case EBlendMode::ADDITIVE:
3137 return VULKAN_BACKEND_BLEND_MODE_ADDITATIVE;
3138 default:
3139 dbg_assert_failed("Invalid blend mode: %d", (int)State.m_BlendMode);
3140 };
3141 }
3142
3143 size_t GetDynamicModeIndexFromState(const CCommandBuffer::SState &State) const
3144 {
3145 return (State.m_ClipEnable || m_HasDynamicViewport || m_VKSwapImgAndViewportExtent.m_HasForcedViewport) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE;
3146 }
3147
3148 size_t GetDynamicModeIndexFromExecBuffer(const SRenderCommandExecuteBuffer &ExecBuffer)
3149 {
3150 return (ExecBuffer.m_HasDynamicState) ? VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT : VULKAN_BACKEND_CLIP_MODE_NONE;
3151 }
3152
3153 VkPipeline &GetPipeline(SPipelineContainer &Container, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex)
3154 {
3155 return Container.m_aaaPipelines[BlendModeIndex][DynamicIndex][(size_t)IsTextured];
3156 }
3157
3158 VkPipelineLayout &GetPipeLayout(SPipelineContainer &Container, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex)
3159 {
3160 return Container.m_aaaPipelineLayouts[BlendModeIndex][DynamicIndex][(size_t)IsTextured];
3161 }
3162
3163 VkPipelineLayout &GetStandardPipeLayout(bool IsLineGeometry, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex)
3164 {
3165 if(IsLineGeometry)
3166 return GetPipeLayout(Container&: m_StandardLinePipeline, IsTextured, BlendModeIndex, DynamicIndex);
3167 else
3168 return GetPipeLayout(Container&: m_StandardPipeline, IsTextured, BlendModeIndex, DynamicIndex);
3169 }
3170
3171 VkPipeline &GetStandardPipe(bool IsLineGeometry, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex)
3172 {
3173 if(IsLineGeometry)
3174 return GetPipeline(Container&: m_StandardLinePipeline, IsTextured, BlendModeIndex, DynamicIndex);
3175 else
3176 return GetPipeline(Container&: m_StandardPipeline, IsTextured, BlendModeIndex, DynamicIndex);
3177 }
3178
3179 VkPipelineLayout &GetTileLayerPipeLayout(bool IsBorder, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex)
3180 {
3181 if(!IsBorder)
3182 return GetPipeLayout(Container&: m_TilePipeline, IsTextured, BlendModeIndex, DynamicIndex);
3183 else
3184 return GetPipeLayout(Container&: m_TileBorderPipeline, IsTextured, BlendModeIndex, DynamicIndex);
3185 }
3186
3187 VkPipeline &GetTileLayerPipe(bool IsBorder, bool IsTextured, size_t BlendModeIndex, size_t DynamicIndex)
3188 {
3189 if(!IsBorder)
3190 return GetPipeline(Container&: m_TilePipeline, IsTextured, BlendModeIndex, DynamicIndex);
3191 else
3192 return GetPipeline(Container&: m_TileBorderPipeline, IsTextured, BlendModeIndex, DynamicIndex);
3193 }
3194
3195 void GetStateIndices(const SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SState &State, bool &IsTextured, size_t &BlendModeIndex, size_t &DynamicIndex, size_t &AddressModeIndex)
3196 {
3197 IsTextured = GetIsTextured(State);
3198 AddressModeIndex = GetAddressModeIndex(State);
3199 BlendModeIndex = GetBlendModeIndex(State);
3200 DynamicIndex = GetDynamicModeIndexFromExecBuffer(ExecBuffer);
3201 }
3202
3203 void ExecBufferFillDynamicStates(const CCommandBuffer::SState &State, SRenderCommandExecuteBuffer &ExecBuffer)
3204 {
3205 // Workaround for a bug in molten-vk: https://github.com/KhronosGroup/MoltenVK/issues/2304
3206#ifdef CONF_PLATFORM_MACOS
3207 auto HasDynamicState = true;
3208#else
3209 size_t DynamicStateIndex = GetDynamicModeIndexFromState(State);
3210 auto HasDynamicState = DynamicStateIndex == VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT;
3211#endif
3212
3213 if(HasDynamicState)
3214 {
3215 VkViewport Viewport;
3216 if(m_HasDynamicViewport)
3217 {
3218 Viewport.x = (float)m_DynamicViewportOffset.x;
3219 Viewport.y = (float)m_DynamicViewportOffset.y;
3220 Viewport.width = (float)m_DynamicViewportSize.width;
3221 Viewport.height = (float)m_DynamicViewportSize.height;
3222 Viewport.minDepth = 0.0f;
3223 Viewport.maxDepth = 1.0f;
3224 }
3225 // else check if there is a forced viewport
3226 else if(m_VKSwapImgAndViewportExtent.m_HasForcedViewport)
3227 {
3228 Viewport.x = 0.0f;
3229 Viewport.y = 0.0f;
3230 Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_ForcedViewport.width;
3231 Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_ForcedViewport.height;
3232 Viewport.minDepth = 0.0f;
3233 Viewport.maxDepth = 1.0f;
3234 }
3235 else
3236 {
3237 Viewport.x = 0.0f;
3238 Viewport.y = 0.0f;
3239 Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width;
3240 Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height;
3241 Viewport.minDepth = 0.0f;
3242 Viewport.maxDepth = 1.0f;
3243 }
3244
3245 VkRect2D Scissor;
3246 // convert from OGL to vulkan clip
3247
3248 // the scissor always assumes the presented viewport, because the front-end keeps the calculation
3249 // for the forced viewport in sync
3250 auto ScissorViewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport();
3251 if(State.m_ClipEnable)
3252 {
3253 int32_t ScissorY = (int32_t)ScissorViewport.height - ((int32_t)State.m_ClipY + (int32_t)State.m_ClipH);
3254 uint32_t ScissorH = (int32_t)State.m_ClipH;
3255 Scissor.offset = {.x: (int32_t)State.m_ClipX, .y: ScissorY};
3256 Scissor.extent = {.width: (uint32_t)State.m_ClipW, .height: ScissorH};
3257 }
3258 else
3259 {
3260 Scissor.offset = {.x: 0, .y: 0};
3261 Scissor.extent = {.width: ScissorViewport.width, .height: ScissorViewport.height};
3262 }
3263
3264 // if there is a dynamic viewport make sure the scissor data is scaled down to that
3265 if(m_HasDynamicViewport)
3266 {
3267 Scissor.offset.x = (int32_t)(((float)Scissor.offset.x / (float)ScissorViewport.width) * (float)m_DynamicViewportSize.width) + m_DynamicViewportOffset.x;
3268 Scissor.offset.y = (int32_t)(((float)Scissor.offset.y / (float)ScissorViewport.height) * (float)m_DynamicViewportSize.height) + m_DynamicViewportOffset.y;
3269 Scissor.extent.width = (uint32_t)(((float)Scissor.extent.width / (float)ScissorViewport.width) * (float)m_DynamicViewportSize.width);
3270 Scissor.extent.height = (uint32_t)(((float)Scissor.extent.height / (float)ScissorViewport.height) * (float)m_DynamicViewportSize.height);
3271 }
3272
3273 Viewport.x = std::clamp(val: Viewport.x, lo: 0.0f, hi: std::numeric_limits<decltype(Viewport.x)>::max());
3274 Viewport.y = std::clamp(val: Viewport.y, lo: 0.0f, hi: std::numeric_limits<decltype(Viewport.y)>::max());
3275
3276 Scissor.offset.x = std::clamp(val: Scissor.offset.x, lo: 0, hi: std::numeric_limits<decltype(Scissor.offset.x)>::max());
3277 Scissor.offset.y = std::clamp(val: Scissor.offset.y, lo: 0, hi: std::numeric_limits<decltype(Scissor.offset.y)>::max());
3278
3279 ExecBuffer.m_HasDynamicState = true;
3280 ExecBuffer.m_Viewport = Viewport;
3281 ExecBuffer.m_Scissor = Scissor;
3282 }
3283 else
3284 {
3285 ExecBuffer.m_HasDynamicState = false;
3286 }
3287 }
3288
3289 void BindPipeline(size_t RenderThreadIndex, VkCommandBuffer &CommandBuffer, SRenderCommandExecuteBuffer &ExecBuffer, VkPipeline &BindingPipe, const CCommandBuffer::SState &State)
3290 {
3291 if(m_vLastPipeline[RenderThreadIndex] != BindingPipe)
3292 {
3293 vkCmdBindPipeline(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline: BindingPipe);
3294 m_vLastPipeline[RenderThreadIndex] = BindingPipe;
3295 }
3296
3297 size_t DynamicStateIndex = GetDynamicModeIndexFromExecBuffer(ExecBuffer);
3298 if(DynamicStateIndex == VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT)
3299 {
3300 vkCmdSetViewport(commandBuffer: CommandBuffer, firstViewport: 0, viewportCount: 1, pViewports: &ExecBuffer.m_Viewport);
3301 vkCmdSetScissor(commandBuffer: CommandBuffer, firstScissor: 0, scissorCount: 1, pScissors: &ExecBuffer.m_Scissor);
3302 }
3303 }
3304
3305 /**************************
3306 * RENDERING IMPLEMENTATION
3307 ***************************/
3308
3309 void RenderTileLayer_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, size_t DrawCalls, const CCommandBuffer::SState &State, size_t BufferContainerIndex)
3310 {
3311 size_t BufferObjectIndex = (size_t)m_vBufferContainers[BufferContainerIndex].m_BufferObjectIndex;
3312 const auto &BufferObject = m_vBufferObjects[BufferObjectIndex];
3313
3314 ExecBuffer.m_Buffer = BufferObject.m_CurBuffer;
3315 ExecBuffer.m_BufferOff = BufferObject.m_CurBufferOffset;
3316
3317 bool IsTextured = GetIsTextured(State);
3318 if(IsTextured)
3319 {
3320 ExecBuffer.m_aDescriptors[0] = m_vTextures[State.m_Texture].m_VKStandard3DTexturedDescrSet;
3321 }
3322
3323 ExecBuffer.m_IndexBuffer = m_RenderIndexBuffer;
3324
3325 ExecBuffer.m_EstimatedRenderCallCount = DrawCalls;
3326
3327 ExecBufferFillDynamicStates(State, ExecBuffer);
3328 }
3329
3330 [[nodiscard]] bool RenderTileLayer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SState &State, bool IsBorder, const GL_SColorf &Color, const vec2 &Scale, const vec2 &Off, size_t IndicesDrawNum, char *const *pIndicesOffsets, const unsigned int *pDrawCount)
3331 {
3332 std::array<float, (size_t)4 * 2> m;
3333 GetStateMatrix(State, Matrix&: m);
3334
3335 bool IsTextured;
3336 size_t BlendModeIndex;
3337 size_t DynamicIndex;
3338 size_t AddressModeIndex;
3339 GetStateIndices(ExecBuffer, State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
3340 auto &PipeLayout = GetTileLayerPipeLayout(IsBorder, IsTextured, BlendModeIndex, DynamicIndex);
3341 auto &PipeLine = GetTileLayerPipe(IsBorder, IsTextured, BlendModeIndex, DynamicIndex);
3342
3343 VkCommandBuffer *pCommandBuffer;
3344 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
3345 return false;
3346 auto &CommandBuffer = *pCommandBuffer;
3347
3348 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State);
3349
3350 std::array<VkBuffer, 1> aVertexBuffers = {ExecBuffer.m_Buffer};
3351 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)ExecBuffer.m_BufferOff};
3352 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
3353
3354 if(IsTextured)
3355 {
3356 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
3357 }
3358
3359 SUniformTileGPosBorder VertexPushConstants;
3360 size_t VertexPushConstantSize = sizeof(SUniformTileGPos);
3361 SUniformTileGVertColor FragPushConstants;
3362 size_t FragPushConstantSize = sizeof(SUniformTileGVertColor);
3363
3364 mem_copy(dest: VertexPushConstants.m_aPos, source: m.data(), size: m.size() * sizeof(float));
3365 FragPushConstants = Color;
3366
3367 if(IsBorder)
3368 {
3369 VertexPushConstants.m_Scale = Scale;
3370 VertexPushConstants.m_Offset = Off;
3371 VertexPushConstantSize = sizeof(SUniformTileGPosBorder);
3372 }
3373
3374 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: VertexPushConstantSize, pValues: &VertexPushConstants);
3375 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, offset: sizeof(SUniformTileGPosBorder) + sizeof(SUniformTileGVertColorAlign), size: FragPushConstantSize, pValues: &FragPushConstants);
3376
3377 size_t DrawCount = IndicesDrawNum;
3378 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: 0, indexType: VK_INDEX_TYPE_UINT32);
3379 for(size_t i = 0; i < DrawCount; ++i)
3380 {
3381 VkDeviceSize IndexOffset = (VkDeviceSize)((ptrdiff_t)pIndicesOffsets[i] / sizeof(uint32_t));
3382
3383 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(pDrawCount[i]), instanceCount: 1, firstIndex: IndexOffset, vertexOffset: 0, firstInstance: 0);
3384 }
3385
3386 return true;
3387 }
3388
3389 template<typename TName, bool Is3DTextured>
3390 [[nodiscard]] bool RenderStandard(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SState &State, EPrimitiveType PrimType, const TName *pVertices, int PrimitiveCount)
3391 {
3392 std::array<float, (size_t)4 * 2> m;
3393 GetStateMatrix(State, Matrix&: m);
3394
3395 bool IsLineGeometry = PrimType == EPrimitiveType::LINES;
3396
3397 bool IsTextured;
3398 size_t BlendModeIndex;
3399 size_t DynamicIndex;
3400 size_t AddressModeIndex;
3401 GetStateIndices(ExecBuffer, State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
3402 auto &PipeLayout = Is3DTextured ? GetPipeLayout(Container&: m_Standard3DPipeline, IsTextured, BlendModeIndex, DynamicIndex) : GetStandardPipeLayout(IsLineGeometry, IsTextured, BlendModeIndex, DynamicIndex);
3403 auto &PipeLine = Is3DTextured ? GetPipeline(Container&: m_Standard3DPipeline, IsTextured, BlendModeIndex, DynamicIndex) : GetStandardPipe(IsLineGeometry, IsTextured, BlendModeIndex, DynamicIndex);
3404
3405 VkCommandBuffer *pCommandBuffer;
3406 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
3407 return false;
3408 auto &CommandBuffer = *pCommandBuffer;
3409
3410 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State);
3411
3412 size_t VertPerPrim = 2;
3413 bool IsIndexed = false;
3414 if(PrimType == EPrimitiveType::QUADS)
3415 {
3416 VertPerPrim = 4;
3417 IsIndexed = true;
3418 }
3419 else if(PrimType == EPrimitiveType::TRIANGLES)
3420 {
3421 VertPerPrim = 3;
3422 }
3423
3424 VkBuffer VKBuffer;
3425 SDeviceMemoryBlock VKBufferMem;
3426 size_t BufferOff = 0;
3427 if(!CreateStreamVertexBuffer(RenderThreadIndex: ExecBuffer.m_ThreadIndex, NewBuffer&: VKBuffer, NewBufferMem&: VKBufferMem, BufferOffset&: BufferOff, pData: pVertices, DataSize: VertPerPrim * sizeof(TName) * PrimitiveCount))
3428 return false;
3429
3430 std::array<VkBuffer, 1> aVertexBuffers = {VKBuffer};
3431 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)BufferOff};
3432 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
3433
3434 if(IsIndexed)
3435 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: 0, indexType: VK_INDEX_TYPE_UINT32);
3436
3437 if(IsTextured)
3438 {
3439 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
3440 }
3441
3442 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: sizeof(SUniformGPos), pValues: m.data());
3443
3444 if(IsIndexed)
3445 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(PrimitiveCount * 6), instanceCount: 1, firstIndex: 0, vertexOffset: 0, firstInstance: 0);
3446 else
3447 vkCmdDraw(commandBuffer: CommandBuffer, vertexCount: static_cast<uint32_t>(PrimitiveCount * VertPerPrim), instanceCount: 1, firstVertex: 0, firstInstance: 0);
3448
3449 return true;
3450 }
3451
3452public:
3453 CCommandProcessorFragment_Vulkan()
3454 {
3455 m_vTextures.reserve(n: CCommandBuffer::MAX_TEXTURES);
3456 }
3457
3458 /************************
3459 * VULKAN SETUP CODE
3460 ************************/
3461
3462 [[nodiscard]] bool GetVulkanExtensions(SDL_Window *pWindow, std::vector<std::string> &vVKExtensions)
3463 {
3464 unsigned int ExtCount = 0;
3465 if(!SDL_Vulkan_GetInstanceExtensions(window: pWindow, pCount: &ExtCount, pNames: nullptr))
3466 {
3467 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not get instance extensions from SDL.");
3468 return false;
3469 }
3470
3471 std::vector<const char *> vExtensionList(ExtCount);
3472 if(!SDL_Vulkan_GetInstanceExtensions(window: pWindow, pCount: &ExtCount, pNames: vExtensionList.data()))
3473 {
3474 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not get instance extensions from SDL.");
3475 return false;
3476 }
3477
3478 vVKExtensions.reserve(n: ExtCount);
3479 for(uint32_t i = 0; i < ExtCount; i++)
3480 {
3481 vVKExtensions.emplace_back(args&: vExtensionList[i]);
3482 }
3483
3484 return true;
3485 }
3486
3487 std::set<std::string> OurVKLayers()
3488 {
3489 std::set<std::string> OurLayers;
3490
3491 if(g_Config.m_DbgGfx == DEBUG_GFX_MODE_MINIMUM || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL)
3492 {
3493 OurLayers.emplace(args: "VK_LAYER_KHRONOS_validation");
3494 // deprecated, but VK_LAYER_KHRONOS_validation was released after vulkan 1.1
3495 OurLayers.emplace(args: "VK_LAYER_LUNARG_standard_validation");
3496 }
3497
3498 return OurLayers;
3499 }
3500
3501 std::set<std::string> OurDeviceExtensions()
3502 {
3503 std::set<std::string> OurExt;
3504 OurExt.emplace(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
3505 return OurExt;
3506 }
3507
3508 std::vector<VkImageUsageFlags> OurImageUsages()
3509 {
3510 std::vector<VkImageUsageFlags> vImgUsages;
3511
3512 vImgUsages.emplace_back(args: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
3513 vImgUsages.emplace_back(args: VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
3514
3515 return vImgUsages;
3516 }
3517
3518 [[nodiscard]] bool GetVulkanLayers(std::vector<std::string> &vVKLayers)
3519 {
3520 uint32_t LayerCount = 0;
3521 VkResult Res = vkEnumerateInstanceLayerProperties(pPropertyCount: &LayerCount, NULL);
3522 if(Res != VK_SUCCESS)
3523 {
3524 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not get Vulkan layers.");
3525 return false;
3526 }
3527
3528 std::vector<VkLayerProperties> vVKInstanceLayers(LayerCount);
3529 Res = vkEnumerateInstanceLayerProperties(pPropertyCount: &LayerCount, pProperties: vVKInstanceLayers.data());
3530 if(Res != VK_SUCCESS)
3531 {
3532 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not get Vulkan layers.");
3533 return false;
3534 }
3535
3536 std::set<std::string> ReqLayerNames = OurVKLayers();
3537 vVKLayers.clear();
3538 for(const auto &LayerName : vVKInstanceLayers)
3539 {
3540 if(ReqLayerNames.contains(x: std::string(LayerName.layerName)))
3541 {
3542 vVKLayers.emplace_back(args: LayerName.layerName);
3543 }
3544 }
3545
3546 return true;
3547 }
3548
3549 bool IsGpuDenied(uint32_t Vendor, uint32_t DriverVersion, uint32_t ApiMajor, uint32_t ApiMinor, uint32_t ApiPatch)
3550 {
3551#ifdef CONF_FAMILY_WINDOWS
3552 // AMD
3553 if(0x1002 == Vendor)
3554 {
3555 auto Major = (DriverVersion >> 22);
3556 auto Minor = (DriverVersion >> 12) & 0x3ff;
3557 auto Patch = DriverVersion & 0xfff;
3558
3559 return Major == 2 && Minor == 0 && Patch > 137 && Patch < 220 && ((ApiMajor <= 1 && ApiMinor < 3) || (ApiMajor <= 1 && ApiMinor == 3 && ApiPatch < 206));
3560 }
3561#endif
3562 return false;
3563 }
3564
3565 [[nodiscard]] bool CreateVulkanInstance(const std::vector<std::string> &vVKLayers, const std::vector<std::string> &vVKExtensions, bool TryDebugExtensions)
3566 {
3567 std::vector<const char *> vLayersCStr;
3568 vLayersCStr.reserve(n: vVKLayers.size());
3569 for(const auto &Layer : vVKLayers)
3570 vLayersCStr.emplace_back(args: Layer.c_str());
3571
3572 std::vector<const char *> vExtCStr;
3573 vExtCStr.reserve(n: vVKExtensions.size() + 1);
3574 for(const auto &Ext : vVKExtensions)
3575 vExtCStr.emplace_back(args: Ext.c_str());
3576
3577#ifdef VK_EXT_debug_utils
3578 if(TryDebugExtensions && (g_Config.m_DbgGfx == DEBUG_GFX_MODE_MINIMUM || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL))
3579 {
3580 // debug message support
3581 vExtCStr.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
3582 }
3583#endif
3584
3585 VkApplicationInfo VKAppInfo = {};
3586 VKAppInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
3587 VKAppInfo.pNext = NULL;
3588 VKAppInfo.pApplicationName = "DDNet";
3589 VKAppInfo.applicationVersion = 1;
3590 VKAppInfo.pEngineName = "DDNet-Vulkan";
3591 VKAppInfo.engineVersion = 1;
3592 VKAppInfo.apiVersion = VK_API_VERSION_1_1;
3593
3594 void *pExt = nullptr;
3595#if defined(VK_EXT_validation_features) && VK_EXT_VALIDATION_FEATURES_SPEC_VERSION >= 5
3596 VkValidationFeaturesEXT Features = {};
3597 std::array<VkValidationFeatureEnableEXT, 2> aEnables = {VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT, VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT};
3598 if(TryDebugExtensions && (g_Config.m_DbgGfx == DEBUG_GFX_MODE_AFFECTS_PERFORMANCE || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL))
3599 {
3600 Features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
3601 Features.enabledValidationFeatureCount = aEnables.size();
3602 Features.pEnabledValidationFeatures = aEnables.data();
3603
3604 pExt = &Features;
3605 }
3606#endif
3607
3608 VkInstanceCreateInfo VKInstanceInfo = {};
3609 VKInstanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
3610 VKInstanceInfo.pNext = pExt;
3611 VKInstanceInfo.flags = 0;
3612 VKInstanceInfo.pApplicationInfo = &VKAppInfo;
3613 VKInstanceInfo.enabledExtensionCount = static_cast<uint32_t>(vExtCStr.size());
3614 VKInstanceInfo.ppEnabledExtensionNames = vExtCStr.data();
3615 VKInstanceInfo.enabledLayerCount = static_cast<uint32_t>(vLayersCStr.size());
3616 VKInstanceInfo.ppEnabledLayerNames = vLayersCStr.data();
3617
3618 bool TryAgain = false;
3619
3620 VkResult Res = vkCreateInstance(pCreateInfo: &VKInstanceInfo, NULL, pInstance: &m_VKInstance);
3621 const char *pCritErrorMsg = CheckVulkanCriticalError(CallResult: Res);
3622 if(pCritErrorMsg != nullptr)
3623 {
3624 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating instance failed.", pErrStrExtra: pCritErrorMsg);
3625 return false;
3626 }
3627 else if(Res == VK_ERROR_LAYER_NOT_PRESENT || Res == VK_ERROR_EXTENSION_NOT_PRESENT)
3628 TryAgain = true;
3629
3630 if(TryAgain && TryDebugExtensions)
3631 return CreateVulkanInstance(vVKLayers, vVKExtensions, TryDebugExtensions: false);
3632
3633 return true;
3634 }
3635
3636 STWGraphicGpu::ETWGraphicsGpuType VKGPUTypeToGraphicsGpuType(VkPhysicalDeviceType VKGPUType)
3637 {
3638 if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
3639 return STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_DISCRETE;
3640 else if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
3641 return STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_INTEGRATED;
3642 else if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU)
3643 return STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_VIRTUAL;
3644 else if(VKGPUType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_CPU)
3645 return STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_CPU;
3646
3647 return STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_CPU;
3648 }
3649
3650 static void GetVendorString(uint32_t VendorId, char *pVendorStr, size_t Size)
3651 {
3652 switch(VendorId)
3653 {
3654 case 0x1002:
3655 case 0x1022:
3656 str_copy(dst: pVendorStr, src: "AMD", dst_size: Size);
3657 break;
3658 case 0x1010:
3659 str_copy(dst: pVendorStr, src: "ImgTec", dst_size: Size);
3660 break;
3661 case 0x106B:
3662 str_copy(dst: pVendorStr, src: "Apple", dst_size: Size);
3663 break;
3664 case 0x10DE:
3665 str_copy(dst: pVendorStr, src: "NVIDIA", dst_size: Size);
3666 break;
3667 case 0x13B5:
3668 str_copy(dst: pVendorStr, src: "ARM", dst_size: Size);
3669 break;
3670 case 0x5143:
3671 str_copy(dst: pVendorStr, src: "Qualcomm", dst_size: Size);
3672 break;
3673 case 0x8086:
3674 str_copy(dst: pVendorStr, src: "Intel", dst_size: Size);
3675 break;
3676 case 0x10005:
3677 str_copy(dst: pVendorStr, src: "Mesa", dst_size: Size);
3678 break;
3679 default:
3680 log_warn("gfx/vulkan", "Unknown GPU vendor ID %08X.", VendorId);
3681 str_format(buffer: pVendorStr, buffer_size: Size, format: "Unknown (%08X)", VendorId);
3682 break;
3683 }
3684 }
3685
3686 // from: https://github.com/SaschaWillems/vulkan.gpuinfo.org/blob/5c3986798afc39d736b825bf8a5fbf92b8d9ed49/includes/functions.php#L364
3687 void FormatDriverVersion(char (&aDriverVersion)[256], uint32_t DriverVersion, uint32_t VendorId)
3688 {
3689 if(VendorId == 0x10DE) // NVIDIA
3690 {
3691 str_format(buffer: aDriverVersion, buffer_size: std::size(aDriverVersion), format: "%d.%d.%d.%d",
3692 (DriverVersion >> 22) & 0x3ff,
3693 (DriverVersion >> 14) & 0x0ff,
3694 (DriverVersion >> 6) & 0x0ff,
3695 (DriverVersion) & 0x003f);
3696 }
3697#ifdef CONF_FAMILY_WINDOWS
3698 else if(VendorId == 0x8086) // Windows with Intel only
3699 {
3700 str_format(aDriverVersion, std::size(aDriverVersion),
3701 "%d.%d",
3702 (DriverVersion >> 14),
3703 (DriverVersion) & 0x3fff);
3704 }
3705#endif
3706 else
3707 {
3708 // Use Vulkan version conventions if vendor mapping is not available
3709 str_format(buffer: aDriverVersion, buffer_size: std::size(aDriverVersion),
3710 format: "%d.%d.%d",
3711 (DriverVersion >> 22),
3712 (DriverVersion >> 12) & 0x3ff,
3713 DriverVersion & 0xfff);
3714 }
3715 }
3716
3717 [[nodiscard]] bool SelectGpu(char *pRendererName, char *pVendorName, char *pVersionName)
3718 {
3719 uint32_t DevicesCount = 0;
3720 auto Res = vkEnumeratePhysicalDevices(instance: m_VKInstance, pPhysicalDeviceCount: &DevicesCount, pPhysicalDevices: nullptr);
3721 if(Res != VK_SUCCESS)
3722 {
3723 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: CheckVulkanCriticalError(CallResult: Res));
3724 return false;
3725 }
3726 if(DevicesCount == 0)
3727 {
3728 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "No Vulkan compatible devices found.");
3729 return false;
3730 }
3731
3732 std::vector<VkPhysicalDevice> vDeviceList(DevicesCount);
3733 Res = vkEnumeratePhysicalDevices(instance: m_VKInstance, pPhysicalDeviceCount: &DevicesCount, pPhysicalDevices: vDeviceList.data());
3734 if(Res != VK_SUCCESS && Res != VK_INCOMPLETE)
3735 {
3736 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: CheckVulkanCriticalError(CallResult: Res));
3737 return false;
3738 }
3739 if(DevicesCount == 0)
3740 {
3741 SetWarning(WarningType: EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED_MISSING_INTEGRATED_GPU_DRIVER, pWarning: "No Vulkan compatible devices found.");
3742 return false;
3743 }
3744 // make sure to use the correct amount of devices available
3745 // the amount of physical devices can be smaller than the amount of devices reported
3746 // see vkEnumeratePhysicalDevices for details
3747 vDeviceList.resize(sz: DevicesCount);
3748
3749 size_t Index = 0;
3750 std::vector<VkPhysicalDeviceProperties> vDevicePropList(vDeviceList.size());
3751 m_pGpuList->m_vGpus.reserve(n: vDeviceList.size());
3752
3753 size_t FoundDeviceIndex = 0;
3754
3755 STWGraphicGpu::ETWGraphicsGpuType AutoGpuType = STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_INVALID;
3756
3757 bool IsAutoGpu = str_comp(a: g_Config.m_GfxGpuName, b: "auto") == 0;
3758
3759 bool UserSelectedGpuChosen = false;
3760 for(auto &CurDevice : vDeviceList)
3761 {
3762 vkGetPhysicalDeviceProperties(physicalDevice: CurDevice, pProperties: &(vDevicePropList[Index]));
3763
3764 auto &DeviceProp = vDevicePropList[Index];
3765
3766 STWGraphicGpu::ETWGraphicsGpuType GPUType = VKGPUTypeToGraphicsGpuType(VKGPUType: DeviceProp.deviceType);
3767
3768 int DevApiMajor = (int)VK_API_VERSION_MAJOR(DeviceProp.apiVersion);
3769 int DevApiMinor = (int)VK_API_VERSION_MINOR(DeviceProp.apiVersion);
3770 int DevApiPatch = (int)VK_API_VERSION_PATCH(DeviceProp.apiVersion);
3771
3772 auto IsDenied = CCommandProcessorFragment_Vulkan::IsGpuDenied(Vendor: DeviceProp.vendorID, DriverVersion: DeviceProp.driverVersion, ApiMajor: DevApiMajor, ApiMinor: DevApiMinor, ApiPatch: DevApiPatch);
3773 if((DevApiMajor > BACKEND_VULKAN_VERSION_MAJOR || (DevApiMajor == BACKEND_VULKAN_VERSION_MAJOR && DevApiMinor >= BACKEND_VULKAN_VERSION_MINOR)) && !IsDenied)
3774 {
3775 STWGraphicGpu::STWGraphicGpuItem NewGpu;
3776 str_copy(dst&: NewGpu.m_aName, src: DeviceProp.deviceName);
3777 NewGpu.m_GpuType = GPUType;
3778 m_pGpuList->m_vGpus.push_back(x: NewGpu);
3779
3780 // We always decide what the 'auto' GPU would be, even if user is forcing a GPU by name in config
3781 // Reminder: A worse GPU enumeration has a higher value than a better GPU enumeration, thus the '>'
3782 if(AutoGpuType > STWGraphicGpu::ETWGraphicsGpuType::GRAPHICS_GPU_TYPE_INTEGRATED)
3783 {
3784 str_copy(dst&: m_pGpuList->m_AutoGpu.m_aName, src: DeviceProp.deviceName);
3785 m_pGpuList->m_AutoGpu.m_GpuType = GPUType;
3786
3787 AutoGpuType = GPUType;
3788
3789 if(IsAutoGpu)
3790 FoundDeviceIndex = Index;
3791 }
3792 // We only select the first GPU that matches, because it comes first in the enumeration array, it's preferred by the system
3793 // Reminder: We can't break the cycle here if the name matches because we need to choose the best GPU for 'auto' mode
3794 if(!IsAutoGpu && !UserSelectedGpuChosen && str_comp(a: DeviceProp.deviceName, b: g_Config.m_GfxGpuName) == 0)
3795 {
3796 FoundDeviceIndex = Index;
3797 UserSelectedGpuChosen = true;
3798 }
3799 }
3800 Index++;
3801 }
3802
3803 if(m_pGpuList->m_vGpus.empty())
3804 {
3805 SetWarning(WarningType: EGfxWarningType::GFX_WARNING_TYPE_INIT_FAILED_NO_DEVICE_WITH_REQUIRED_VERSION, pWarning: "No devices with required Vulkan version found.");
3806 return false;
3807 }
3808
3809 {
3810 auto &DeviceProp = vDevicePropList[FoundDeviceIndex];
3811
3812 int DevApiMajor = (int)VK_API_VERSION_MAJOR(DeviceProp.apiVersion);
3813 int DevApiMinor = (int)VK_API_VERSION_MINOR(DeviceProp.apiVersion);
3814 int DevApiPatch = (int)VK_API_VERSION_PATCH(DeviceProp.apiVersion);
3815
3816 str_copy(dst: pRendererName, src: DeviceProp.deviceName, dst_size: GPU_INFO_STRING_SIZE);
3817 GetVendorString(VendorId: DeviceProp.vendorID, pVendorStr: pVendorName, Size: GPU_INFO_STRING_SIZE);
3818 char aDriverVersion[256];
3819 FormatDriverVersion(aDriverVersion, DriverVersion: DeviceProp.driverVersion, VendorId: DeviceProp.vendorID);
3820 str_format(buffer: pVersionName, buffer_size: GPU_INFO_STRING_SIZE, format: "Vulkan %d.%d.%d (driver: %s)",
3821 DevApiMajor, DevApiMinor, DevApiPatch, aDriverVersion);
3822
3823 // get important device limits
3824 m_NonCoherentMemAlignment = DeviceProp.limits.nonCoherentAtomSize;
3825 m_OptimalImageCopyMemAlignment = DeviceProp.limits.optimalBufferCopyOffsetAlignment;
3826 m_MaxTextureSize = DeviceProp.limits.maxImageDimension2D;
3827 m_MaxSamplerAnisotropy = DeviceProp.limits.maxSamplerAnisotropy;
3828
3829 m_MinUniformAlign = DeviceProp.limits.minUniformBufferOffsetAlignment;
3830 m_MaxMultiSample = DeviceProp.limits.framebufferColorSampleCounts;
3831
3832 if(IsVerbose())
3833 {
3834 log_debug("gfx/vulkan", "Device prop: non-coherent align: %" PRIzu ", optimal image copy align: %" PRIzu ", max texture size: %u, max sampler anisotropy: %u",
3835 (size_t)m_NonCoherentMemAlignment, (size_t)m_OptimalImageCopyMemAlignment, m_MaxTextureSize, m_MaxSamplerAnisotropy);
3836 log_debug("gfx/vulkan", "Device prop: min uniform align: %u, multi sample: %u",
3837 m_MinUniformAlign, (uint32_t)m_MaxMultiSample);
3838 }
3839 }
3840
3841 VkPhysicalDevice CurDevice = vDeviceList[FoundDeviceIndex];
3842
3843 uint32_t FamQueueCount = 0;
3844 vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice: CurDevice, pQueueFamilyPropertyCount: &FamQueueCount, pQueueFamilyProperties: nullptr);
3845 if(FamQueueCount == 0)
3846 {
3847 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "No Vulkan queue family properties found.");
3848 return false;
3849 }
3850
3851 std::vector<VkQueueFamilyProperties> vQueuePropList(FamQueueCount);
3852 vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice: CurDevice, pQueueFamilyPropertyCount: &FamQueueCount, pQueueFamilyProperties: vQueuePropList.data());
3853
3854 uint32_t QueueNodeIndex = std::numeric_limits<uint32_t>::max();
3855 for(uint32_t i = 0; i < FamQueueCount; i++)
3856 {
3857 if(vQueuePropList[i].queueCount > 0 && (vQueuePropList[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
3858 {
3859 QueueNodeIndex = i;
3860 }
3861 /*if(vQueuePropList[i].queueCount > 0 && (vQueuePropList[i].queueFlags & VK_QUEUE_COMPUTE_BIT))
3862 {
3863 QueueNodeIndex = i;
3864 }*/
3865 }
3866
3867 if(QueueNodeIndex == std::numeric_limits<uint32_t>::max())
3868 {
3869 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "No Vulkan queue found that matches the requirements: graphics queue.");
3870 return false;
3871 }
3872
3873 m_VKGPU = CurDevice;
3874 m_VKGraphicsQueueIndex = QueueNodeIndex;
3875 return true;
3876 }
3877
3878 [[nodiscard]] bool CreateLogicalDevice(const std::vector<std::string> &vVKLayers)
3879 {
3880 std::vector<const char *> vLayerCNames;
3881 vLayerCNames.reserve(n: vVKLayers.size());
3882 for(const auto &Layer : vVKLayers)
3883 vLayerCNames.emplace_back(args: Layer.c_str());
3884
3885 uint32_t DevPropCount = 0;
3886 if(vkEnumerateDeviceExtensionProperties(physicalDevice: m_VKGPU, NULL, pPropertyCount: &DevPropCount, NULL) != VK_SUCCESS)
3887 {
3888 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Querying logical device extension properties failed.");
3889 return false;
3890 }
3891
3892 std::vector<VkExtensionProperties> vDevPropList(DevPropCount);
3893 if(vkEnumerateDeviceExtensionProperties(physicalDevice: m_VKGPU, NULL, pPropertyCount: &DevPropCount, pProperties: vDevPropList.data()) != VK_SUCCESS)
3894 {
3895 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Querying logical device extension properties failed.");
3896 return false;
3897 }
3898
3899 std::vector<const char *> vDevPropCNames;
3900 std::set<std::string> OurDevExt = OurDeviceExtensions();
3901
3902 for(const auto &CurExtProp : vDevPropList)
3903 {
3904 if(OurDevExt.contains(x: std::string(CurExtProp.extensionName)))
3905 {
3906 vDevPropCNames.emplace_back(args: CurExtProp.extensionName);
3907 }
3908 }
3909
3910 VkDeviceQueueCreateInfo VKQueueCreateInfo;
3911 VKQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
3912 VKQueueCreateInfo.queueFamilyIndex = m_VKGraphicsQueueIndex;
3913 VKQueueCreateInfo.queueCount = 1;
3914 float QueuePrio = 1.0f;
3915 VKQueueCreateInfo.pQueuePriorities = &QueuePrio;
3916 VKQueueCreateInfo.pNext = NULL;
3917 VKQueueCreateInfo.flags = 0;
3918
3919 VkDeviceCreateInfo VKCreateInfo;
3920 VKCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
3921 VKCreateInfo.queueCreateInfoCount = 1;
3922 VKCreateInfo.pQueueCreateInfos = &VKQueueCreateInfo;
3923 VKCreateInfo.ppEnabledLayerNames = vLayerCNames.data();
3924 VKCreateInfo.enabledLayerCount = static_cast<uint32_t>(vLayerCNames.size());
3925 VKCreateInfo.ppEnabledExtensionNames = vDevPropCNames.data();
3926 VKCreateInfo.enabledExtensionCount = static_cast<uint32_t>(vDevPropCNames.size());
3927 VKCreateInfo.pNext = NULL;
3928 VKCreateInfo.pEnabledFeatures = NULL;
3929 VKCreateInfo.flags = 0;
3930
3931 if(vkCreateDevice(physicalDevice: m_VKGPU, pCreateInfo: &VKCreateInfo, pAllocator: nullptr, pDevice: &m_VKDevice) != VK_SUCCESS)
3932 {
3933 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Logical device could not be created.");
3934 return false;
3935 }
3936
3937 return true;
3938 }
3939
3940 [[nodiscard]] bool CreateSurface(SDL_Window *pWindow)
3941 {
3942 if(!SDL_Vulkan_CreateSurface(window: pWindow, instance: m_VKInstance, surface: &m_VKPresentSurface))
3943 {
3944 log_error("gfx/vulkan", "Failed to create surface. SDL error: %s", SDL_GetError());
3945 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating a Vulkan surface for the SDL window failed.");
3946 return false;
3947 }
3948
3949 VkBool32 IsSupported = false;
3950 vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice: m_VKGPU, queueFamilyIndex: m_VKGraphicsQueueIndex, surface: m_VKPresentSurface, pSupported: &IsSupported);
3951 if(!IsSupported)
3952 {
3953 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "The device surface does not support presenting the framebuffer to a screen. Maybe the wrong GPU was selected?");
3954 return false;
3955 }
3956
3957 return true;
3958 }
3959
3960 void DestroySurface()
3961 {
3962 vkDestroySurfaceKHR(instance: m_VKInstance, surface: m_VKPresentSurface, pAllocator: nullptr);
3963 }
3964
3965 [[nodiscard]] bool GetPresentationMode(VkPresentModeKHR &VKIOMode)
3966 {
3967 uint32_t PresentModeCount = 0;
3968 if(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice: m_VKGPU, surface: m_VKPresentSurface, pPresentModeCount: &PresentModeCount, NULL) != VK_SUCCESS)
3969 {
3970 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "The device surface presentation modes could not be fetched.");
3971 return false;
3972 }
3973
3974 std::vector<VkPresentModeKHR> vPresentModeList(PresentModeCount);
3975 if(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice: m_VKGPU, surface: m_VKPresentSurface, pPresentModeCount: &PresentModeCount, pPresentModes: vPresentModeList.data()) != VK_SUCCESS)
3976 {
3977 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "The device surface presentation modes could not be fetched.");
3978 return false;
3979 }
3980
3981 VKIOMode = g_Config.m_GfxVsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR;
3982 for(const auto &Mode : vPresentModeList)
3983 {
3984 if(Mode == VKIOMode)
3985 return true;
3986 }
3987
3988 log_warn("gfx/vulkan", "Requested presentation mode was not available. Falling back to mailbox / FIFO relaxed.");
3989 VKIOMode = g_Config.m_GfxVsync ? VK_PRESENT_MODE_FIFO_RELAXED_KHR : VK_PRESENT_MODE_MAILBOX_KHR;
3990 for(const auto &Mode : vPresentModeList)
3991 {
3992 if(Mode == VKIOMode)
3993 return true;
3994 }
3995
3996 log_warn("gfx/vulkan", "Requested presentation mode was not available. Using first available.");
3997 if(PresentModeCount > 0)
3998 VKIOMode = vPresentModeList[0];
3999
4000 return true;
4001 }
4002
4003 [[nodiscard]] bool GetSurfaceProperties(VkSurfaceCapabilitiesKHR &VKSurfCapabilities)
4004 {
4005 if(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice: m_VKGPU, surface: m_VKPresentSurface, pSurfaceCapabilities: &VKSurfCapabilities) != VK_SUCCESS)
4006 {
4007 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "The device surface capabilities could not be fetched.");
4008 return false;
4009 }
4010 return true;
4011 }
4012
4013 uint32_t GetNumberOfSwapImages(const VkSurfaceCapabilitiesKHR &VKCapabilities)
4014 {
4015 uint32_t ImgNumber = VKCapabilities.minImageCount + 1;
4016 if(IsVerbose())
4017 {
4018 log_debug("gfx/vulkan", "Minimal swap image count: %u", VKCapabilities.minImageCount);
4019 }
4020 return (VKCapabilities.maxImageCount > 0 && ImgNumber > VKCapabilities.maxImageCount) ? VKCapabilities.maxImageCount : ImgNumber;
4021 }
4022
4023 SSwapImgViewportExtent GetSwapImageSize(const VkSurfaceCapabilitiesKHR &VKCapabilities)
4024 {
4025 VkExtent2D RetSize = {.width: m_CanvasWidth, .height: m_CanvasHeight};
4026
4027 if(VKCapabilities.currentExtent.width == std::numeric_limits<uint32_t>::max())
4028 {
4029 RetSize.width = std::clamp<uint32_t>(val: RetSize.width, lo: VKCapabilities.minImageExtent.width, hi: VKCapabilities.maxImageExtent.width);
4030 RetSize.height = std::clamp<uint32_t>(val: RetSize.height, lo: VKCapabilities.minImageExtent.height, hi: VKCapabilities.maxImageExtent.height);
4031 }
4032 else
4033 {
4034 RetSize = VKCapabilities.currentExtent;
4035 }
4036
4037 VkExtent2D AutoViewportExtent = RetSize;
4038 bool UsesForcedViewport = false;
4039 // keep this in sync with graphics_threaded AdjustViewport's check
4040 if(AutoViewportExtent.height > 4 * AutoViewportExtent.width / 5)
4041 {
4042 AutoViewportExtent.height = 4 * AutoViewportExtent.width / 5;
4043 UsesForcedViewport = true;
4044 }
4045
4046 SSwapImgViewportExtent Ext;
4047 Ext.m_SwapImageViewport = RetSize;
4048 Ext.m_ForcedViewport = AutoViewportExtent;
4049 Ext.m_HasForcedViewport = UsesForcedViewport;
4050
4051 return Ext;
4052 }
4053
4054 [[nodiscard]] bool GetImageUsage(const VkSurfaceCapabilitiesKHR &VKCapabilities, VkImageUsageFlags &VKOutUsage)
4055 {
4056 std::vector<VkImageUsageFlags> vOurImgUsages = OurImageUsages();
4057 if(vOurImgUsages.empty())
4058 {
4059 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Framebuffer image attachment types not supported.");
4060 return false;
4061 }
4062
4063 VKOutUsage = vOurImgUsages[0];
4064
4065 for(const auto &ImgUsage : vOurImgUsages)
4066 {
4067 VkImageUsageFlags ImgUsageFlags = ImgUsage & VKCapabilities.supportedUsageFlags;
4068 if(ImgUsageFlags != ImgUsage)
4069 {
4070 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Framebuffer image attachment types not supported.");
4071 return false;
4072 }
4073
4074 VKOutUsage = (VKOutUsage | ImgUsage);
4075 }
4076
4077 return true;
4078 }
4079
4080 VkSurfaceTransformFlagBitsKHR GetTransform(const VkSurfaceCapabilitiesKHR &VKCapabilities)
4081 {
4082 if(VKCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
4083 return VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
4084 return VKCapabilities.currentTransform;
4085 }
4086
4087 [[nodiscard]] bool GetFormat()
4088 {
4089 uint32_t SurfFormats = 0;
4090 VkResult Res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice: m_VKGPU, surface: m_VKPresentSurface, pSurfaceFormatCount: &SurfFormats, pSurfaceFormats: nullptr);
4091 if(Res != VK_SUCCESS && Res != VK_INCOMPLETE)
4092 {
4093 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "The device surface format fetching failed.");
4094 return false;
4095 }
4096
4097 std::vector<VkSurfaceFormatKHR> vSurfFormatList(SurfFormats);
4098 Res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice: m_VKGPU, surface: m_VKPresentSurface, pSurfaceFormatCount: &SurfFormats, pSurfaceFormats: vSurfFormatList.data());
4099 if(Res != VK_SUCCESS && Res != VK_INCOMPLETE)
4100 {
4101 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "The device surface format fetching failed.");
4102 return false;
4103 }
4104
4105 if(Res == VK_INCOMPLETE)
4106 {
4107 log_warn("gfx/vulkan", "Not all surface formats are requestable with your current settings.");
4108 }
4109
4110 if(vSurfFormatList.size() == 1 && vSurfFormatList[0].format == VK_FORMAT_UNDEFINED)
4111 {
4112 m_VKSurfFormat.format = VK_FORMAT_B8G8R8A8_UNORM;
4113 m_VKSurfFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
4114 log_warn("gfx/vulkan", "Surface format was undefined. This can potentially cause bugs.");
4115 return true;
4116 }
4117
4118 for(const auto &FindFormat : vSurfFormatList)
4119 {
4120 if(FindFormat.format == VK_FORMAT_B8G8R8A8_UNORM && FindFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
4121 {
4122 m_VKSurfFormat = FindFormat;
4123 return true;
4124 }
4125 else if(FindFormat.format == VK_FORMAT_R8G8B8A8_UNORM && FindFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
4126 {
4127 m_VKSurfFormat = FindFormat;
4128 return true;
4129 }
4130 }
4131
4132 log_warn("gfx/vulkan", "Surface format was not RGBA (or variants of it). This can potentially cause weird looking images (too bright etc.).");
4133 m_VKSurfFormat = vSurfFormatList[0];
4134 return true;
4135 }
4136
4137 [[nodiscard]] bool CreateSwapChain(VkSwapchainKHR &OldSwapChain)
4138 {
4139 VkSurfaceCapabilitiesKHR VKSurfCap;
4140 if(!GetSurfaceProperties(VKSurfCapabilities&: VKSurfCap))
4141 return false;
4142
4143 VkPresentModeKHR PresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
4144 if(!GetPresentationMode(VKIOMode&: PresentMode))
4145 return false;
4146
4147 uint32_t SwapImgCount = GetNumberOfSwapImages(VKCapabilities: VKSurfCap);
4148
4149 m_VKSwapImgAndViewportExtent = GetSwapImageSize(VKCapabilities: VKSurfCap);
4150
4151 VkImageUsageFlags UsageFlags;
4152 if(!GetImageUsage(VKCapabilities: VKSurfCap, VKOutUsage&: UsageFlags))
4153 return false;
4154
4155 VkSurfaceTransformFlagBitsKHR TransformFlagBits = GetTransform(VKCapabilities: VKSurfCap);
4156
4157 if(!GetFormat())
4158 return false;
4159
4160 OldSwapChain = m_VKSwapChain;
4161
4162 VkSwapchainCreateInfoKHR SwapInfo;
4163 SwapInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
4164 SwapInfo.pNext = nullptr;
4165 SwapInfo.flags = 0;
4166 SwapInfo.surface = m_VKPresentSurface;
4167 SwapInfo.minImageCount = SwapImgCount;
4168 SwapInfo.imageFormat = m_VKSurfFormat.format;
4169 SwapInfo.imageColorSpace = m_VKSurfFormat.colorSpace;
4170 SwapInfo.imageExtent = m_VKSwapImgAndViewportExtent.m_SwapImageViewport;
4171 SwapInfo.imageArrayLayers = 1;
4172 SwapInfo.imageUsage = UsageFlags;
4173 SwapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
4174 SwapInfo.queueFamilyIndexCount = 0;
4175 SwapInfo.pQueueFamilyIndices = nullptr;
4176 SwapInfo.preTransform = TransformFlagBits;
4177 SwapInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
4178 SwapInfo.presentMode = PresentMode;
4179 SwapInfo.clipped = true;
4180 SwapInfo.oldSwapchain = OldSwapChain;
4181
4182 m_VKSwapChain = VK_NULL_HANDLE;
4183 VkResult SwapchainCreateRes = vkCreateSwapchainKHR(device: m_VKDevice, pCreateInfo: &SwapInfo, pAllocator: nullptr, pSwapchain: &m_VKSwapChain);
4184 const char *pCritErrorMsg = CheckVulkanCriticalError(CallResult: SwapchainCreateRes);
4185 if(pCritErrorMsg != nullptr)
4186 {
4187 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating the swap chain failed.", pErrStrExtra: pCritErrorMsg);
4188 return false;
4189 }
4190 else if(SwapchainCreateRes == VK_ERROR_NATIVE_WINDOW_IN_USE_KHR)
4191 return false;
4192
4193 return true;
4194 }
4195
4196 void DestroySwapChain(bool ForceDestroy)
4197 {
4198 if(ForceDestroy)
4199 {
4200 vkDestroySwapchainKHR(device: m_VKDevice, swapchain: m_VKSwapChain, pAllocator: nullptr);
4201 m_VKSwapChain = VK_NULL_HANDLE;
4202 }
4203 }
4204
4205 [[nodiscard]] bool GetSwapChainImageHandles()
4206 {
4207 uint32_t ImgCount = 0;
4208 if(vkGetSwapchainImagesKHR(device: m_VKDevice, swapchain: m_VKSwapChain, pSwapchainImageCount: &ImgCount, pSwapchainImages: nullptr) != VK_SUCCESS)
4209 {
4210 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not get swap chain images.");
4211 return false;
4212 }
4213
4214 m_SwapChainImageCount = ImgCount;
4215
4216 m_vSwapChainImages.resize(sz: ImgCount);
4217 if(vkGetSwapchainImagesKHR(device: m_VKDevice, swapchain: m_VKSwapChain, pSwapchainImageCount: &ImgCount, pSwapchainImages: m_vSwapChainImages.data()) != VK_SUCCESS)
4218 {
4219 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not get swap chain images.");
4220 return false;
4221 }
4222
4223 return true;
4224 }
4225
4226 void ClearSwapChainImageHandles()
4227 {
4228 m_vSwapChainImages.clear();
4229 }
4230
4231 void GetDeviceQueue()
4232 {
4233 vkGetDeviceQueue(device: m_VKDevice, queueFamilyIndex: m_VKGraphicsQueueIndex, queueIndex: 0, pQueue: &m_VKGraphicsQueue);
4234 vkGetDeviceQueue(device: m_VKDevice, queueFamilyIndex: m_VKGraphicsQueueIndex, queueIndex: 0, pQueue: &m_VKPresentQueue);
4235 }
4236
4237#ifdef VK_EXT_debug_utils
4238 static VKAPI_ATTR VkBool32 VKAPI_CALL VKDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT MessageSeverity, VkDebugUtilsMessageTypeFlagsEXT MessageType, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData)
4239 {
4240 if((MessageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0)
4241 {
4242 log_error("gfx/vulkan", "Validation error: %s", pCallbackData->pMessage);
4243 }
4244 else
4245 {
4246 log_info("gfx/vulkan", "Validation info: %s", pCallbackData->pMessage);
4247 }
4248
4249 return VK_FALSE;
4250 }
4251
4252 VkResult CreateDebugUtilsMessengerEXT(const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDebugUtilsMessengerEXT *pDebugMessenger)
4253 {
4254 auto pfnVulkanCreateDebugUtilsFunction = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance: m_VKInstance, pName: "vkCreateDebugUtilsMessengerEXT");
4255 if(pfnVulkanCreateDebugUtilsFunction != nullptr)
4256 {
4257 return pfnVulkanCreateDebugUtilsFunction(m_VKInstance, pCreateInfo, pAllocator, pDebugMessenger);
4258 }
4259 else
4260 {
4261 return VK_ERROR_EXTENSION_NOT_PRESENT;
4262 }
4263 }
4264
4265 void DestroyDebugUtilsMessengerEXT(VkDebugUtilsMessengerEXT &DebugMessenger)
4266 {
4267 auto pfnVulkanDestroyDebugUtilsFunction = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance: m_VKInstance, pName: "vkDestroyDebugUtilsMessengerEXT");
4268 if(pfnVulkanDestroyDebugUtilsFunction != nullptr)
4269 {
4270 pfnVulkanDestroyDebugUtilsFunction(m_VKInstance, DebugMessenger, nullptr);
4271 }
4272 }
4273#endif
4274
4275 void SetupDebugCallback()
4276 {
4277#ifdef VK_EXT_debug_utils
4278 VkDebugUtilsMessengerCreateInfoEXT CreateInfo = {};
4279 CreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
4280 CreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
4281 CreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; // | VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT <- too annoying
4282 CreateInfo.pfnUserCallback = VKDebugCallback;
4283
4284 if(CreateDebugUtilsMessengerEXT(pCreateInfo: &CreateInfo, pAllocator: nullptr, pDebugMessenger: &m_DebugMessenger) != VK_SUCCESS)
4285 {
4286 m_DebugMessenger = VK_NULL_HANDLE;
4287 log_warn("gfx/vulkan", "Could not find Vulkan debug layer.");
4288 }
4289 else
4290 {
4291 log_info("gfx/vulkan", "Enabled Vulkan debug context.");
4292 }
4293#endif
4294 }
4295
4296 void UnregisterDebugCallback()
4297 {
4298#ifdef VK_EXT_debug_utils
4299 if(m_DebugMessenger != VK_NULL_HANDLE)
4300 DestroyDebugUtilsMessengerEXT(DebugMessenger&: m_DebugMessenger);
4301#endif
4302 }
4303
4304 [[nodiscard]] bool CreateImageViews()
4305 {
4306 m_vSwapChainImageViewList.resize(sz: m_SwapChainImageCount);
4307
4308 for(size_t i = 0; i < m_SwapChainImageCount; i++)
4309 {
4310 VkImageViewCreateInfo CreateInfo{};
4311 CreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
4312 CreateInfo.image = m_vSwapChainImages[i];
4313 CreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
4314 CreateInfo.format = m_VKSurfFormat.format;
4315 CreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
4316 CreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
4317 CreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
4318 CreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
4319 CreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
4320 CreateInfo.subresourceRange.baseMipLevel = 0;
4321 CreateInfo.subresourceRange.levelCount = 1;
4322 CreateInfo.subresourceRange.baseArrayLayer = 0;
4323 CreateInfo.subresourceRange.layerCount = 1;
4324
4325 if(vkCreateImageView(device: m_VKDevice, pCreateInfo: &CreateInfo, pAllocator: nullptr, pView: &m_vSwapChainImageViewList[i]) != VK_SUCCESS)
4326 {
4327 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Could not create image views for the swap chain framebuffers.");
4328 return false;
4329 }
4330 }
4331
4332 return true;
4333 }
4334
4335 void DestroyImageViews()
4336 {
4337 for(auto &ImageView : m_vSwapChainImageViewList)
4338 {
4339 vkDestroyImageView(device: m_VKDevice, imageView: ImageView, pAllocator: nullptr);
4340 }
4341
4342 m_vSwapChainImageViewList.clear();
4343 }
4344
4345 [[nodiscard]] bool CreateMultiSamplerImageAttachments()
4346 {
4347 m_vSwapChainMultiSamplingImages.resize(sz: m_SwapChainImageCount);
4348 if(HasMultiSampling())
4349 {
4350 for(size_t i = 0; i < m_SwapChainImageCount; ++i)
4351 {
4352 if(!CreateImage(Width: m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width, Height: m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height, Depth: 1, MipMapLevelCount: 1, Format: m_VKSurfFormat.format, Tiling: VK_IMAGE_TILING_OPTIMAL, Image&: m_vSwapChainMultiSamplingImages[i].m_Image, ImageMemory&: m_vSwapChainMultiSamplingImages[i].m_ImgMem, ImageUsage: VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT))
4353 return false;
4354 m_vSwapChainMultiSamplingImages[i].m_ImgView = CreateImageView(Image: m_vSwapChainMultiSamplingImages[i].m_Image, Format: m_VKSurfFormat.format, ViewType: VK_IMAGE_VIEW_TYPE_2D, Depth: 1, MipMapLevelCount: 1);
4355 }
4356 }
4357
4358 return true;
4359 }
4360
4361 void DestroyMultiSamplerImageAttachments()
4362 {
4363 if(HasMultiSampling())
4364 {
4365 m_vSwapChainMultiSamplingImages.resize(sz: m_SwapChainImageCount);
4366 for(size_t i = 0; i < m_SwapChainImageCount; ++i)
4367 {
4368 vkDestroyImage(device: m_VKDevice, image: m_vSwapChainMultiSamplingImages[i].m_Image, pAllocator: nullptr);
4369 vkDestroyImageView(device: m_VKDevice, imageView: m_vSwapChainMultiSamplingImages[i].m_ImgView, pAllocator: nullptr);
4370 FreeImageMemBlock(Block&: m_vSwapChainMultiSamplingImages[i].m_ImgMem);
4371 }
4372 }
4373 m_vSwapChainMultiSamplingImages.clear();
4374 }
4375
4376 [[nodiscard]] bool CreateRenderPass(bool ClearAttachments)
4377 {
4378 bool HasMultiSamplingTargets = HasMultiSampling();
4379 VkAttachmentDescription MultiSamplingColorAttachment{};
4380 MultiSamplingColorAttachment.format = m_VKSurfFormat.format;
4381 MultiSamplingColorAttachment.samples = GetSampleCount();
4382 MultiSamplingColorAttachment.loadOp = ClearAttachments ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
4383 MultiSamplingColorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
4384 MultiSamplingColorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
4385 MultiSamplingColorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
4386 MultiSamplingColorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
4387 MultiSamplingColorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
4388
4389 VkAttachmentDescription ColorAttachment{};
4390 ColorAttachment.format = m_VKSurfFormat.format;
4391 ColorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
4392 ColorAttachment.loadOp = ClearAttachments && !HasMultiSamplingTargets ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
4393 ColorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
4394 ColorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
4395 ColorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
4396 ColorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
4397 ColorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
4398
4399 VkAttachmentReference MultiSamplingColorAttachmentRef{};
4400 MultiSamplingColorAttachmentRef.attachment = 0;
4401 MultiSamplingColorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
4402
4403 VkAttachmentReference ColorAttachmentRef{};
4404 ColorAttachmentRef.attachment = HasMultiSamplingTargets ? 1 : 0;
4405 ColorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
4406
4407 VkSubpassDescription Subpass{};
4408 Subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
4409 Subpass.colorAttachmentCount = 1;
4410 Subpass.pColorAttachments = HasMultiSamplingTargets ? &MultiSamplingColorAttachmentRef : &ColorAttachmentRef;
4411 Subpass.pResolveAttachments = HasMultiSamplingTargets ? &ColorAttachmentRef : nullptr;
4412
4413 std::array<VkAttachmentDescription, 2> aAttachments;
4414 aAttachments[0] = MultiSamplingColorAttachment;
4415 aAttachments[1] = ColorAttachment;
4416
4417 VkSubpassDependency Dependency{};
4418 Dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
4419 Dependency.dstSubpass = 0;
4420 Dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
4421 Dependency.srcAccessMask = 0;
4422 Dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
4423 Dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
4424
4425 VkRenderPassCreateInfo CreateRenderPassInfo{};
4426 CreateRenderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
4427 CreateRenderPassInfo.attachmentCount = HasMultiSamplingTargets ? 2 : 1;
4428 CreateRenderPassInfo.pAttachments = HasMultiSamplingTargets ? aAttachments.data() : aAttachments.data() + 1;
4429 CreateRenderPassInfo.subpassCount = 1;
4430 CreateRenderPassInfo.pSubpasses = &Subpass;
4431 CreateRenderPassInfo.dependencyCount = 1;
4432 CreateRenderPassInfo.pDependencies = &Dependency;
4433
4434 if(vkCreateRenderPass(device: m_VKDevice, pCreateInfo: &CreateRenderPassInfo, pAllocator: nullptr, pRenderPass: &m_VKRenderPass) != VK_SUCCESS)
4435 {
4436 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating the render pass failed.");
4437 return false;
4438 }
4439
4440 return true;
4441 }
4442
4443 void DestroyRenderPass()
4444 {
4445 vkDestroyRenderPass(device: m_VKDevice, renderPass: m_VKRenderPass, pAllocator: nullptr);
4446 }
4447
4448 [[nodiscard]] bool CreateFramebuffers()
4449 {
4450 m_vFramebufferList.resize(sz: m_SwapChainImageCount);
4451
4452 for(size_t i = 0; i < m_SwapChainImageCount; i++)
4453 {
4454 std::array<VkImageView, 2> aAttachments = {
4455 m_vSwapChainMultiSamplingImages[i].m_ImgView,
4456 m_vSwapChainImageViewList[i]};
4457
4458 bool HasMultiSamplingTargets = HasMultiSampling();
4459
4460 VkFramebufferCreateInfo FramebufferInfo{};
4461 FramebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
4462 FramebufferInfo.renderPass = m_VKRenderPass;
4463 FramebufferInfo.attachmentCount = HasMultiSamplingTargets ? aAttachments.size() : aAttachments.size() - 1;
4464 FramebufferInfo.pAttachments = HasMultiSamplingTargets ? aAttachments.data() : aAttachments.data() + 1;
4465 FramebufferInfo.width = m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width;
4466 FramebufferInfo.height = m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height;
4467 FramebufferInfo.layers = 1;
4468
4469 if(vkCreateFramebuffer(device: m_VKDevice, pCreateInfo: &FramebufferInfo, pAllocator: nullptr, pFramebuffer: &m_vFramebufferList[i]) != VK_SUCCESS)
4470 {
4471 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating the framebuffers failed.");
4472 return false;
4473 }
4474 }
4475
4476 return true;
4477 }
4478
4479 void DestroyFramebuffers()
4480 {
4481 for(auto &FrameBuffer : m_vFramebufferList)
4482 {
4483 vkDestroyFramebuffer(device: m_VKDevice, framebuffer: FrameBuffer, pAllocator: nullptr);
4484 }
4485
4486 m_vFramebufferList.clear();
4487 }
4488
4489 [[nodiscard]] bool CreateShaderModule(const std::vector<uint8_t> &vCode, VkShaderModule &ShaderModule)
4490 {
4491 VkShaderModuleCreateInfo CreateInfo{};
4492 CreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
4493 CreateInfo.codeSize = vCode.size();
4494 CreateInfo.pCode = (const uint32_t *)(vCode.data());
4495
4496 if(vkCreateShaderModule(device: m_VKDevice, pCreateInfo: &CreateInfo, pAllocator: nullptr, pShaderModule: &ShaderModule) != VK_SUCCESS)
4497 {
4498 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Shader module was not created.");
4499 return false;
4500 }
4501
4502 return true;
4503 }
4504
4505 [[nodiscard]] bool CreateDescriptorSetLayouts()
4506 {
4507 VkDescriptorSetLayoutBinding SamplerLayoutBinding{};
4508 SamplerLayoutBinding.binding = 0;
4509 SamplerLayoutBinding.descriptorCount = 1;
4510 SamplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
4511 SamplerLayoutBinding.pImmutableSamplers = nullptr;
4512 SamplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
4513
4514 std::array<VkDescriptorSetLayoutBinding, 1> aBindings = {SamplerLayoutBinding};
4515 VkDescriptorSetLayoutCreateInfo LayoutInfo{};
4516 LayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
4517 LayoutInfo.bindingCount = aBindings.size();
4518 LayoutInfo.pBindings = aBindings.data();
4519
4520 if(vkCreateDescriptorSetLayout(device: m_VKDevice, pCreateInfo: &LayoutInfo, pAllocator: nullptr, pSetLayout: &m_StandardTexturedDescriptorSetLayout) != VK_SUCCESS)
4521 {
4522 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating descriptor layout failed.");
4523 return false;
4524 }
4525
4526 if(vkCreateDescriptorSetLayout(device: m_VKDevice, pCreateInfo: &LayoutInfo, pAllocator: nullptr, pSetLayout: &m_Standard3DTexturedDescriptorSetLayout) != VK_SUCCESS)
4527 {
4528 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating descriptor layout failed.");
4529 return false;
4530 }
4531 return true;
4532 }
4533
4534 void DestroyDescriptorSetLayouts()
4535 {
4536 vkDestroyDescriptorSetLayout(device: m_VKDevice, descriptorSetLayout: m_StandardTexturedDescriptorSetLayout, pAllocator: nullptr);
4537 vkDestroyDescriptorSetLayout(device: m_VKDevice, descriptorSetLayout: m_Standard3DTexturedDescriptorSetLayout, pAllocator: nullptr);
4538 }
4539
4540 [[nodiscard]] bool LoadShader(const char *pFilename, std::vector<uint8_t> *&pvShaderData)
4541 {
4542 auto ShaderFileIterator = m_ShaderFiles.find(key: pFilename);
4543 if(ShaderFileIterator == m_ShaderFiles.end())
4544 {
4545 void *pShaderBuff;
4546 unsigned FileSize;
4547 if(!m_pStorage->ReadFile(pFilename, Type: IStorage::TYPE_ALL, ppResult: &pShaderBuff, pResultLen: &FileSize))
4548 return false;
4549
4550 std::vector<uint8_t> vShaderBuff;
4551 vShaderBuff.resize(sz: FileSize);
4552 mem_copy(dest: vShaderBuff.data(), source: pShaderBuff, size: FileSize);
4553 free(ptr: pShaderBuff);
4554
4555 ShaderFileIterator = m_ShaderFiles.insert(x: {pFilename, {.m_vBinary: std::move(vShaderBuff)}}).first;
4556 }
4557
4558 pvShaderData = &ShaderFileIterator->second.m_vBinary;
4559
4560 return true;
4561 }
4562
4563 [[nodiscard]] bool CreateShaders(const char *pVertName, const char *pFragName, VkPipelineShaderStageCreateInfo (&aShaderStages)[2], SShaderModule &ShaderModule)
4564 {
4565 bool ShaderLoaded = true;
4566
4567 std::vector<uint8_t> *pvVertBuff;
4568 std::vector<uint8_t> *pvFragBuff;
4569 ShaderLoaded &= LoadShader(pFilename: pVertName, pvShaderData&: pvVertBuff);
4570 ShaderLoaded &= LoadShader(pFilename: pFragName, pvShaderData&: pvFragBuff);
4571
4572 ShaderModule.m_VKDevice = m_VKDevice;
4573
4574 if(!ShaderLoaded)
4575 {
4576 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "A shader file could not load correctly.");
4577 return false;
4578 }
4579
4580 if(!CreateShaderModule(vCode: *pvVertBuff, ShaderModule&: ShaderModule.m_VertShaderModule))
4581 return false;
4582
4583 if(!CreateShaderModule(vCode: *pvFragBuff, ShaderModule&: ShaderModule.m_FragShaderModule))
4584 return false;
4585
4586 VkPipelineShaderStageCreateInfo &VertShaderStageInfo = aShaderStages[0];
4587 VertShaderStageInfo = {};
4588 VertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
4589 VertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
4590 VertShaderStageInfo.module = ShaderModule.m_VertShaderModule;
4591 VertShaderStageInfo.pName = "main";
4592
4593 VkPipelineShaderStageCreateInfo &FragShaderStageInfo = aShaderStages[1];
4594 FragShaderStageInfo = {};
4595 FragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
4596 FragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
4597 FragShaderStageInfo.module = ShaderModule.m_FragShaderModule;
4598 FragShaderStageInfo.pName = "main";
4599 return true;
4600 }
4601
4602 bool GetStandardPipelineInfo(VkPipelineInputAssemblyStateCreateInfo &InputAssembly,
4603 VkViewport &Viewport,
4604 VkRect2D &Scissor,
4605 VkPipelineViewportStateCreateInfo &ViewportState,
4606 VkPipelineRasterizationStateCreateInfo &Rasterizer,
4607 VkPipelineMultisampleStateCreateInfo &Multisampling,
4608 VkPipelineColorBlendAttachmentState &ColorBlendAttachment,
4609 VkPipelineColorBlendStateCreateInfo &ColorBlending) const
4610 {
4611 InputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
4612 InputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
4613 InputAssembly.primitiveRestartEnable = VK_FALSE;
4614
4615 Viewport.x = 0.0f;
4616 Viewport.y = 0.0f;
4617 Viewport.width = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.width;
4618 Viewport.height = (float)m_VKSwapImgAndViewportExtent.m_SwapImageViewport.height;
4619 Viewport.minDepth = 0.0f;
4620 Viewport.maxDepth = 1.0f;
4621
4622 Scissor.offset = {.x: 0, .y: 0};
4623 Scissor.extent = m_VKSwapImgAndViewportExtent.m_SwapImageViewport;
4624
4625 ViewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
4626 ViewportState.viewportCount = 1;
4627 ViewportState.pViewports = &Viewport;
4628 ViewportState.scissorCount = 1;
4629 ViewportState.pScissors = &Scissor;
4630
4631 Rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
4632 Rasterizer.depthClampEnable = VK_FALSE;
4633 Rasterizer.rasterizerDiscardEnable = VK_FALSE;
4634 Rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
4635 Rasterizer.lineWidth = 1.0f;
4636 Rasterizer.cullMode = VK_CULL_MODE_NONE;
4637 Rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
4638 Rasterizer.depthBiasEnable = VK_FALSE;
4639
4640 Multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
4641 Multisampling.sampleShadingEnable = VK_FALSE;
4642 Multisampling.rasterizationSamples = GetSampleCount();
4643
4644 ColorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
4645 ColorBlendAttachment.blendEnable = VK_TRUE;
4646
4647 ColorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
4648 ColorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4649 ColorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
4650 ColorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
4651 ColorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
4652 ColorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
4653
4654 ColorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
4655 ColorBlending.logicOpEnable = VK_FALSE;
4656 ColorBlending.logicOp = VK_LOGIC_OP_COPY;
4657 ColorBlending.attachmentCount = 1;
4658 ColorBlending.pAttachments = &ColorBlendAttachment;
4659 ColorBlending.blendConstants[0] = 0.0f;
4660 ColorBlending.blendConstants[1] = 0.0f;
4661 ColorBlending.blendConstants[2] = 0.0f;
4662 ColorBlending.blendConstants[3] = 0.0f;
4663
4664 return true;
4665 }
4666
4667 template<bool ForceRequireDescriptors, size_t ArraySize, size_t DescrArraySize, size_t PushArraySize>
4668 [[nodiscard]] bool CreateGraphicsPipeline(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, uint32_t Stride, std::array<VkVertexInputAttributeDescription, ArraySize> &aInputAttr,
4669 std::array<VkDescriptorSetLayout, DescrArraySize> &aSetLayouts, std::array<VkPushConstantRange, PushArraySize> &aPushConstants, EVulkanBackendTextureModes TexMode,
4670 EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode, bool IsLinePrim = false)
4671 {
4672 VkPipelineShaderStageCreateInfo aShaderStages[2];
4673 SShaderModule Module;
4674 if(!CreateShaders(pVertName, pFragName, aShaderStages, ShaderModule&: Module))
4675 return false;
4676
4677 bool HasSampler = TexMode == VULKAN_BACKEND_TEXTURE_MODE_TEXTURED;
4678
4679 VkPipelineVertexInputStateCreateInfo VertexInputInfo{};
4680 VertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
4681 VkVertexInputBindingDescription BindingDescription{};
4682 BindingDescription.binding = 0;
4683 BindingDescription.stride = Stride;
4684 BindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
4685
4686 VertexInputInfo.vertexBindingDescriptionCount = 1;
4687 VertexInputInfo.vertexAttributeDescriptionCount = aInputAttr.size();
4688 VertexInputInfo.pVertexBindingDescriptions = &BindingDescription;
4689 VertexInputInfo.pVertexAttributeDescriptions = aInputAttr.data();
4690
4691 VkPipelineInputAssemblyStateCreateInfo InputAssembly{};
4692 VkViewport Viewport{};
4693 VkRect2D Scissor{};
4694 VkPipelineViewportStateCreateInfo ViewportState{};
4695 VkPipelineRasterizationStateCreateInfo Rasterizer{};
4696 VkPipelineMultisampleStateCreateInfo Multisampling{};
4697 VkPipelineColorBlendAttachmentState ColorBlendAttachment{};
4698 VkPipelineColorBlendStateCreateInfo ColorBlending{};
4699
4700 GetStandardPipelineInfo(InputAssembly, Viewport, Scissor, ViewportState, Rasterizer, Multisampling, ColorBlendAttachment, ColorBlending);
4701 InputAssembly.topology = IsLinePrim ? VK_PRIMITIVE_TOPOLOGY_LINE_LIST : VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
4702
4703 VkPipelineLayoutCreateInfo PipelineLayoutInfo{};
4704 PipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
4705 PipelineLayoutInfo.setLayoutCount = (HasSampler || ForceRequireDescriptors) ? aSetLayouts.size() : 0;
4706 PipelineLayoutInfo.pSetLayouts = (HasSampler || ForceRequireDescriptors) && !aSetLayouts.empty() ? aSetLayouts.data() : nullptr;
4707
4708 PipelineLayoutInfo.pushConstantRangeCount = aPushConstants.size();
4709 PipelineLayoutInfo.pPushConstantRanges = !aPushConstants.empty() ? aPushConstants.data() : nullptr;
4710
4711 VkPipelineLayout &PipeLayout = GetPipeLayout(Container&: PipeContainer, IsTextured: HasSampler, BlendModeIndex: size_t(BlendMode), DynamicIndex: size_t(DynamicMode));
4712 VkPipeline &Pipeline = GetPipeline(Container&: PipeContainer, IsTextured: HasSampler, BlendModeIndex: size_t(BlendMode), DynamicIndex: size_t(DynamicMode));
4713
4714 if(vkCreatePipelineLayout(device: m_VKDevice, pCreateInfo: &PipelineLayoutInfo, pAllocator: nullptr, pPipelineLayout: &PipeLayout) != VK_SUCCESS)
4715 {
4716 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating pipeline layout failed.");
4717 return false;
4718 }
4719
4720 VkGraphicsPipelineCreateInfo PipelineInfo{};
4721 PipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
4722 PipelineInfo.stageCount = 2;
4723 PipelineInfo.pStages = aShaderStages;
4724 PipelineInfo.pVertexInputState = &VertexInputInfo;
4725 PipelineInfo.pInputAssemblyState = &InputAssembly;
4726 PipelineInfo.pViewportState = &ViewportState;
4727 PipelineInfo.pRasterizationState = &Rasterizer;
4728 PipelineInfo.pMultisampleState = &Multisampling;
4729 PipelineInfo.pColorBlendState = &ColorBlending;
4730 PipelineInfo.layout = PipeLayout;
4731 PipelineInfo.renderPass = m_VKRenderPass;
4732 PipelineInfo.subpass = 0;
4733 PipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
4734
4735 std::array<VkDynamicState, 2> aDynamicStates = {
4736 VK_DYNAMIC_STATE_VIEWPORT,
4737 VK_DYNAMIC_STATE_SCISSOR,
4738 };
4739
4740 VkPipelineDynamicStateCreateInfo DynamicStateCreate{};
4741 DynamicStateCreate.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
4742 DynamicStateCreate.dynamicStateCount = aDynamicStates.size();
4743 DynamicStateCreate.pDynamicStates = aDynamicStates.data();
4744
4745 if(DynamicMode == VULKAN_BACKEND_CLIP_MODE_DYNAMIC_SCISSOR_AND_VIEWPORT)
4746 {
4747 PipelineInfo.pDynamicState = &DynamicStateCreate;
4748 }
4749
4750 if(vkCreateGraphicsPipelines(device: m_VKDevice, VK_NULL_HANDLE, createInfoCount: 1, pCreateInfos: &PipelineInfo, pAllocator: nullptr, pPipelines: &Pipeline) != VK_SUCCESS)
4751 {
4752 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating the graphic pipeline failed.");
4753 return false;
4754 }
4755
4756 return true;
4757 }
4758
4759 [[nodiscard]] bool CreateStandardGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode, bool IsLinePrim)
4760 {
4761 std::array<VkVertexInputAttributeDescription, 3> aAttributeDescriptions = {};
4762
4763 aAttributeDescriptions[0] = {.location: 0, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: 0};
4764 aAttributeDescriptions[1] = {.location: 1, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: sizeof(float) * 2};
4765 aAttributeDescriptions[2] = {.location: 2, .binding: 0, .format: VK_FORMAT_R8G8B8A8_UNORM, .offset: sizeof(float) * (2 + 2)};
4766
4767 std::array<VkDescriptorSetLayout, 1> aSetLayouts = {m_StandardTexturedDescriptorSetLayout};
4768
4769 std::array<VkPushConstantRange, 1> aPushConstants{};
4770 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: sizeof(SUniformGPos)};
4771
4772 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, Stride: sizeof(float) * (2 + 2) + sizeof(uint8_t) * 4, aInputAttr&: aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode, IsLinePrim);
4773 }
4774
4775 [[nodiscard]] bool CreateStandardGraphicsPipeline(const char *pVertName, const char *pFragName, bool HasSampler, bool IsLinePipe)
4776 {
4777 bool Ret = true;
4778
4779 EVulkanBackendTextureModes TexMode = HasSampler ? VULKAN_BACKEND_TEXTURE_MODE_TEXTURED : VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED;
4780
4781 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
4782 {
4783 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
4784 {
4785 Ret &= CreateStandardGraphicsPipelineImpl(pVertName, pFragName, PipeContainer&: IsLinePipe ? m_StandardLinePipeline : m_StandardPipeline, TexMode, BlendMode: EVulkanBackendBlendModes(i), DynamicMode: EVulkanBackendClipModes(j), IsLinePrim: IsLinePipe);
4786 }
4787 }
4788
4789 return Ret;
4790 }
4791
4792 [[nodiscard]] bool CreateStandard3DGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
4793 {
4794 std::array<VkVertexInputAttributeDescription, 3> aAttributeDescriptions = {};
4795
4796 aAttributeDescriptions[0] = {.location: 0, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: 0};
4797 aAttributeDescriptions[1] = {.location: 1, .binding: 0, .format: VK_FORMAT_R8G8B8A8_UNORM, .offset: sizeof(float) * 2};
4798 aAttributeDescriptions[2] = {.location: 2, .binding: 0, .format: VK_FORMAT_R32G32B32_SFLOAT, .offset: sizeof(float) * 2 + sizeof(uint8_t) * 4};
4799
4800 std::array<VkDescriptorSetLayout, 1> aSetLayouts = {m_Standard3DTexturedDescriptorSetLayout};
4801
4802 std::array<VkPushConstantRange, 1> aPushConstants{};
4803 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: sizeof(SUniformGPos)};
4804
4805 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, Stride: sizeof(float) * 2 + sizeof(uint8_t) * 4 + sizeof(float) * 3, aInputAttr&: aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
4806 }
4807
4808 [[nodiscard]] bool CreateStandard3DGraphicsPipeline(const char *pVertName, const char *pFragName, bool HasSampler)
4809 {
4810 bool Ret = true;
4811
4812 EVulkanBackendTextureModes TexMode = HasSampler ? VULKAN_BACKEND_TEXTURE_MODE_TEXTURED : VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED;
4813
4814 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
4815 {
4816 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
4817 {
4818 Ret &= CreateStandard3DGraphicsPipelineImpl(pVertName, pFragName, PipeContainer&: m_Standard3DPipeline, TexMode, BlendMode: EVulkanBackendBlendModes(i), DynamicMode: EVulkanBackendClipModes(j));
4819 }
4820 }
4821
4822 return Ret;
4823 }
4824
4825 [[nodiscard]] bool CreateTextDescriptorSetLayout()
4826 {
4827 VkDescriptorSetLayoutBinding SamplerLayoutBinding{};
4828 SamplerLayoutBinding.binding = 0;
4829 SamplerLayoutBinding.descriptorCount = 1;
4830 SamplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
4831 SamplerLayoutBinding.pImmutableSamplers = nullptr;
4832 SamplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
4833
4834 auto SamplerLayoutBinding2 = SamplerLayoutBinding;
4835 SamplerLayoutBinding2.binding = 1;
4836
4837 std::array<VkDescriptorSetLayoutBinding, 2> aBindings = {SamplerLayoutBinding, SamplerLayoutBinding2};
4838 VkDescriptorSetLayoutCreateInfo LayoutInfo{};
4839 LayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
4840 LayoutInfo.bindingCount = aBindings.size();
4841 LayoutInfo.pBindings = aBindings.data();
4842
4843 if(vkCreateDescriptorSetLayout(device: m_VKDevice, pCreateInfo: &LayoutInfo, pAllocator: nullptr, pSetLayout: &m_TextDescriptorSetLayout) != VK_SUCCESS)
4844 {
4845 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating descriptor layout failed.");
4846 return false;
4847 }
4848
4849 return true;
4850 }
4851
4852 void DestroyTextDescriptorSetLayout()
4853 {
4854 vkDestroyDescriptorSetLayout(device: m_VKDevice, descriptorSetLayout: m_TextDescriptorSetLayout, pAllocator: nullptr);
4855 }
4856
4857 [[nodiscard]] bool CreateTextGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
4858 {
4859 std::array<VkVertexInputAttributeDescription, 3> aAttributeDescriptions = {};
4860 aAttributeDescriptions[0] = {.location: 0, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: 0};
4861 aAttributeDescriptions[1] = {.location: 1, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: sizeof(float) * 2};
4862 aAttributeDescriptions[2] = {.location: 2, .binding: 0, .format: VK_FORMAT_R8G8B8A8_UNORM, .offset: sizeof(float) * (2 + 2)};
4863
4864 std::array<VkDescriptorSetLayout, 1> aSetLayouts = {m_TextDescriptorSetLayout};
4865
4866 std::array<VkPushConstantRange, 2> aPushConstants{};
4867 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: sizeof(SUniformGTextPos)};
4868 aPushConstants[1] = {.stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, .offset: sizeof(SUniformGTextPos) + sizeof(SUniformTextGFragmentOffset), .size: sizeof(SUniformTextGFragmentConstants)};
4869
4870 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, Stride: sizeof(float) * (2 + 2) + sizeof(uint8_t) * 4, aInputAttr&: aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
4871 }
4872
4873 [[nodiscard]] bool CreateTextGraphicsPipeline(const char *pVertName, const char *pFragName)
4874 {
4875 bool Ret = true;
4876
4877 EVulkanBackendTextureModes TexMode = VULKAN_BACKEND_TEXTURE_MODE_TEXTURED;
4878
4879 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
4880 {
4881 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
4882 {
4883 Ret &= CreateTextGraphicsPipelineImpl(pVertName, pFragName, PipeContainer&: m_TextPipeline, TexMode, BlendMode: EVulkanBackendBlendModes(i), DynamicMode: EVulkanBackendClipModes(j));
4884 }
4885 }
4886
4887 return Ret;
4888 }
4889
4890 template<bool HasSampler>
4891 [[nodiscard]] bool CreateTileGraphicsPipelineImpl(const char *pVertName, const char *pFragName, bool IsBorder, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
4892 {
4893 std::array<VkVertexInputAttributeDescription, HasSampler ? 2 : 1> aAttributeDescriptions = {};
4894 aAttributeDescriptions[0] = {0, 0, VK_FORMAT_R32G32_SFLOAT, 0};
4895 if(HasSampler)
4896 aAttributeDescriptions[1] = {1, 0, VK_FORMAT_R8G8B8A8_UINT, sizeof(float) * 2};
4897
4898 std::array<VkDescriptorSetLayout, 1> aSetLayouts;
4899 aSetLayouts[0] = m_Standard3DTexturedDescriptorSetLayout;
4900
4901 uint32_t VertPushConstantSize = sizeof(SUniformTileGPos);
4902 if(IsBorder)
4903 VertPushConstantSize = sizeof(SUniformTileGPosBorder);
4904
4905 uint32_t FragPushConstantSize = sizeof(SUniformTileGVertColor);
4906
4907 std::array<VkPushConstantRange, 2> aPushConstants{};
4908 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: VertPushConstantSize};
4909 aPushConstants[1] = {.stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, .offset: sizeof(SUniformTileGPosBorder) + sizeof(SUniformTileGVertColorAlign), .size: FragPushConstantSize};
4910
4911 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, HasSampler ? (sizeof(float) * 2 + sizeof(uint8_t) * 4) : (sizeof(float) * 2), aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
4912 }
4913
4914 template<bool HasSampler>
4915 [[nodiscard]] bool CreateTileGraphicsPipeline(const char *pVertName, const char *pFragName, bool IsBorder)
4916 {
4917 bool Ret = true;
4918
4919 EVulkanBackendTextureModes TexMode = HasSampler ? VULKAN_BACKEND_TEXTURE_MODE_TEXTURED : VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED;
4920
4921 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
4922 {
4923 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
4924 {
4925 Ret &= CreateTileGraphicsPipelineImpl<HasSampler>(pVertName, pFragName, IsBorder, !IsBorder ? m_TilePipeline : m_TileBorderPipeline, TexMode, EVulkanBackendBlendModes(i), EVulkanBackendClipModes(j));
4926 }
4927 }
4928
4929 return Ret;
4930 }
4931
4932 [[nodiscard]] bool CreatePrimExGraphicsPipelineImpl(const char *pVertName, const char *pFragName, bool Rotationless, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
4933 {
4934 std::array<VkVertexInputAttributeDescription, 3> aAttributeDescriptions = {};
4935 aAttributeDescriptions[0] = {.location: 0, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: 0};
4936 aAttributeDescriptions[1] = {.location: 1, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: sizeof(float) * 2};
4937 aAttributeDescriptions[2] = {.location: 2, .binding: 0, .format: VK_FORMAT_R8G8B8A8_UNORM, .offset: sizeof(float) * (2 + 2)};
4938
4939 std::array<VkDescriptorSetLayout, 1> aSetLayouts;
4940 aSetLayouts[0] = m_StandardTexturedDescriptorSetLayout;
4941 uint32_t VertPushConstantSize = sizeof(SUniformPrimExGPos);
4942 if(Rotationless)
4943 VertPushConstantSize = sizeof(SUniformPrimExGPosRotationless);
4944
4945 uint32_t FragPushConstantSize = sizeof(SUniformPrimExGVertColor);
4946
4947 std::array<VkPushConstantRange, 2> aPushConstants{};
4948 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: VertPushConstantSize};
4949 aPushConstants[1] = {.stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, .offset: sizeof(SUniformPrimExGPos) + sizeof(SUniformPrimExGVertColorAlign), .size: FragPushConstantSize};
4950
4951 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, Stride: sizeof(float) * (2 + 2) + sizeof(uint8_t) * 4, aInputAttr&: aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
4952 }
4953
4954 [[nodiscard]] bool CreatePrimExGraphicsPipeline(const char *pVertName, const char *pFragName, bool HasSampler, bool Rotationless)
4955 {
4956 bool Ret = true;
4957
4958 EVulkanBackendTextureModes TexMode = HasSampler ? VULKAN_BACKEND_TEXTURE_MODE_TEXTURED : VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED;
4959
4960 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
4961 {
4962 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
4963 {
4964 Ret &= CreatePrimExGraphicsPipelineImpl(pVertName, pFragName, Rotationless, PipeContainer&: Rotationless ? m_PrimExRotationlessPipeline : m_PrimExPipeline, TexMode, BlendMode: EVulkanBackendBlendModes(i), DynamicMode: EVulkanBackendClipModes(j));
4965 }
4966 }
4967
4968 return Ret;
4969 }
4970
4971 [[nodiscard]] bool CreateUniformDescriptorSetLayout(VkDescriptorSetLayout &SetLayout, VkShaderStageFlags StageFlags)
4972 {
4973 VkDescriptorSetLayoutBinding SamplerLayoutBinding{};
4974 SamplerLayoutBinding.binding = 1;
4975 SamplerLayoutBinding.descriptorCount = 1;
4976 SamplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
4977 SamplerLayoutBinding.pImmutableSamplers = nullptr;
4978 SamplerLayoutBinding.stageFlags = StageFlags;
4979
4980 std::array<VkDescriptorSetLayoutBinding, 1> aBindings = {SamplerLayoutBinding};
4981 VkDescriptorSetLayoutCreateInfo LayoutInfo{};
4982 LayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
4983 LayoutInfo.bindingCount = aBindings.size();
4984 LayoutInfo.pBindings = aBindings.data();
4985
4986 if(vkCreateDescriptorSetLayout(device: m_VKDevice, pCreateInfo: &LayoutInfo, pAllocator: nullptr, pSetLayout: &SetLayout) != VK_SUCCESS)
4987 {
4988 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating descriptor layout failed.");
4989 return false;
4990 }
4991 return true;
4992 }
4993
4994 [[nodiscard]] bool CreateSpriteMultiUniformDescriptorSetLayout()
4995 {
4996 return CreateUniformDescriptorSetLayout(SetLayout&: m_SpriteMultiUniformDescriptorSetLayout, StageFlags: VK_SHADER_STAGE_VERTEX_BIT);
4997 }
4998
4999 [[nodiscard]] bool CreateQuadUniformDescriptorSetLayout()
5000 {
5001 return CreateUniformDescriptorSetLayout(SetLayout&: m_QuadUniformDescriptorSetLayout, StageFlags: VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT);
5002 }
5003
5004 void DestroyUniformDescriptorSetLayouts()
5005 {
5006 vkDestroyDescriptorSetLayout(device: m_VKDevice, descriptorSetLayout: m_QuadUniformDescriptorSetLayout, pAllocator: nullptr);
5007 vkDestroyDescriptorSetLayout(device: m_VKDevice, descriptorSetLayout: m_SpriteMultiUniformDescriptorSetLayout, pAllocator: nullptr);
5008 }
5009
5010 [[nodiscard]] bool CreateUniformDescriptorSets(size_t RenderThreadIndex, VkDescriptorSetLayout &SetLayout, SDeviceDescriptorSet *pSets, size_t SetCount, VkBuffer BindBuffer, size_t SingleBufferInstanceSize, VkDeviceSize MemoryOffset)
5011 {
5012 VkDescriptorPool RetDescr;
5013 if(!GetDescriptorPoolForAlloc(RetDescr, DescriptorPools&: m_vUniformBufferDescrPools[RenderThreadIndex], pSets, AllocNum: SetCount))
5014 return false;
5015 VkDescriptorSetAllocateInfo DesAllocInfo{};
5016 DesAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
5017 DesAllocInfo.descriptorSetCount = 1;
5018 DesAllocInfo.pSetLayouts = &SetLayout;
5019 for(size_t i = 0; i < SetCount; ++i)
5020 {
5021 DesAllocInfo.descriptorPool = pSets[i].m_pPools->m_vPools[pSets[i].m_PoolIndex].m_Pool;
5022 if(vkAllocateDescriptorSets(device: m_VKDevice, pAllocateInfo: &DesAllocInfo, pDescriptorSets: &pSets[i].m_Descriptor) != VK_SUCCESS)
5023 {
5024 return false;
5025 }
5026
5027 VkDescriptorBufferInfo BufferInfo{};
5028 BufferInfo.buffer = BindBuffer;
5029 BufferInfo.offset = MemoryOffset + SingleBufferInstanceSize * i;
5030 BufferInfo.range = SingleBufferInstanceSize;
5031
5032 std::array<VkWriteDescriptorSet, 1> aDescriptorWrites{};
5033
5034 aDescriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
5035 aDescriptorWrites[0].dstSet = pSets[i].m_Descriptor;
5036 aDescriptorWrites[0].dstBinding = 1;
5037 aDescriptorWrites[0].dstArrayElement = 0;
5038 aDescriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
5039 aDescriptorWrites[0].descriptorCount = 1;
5040 aDescriptorWrites[0].pBufferInfo = &BufferInfo;
5041
5042 vkUpdateDescriptorSets(device: m_VKDevice, descriptorWriteCount: static_cast<uint32_t>(aDescriptorWrites.size()), pDescriptorWrites: aDescriptorWrites.data(), descriptorCopyCount: 0, pDescriptorCopies: nullptr);
5043 }
5044
5045 return true;
5046 }
5047
5048 void DestroyUniformDescriptorSets(SDeviceDescriptorSet *pSets, size_t SetCount)
5049 {
5050 for(size_t i = 0; i < SetCount; ++i)
5051 {
5052 vkFreeDescriptorSets(device: m_VKDevice, descriptorPool: pSets[i].m_pPools->m_vPools[pSets[i].m_PoolIndex].m_Pool, descriptorSetCount: 1, pDescriptorSets: &pSets[i].m_Descriptor);
5053 pSets[i].m_Descriptor = VK_NULL_HANDLE;
5054 }
5055 }
5056
5057 [[nodiscard]] bool CreateSpriteMultiGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
5058 {
5059 std::array<VkVertexInputAttributeDescription, 3> aAttributeDescriptions = {};
5060 aAttributeDescriptions[0] = {.location: 0, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: 0};
5061 aAttributeDescriptions[1] = {.location: 1, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: sizeof(float) * 2};
5062 aAttributeDescriptions[2] = {.location: 2, .binding: 0, .format: VK_FORMAT_R8G8B8A8_UNORM, .offset: sizeof(float) * (2 + 2)};
5063
5064 std::array<VkDescriptorSetLayout, 2> aSetLayouts;
5065 aSetLayouts[0] = m_StandardTexturedDescriptorSetLayout;
5066 aSetLayouts[1] = m_SpriteMultiUniformDescriptorSetLayout;
5067
5068 uint32_t VertPushConstantSize = sizeof(SUniformSpriteMultiGPos);
5069 uint32_t FragPushConstantSize = sizeof(SUniformSpriteMultiGVertColor);
5070
5071 std::array<VkPushConstantRange, 2> aPushConstants{};
5072 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: VertPushConstantSize};
5073 aPushConstants[1] = {.stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, .offset: sizeof(SUniformSpriteMultiGPos) + sizeof(SUniformSpriteMultiGVertColorAlign), .size: FragPushConstantSize};
5074
5075 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, Stride: sizeof(float) * (2 + 2) + sizeof(uint8_t) * 4, aInputAttr&: aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
5076 }
5077
5078 [[nodiscard]] bool CreateSpriteMultiGraphicsPipeline(const char *pVertName, const char *pFragName)
5079 {
5080 bool Ret = true;
5081
5082 EVulkanBackendTextureModes TexMode = VULKAN_BACKEND_TEXTURE_MODE_TEXTURED;
5083
5084 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
5085 {
5086 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
5087 {
5088 Ret &= CreateSpriteMultiGraphicsPipelineImpl(pVertName, pFragName, PipeContainer&: m_SpriteMultiPipeline, TexMode, BlendMode: EVulkanBackendBlendModes(i), DynamicMode: EVulkanBackendClipModes(j));
5089 }
5090 }
5091
5092 return Ret;
5093 }
5094
5095 [[nodiscard]] bool CreateSpriteMultiPushGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
5096 {
5097 std::array<VkVertexInputAttributeDescription, 3> aAttributeDescriptions = {};
5098 aAttributeDescriptions[0] = {.location: 0, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: 0};
5099 aAttributeDescriptions[1] = {.location: 1, .binding: 0, .format: VK_FORMAT_R32G32_SFLOAT, .offset: sizeof(float) * 2};
5100 aAttributeDescriptions[2] = {.location: 2, .binding: 0, .format: VK_FORMAT_R8G8B8A8_UNORM, .offset: sizeof(float) * (2 + 2)};
5101
5102 std::array<VkDescriptorSetLayout, 1> aSetLayouts;
5103 aSetLayouts[0] = m_StandardTexturedDescriptorSetLayout;
5104
5105 uint32_t VertPushConstantSize = sizeof(SUniformSpriteMultiPushGPos);
5106 uint32_t FragPushConstantSize = sizeof(SUniformSpriteMultiPushGVertColor);
5107
5108 std::array<VkPushConstantRange, 2> aPushConstants{};
5109 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: VertPushConstantSize};
5110 aPushConstants[1] = {.stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, .offset: sizeof(SUniformSpriteMultiPushGPos), .size: FragPushConstantSize};
5111
5112 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, Stride: sizeof(float) * (2 + 2) + sizeof(uint8_t) * 4, aInputAttr&: aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
5113 }
5114
5115 [[nodiscard]] bool CreateSpriteMultiPushGraphicsPipeline(const char *pVertName, const char *pFragName)
5116 {
5117 bool Ret = true;
5118
5119 EVulkanBackendTextureModes TexMode = VULKAN_BACKEND_TEXTURE_MODE_TEXTURED;
5120
5121 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
5122 {
5123 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
5124 {
5125 Ret &= CreateSpriteMultiPushGraphicsPipelineImpl(pVertName, pFragName, PipeContainer&: m_SpriteMultiPushPipeline, TexMode, BlendMode: EVulkanBackendBlendModes(i), DynamicMode: EVulkanBackendClipModes(j));
5126 }
5127 }
5128
5129 return Ret;
5130 }
5131
5132 template<bool IsTextured>
5133 [[nodiscard]] bool CreateQuadGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
5134 {
5135 std::array<VkVertexInputAttributeDescription, IsTextured ? 3 : 2> aAttributeDescriptions = {};
5136 aAttributeDescriptions[0] = {0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0};
5137 aAttributeDescriptions[1] = {1, 0, VK_FORMAT_R8G8B8A8_UNORM, sizeof(float) * 4};
5138 if(IsTextured)
5139 aAttributeDescriptions[2] = {2, 0, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 4 + sizeof(uint8_t) * 4};
5140
5141 std::array<VkDescriptorSetLayout, IsTextured ? 2 : 1> aSetLayouts;
5142 if(IsTextured)
5143 {
5144 aSetLayouts[0] = m_StandardTexturedDescriptorSetLayout;
5145 aSetLayouts[1] = m_QuadUniformDescriptorSetLayout;
5146 }
5147 else
5148 {
5149 aSetLayouts[0] = m_QuadUniformDescriptorSetLayout;
5150 }
5151
5152 uint32_t PushConstantSize = sizeof(SUniformQuadGPos);
5153
5154 std::array<VkPushConstantRange, 1> aPushConstants{};
5155 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT, .offset: 0, .size: PushConstantSize};
5156
5157 return CreateGraphicsPipeline<true>(pVertName, pFragName, PipeContainer, sizeof(float) * 4 + sizeof(uint8_t) * 4 + (IsTextured ? (sizeof(float) * 2) : 0), aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
5158 }
5159
5160 template<bool HasSampler>
5161 [[nodiscard]] bool CreateQuadGraphicsPipeline(const char *pVertName, const char *pFragName)
5162 {
5163 bool Ret = true;
5164
5165 EVulkanBackendTextureModes TexMode = HasSampler ? VULKAN_BACKEND_TEXTURE_MODE_TEXTURED : VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED;
5166
5167 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
5168 {
5169 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
5170 {
5171 Ret &= CreateQuadGraphicsPipelineImpl<HasSampler>(pVertName, pFragName, m_QuadPipeline, TexMode, EVulkanBackendBlendModes(i), EVulkanBackendClipModes(j));
5172 }
5173 }
5174
5175 return Ret;
5176 }
5177
5178 template<bool IsTextured>
5179 [[nodiscard]] bool CreateQuadGroupedGraphicsPipelineImpl(const char *pVertName, const char *pFragName, SPipelineContainer &PipeContainer, EVulkanBackendTextureModes TexMode, EVulkanBackendBlendModes BlendMode, EVulkanBackendClipModes DynamicMode)
5180 {
5181 std::array<VkVertexInputAttributeDescription, IsTextured ? 3 : 2> aAttributeDescriptions = {};
5182 aAttributeDescriptions[0] = {0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0};
5183 aAttributeDescriptions[1] = {1, 0, VK_FORMAT_R8G8B8A8_UNORM, sizeof(float) * 4};
5184 if(IsTextured)
5185 aAttributeDescriptions[2] = {2, 0, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 4 + sizeof(uint8_t) * 4};
5186
5187 std::array<VkDescriptorSetLayout, 1> aSetLayouts;
5188 aSetLayouts[0] = m_StandardTexturedDescriptorSetLayout;
5189
5190 uint32_t PushConstantSize = sizeof(SUniformQuadGroupedGPos);
5191
5192 std::array<VkPushConstantRange, 1> aPushConstants{};
5193 aPushConstants[0] = {.stageFlags: VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, .offset: 0, .size: PushConstantSize};
5194
5195 return CreateGraphicsPipeline<false>(pVertName, pFragName, PipeContainer, sizeof(float) * 4 + sizeof(uint8_t) * 4 + (IsTextured ? (sizeof(float) * 2) : 0), aAttributeDescriptions, aSetLayouts, aPushConstants, TexMode, BlendMode, DynamicMode);
5196 }
5197
5198 template<bool HasSampler>
5199 [[nodiscard]] bool CreateQuadGroupedGraphicsPipeline(const char *pVertName, const char *pFragName)
5200 {
5201 bool Ret = true;
5202
5203 EVulkanBackendTextureModes TexMode = HasSampler ? VULKAN_BACKEND_TEXTURE_MODE_TEXTURED : VULKAN_BACKEND_TEXTURE_MODE_NOT_TEXTURED;
5204
5205 for(size_t i = 0; i < VULKAN_BACKEND_BLEND_MODE_COUNT; ++i)
5206 {
5207 for(size_t j = 0; j < VULKAN_BACKEND_CLIP_MODE_COUNT; ++j)
5208 {
5209 Ret &= CreateQuadGroupedGraphicsPipelineImpl<HasSampler>(pVertName, pFragName, m_QuadGroupedPipeline, TexMode, EVulkanBackendBlendModes(i), EVulkanBackendClipModes(j));
5210 }
5211 }
5212
5213 return Ret;
5214 }
5215
5216 [[nodiscard]] bool CreateCommandPool()
5217 {
5218 VkCommandPoolCreateInfo CreatePoolInfo{};
5219 CreatePoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
5220 CreatePoolInfo.queueFamilyIndex = m_VKGraphicsQueueIndex;
5221 CreatePoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
5222
5223 m_vCommandPools.resize(sz: m_ThreadCount);
5224 for(size_t i = 0; i < m_ThreadCount; ++i)
5225 {
5226 if(vkCreateCommandPool(device: m_VKDevice, pCreateInfo: &CreatePoolInfo, pAllocator: nullptr, pCommandPool: &m_vCommandPools[i]) != VK_SUCCESS)
5227 {
5228 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating the command pool failed.");
5229 return false;
5230 }
5231 }
5232 return true;
5233 }
5234
5235 void DestroyCommandPool()
5236 {
5237 for(size_t i = 0; i < m_ThreadCount; ++i)
5238 {
5239 vkDestroyCommandPool(device: m_VKDevice, commandPool: m_vCommandPools[i], pAllocator: nullptr);
5240 }
5241 }
5242
5243 [[nodiscard]] bool CreateCommandBuffers()
5244 {
5245 m_vMainDrawCommandBuffers.resize(sz: m_SwapChainImageCount);
5246 if(m_ThreadCount > 1)
5247 {
5248 m_vvThreadDrawCommandBuffers.resize(sz: m_ThreadCount);
5249 m_vvUsedThreadDrawCommandBuffer.resize(sz: m_ThreadCount);
5250 m_vHelperThreadDrawCommandBuffers.resize(sz: m_ThreadCount);
5251 for(auto &ThreadDrawCommandBuffers : m_vvThreadDrawCommandBuffers)
5252 {
5253 ThreadDrawCommandBuffers.resize(sz: m_SwapChainImageCount);
5254 }
5255 for(auto &UsedThreadDrawCommandBuffer : m_vvUsedThreadDrawCommandBuffer)
5256 {
5257 UsedThreadDrawCommandBuffer.resize(sz: m_SwapChainImageCount, c: false);
5258 }
5259 }
5260 m_vMemoryCommandBuffers.resize(sz: m_SwapChainImageCount);
5261 m_vUsedMemoryCommandBuffer.resize(sz: m_SwapChainImageCount, c: false);
5262
5263 VkCommandBufferAllocateInfo AllocInfo{};
5264 AllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
5265 AllocInfo.commandPool = m_vCommandPools[0];
5266 AllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
5267 AllocInfo.commandBufferCount = (uint32_t)m_vMainDrawCommandBuffers.size();
5268
5269 if(vkAllocateCommandBuffers(device: m_VKDevice, pAllocateInfo: &AllocInfo, pCommandBuffers: m_vMainDrawCommandBuffers.data()) != VK_SUCCESS)
5270 {
5271 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Allocating command buffers failed.");
5272 return false;
5273 }
5274
5275 AllocInfo.commandBufferCount = (uint32_t)m_vMemoryCommandBuffers.size();
5276
5277 if(vkAllocateCommandBuffers(device: m_VKDevice, pAllocateInfo: &AllocInfo, pCommandBuffers: m_vMemoryCommandBuffers.data()) != VK_SUCCESS)
5278 {
5279 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Allocating memory command buffers failed.");
5280 return false;
5281 }
5282
5283 if(m_ThreadCount > 1)
5284 {
5285 size_t Count = 0;
5286 for(auto &ThreadDrawCommandBuffers : m_vvThreadDrawCommandBuffers)
5287 {
5288 AllocInfo.commandPool = m_vCommandPools[Count];
5289 ++Count;
5290 AllocInfo.commandBufferCount = (uint32_t)ThreadDrawCommandBuffers.size();
5291 AllocInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
5292 if(vkAllocateCommandBuffers(device: m_VKDevice, pAllocateInfo: &AllocInfo, pCommandBuffers: ThreadDrawCommandBuffers.data()) != VK_SUCCESS)
5293 {
5294 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Allocating thread command buffers failed.");
5295 return false;
5296 }
5297 }
5298 }
5299
5300 return true;
5301 }
5302
5303 void DestroyCommandBuffer()
5304 {
5305 if(m_ThreadCount > 1)
5306 {
5307 size_t Count = 0;
5308 for(auto &ThreadDrawCommandBuffers : m_vvThreadDrawCommandBuffers)
5309 {
5310 vkFreeCommandBuffers(device: m_VKDevice, commandPool: m_vCommandPools[Count], commandBufferCount: static_cast<uint32_t>(ThreadDrawCommandBuffers.size()), pCommandBuffers: ThreadDrawCommandBuffers.data());
5311 ++Count;
5312 }
5313 }
5314
5315 vkFreeCommandBuffers(device: m_VKDevice, commandPool: m_vCommandPools[0], commandBufferCount: static_cast<uint32_t>(m_vMemoryCommandBuffers.size()), pCommandBuffers: m_vMemoryCommandBuffers.data());
5316 vkFreeCommandBuffers(device: m_VKDevice, commandPool: m_vCommandPools[0], commandBufferCount: static_cast<uint32_t>(m_vMainDrawCommandBuffers.size()), pCommandBuffers: m_vMainDrawCommandBuffers.data());
5317
5318 m_vvThreadDrawCommandBuffers.clear();
5319 m_vvUsedThreadDrawCommandBuffer.clear();
5320 m_vHelperThreadDrawCommandBuffers.clear();
5321
5322 m_vMainDrawCommandBuffers.clear();
5323 m_vMemoryCommandBuffers.clear();
5324 m_vUsedMemoryCommandBuffer.clear();
5325 }
5326
5327 [[nodiscard]] bool CreateSyncObjects()
5328 {
5329 auto SyncObjectCount = m_SwapChainImageCount;
5330 m_vQueueSubmitSemaphores.resize(sz: SyncObjectCount);
5331 m_vBusyAcquireImageSemaphores.resize(sz: SyncObjectCount);
5332
5333 m_vQueueSubmitFences.resize(sz: SyncObjectCount);
5334
5335 VkSemaphoreCreateInfo CreateSemaphoreInfo{};
5336 CreateSemaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
5337
5338 VkFenceCreateInfo FenceInfo{};
5339 FenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
5340 FenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
5341
5342 if(vkCreateSemaphore(device: m_VKDevice, pCreateInfo: &CreateSemaphoreInfo, pAllocator: nullptr, pSemaphore: &m_AcquireImageSemaphore) != VK_SUCCESS)
5343 {
5344 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating acquire next image semaphore failed.");
5345 return false;
5346 }
5347 for(size_t i = 0; i < SyncObjectCount; i++)
5348 {
5349 if(vkCreateSemaphore(device: m_VKDevice, pCreateInfo: &CreateSemaphoreInfo, pAllocator: nullptr, pSemaphore: &m_vQueueSubmitSemaphores[i]) != VK_SUCCESS ||
5350 vkCreateSemaphore(device: m_VKDevice, pCreateInfo: &CreateSemaphoreInfo, pAllocator: nullptr, pSemaphore: &m_vBusyAcquireImageSemaphores[i]) != VK_SUCCESS ||
5351 vkCreateFence(device: m_VKDevice, pCreateInfo: &FenceInfo, pAllocator: nullptr, pFence: &m_vQueueSubmitFences[i]) != VK_SUCCESS)
5352 {
5353 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating swap chain sync objects(fences, semaphores) failed.");
5354 return false;
5355 }
5356 }
5357
5358 return true;
5359 }
5360
5361 void DestroySyncObjects()
5362 {
5363 for(size_t i = 0; i < m_vBusyAcquireImageSemaphores.size(); i++)
5364 {
5365 vkDestroySemaphore(device: m_VKDevice, semaphore: m_vBusyAcquireImageSemaphores[i], pAllocator: nullptr);
5366 vkDestroySemaphore(device: m_VKDevice, semaphore: m_vQueueSubmitSemaphores[i], pAllocator: nullptr);
5367 vkDestroyFence(device: m_VKDevice, fence: m_vQueueSubmitFences[i], pAllocator: nullptr);
5368 }
5369 vkDestroySemaphore(device: m_VKDevice, semaphore: m_AcquireImageSemaphore, pAllocator: nullptr);
5370
5371 m_vBusyAcquireImageSemaphores.clear();
5372 m_vQueueSubmitSemaphores.clear();
5373
5374 m_vQueueSubmitFences.clear();
5375 }
5376
5377 void DestroyBufferOfFrame(size_t ImageIndex, SFrameBuffers &Buffer)
5378 {
5379 CleanBufferPair(ImageIndex, Buffer&: Buffer.m_Buffer, BufferMem&: Buffer.m_BufferMem);
5380 }
5381
5382 void DestroyUniBufferOfFrame(size_t ImageIndex, SFrameUniformBuffers &Buffer)
5383 {
5384 CleanBufferPair(ImageIndex, Buffer&: Buffer.m_Buffer, BufferMem&: Buffer.m_BufferMem);
5385 for(auto &DescrSet : Buffer.m_aUniformSets)
5386 {
5387 if(DescrSet.m_Descriptor != VK_NULL_HANDLE)
5388 {
5389 DestroyUniformDescriptorSets(pSets: &DescrSet, SetCount: 1);
5390 }
5391 }
5392 }
5393
5394 /*************
5395 * SWAP CHAIN
5396 **************/
5397
5398 void CleanupVulkanSwapChain(bool ForceSwapChainDestruct)
5399 {
5400 m_StandardPipeline.Destroy(Device&: m_VKDevice);
5401 m_StandardLinePipeline.Destroy(Device&: m_VKDevice);
5402 m_Standard3DPipeline.Destroy(Device&: m_VKDevice);
5403 m_TextPipeline.Destroy(Device&: m_VKDevice);
5404 m_TilePipeline.Destroy(Device&: m_VKDevice);
5405 m_TileBorderPipeline.Destroy(Device&: m_VKDevice);
5406 m_PrimExPipeline.Destroy(Device&: m_VKDevice);
5407 m_PrimExRotationlessPipeline.Destroy(Device&: m_VKDevice);
5408 m_SpriteMultiPipeline.Destroy(Device&: m_VKDevice);
5409 m_SpriteMultiPushPipeline.Destroy(Device&: m_VKDevice);
5410 m_QuadPipeline.Destroy(Device&: m_VKDevice);
5411 m_QuadGroupedPipeline.Destroy(Device&: m_VKDevice);
5412
5413 DestroyFramebuffers();
5414
5415 DestroyRenderPass();
5416
5417 DestroyMultiSamplerImageAttachments();
5418
5419 DestroyImageViews();
5420 ClearSwapChainImageHandles();
5421
5422 DestroySwapChain(ForceDestroy: ForceSwapChainDestruct);
5423
5424 m_SwapchainCreated = false;
5425 }
5426
5427 template<bool IsLastCleanup>
5428 void CleanupVulkan(size_t SwapchainCount)
5429 {
5430 if(IsLastCleanup)
5431 {
5432 if(m_SwapchainCreated)
5433 CleanupVulkanSwapChain(ForceSwapChainDestruct: true);
5434
5435 // clean all images, buffers, buffer containers
5436 for(auto &Texture : m_vTextures)
5437 {
5438 if(Texture.m_VKTextDescrSet.m_Descriptor != VK_NULL_HANDLE && IsVerbose())
5439 {
5440 log_warn("gfx/vulkan", "Text textures were not cleared over command.");
5441 }
5442 DestroyTexture(Texture);
5443 }
5444
5445 for(auto &BufferObject : m_vBufferObjects)
5446 {
5447 if(!BufferObject.m_IsStreamedBuffer)
5448 FreeVertexMemBlock(Block&: BufferObject.m_BufferObject.m_Mem);
5449 }
5450
5451 m_vBufferContainers.clear();
5452 }
5453
5454 m_vImageLastFrameCheck.clear();
5455
5456 m_vLastPipeline.clear();
5457
5458 for(size_t i = 0; i < m_ThreadCount; ++i)
5459 {
5460 m_vStreamedVertexBuffers[i].Destroy(DestroyBuffer: [&](size_t ImageIndex, SFrameBuffers &Buffer) { DestroyBufferOfFrame(ImageIndex, Buffer); });
5461 m_vStreamedUniformBuffers[i].Destroy(DestroyBuffer: [&](size_t ImageIndex, SFrameUniformBuffers &Buffer) { DestroyUniBufferOfFrame(ImageIndex, Buffer); });
5462 }
5463 m_vStreamedVertexBuffers.clear();
5464 m_vStreamedUniformBuffers.clear();
5465
5466 for(size_t i = 0; i < SwapchainCount; ++i)
5467 {
5468 ClearFrameData(FrameImageIndex: i);
5469 }
5470
5471 m_vvFrameDelayedBufferCleanup.clear();
5472 m_vvFrameDelayedTextureCleanup.clear();
5473 m_vvFrameDelayedTextTexturesCleanup.clear();
5474
5475 m_StagingBufferCache.DestroyFrameData(ImageCount: SwapchainCount);
5476 m_StagingBufferCacheImage.DestroyFrameData(ImageCount: SwapchainCount);
5477 m_VertexBufferCache.DestroyFrameData(ImageCount: SwapchainCount);
5478 for(auto &ImageBufferCache : m_ImageBufferCaches)
5479 ImageBufferCache.second.DestroyFrameData(ImageCount: SwapchainCount);
5480
5481 if(IsLastCleanup)
5482 {
5483 m_StagingBufferCache.Destroy(Device&: m_VKDevice);
5484 m_StagingBufferCacheImage.Destroy(Device&: m_VKDevice);
5485 m_VertexBufferCache.Destroy(Device&: m_VKDevice);
5486 for(auto &ImageBufferCache : m_ImageBufferCaches)
5487 ImageBufferCache.second.Destroy(Device&: m_VKDevice);
5488
5489 m_ImageBufferCaches.clear();
5490
5491 DestroyTextureSamplers();
5492 DestroyDescriptorPools();
5493
5494 DeletePresentedImageDataImage();
5495 }
5496
5497 DestroySyncObjects();
5498 DestroyCommandBuffer();
5499
5500 if(IsLastCleanup)
5501 {
5502 DestroyCommandPool();
5503 }
5504
5505 if(IsLastCleanup)
5506 {
5507 DestroyUniformDescriptorSetLayouts();
5508 DestroyTextDescriptorSetLayout();
5509 DestroyDescriptorSetLayouts();
5510 }
5511 }
5512
5513 void CleanupVulkanSDL()
5514 {
5515 if(m_VKInstance != VK_NULL_HANDLE)
5516 {
5517 DestroySurface();
5518 vkDestroyDevice(device: m_VKDevice, pAllocator: nullptr);
5519
5520 if(g_Config.m_DbgGfx == DEBUG_GFX_MODE_MINIMUM || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL)
5521 {
5522 UnregisterDebugCallback();
5523 }
5524 vkDestroyInstance(instance: m_VKInstance, pAllocator: nullptr);
5525 m_VKInstance = VK_NULL_HANDLE;
5526 }
5527 }
5528
5529 int RecreateSwapChain()
5530 {
5531 int Ret = 0;
5532 vkDeviceWaitIdle(device: m_VKDevice);
5533
5534 if(IsVerbose())
5535 {
5536 log_info("gfx/vulkan", "Recreating swap chain.");
5537 }
5538
5539 VkSwapchainKHR OldSwapChain = VK_NULL_HANDLE;
5540 uint32_t OldSwapChainImageCount = m_SwapChainImageCount;
5541
5542 if(m_SwapchainCreated)
5543 CleanupVulkanSwapChain(ForceSwapChainDestruct: false);
5544
5545 // set new multi sampling if it was requested
5546 if(m_NextMultiSamplingCount != std::numeric_limits<uint32_t>::max())
5547 {
5548 m_MultiSamplingCount = m_NextMultiSamplingCount;
5549 m_NextMultiSamplingCount = std::numeric_limits<uint32_t>::max();
5550 }
5551
5552 if(!m_SwapchainCreated)
5553 Ret = InitVulkanSwapChain(OldSwapChain);
5554
5555 if(OldSwapChainImageCount != m_SwapChainImageCount)
5556 {
5557 CleanupVulkan<false>(SwapchainCount: OldSwapChainImageCount);
5558 InitVulkan<false>();
5559 }
5560
5561 if(OldSwapChain != VK_NULL_HANDLE)
5562 {
5563 vkDestroySwapchainKHR(device: m_VKDevice, swapchain: OldSwapChain, pAllocator: nullptr);
5564 }
5565
5566 if(Ret != 0 && IsVerbose())
5567 {
5568 log_warn("gfx/vulkan", "Recreating swap chain failed.");
5569 }
5570
5571 return Ret;
5572 }
5573
5574 int InitVulkanSDL(SDL_Window *pWindow, uint32_t CanvasWidth, uint32_t CanvasHeight, char *pRendererString, char *pVendorString, char *pVersionString)
5575 {
5576 std::vector<std::string> vVKExtensions;
5577 std::vector<std::string> vVKLayers;
5578
5579 m_CanvasWidth = CanvasWidth;
5580 m_CanvasHeight = CanvasHeight;
5581
5582 if(!GetVulkanExtensions(pWindow, vVKExtensions))
5583 return -1;
5584
5585 if(!GetVulkanLayers(vVKLayers))
5586 return -1;
5587
5588 if(!CreateVulkanInstance(vVKLayers, vVKExtensions, TryDebugExtensions: true))
5589 return -1;
5590
5591 if(g_Config.m_DbgGfx == DEBUG_GFX_MODE_MINIMUM || g_Config.m_DbgGfx == DEBUG_GFX_MODE_ALL)
5592 {
5593 SetupDebugCallback();
5594
5595 for(auto &VKLayer : vVKLayers)
5596 {
5597 log_info("gfx/vulkan", "Validation layer: %s", VKLayer.c_str());
5598 }
5599 }
5600
5601 if(!SelectGpu(pRendererName: pRendererString, pVendorName: pVendorString, pVersionName: pVersionString))
5602 return -1;
5603
5604 if(!CreateLogicalDevice(vVKLayers))
5605 return -1;
5606
5607 GetDeviceQueue();
5608
5609 if(!CreateSurface(pWindow))
5610 return -1;
5611
5612 return 0;
5613 }
5614
5615 /************************
5616 * MEMORY MANAGEMENT
5617 ************************/
5618
5619 uint32_t FindMemoryType(VkPhysicalDevice PhyDevice, uint32_t TypeFilter, VkMemoryPropertyFlags Properties)
5620 {
5621 VkPhysicalDeviceMemoryProperties MemProperties;
5622 vkGetPhysicalDeviceMemoryProperties(physicalDevice: PhyDevice, pMemoryProperties: &MemProperties);
5623
5624 for(uint32_t i = 0; i < MemProperties.memoryTypeCount; i++)
5625 {
5626 if((TypeFilter & (1 << i)) && (MemProperties.memoryTypes[i].propertyFlags & Properties) == Properties)
5627 {
5628 return i;
5629 }
5630 }
5631
5632 return 0;
5633 }
5634
5635 [[nodiscard]] bool CreateBuffer(VkDeviceSize BufferSize, EMemoryBlockUsage MemUsage, VkBufferUsageFlags BufferUsage, VkMemoryPropertyFlags MemoryProperties, VkBuffer &VKBuffer, SDeviceMemoryBlock &VKBufferMemory)
5636 {
5637 VkBufferCreateInfo BufferInfo{};
5638 BufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
5639 BufferInfo.size = BufferSize;
5640 BufferInfo.usage = BufferUsage;
5641 BufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
5642
5643 if(vkCreateBuffer(device: m_VKDevice, pCreateInfo: &BufferInfo, pAllocator: nullptr, pBuffer: &VKBuffer) != VK_SUCCESS)
5644 {
5645 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, pErr: "Buffer creation failed.");
5646 return false;
5647 }
5648
5649 VkMemoryRequirements MemRequirements;
5650 vkGetBufferMemoryRequirements(device: m_VKDevice, buffer: VKBuffer, pMemoryRequirements: &MemRequirements);
5651
5652 VkMemoryAllocateInfo MemAllocInfo{};
5653 MemAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
5654 MemAllocInfo.allocationSize = MemRequirements.size;
5655 MemAllocInfo.memoryTypeIndex = FindMemoryType(PhyDevice: m_VKGPU, TypeFilter: MemRequirements.memoryTypeBits, Properties: MemoryProperties);
5656
5657 VKBufferMemory.m_Size = MemRequirements.size;
5658
5659 if(MemUsage == MEMORY_BLOCK_USAGE_BUFFER)
5660 m_pBufferMemoryUsage->store(i: m_pBufferMemoryUsage->load(m: std::memory_order_relaxed) + MemRequirements.size, m: std::memory_order_relaxed);
5661 else if(MemUsage == MEMORY_BLOCK_USAGE_STAGING)
5662 m_pStagingMemoryUsage->store(i: m_pStagingMemoryUsage->load(m: std::memory_order_relaxed) + MemRequirements.size, m: std::memory_order_relaxed);
5663 else if(MemUsage == MEMORY_BLOCK_USAGE_STREAM)
5664 m_pStreamMemoryUsage->store(i: m_pStreamMemoryUsage->load(m: std::memory_order_relaxed) + MemRequirements.size, m: std::memory_order_relaxed);
5665
5666 if(IsVerbose())
5667 {
5668 VerboseAllocatedMemory(Size: MemRequirements.size, FrameImageIndex: m_CurImageIndex, MemUsage);
5669 }
5670
5671 if(!AllocateVulkanMemory(pAllocateInfo: &MemAllocInfo, pMemory: &VKBufferMemory.m_Mem))
5672 {
5673 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, pErr: "Allocation for buffer object failed.");
5674 return false;
5675 }
5676
5677 VKBufferMemory.m_UsageType = MemUsage;
5678
5679 if(vkBindBufferMemory(device: m_VKDevice, buffer: VKBuffer, memory: VKBufferMemory.m_Mem, memoryOffset: 0) != VK_SUCCESS)
5680 {
5681 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, pErr: "Binding memory to buffer failed.");
5682 return false;
5683 }
5684
5685 return true;
5686 }
5687
5688 [[nodiscard]] bool AllocateDescriptorPool(SDeviceDescriptorPools &DescriptorPools, size_t AllocPoolSize)
5689 {
5690 SDeviceDescriptorPool NewPool;
5691 NewPool.m_Size = AllocPoolSize;
5692
5693 VkDescriptorPoolSize PoolSize{};
5694 if(DescriptorPools.m_IsUniformPool)
5695 PoolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
5696 else
5697 PoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
5698 PoolSize.descriptorCount = AllocPoolSize;
5699
5700 VkDescriptorPoolCreateInfo PoolInfo{};
5701 PoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
5702 PoolInfo.poolSizeCount = 1;
5703 PoolInfo.pPoolSizes = &PoolSize;
5704 PoolInfo.maxSets = AllocPoolSize;
5705 PoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
5706
5707 if(vkCreateDescriptorPool(device: m_VKDevice, pCreateInfo: &PoolInfo, pAllocator: nullptr, pDescriptorPool: &NewPool.m_Pool) != VK_SUCCESS)
5708 {
5709 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_INIT, pErr: "Creating the descriptor pool failed.");
5710 return false;
5711 }
5712
5713 DescriptorPools.m_vPools.push_back(x: NewPool);
5714
5715 return true;
5716 }
5717
5718 [[nodiscard]] bool CreateDescriptorPools()
5719 {
5720 m_StandardTextureDescrPool.m_IsUniformPool = false;
5721 m_StandardTextureDescrPool.m_DefaultAllocSize = 1024;
5722 m_TextTextureDescrPool.m_IsUniformPool = false;
5723 m_TextTextureDescrPool.m_DefaultAllocSize = 8;
5724
5725 m_vUniformBufferDescrPools.resize(sz: m_ThreadCount);
5726 for(auto &UniformBufferDescrPool : m_vUniformBufferDescrPools)
5727 {
5728 UniformBufferDescrPool.m_IsUniformPool = true;
5729 UniformBufferDescrPool.m_DefaultAllocSize = 512;
5730 }
5731
5732 bool Ret = AllocateDescriptorPool(DescriptorPools&: m_StandardTextureDescrPool, AllocPoolSize: CCommandBuffer::MAX_TEXTURES);
5733 Ret |= AllocateDescriptorPool(DescriptorPools&: m_TextTextureDescrPool, AllocPoolSize: 8);
5734
5735 for(auto &UniformBufferDescrPool : m_vUniformBufferDescrPools)
5736 {
5737 Ret |= AllocateDescriptorPool(DescriptorPools&: UniformBufferDescrPool, AllocPoolSize: 64);
5738 }
5739
5740 return Ret;
5741 }
5742
5743 void DestroyDescriptorPools()
5744 {
5745 for(auto &DescrPool : m_StandardTextureDescrPool.m_vPools)
5746 vkDestroyDescriptorPool(device: m_VKDevice, descriptorPool: DescrPool.m_Pool, pAllocator: nullptr);
5747 for(auto &DescrPool : m_TextTextureDescrPool.m_vPools)
5748 vkDestroyDescriptorPool(device: m_VKDevice, descriptorPool: DescrPool.m_Pool, pAllocator: nullptr);
5749
5750 for(auto &UniformBufferDescrPool : m_vUniformBufferDescrPools)
5751 {
5752 for(auto &DescrPool : UniformBufferDescrPool.m_vPools)
5753 vkDestroyDescriptorPool(device: m_VKDevice, descriptorPool: DescrPool.m_Pool, pAllocator: nullptr);
5754 }
5755 m_vUniformBufferDescrPools.clear();
5756 }
5757
5758 [[nodiscard]] bool GetDescriptorPoolForAlloc(VkDescriptorPool &RetDescr, SDeviceDescriptorPools &DescriptorPools, SDeviceDescriptorSet *pSets, size_t AllocNum)
5759 {
5760 size_t CurAllocNum = AllocNum;
5761 size_t CurAllocOffset = 0;
5762 RetDescr = VK_NULL_HANDLE;
5763
5764 while(CurAllocNum > 0)
5765 {
5766 size_t AllocatedInThisRun = 0;
5767
5768 bool Found = false;
5769 size_t DescriptorPoolIndex = std::numeric_limits<size_t>::max();
5770 for(size_t i = 0; i < DescriptorPools.m_vPools.size(); ++i)
5771 {
5772 auto &Pool = DescriptorPools.m_vPools[i];
5773 if(Pool.m_CurSize + CurAllocNum < Pool.m_Size)
5774 {
5775 AllocatedInThisRun = CurAllocNum;
5776 Pool.m_CurSize += CurAllocNum;
5777 Found = true;
5778 if(RetDescr == VK_NULL_HANDLE)
5779 RetDescr = Pool.m_Pool;
5780 DescriptorPoolIndex = i;
5781 break;
5782 }
5783 else
5784 {
5785 size_t RemainingPoolCount = Pool.m_Size - Pool.m_CurSize;
5786 if(RemainingPoolCount > 0)
5787 {
5788 AllocatedInThisRun = RemainingPoolCount;
5789 Pool.m_CurSize += RemainingPoolCount;
5790 Found = true;
5791 if(RetDescr == VK_NULL_HANDLE)
5792 RetDescr = Pool.m_Pool;
5793 DescriptorPoolIndex = i;
5794 break;
5795 }
5796 }
5797 }
5798
5799 if(!Found)
5800 {
5801 DescriptorPoolIndex = DescriptorPools.m_vPools.size();
5802
5803 if(!AllocateDescriptorPool(DescriptorPools, AllocPoolSize: DescriptorPools.m_DefaultAllocSize))
5804 return false;
5805
5806 AllocatedInThisRun = minimum(a: (size_t)DescriptorPools.m_DefaultAllocSize, b: CurAllocNum);
5807
5808 auto &Pool = DescriptorPools.m_vPools.back();
5809 Pool.m_CurSize += AllocatedInThisRun;
5810 if(RetDescr == VK_NULL_HANDLE)
5811 RetDescr = Pool.m_Pool;
5812 }
5813
5814 for(size_t i = CurAllocOffset; i < CurAllocOffset + AllocatedInThisRun; ++i)
5815 {
5816 pSets[i].m_pPools = &DescriptorPools;
5817 pSets[i].m_PoolIndex = DescriptorPoolIndex;
5818 }
5819 CurAllocOffset += AllocatedInThisRun;
5820 CurAllocNum -= AllocatedInThisRun;
5821 }
5822
5823 return true;
5824 }
5825
5826 void FreeDescriptorSetFromPool(SDeviceDescriptorSet &DescrSet)
5827 {
5828 if(DescrSet.m_PoolIndex != std::numeric_limits<size_t>::max())
5829 {
5830 vkFreeDescriptorSets(device: m_VKDevice, descriptorPool: DescrSet.m_pPools->m_vPools[DescrSet.m_PoolIndex].m_Pool, descriptorSetCount: 1, pDescriptorSets: &DescrSet.m_Descriptor);
5831 DescrSet.m_pPools->m_vPools[DescrSet.m_PoolIndex].m_CurSize -= 1;
5832 }
5833 }
5834
5835 [[nodiscard]] bool CreateNewTexturedStandardDescriptorSets(size_t TextureSlot, size_t DescrIndex)
5836 {
5837 auto &Texture = m_vTextures[TextureSlot];
5838
5839 auto &DescrSet = Texture.m_aVKStandardTexturedDescrSets[DescrIndex];
5840
5841 VkDescriptorSetAllocateInfo DesAllocInfo{};
5842 DesAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
5843 if(!GetDescriptorPoolForAlloc(RetDescr&: DesAllocInfo.descriptorPool, DescriptorPools&: m_StandardTextureDescrPool, pSets: &DescrSet, AllocNum: 1))
5844 return false;
5845 DesAllocInfo.descriptorSetCount = 1;
5846 DesAllocInfo.pSetLayouts = &m_StandardTexturedDescriptorSetLayout;
5847
5848 if(vkAllocateDescriptorSets(device: m_VKDevice, pAllocateInfo: &DesAllocInfo, pDescriptorSets: &DescrSet.m_Descriptor) != VK_SUCCESS)
5849 {
5850 return false;
5851 }
5852
5853 VkDescriptorImageInfo ImageInfo{};
5854 ImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5855 ImageInfo.imageView = Texture.m_ImgView;
5856 ImageInfo.sampler = Texture.m_aSamplers[DescrIndex];
5857
5858 std::array<VkWriteDescriptorSet, 1> aDescriptorWrites{};
5859
5860 aDescriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
5861 aDescriptorWrites[0].dstSet = DescrSet.m_Descriptor;
5862 aDescriptorWrites[0].dstBinding = 0;
5863 aDescriptorWrites[0].dstArrayElement = 0;
5864 aDescriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
5865 aDescriptorWrites[0].descriptorCount = 1;
5866 aDescriptorWrites[0].pImageInfo = &ImageInfo;
5867
5868 vkUpdateDescriptorSets(device: m_VKDevice, descriptorWriteCount: static_cast<uint32_t>(aDescriptorWrites.size()), pDescriptorWrites: aDescriptorWrites.data(), descriptorCopyCount: 0, pDescriptorCopies: nullptr);
5869
5870 return true;
5871 }
5872
5873 void DestroyTexturedStandardDescriptorSets(CTexture &Texture, size_t DescrIndex)
5874 {
5875 auto &DescrSet = Texture.m_aVKStandardTexturedDescrSets[DescrIndex];
5876 FreeDescriptorSetFromPool(DescrSet);
5877 DescrSet = {};
5878 }
5879
5880 [[nodiscard]] bool CreateNew3DTexturedStandardDescriptorSets(size_t TextureSlot)
5881 {
5882 auto &Texture = m_vTextures[TextureSlot];
5883
5884 auto &DescrSet = Texture.m_VKStandard3DTexturedDescrSet;
5885
5886 VkDescriptorSetAllocateInfo DesAllocInfo{};
5887 DesAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
5888 if(!GetDescriptorPoolForAlloc(RetDescr&: DesAllocInfo.descriptorPool, DescriptorPools&: m_StandardTextureDescrPool, pSets: &DescrSet, AllocNum: 1))
5889 return false;
5890 DesAllocInfo.descriptorSetCount = 1;
5891 DesAllocInfo.pSetLayouts = &m_Standard3DTexturedDescriptorSetLayout;
5892
5893 if(vkAllocateDescriptorSets(device: m_VKDevice, pAllocateInfo: &DesAllocInfo, pDescriptorSets: &DescrSet.m_Descriptor) != VK_SUCCESS)
5894 {
5895 return false;
5896 }
5897
5898 VkDescriptorImageInfo ImageInfo{};
5899 ImageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5900 ImageInfo.imageView = Texture.m_Img3DView;
5901 ImageInfo.sampler = Texture.m_Sampler3D;
5902
5903 std::array<VkWriteDescriptorSet, 1> aDescriptorWrites{};
5904
5905 aDescriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
5906 aDescriptorWrites[0].dstSet = DescrSet.m_Descriptor;
5907 aDescriptorWrites[0].dstBinding = 0;
5908 aDescriptorWrites[0].dstArrayElement = 0;
5909 aDescriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
5910 aDescriptorWrites[0].descriptorCount = 1;
5911 aDescriptorWrites[0].pImageInfo = &ImageInfo;
5912
5913 vkUpdateDescriptorSets(device: m_VKDevice, descriptorWriteCount: static_cast<uint32_t>(aDescriptorWrites.size()), pDescriptorWrites: aDescriptorWrites.data(), descriptorCopyCount: 0, pDescriptorCopies: nullptr);
5914
5915 return true;
5916 }
5917
5918 void DestroyTextured3DStandardDescriptorSets(CTexture &Texture)
5919 {
5920 auto &DescrSet = Texture.m_VKStandard3DTexturedDescrSet;
5921 FreeDescriptorSetFromPool(DescrSet);
5922 }
5923
5924 [[nodiscard]] bool CreateNewTextDescriptorSets(size_t Texture, size_t TextureOutline)
5925 {
5926 auto &TextureText = m_vTextures[Texture];
5927 auto &TextureTextOutline = m_vTextures[TextureOutline];
5928 auto &DescrSetText = TextureText.m_VKTextDescrSet;
5929
5930 VkDescriptorSetAllocateInfo DesAllocInfo{};
5931 DesAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
5932 if(!GetDescriptorPoolForAlloc(RetDescr&: DesAllocInfo.descriptorPool, DescriptorPools&: m_TextTextureDescrPool, pSets: &DescrSetText, AllocNum: 1))
5933 return false;
5934 DesAllocInfo.descriptorSetCount = 1;
5935 DesAllocInfo.pSetLayouts = &m_TextDescriptorSetLayout;
5936
5937 if(vkAllocateDescriptorSets(device: m_VKDevice, pAllocateInfo: &DesAllocInfo, pDescriptorSets: &DescrSetText.m_Descriptor) != VK_SUCCESS)
5938 {
5939 return false;
5940 }
5941
5942 std::array<VkDescriptorImageInfo, 2> aImageInfo{};
5943 aImageInfo[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5944 aImageInfo[0].imageView = TextureText.m_ImgView;
5945 aImageInfo[0].sampler = TextureText.m_aSamplers[0];
5946 aImageInfo[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5947 aImageInfo[1].imageView = TextureTextOutline.m_ImgView;
5948 aImageInfo[1].sampler = TextureTextOutline.m_aSamplers[0];
5949
5950 std::array<VkWriteDescriptorSet, 2> aDescriptorWrites{};
5951
5952 aDescriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
5953 aDescriptorWrites[0].dstSet = DescrSetText.m_Descriptor;
5954 aDescriptorWrites[0].dstBinding = 0;
5955 aDescriptorWrites[0].dstArrayElement = 0;
5956 aDescriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
5957 aDescriptorWrites[0].descriptorCount = 1;
5958 aDescriptorWrites[0].pImageInfo = aImageInfo.data();
5959 aDescriptorWrites[1] = aDescriptorWrites[0];
5960 aDescriptorWrites[1].dstBinding = 1;
5961 aDescriptorWrites[1].pImageInfo = &aImageInfo[1];
5962
5963 vkUpdateDescriptorSets(device: m_VKDevice, descriptorWriteCount: static_cast<uint32_t>(aDescriptorWrites.size()), pDescriptorWrites: aDescriptorWrites.data(), descriptorCopyCount: 0, pDescriptorCopies: nullptr);
5964
5965 return true;
5966 }
5967
5968 void DestroyTextDescriptorSets(CTexture &Texture, CTexture &TextureOutline)
5969 {
5970 auto &DescrSet = Texture.m_VKTextDescrSet;
5971 FreeDescriptorSetFromPool(DescrSet);
5972 }
5973
5974 [[nodiscard]] bool HasMultiSampling() const
5975 {
5976 return GetSampleCount() != VK_SAMPLE_COUNT_1_BIT;
5977 }
5978
5979 VkSampleCountFlagBits GetMaxSampleCount() const
5980 {
5981 if(m_MaxMultiSample & VK_SAMPLE_COUNT_64_BIT)
5982 return VK_SAMPLE_COUNT_64_BIT;
5983 else if(m_MaxMultiSample & VK_SAMPLE_COUNT_32_BIT)
5984 return VK_SAMPLE_COUNT_32_BIT;
5985 else if(m_MaxMultiSample & VK_SAMPLE_COUNT_16_BIT)
5986 return VK_SAMPLE_COUNT_16_BIT;
5987 else if(m_MaxMultiSample & VK_SAMPLE_COUNT_8_BIT)
5988 return VK_SAMPLE_COUNT_8_BIT;
5989 else if(m_MaxMultiSample & VK_SAMPLE_COUNT_4_BIT)
5990 return VK_SAMPLE_COUNT_4_BIT;
5991 else if(m_MaxMultiSample & VK_SAMPLE_COUNT_2_BIT)
5992 return VK_SAMPLE_COUNT_2_BIT;
5993
5994 return VK_SAMPLE_COUNT_1_BIT;
5995 }
5996
5997 VkSampleCountFlagBits GetSampleCount() const
5998 {
5999 auto MaxSampleCount = GetMaxSampleCount();
6000 if(m_MultiSamplingCount >= 64 && MaxSampleCount >= VK_SAMPLE_COUNT_64_BIT)
6001 return VK_SAMPLE_COUNT_64_BIT;
6002 else if(m_MultiSamplingCount >= 32 && MaxSampleCount >= VK_SAMPLE_COUNT_32_BIT)
6003 return VK_SAMPLE_COUNT_32_BIT;
6004 else if(m_MultiSamplingCount >= 16 && MaxSampleCount >= VK_SAMPLE_COUNT_16_BIT)
6005 return VK_SAMPLE_COUNT_16_BIT;
6006 else if(m_MultiSamplingCount >= 8 && MaxSampleCount >= VK_SAMPLE_COUNT_8_BIT)
6007 return VK_SAMPLE_COUNT_8_BIT;
6008 else if(m_MultiSamplingCount >= 4 && MaxSampleCount >= VK_SAMPLE_COUNT_4_BIT)
6009 return VK_SAMPLE_COUNT_4_BIT;
6010 else if(m_MultiSamplingCount >= 2 && MaxSampleCount >= VK_SAMPLE_COUNT_2_BIT)
6011 return VK_SAMPLE_COUNT_2_BIT;
6012
6013 return VK_SAMPLE_COUNT_1_BIT;
6014 }
6015
6016 int InitVulkanSwapChain(VkSwapchainKHR &OldSwapChain)
6017 {
6018 OldSwapChain = VK_NULL_HANDLE;
6019 if(!CreateSwapChain(OldSwapChain))
6020 return -1;
6021
6022 if(!GetSwapChainImageHandles())
6023 return -1;
6024
6025 if(!CreateImageViews())
6026 return -1;
6027
6028 if(!CreateMultiSamplerImageAttachments())
6029 {
6030 return -1;
6031 }
6032
6033 m_LastPresentedSwapChainImageIndex = std::numeric_limits<decltype(m_LastPresentedSwapChainImageIndex)>::max();
6034
6035 if(!CreateRenderPass(ClearAttachments: true))
6036 return -1;
6037
6038 if(!CreateFramebuffers())
6039 return -1;
6040
6041 if(!CreateStandardGraphicsPipeline(pVertName: "shader/vulkan/prim.vert.spv", pFragName: "shader/vulkan/prim.frag.spv", HasSampler: false, IsLinePipe: false))
6042 return -1;
6043
6044 if(!CreateStandardGraphicsPipeline(pVertName: "shader/vulkan/prim_textured.vert.spv", pFragName: "shader/vulkan/prim_textured.frag.spv", HasSampler: true, IsLinePipe: false))
6045 return -1;
6046
6047 if(!CreateStandardGraphicsPipeline(pVertName: "shader/vulkan/prim.vert.spv", pFragName: "shader/vulkan/prim.frag.spv", HasSampler: false, IsLinePipe: true))
6048 return -1;
6049
6050 if(!CreateStandard3DGraphicsPipeline(pVertName: "shader/vulkan/prim3d.vert.spv", pFragName: "shader/vulkan/prim3d.frag.spv", HasSampler: false))
6051 return -1;
6052
6053 if(!CreateStandard3DGraphicsPipeline(pVertName: "shader/vulkan/prim3d_textured.vert.spv", pFragName: "shader/vulkan/prim3d_textured.frag.spv", HasSampler: true))
6054 return -1;
6055
6056 if(!CreateTextGraphicsPipeline(pVertName: "shader/vulkan/text.vert.spv", pFragName: "shader/vulkan/text.frag.spv"))
6057 return -1;
6058
6059 if(!CreateTileGraphicsPipeline<false>(pVertName: "shader/vulkan/tile.vert.spv", pFragName: "shader/vulkan/tile.frag.spv", IsBorder: false))
6060 return -1;
6061
6062 if(!CreateTileGraphicsPipeline<true>(pVertName: "shader/vulkan/tile_textured.vert.spv", pFragName: "shader/vulkan/tile_textured.frag.spv", IsBorder: false))
6063 return -1;
6064
6065 if(!CreateTileGraphicsPipeline<false>(pVertName: "shader/vulkan/tile_border.vert.spv", pFragName: "shader/vulkan/tile_border.frag.spv", IsBorder: true))
6066 return -1;
6067
6068 if(!CreateTileGraphicsPipeline<true>(pVertName: "shader/vulkan/tile_border_textured.vert.spv", pFragName: "shader/vulkan/tile_border_textured.frag.spv", IsBorder: true))
6069 return -1;
6070
6071 if(!CreatePrimExGraphicsPipeline(pVertName: "shader/vulkan/primex_rotationless.vert.spv", pFragName: "shader/vulkan/primex_rotationless.frag.spv", HasSampler: false, Rotationless: true))
6072 return -1;
6073
6074 if(!CreatePrimExGraphicsPipeline(pVertName: "shader/vulkan/primex_tex_rotationless.vert.spv", pFragName: "shader/vulkan/primex_tex_rotationless.frag.spv", HasSampler: true, Rotationless: true))
6075 return -1;
6076
6077 if(!CreatePrimExGraphicsPipeline(pVertName: "shader/vulkan/primex.vert.spv", pFragName: "shader/vulkan/primex.frag.spv", HasSampler: false, Rotationless: false))
6078 return -1;
6079
6080 if(!CreatePrimExGraphicsPipeline(pVertName: "shader/vulkan/primex_tex.vert.spv", pFragName: "shader/vulkan/primex_tex.frag.spv", HasSampler: true, Rotationless: false))
6081 return -1;
6082
6083 if(!CreateSpriteMultiGraphicsPipeline(pVertName: "shader/vulkan/spritemulti.vert.spv", pFragName: "shader/vulkan/spritemulti.frag.spv"))
6084 return -1;
6085
6086 if(!CreateSpriteMultiPushGraphicsPipeline(pVertName: "shader/vulkan/spritemulti_push.vert.spv", pFragName: "shader/vulkan/spritemulti_push.frag.spv"))
6087 return -1;
6088
6089 if(!CreateQuadGraphicsPipeline<false>(pVertName: "shader/vulkan/quad.vert.spv", pFragName: "shader/vulkan/quad.frag.spv"))
6090 return -1;
6091
6092 if(!CreateQuadGraphicsPipeline<true>(pVertName: "shader/vulkan/quad_textured.vert.spv", pFragName: "shader/vulkan/quad_textured.frag.spv"))
6093 return -1;
6094
6095 if(!CreateQuadGroupedGraphicsPipeline<false>(pVertName: "shader/vulkan/quad_grouped.vert.spv", pFragName: "shader/vulkan/quad_grouped.frag.spv"))
6096 return -1;
6097
6098 if(!CreateQuadGroupedGraphicsPipeline<true>(pVertName: "shader/vulkan/quad_grouped_textured.vert.spv", pFragName: "shader/vulkan/quad_grouped_textured.frag.spv"))
6099 return -1;
6100
6101 m_SwapchainCreated = true;
6102 return 0;
6103 }
6104
6105 template<bool IsFirstInitialization>
6106 int InitVulkan()
6107 {
6108 if(IsFirstInitialization)
6109 {
6110 if(!CreateDescriptorSetLayouts())
6111 return -1;
6112
6113 if(!CreateTextDescriptorSetLayout())
6114 return -1;
6115
6116 if(!CreateSpriteMultiUniformDescriptorSetLayout())
6117 return -1;
6118
6119 if(!CreateQuadUniformDescriptorSetLayout())
6120 return -1;
6121
6122 VkSwapchainKHR OldSwapChain = VK_NULL_HANDLE;
6123 if(InitVulkanSwapChain(OldSwapChain) != 0)
6124 return -1;
6125 }
6126
6127 if(IsFirstInitialization)
6128 {
6129 if(!CreateCommandPool())
6130 return -1;
6131 }
6132
6133 if(!CreateCommandBuffers())
6134 return -1;
6135
6136 if(!CreateSyncObjects())
6137 return -1;
6138
6139 if(IsFirstInitialization)
6140 {
6141 if(!CreateDescriptorPools())
6142 return -1;
6143
6144 if(!CreateTextureSamplers())
6145 return -1;
6146 }
6147
6148 m_vStreamedVertexBuffers.resize(sz: m_ThreadCount);
6149 m_vStreamedUniformBuffers.resize(sz: m_ThreadCount);
6150 for(size_t i = 0; i < m_ThreadCount; ++i)
6151 {
6152 m_vStreamedVertexBuffers[i].Init(FrameImageCount: m_SwapChainImageCount);
6153 m_vStreamedUniformBuffers[i].Init(FrameImageCount: m_SwapChainImageCount);
6154 }
6155
6156 m_vLastPipeline.resize(sz: m_ThreadCount, VK_NULL_HANDLE);
6157
6158 m_vvFrameDelayedBufferCleanup.resize(sz: m_SwapChainImageCount);
6159 m_vvFrameDelayedTextureCleanup.resize(sz: m_SwapChainImageCount);
6160 m_vvFrameDelayedTextTexturesCleanup.resize(sz: m_SwapChainImageCount);
6161 m_StagingBufferCache.Init(SwapChainImageCount: m_SwapChainImageCount);
6162 m_StagingBufferCacheImage.Init(SwapChainImageCount: m_SwapChainImageCount);
6163 m_VertexBufferCache.Init(SwapChainImageCount: m_SwapChainImageCount);
6164 for(auto &ImageBufferCache : m_ImageBufferCaches)
6165 ImageBufferCache.second.Init(SwapChainImageCount: m_SwapChainImageCount);
6166
6167 m_vImageLastFrameCheck.resize(sz: m_SwapChainImageCount, c: 0);
6168
6169 if(IsFirstInitialization)
6170 {
6171 // check if image format supports linear blitting
6172 VkFormatProperties FormatProperties;
6173 vkGetPhysicalDeviceFormatProperties(physicalDevice: m_VKGPU, format: VK_FORMAT_R8G8B8A8_UNORM, pFormatProperties: &FormatProperties);
6174 if((FormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0)
6175 {
6176 m_AllowsLinearBlitting = true;
6177 }
6178 if((FormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 && (FormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0)
6179 {
6180 m_OptimalRGBAImageBlitting = true;
6181 }
6182 // check if image format supports blitting to linear tiled images
6183 if((FormatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0)
6184 {
6185 m_LinearRGBAImageBlitting = true;
6186 }
6187
6188 vkGetPhysicalDeviceFormatProperties(physicalDevice: m_VKGPU, format: m_VKSurfFormat.format, pFormatProperties: &FormatProperties);
6189 if((FormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0)
6190 {
6191 m_OptimalSwapChainImageBlitting = true;
6192 }
6193 }
6194
6195 return 0;
6196 }
6197
6198 [[nodiscard]] bool GetMemoryCommandBuffer(VkCommandBuffer *&pMemCommandBuffer)
6199 {
6200 auto &MemCommandBuffer = m_vMemoryCommandBuffers[m_CurImageIndex];
6201 if(!m_vUsedMemoryCommandBuffer[m_CurImageIndex])
6202 {
6203 m_vUsedMemoryCommandBuffer[m_CurImageIndex] = true;
6204
6205 vkResetCommandBuffer(commandBuffer: MemCommandBuffer, flags: VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
6206
6207 VkCommandBufferBeginInfo BeginInfo{};
6208 BeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
6209 BeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
6210 if(vkBeginCommandBuffer(commandBuffer: MemCommandBuffer, pBeginInfo: &BeginInfo) != VK_SUCCESS)
6211 {
6212 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, pErr: "Command buffer cannot be filled anymore.");
6213 return false;
6214 }
6215 }
6216 pMemCommandBuffer = &MemCommandBuffer;
6217 return true;
6218 }
6219
6220 [[nodiscard]] bool GetGraphicCommandBuffer(VkCommandBuffer *&pDrawCommandBuffer, size_t RenderThreadIndex)
6221 {
6222 if(m_ThreadCount < 2)
6223 {
6224 pDrawCommandBuffer = &m_vMainDrawCommandBuffers[m_CurImageIndex];
6225 return true;
6226 }
6227 else
6228 {
6229 auto &DrawCommandBuffer = m_vvThreadDrawCommandBuffers[RenderThreadIndex][m_CurImageIndex];
6230 if(!m_vvUsedThreadDrawCommandBuffer[RenderThreadIndex][m_CurImageIndex])
6231 {
6232 m_vvUsedThreadDrawCommandBuffer[RenderThreadIndex][m_CurImageIndex] = true;
6233
6234 vkResetCommandBuffer(commandBuffer: DrawCommandBuffer, flags: VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
6235
6236 VkCommandBufferBeginInfo BeginInfo{};
6237 BeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
6238 BeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT | VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
6239
6240 VkCommandBufferInheritanceInfo InheritanceInfo{};
6241 InheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
6242 InheritanceInfo.framebuffer = m_vFramebufferList[m_CurImageIndex];
6243 InheritanceInfo.occlusionQueryEnable = VK_FALSE;
6244 InheritanceInfo.renderPass = m_VKRenderPass;
6245 InheritanceInfo.subpass = 0;
6246
6247 BeginInfo.pInheritanceInfo = &InheritanceInfo;
6248
6249 if(vkBeginCommandBuffer(commandBuffer: DrawCommandBuffer, pBeginInfo: &BeginInfo) != VK_SUCCESS)
6250 {
6251 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_RENDER_RECORDING, pErr: "Thread draw command buffer cannot be filled anymore.");
6252 return false;
6253 }
6254 }
6255 pDrawCommandBuffer = &DrawCommandBuffer;
6256 return true;
6257 }
6258 }
6259
6260 VkCommandBuffer &GetMainGraphicCommandBuffer()
6261 {
6262 return m_vMainDrawCommandBuffers[m_CurImageIndex];
6263 }
6264
6265 /************************
6266 * STREAM BUFFERS SETUP
6267 ************************/
6268
6269 typedef std::function<bool(SFrameBuffers &, VkBuffer, VkDeviceSize)> TNewMemFunc;
6270
6271 // returns true, if the stream memory was just allocated
6272 template<typename TStreamMemName, typename TInstanceTypeName, size_t InstanceTypeCount, size_t BufferCreateCount, bool UsesCurrentCountOffset>
6273 [[nodiscard]] bool CreateStreamBuffer(TStreamMemName *&pBufferMem, TNewMemFunc &&NewMemFunc, SStreamMemory<TStreamMemName> &StreamUniformBuffer, VkBufferUsageFlagBits Usage, VkBuffer &NewBuffer, SDeviceMemoryBlock &NewBufferMem, size_t &BufferOffset, const void *pData, size_t DataSize)
6274 {
6275 VkBuffer Buffer = VK_NULL_HANDLE;
6276 SDeviceMemoryBlock BufferMem;
6277 size_t Offset = 0;
6278
6279 uint8_t *pMem = nullptr;
6280
6281 size_t BufferCountOffset = 0;
6282 if(UsesCurrentCountOffset)
6283 BufferCountOffset = StreamUniformBuffer.GetUsedCount(m_CurImageIndex);
6284 for(; BufferCountOffset < StreamUniformBuffer.GetBuffers(m_CurImageIndex).size(); ++BufferCountOffset)
6285 {
6286 auto &BufferOfFrame = StreamUniformBuffer.GetBuffers(m_CurImageIndex)[BufferCountOffset];
6287 if(BufferOfFrame.m_Size >= DataSize + BufferOfFrame.m_UsedSize)
6288 {
6289 if(BufferOfFrame.m_UsedSize == 0)
6290 StreamUniformBuffer.IncreaseUsedCount(m_CurImageIndex);
6291 Buffer = BufferOfFrame.m_Buffer;
6292 BufferMem = BufferOfFrame.m_BufferMem;
6293 Offset = BufferOfFrame.m_UsedSize;
6294 BufferOfFrame.m_UsedSize += DataSize;
6295 pMem = BufferOfFrame.m_pMappedBufferData;
6296 pBufferMem = &BufferOfFrame;
6297 break;
6298 }
6299 }
6300
6301 if(BufferMem.m_Mem == VK_NULL_HANDLE)
6302 {
6303 // create memory
6304 VkBuffer StreamBuffer;
6305 SDeviceMemoryBlock StreamBufferMemory;
6306 const VkDeviceSize NewBufferSingleSize = sizeof(TInstanceTypeName) * InstanceTypeCount;
6307 const VkDeviceSize NewBufferSize = NewBufferSingleSize * BufferCreateCount;
6308 if(!CreateBuffer(BufferSize: NewBufferSize, MemUsage: MEMORY_BLOCK_USAGE_STREAM, BufferUsage: Usage, MemoryProperties: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, VKBuffer&: StreamBuffer, VKBufferMemory&: StreamBufferMemory))
6309 return false;
6310
6311 void *pMappedData = nullptr;
6312 if(vkMapMemory(device: m_VKDevice, memory: StreamBufferMemory.m_Mem, offset: 0, VK_WHOLE_SIZE, flags: 0, ppData: &pMappedData) != VK_SUCCESS)
6313 return false;
6314
6315 size_t NewBufferIndex = StreamUniformBuffer.GetBuffers(m_CurImageIndex).size();
6316 for(size_t i = 0; i < BufferCreateCount; ++i)
6317 {
6318 StreamUniformBuffer.GetBuffers(m_CurImageIndex).push_back(TStreamMemName(StreamBuffer, StreamBufferMemory, NewBufferSingleSize * i, NewBufferSingleSize, 0, ((uint8_t *)pMappedData) + (NewBufferSingleSize * i)));
6319 StreamUniformBuffer.GetRanges(m_CurImageIndex).push_back({});
6320 if(!NewMemFunc(StreamUniformBuffer.GetBuffers(m_CurImageIndex).back(), StreamBuffer, NewBufferSingleSize * i))
6321 return false;
6322 }
6323 auto &NewStreamBuffer = StreamUniformBuffer.GetBuffers(m_CurImageIndex)[NewBufferIndex];
6324
6325 Buffer = StreamBuffer;
6326 BufferMem = StreamBufferMemory;
6327
6328 pBufferMem = &NewStreamBuffer;
6329 pMem = NewStreamBuffer.m_pMappedBufferData;
6330 Offset = NewStreamBuffer.m_OffsetInBuffer;
6331 NewStreamBuffer.m_UsedSize += DataSize;
6332
6333 StreamUniformBuffer.IncreaseUsedCount(m_CurImageIndex);
6334 }
6335
6336 // Offset here is the offset in the buffer
6337 if(BufferMem.m_Size - Offset < DataSize)
6338 {
6339 SetError(ErrType: EGfxErrorType::GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER, pErr: "Stream buffers are limited to CCommandBuffer::MAX_VERTICES. Exceeding it is a bug in the high level code.");
6340 return false;
6341 }
6342
6343 {
6344 mem_copy(dest: pMem + Offset, source: pData, size: DataSize);
6345 }
6346
6347 NewBuffer = Buffer;
6348 NewBufferMem = BufferMem;
6349 BufferOffset = Offset;
6350
6351 return true;
6352 }
6353
6354 [[nodiscard]] bool CreateStreamVertexBuffer(size_t RenderThreadIndex, VkBuffer &NewBuffer, SDeviceMemoryBlock &NewBufferMem, size_t &BufferOffset, const void *pData, size_t DataSize)
6355 {
6356 SFrameBuffers *pStreamBuffer;
6357 return CreateStreamBuffer<SFrameBuffers, GL_SVertexTex3DStream, CCommandBuffer::MAX_VERTICES * 2, 1, false>(
6358 pBufferMem&: pStreamBuffer, NewMemFunc: [](SFrameBuffers &, VkBuffer, VkDeviceSize) { return true; }, StreamUniformBuffer&: m_vStreamedVertexBuffers[RenderThreadIndex], Usage: VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, NewBuffer, NewBufferMem, BufferOffset, pData, DataSize);
6359 }
6360
6361 template<typename TName, size_t InstanceMaxParticleCount, size_t MaxInstances>
6362 [[nodiscard]] bool GetUniformBufferObjectImpl(size_t RenderThreadIndex, bool RequiresSharedStagesDescriptor, SStreamMemory<SFrameUniformBuffers> &StreamUniformBuffer, SDeviceDescriptorSet &DescrSet, const void *pData, size_t DataSize)
6363 {
6364 VkBuffer NewBuffer;
6365 SDeviceMemoryBlock NewBufferMem;
6366 size_t BufferOffset;
6367 SFrameUniformBuffers *pMem;
6368 if(!CreateStreamBuffer<SFrameUniformBuffers, TName, InstanceMaxParticleCount, MaxInstances, true>(
6369 pMem,
6370 [this, RenderThreadIndex](SFrameBuffers &Mem, VkBuffer Buffer, VkDeviceSize MemOffset) {
6371 if(!CreateUniformDescriptorSets(RenderThreadIndex, SetLayout&: m_SpriteMultiUniformDescriptorSetLayout, pSets: ((SFrameUniformBuffers *)(&Mem))->m_aUniformSets.data(), SetCount: 1, BindBuffer: Buffer, SingleBufferInstanceSize: InstanceMaxParticleCount * sizeof(TName), MemoryOffset: MemOffset))
6372 return false;
6373 if(!CreateUniformDescriptorSets(RenderThreadIndex, SetLayout&: m_QuadUniformDescriptorSetLayout, pSets: &((SFrameUniformBuffers *)(&Mem))->m_aUniformSets[1], SetCount: 1, BindBuffer: Buffer, SingleBufferInstanceSize: InstanceMaxParticleCount * sizeof(TName), MemoryOffset: MemOffset))
6374 return false;
6375 return true;
6376 },
6377 StreamUniformBuffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, NewBuffer, NewBufferMem, BufferOffset, pData, DataSize))
6378 return false;
6379
6380 DescrSet = pMem->m_aUniformSets[RequiresSharedStagesDescriptor ? 1 : 0];
6381 return true;
6382 }
6383
6384 [[nodiscard]] bool GetUniformBufferObject(size_t RenderThreadIndex, bool RequiresSharedStagesDescriptor, SDeviceDescriptorSet &DescrSet, size_t ParticleCount, const void *pData, size_t DataSize)
6385 {
6386 return GetUniformBufferObjectImpl<IGraphics::SRenderSpriteInfo, 512, 128>(RenderThreadIndex, RequiresSharedStagesDescriptor, StreamUniformBuffer&: m_vStreamedUniformBuffers[RenderThreadIndex], DescrSet, pData, DataSize);
6387 }
6388
6389 [[nodiscard]] bool CreateIndexBuffer(void *pData, size_t DataSize, VkBuffer &Buffer, SDeviceMemoryBlock &Memory)
6390 {
6391 VkDeviceSize BufferDataSize = DataSize;
6392
6393 SMemoryBlock<STAGING_BUFFER_CACHE_ID> StagingBuffer;
6394 if(!GetStagingBuffer(ResBlock&: StagingBuffer, pBufferData: pData, RequiredSize: DataSize))
6395 return false;
6396
6397 SDeviceMemoryBlock VertexBufferMemory;
6398 VkBuffer VertexBuffer;
6399 if(!CreateBuffer(BufferSize: BufferDataSize, MemUsage: MEMORY_BLOCK_USAGE_BUFFER, BufferUsage: VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, MemoryProperties: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VKBuffer&: VertexBuffer, VKBufferMemory&: VertexBufferMemory))
6400 return false;
6401
6402 if(!MemoryBarrier(Buffer: VertexBuffer, Offset: 0, Size: BufferDataSize, BufferAccessType: VK_ACCESS_INDEX_READ_BIT, BeforeCommand: true))
6403 return false;
6404 if(!CopyBuffer(SrcBuffer: StagingBuffer.m_Buffer, DstBuffer: VertexBuffer, SrcOffset: StagingBuffer.m_HeapData.m_OffsetToAlign, DstOffset: 0, CopySize: BufferDataSize))
6405 return false;
6406 if(!MemoryBarrier(Buffer: VertexBuffer, Offset: 0, Size: BufferDataSize, BufferAccessType: VK_ACCESS_INDEX_READ_BIT, BeforeCommand: false))
6407 return false;
6408
6409 UploadAndFreeStagingMemBlock(Block&: StagingBuffer);
6410
6411 Buffer = VertexBuffer;
6412 Memory = VertexBufferMemory;
6413 return true;
6414 }
6415
6416 void DestroyIndexBuffer(VkBuffer &Buffer, SDeviceMemoryBlock &Memory)
6417 {
6418 CleanBufferPair(ImageIndex: 0, Buffer, BufferMem&: Memory);
6419 }
6420
6421 /************************
6422 * COMMAND IMPLEMENTATION
6423 ************************/
6424 template<typename TName>
6425 [[nodiscard]] static bool IsInCommandRange(TName CMD, TName Min, TName Max)
6426 {
6427 return CMD >= Min && CMD < Max;
6428 }
6429
6430 [[nodiscard]] ERunCommandReturnTypes RunCommand(const CCommandBuffer::SCommand *pBaseCommand) override
6431 {
6432 if(m_HasError)
6433 {
6434 // ignore all further commands
6435 return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_ERROR;
6436 }
6437
6438 if(IsInCommandRange<decltype(pBaseCommand->m_Cmd)>(CMD: pBaseCommand->m_Cmd, Min: CCommandBuffer::CMD_FIRST, Max: CCommandBuffer::CMD_COUNT))
6439 {
6440 auto &CallbackObj = m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: CCommandBuffer::ECommandBufferCMD(pBaseCommand->m_Cmd))];
6441 SRenderCommandExecuteBuffer Buffer;
6442 Buffer.m_Command = (CCommandBuffer::ECommandBufferCMD)pBaseCommand->m_Cmd;
6443 Buffer.m_pRawCommand = pBaseCommand;
6444 Buffer.m_ThreadIndex = 0;
6445
6446 if(m_CurCommandInPipe + 1 == m_CommandsInPipe)
6447 {
6448 m_LastCommandsInPipeThreadIndex = std::numeric_limits<decltype(m_LastCommandsInPipeThreadIndex)>::max();
6449 }
6450
6451 bool CanStartThread = false;
6452 if(CallbackObj.m_IsRenderCommand)
6453 {
6454 bool ForceSingleThread = m_LastCommandsInPipeThreadIndex == std::numeric_limits<decltype(m_LastCommandsInPipeThreadIndex)>::max();
6455
6456 size_t PotentiallyNextThread = (((m_CurCommandInPipe * (m_ThreadCount - 1)) / m_CommandsInPipe) + 1);
6457 if(PotentiallyNextThread - 1 > m_LastCommandsInPipeThreadIndex)
6458 {
6459 CanStartThread = true;
6460 m_LastCommandsInPipeThreadIndex = PotentiallyNextThread - 1;
6461 }
6462 Buffer.m_ThreadIndex = m_ThreadCount > 1 && !ForceSingleThread ? (m_LastCommandsInPipeThreadIndex + 1) : 0;
6463 CallbackObj.m_FillExecuteBuffer(Buffer, pBaseCommand);
6464 m_CurRenderCallCountInPipe += Buffer.m_EstimatedRenderCallCount;
6465 }
6466 bool Ret = true;
6467 if(!CallbackObj.m_IsRenderCommand || (Buffer.m_ThreadIndex == 0 && !m_RenderingPaused))
6468 {
6469 Ret = CallbackObj.m_CMDIsHandled;
6470 if(!CallbackObj.m_CommandCB(pBaseCommand, Buffer))
6471 {
6472 // an error occurred, stop this command and ignore all further commands
6473 return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_ERROR;
6474 }
6475 }
6476 else if(!m_RenderingPaused)
6477 {
6478 if(CanStartThread)
6479 {
6480 StartRenderThread(ThreadIndex: m_LastCommandsInPipeThreadIndex - 1);
6481 }
6482 m_vvThreadCommandLists[Buffer.m_ThreadIndex - 1].push_back(x: Buffer);
6483 }
6484
6485 ++m_CurCommandInPipe;
6486 return Ret ? ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED : ERunCommandReturnTypes::RUN_COMMAND_COMMAND_UNHANDLED;
6487 }
6488
6489 if(m_CurCommandInPipe + 1 == m_CommandsInPipe)
6490 {
6491 m_LastCommandsInPipeThreadIndex = std::numeric_limits<decltype(m_LastCommandsInPipeThreadIndex)>::max();
6492 }
6493 ++m_CurCommandInPipe;
6494
6495 switch(pBaseCommand->m_Cmd)
6496 {
6497 case CCommandProcessorFragment_GLBase::CMD_INIT:
6498 if(!Cmd_Init(pCommand: static_cast<const SCommand_Init *>(pBaseCommand)))
6499 {
6500 SetWarningPreMsg("Could not initialize Vulkan: ");
6501 return RUN_COMMAND_COMMAND_WARNING;
6502 }
6503 break;
6504 case CCommandProcessorFragment_GLBase::CMD_SHUTDOWN:
6505 if(!Cmd_Shutdown(pCommand: static_cast<const SCommand_Shutdown *>(pBaseCommand)))
6506 {
6507 SetWarningPreMsg("Could not shutdown Vulkan: ");
6508 return RUN_COMMAND_COMMAND_WARNING;
6509 }
6510 break;
6511
6512 case CCommandProcessorFragment_GLBase::CMD_PRE_INIT:
6513 if(!Cmd_PreInit(pCommand: static_cast<const CCommandProcessorFragment_GLBase::SCommand_PreInit *>(pBaseCommand)))
6514 {
6515 SetWarningPreMsg("Could not initialize Vulkan: ");
6516 return RUN_COMMAND_COMMAND_WARNING;
6517 }
6518 break;
6519 case CCommandProcessorFragment_GLBase::CMD_POST_SHUTDOWN:
6520 if(!Cmd_PostShutdown(pCommand: static_cast<const CCommandProcessorFragment_GLBase::SCommand_PostShutdown *>(pBaseCommand)))
6521 {
6522 SetWarningPreMsg("Could not shutdown Vulkan: ");
6523 return RUN_COMMAND_COMMAND_WARNING;
6524 }
6525 break;
6526 default:
6527 return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_UNHANDLED;
6528 }
6529
6530 return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED;
6531 }
6532
6533 [[nodiscard]] bool Cmd_Init(const SCommand_Init *pCommand)
6534 {
6535 pCommand->m_pCapabilities->m_TileBuffering = true;
6536 pCommand->m_pCapabilities->m_QuadBuffering = true;
6537 pCommand->m_pCapabilities->m_TextBuffering = true;
6538 pCommand->m_pCapabilities->m_QuadContainerBuffering = true;
6539 pCommand->m_pCapabilities->m_ShaderSupport = true;
6540
6541 pCommand->m_pCapabilities->m_MipMapping = true;
6542 pCommand->m_pCapabilities->m_3DTextures = false;
6543 pCommand->m_pCapabilities->m_2DArrayTextures = true;
6544 pCommand->m_pCapabilities->m_NPOTTextures = true;
6545
6546 pCommand->m_pCapabilities->m_ContextMajor = 1;
6547 pCommand->m_pCapabilities->m_ContextMinor = 1;
6548 pCommand->m_pCapabilities->m_ContextPatch = 0;
6549
6550 pCommand->m_pCapabilities->m_TrianglesAsQuads = true;
6551
6552 m_GlobalTextureLodBIAS = g_Config.m_GfxGLTextureLODBIAS;
6553 m_pTextureMemoryUsage = pCommand->m_pTextureMemoryUsage;
6554 m_pBufferMemoryUsage = pCommand->m_pBufferMemoryUsage;
6555 m_pStreamMemoryUsage = pCommand->m_pStreamMemoryUsage;
6556 m_pStagingMemoryUsage = pCommand->m_pStagingMemoryUsage;
6557
6558 m_MultiSamplingCount = (g_Config.m_GfxFsaaSamples & 0xFFFFFFFE); // ignore the uneven bit, only even multi sampling works
6559
6560 *pCommand->m_pReadPresentedImageDataFunc = [this](uint32_t &Width, uint32_t &Height, CImageInfo::EImageFormat &Format, std::vector<uint8_t> &vDstData) {
6561 return GetPresentedImageData(Width, Height, Format, vDstData);
6562 };
6563
6564 m_pWindow = pCommand->m_pWindow;
6565
6566 *pCommand->m_pInitError = m_VKInstance != VK_NULL_HANDLE ? 0 : -1;
6567
6568 if(m_VKInstance == VK_NULL_HANDLE)
6569 {
6570 *pCommand->m_pInitError = -2;
6571 return false;
6572 }
6573
6574 m_pStorage = pCommand->m_pStorage;
6575 if(InitVulkan<true>() != 0)
6576 {
6577 *pCommand->m_pInitError = -2;
6578 return false;
6579 }
6580
6581 std::array<uint32_t, (size_t)CCommandBuffer::MAX_VERTICES / 4 * 6> aIndices;
6582 int Primq = 0;
6583 for(int i = 0; i < CCommandBuffer::MAX_VERTICES / 4 * 6; i += 6)
6584 {
6585 aIndices[i] = Primq;
6586 aIndices[i + 1] = Primq + 1;
6587 aIndices[i + 2] = Primq + 2;
6588 aIndices[i + 3] = Primq;
6589 aIndices[i + 4] = Primq + 2;
6590 aIndices[i + 5] = Primq + 3;
6591 Primq += 4;
6592 }
6593
6594 if(!PrepareFrame())
6595 return false;
6596 if(m_HasError)
6597 {
6598 *pCommand->m_pInitError = -2;
6599 return false;
6600 }
6601
6602 if(!CreateIndexBuffer(pData: aIndices.data(), DataSize: sizeof(uint32_t) * aIndices.size(), Buffer&: m_IndexBuffer, Memory&: m_IndexBufferMemory))
6603 {
6604 *pCommand->m_pInitError = -2;
6605 return false;
6606 }
6607 if(!CreateIndexBuffer(pData: aIndices.data(), DataSize: sizeof(uint32_t) * aIndices.size(), Buffer&: m_RenderIndexBuffer, Memory&: m_RenderIndexBufferMemory))
6608 {
6609 *pCommand->m_pInitError = -2;
6610 return false;
6611 }
6612 m_CurRenderIndexPrimitiveCount = CCommandBuffer::MAX_VERTICES / 4;
6613
6614 m_CanAssert = true;
6615
6616 return true;
6617 }
6618
6619 [[nodiscard]] bool Cmd_Shutdown(const SCommand_Shutdown *pCommand)
6620 {
6621 vkDeviceWaitIdle(device: m_VKDevice);
6622
6623 DestroyIndexBuffer(Buffer&: m_IndexBuffer, Memory&: m_IndexBufferMemory);
6624 DestroyIndexBuffer(Buffer&: m_RenderIndexBuffer, Memory&: m_RenderIndexBufferMemory);
6625
6626 CleanupVulkan<true>(SwapchainCount: m_SwapChainImageCount);
6627
6628 return true;
6629 }
6630
6631 [[nodiscard]] bool Cmd_Texture_Destroy(const CCommandBuffer::SCommand_Texture_Destroy *pCommand)
6632 {
6633 size_t ImageIndex = (size_t)pCommand->m_Slot;
6634 auto &Texture = m_vTextures[ImageIndex];
6635
6636 m_vvFrameDelayedTextureCleanup[m_CurImageIndex].push_back(x: Texture);
6637
6638 Texture = CTexture{};
6639
6640 return true;
6641 }
6642
6643 [[nodiscard]] bool Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand)
6644 {
6645 int Slot = pCommand->m_Slot;
6646 int Width = pCommand->m_Width;
6647 int Height = pCommand->m_Height;
6648 int Flags = pCommand->m_Flags;
6649 uint8_t *pData = pCommand->m_pData;
6650
6651 if(!CreateTextureCMD(Slot, Width, Height, Format: VK_FORMAT_R8G8B8A8_UNORM, StoreFormat: VK_FORMAT_R8G8B8A8_UNORM, Flags, pData))
6652 return false;
6653
6654 free(ptr: pData);
6655
6656 return true;
6657 }
6658
6659 [[nodiscard]] bool Cmd_TextTextures_Create(const CCommandBuffer::SCommand_TextTextures_Create *pCommand)
6660 {
6661 int Slot = pCommand->m_Slot;
6662 int SlotOutline = pCommand->m_SlotOutline;
6663 int Width = pCommand->m_Width;
6664 int Height = pCommand->m_Height;
6665
6666 uint8_t *pTmpData = pCommand->m_pTextData;
6667 uint8_t *pTmpData2 = pCommand->m_pTextOutlineData;
6668
6669 if(!CreateTextureCMD(Slot, Width, Height, Format: VK_FORMAT_R8_UNORM, StoreFormat: VK_FORMAT_R8_UNORM, Flags: TextureFlag::NO_MIPMAPS, pData&: pTmpData))
6670 return false;
6671 if(!CreateTextureCMD(Slot: SlotOutline, Width, Height, Format: VK_FORMAT_R8_UNORM, StoreFormat: VK_FORMAT_R8_UNORM, Flags: TextureFlag::NO_MIPMAPS, pData&: pTmpData2))
6672 return false;
6673
6674 if(!CreateNewTextDescriptorSets(Texture: Slot, TextureOutline: SlotOutline))
6675 return false;
6676
6677 free(ptr: pTmpData);
6678 free(ptr: pTmpData2);
6679
6680 return true;
6681 }
6682
6683 [[nodiscard]] bool Cmd_TextTextures_Destroy(const CCommandBuffer::SCommand_TextTextures_Destroy *pCommand)
6684 {
6685 size_t ImageIndex = (size_t)pCommand->m_Slot;
6686 size_t ImageIndexOutline = (size_t)pCommand->m_SlotOutline;
6687 auto &Texture = m_vTextures[ImageIndex];
6688 auto &TextureOutline = m_vTextures[ImageIndexOutline];
6689
6690 m_vvFrameDelayedTextTexturesCleanup[m_CurImageIndex].emplace_back(args&: Texture, args&: TextureOutline);
6691
6692 Texture = {};
6693 TextureOutline = {};
6694
6695 return true;
6696 }
6697
6698 [[nodiscard]] bool Cmd_TextTexture_Update(const CCommandBuffer::SCommand_TextTexture_Update *pCommand)
6699 {
6700 size_t IndexTex = pCommand->m_Slot;
6701 uint8_t *pData = pCommand->m_pData;
6702
6703 if(!UpdateTexture(TextureSlot: IndexTex, Format: VK_FORMAT_R8_UNORM, pData, XOff: pCommand->m_X, YOff: pCommand->m_Y, Width: pCommand->m_Width, Height: pCommand->m_Height))
6704 return false;
6705
6706 free(ptr: pData);
6707
6708 return true;
6709 }
6710
6711 void Cmd_Clear_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_Clear *pCommand)
6712 {
6713 if(!pCommand->m_ForceClear)
6714 {
6715 bool ColorChanged = m_aClearColor[0] != pCommand->m_Color.r || m_aClearColor[1] != pCommand->m_Color.g ||
6716 m_aClearColor[2] != pCommand->m_Color.b || m_aClearColor[3] != pCommand->m_Color.a;
6717 m_aClearColor[0] = pCommand->m_Color.r;
6718 m_aClearColor[1] = pCommand->m_Color.g;
6719 m_aClearColor[2] = pCommand->m_Color.b;
6720 m_aClearColor[3] = pCommand->m_Color.a;
6721 if(ColorChanged)
6722 ExecBuffer.m_ClearColorInRenderThread = true;
6723 }
6724 else
6725 {
6726 ExecBuffer.m_ClearColorInRenderThread = true;
6727 }
6728 ExecBuffer.m_EstimatedRenderCallCount = 0;
6729 }
6730
6731 [[nodiscard]] bool Cmd_Clear(const SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_Clear *pCommand)
6732 {
6733 if(ExecBuffer.m_ClearColorInRenderThread)
6734 {
6735 std::array<VkClearAttachment, 1> aAttachments = {VkClearAttachment{.aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, .colorAttachment: 0, .clearValue: VkClearValue{.color: VkClearColorValue{.float32: {pCommand->m_Color.r, pCommand->m_Color.g, pCommand->m_Color.b, pCommand->m_Color.a}}}}};
6736 std::array<VkClearRect, 1> aClearRects = {VkClearRect{.rect: {.offset: {.x: 0, .y: 0}, .extent: m_VKSwapImgAndViewportExtent.m_SwapImageViewport}, .baseArrayLayer: 0, .layerCount: 1}};
6737
6738 VkCommandBuffer *pCommandBuffer;
6739 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
6740 return false;
6741 auto &CommandBuffer = *pCommandBuffer;
6742 vkCmdClearAttachments(commandBuffer: CommandBuffer, attachmentCount: aAttachments.size(), pAttachments: aAttachments.data(), rectCount: aClearRects.size(), pRects: aClearRects.data());
6743 }
6744
6745 return true;
6746 }
6747
6748 void Cmd_Render_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_Render *pCommand)
6749 {
6750 bool IsTextured = GetIsTextured(State: pCommand->m_State);
6751 if(IsTextured)
6752 {
6753 size_t AddressModeIndex = GetAddressModeIndex(State: pCommand->m_State);
6754 ExecBuffer.m_aDescriptors[0] = m_vTextures[pCommand->m_State.m_Texture].m_aVKStandardTexturedDescrSets[AddressModeIndex];
6755 }
6756
6757 ExecBuffer.m_IndexBuffer = m_IndexBuffer;
6758
6759 ExecBuffer.m_EstimatedRenderCallCount = 1;
6760
6761 ExecBufferFillDynamicStates(State: pCommand->m_State, ExecBuffer);
6762 }
6763
6764 [[nodiscard]] bool Cmd_Render(const CCommandBuffer::SCommand_Render *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
6765 {
6766 return RenderStandard<CCommandBuffer::SVertex, false>(ExecBuffer, State: pCommand->m_State, PrimType: pCommand->m_PrimType, pVertices: pCommand->m_pVertices, PrimitiveCount: pCommand->m_PrimCount);
6767 }
6768
6769 [[nodiscard]] bool Cmd_ReadPixel(const CCommandBuffer::SCommand_TrySwapAndReadPixel *pCommand)
6770 {
6771 if(!*pCommand->m_pSwapped && !NextFrame())
6772 return false;
6773 *pCommand->m_pSwapped = true;
6774
6775 uint32_t Width;
6776 uint32_t Height;
6777 CImageInfo::EImageFormat Format;
6778 if(GetPresentedImageDataImpl(Width, Height, Format, vDstData&: m_vReadPixelHelper, ResetAlpha: false, PixelOffset: pCommand->m_Position))
6779 {
6780 *pCommand->m_pColor = ColorRGBA(m_vReadPixelHelper[0] / 255.0f, m_vReadPixelHelper[1] / 255.0f, m_vReadPixelHelper[2] / 255.0f, 1.0f);
6781 }
6782 else
6783 {
6784 *pCommand->m_pColor = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
6785 }
6786
6787 return true;
6788 }
6789
6790 [[nodiscard]] bool Cmd_Screenshot(const CCommandBuffer::SCommand_TrySwapAndScreenshot *pCommand)
6791 {
6792 if(!*pCommand->m_pSwapped && !NextFrame())
6793 return false;
6794 *pCommand->m_pSwapped = true;
6795
6796 uint32_t Width;
6797 uint32_t Height;
6798 CImageInfo::EImageFormat Format;
6799
6800 if(GetPresentedImageDataImpl(Width, Height, Format, vDstData&: m_vScreenshotHelper, ResetAlpha: true, PixelOffset: {}))
6801 {
6802 pCommand->m_pImage->m_Width = (int)Width;
6803 pCommand->m_pImage->m_Height = (int)Height;
6804 pCommand->m_pImage->m_Format = Format;
6805 pCommand->m_pImage->Allocate();
6806 mem_copy(dest: pCommand->m_pImage->m_pData, source: m_vScreenshotHelper.data(), size: pCommand->m_pImage->DataSize());
6807 }
6808 else
6809 {
6810 pCommand->m_pImage->m_Width = 0;
6811 pCommand->m_pImage->m_Height = 0;
6812 pCommand->m_pImage->m_Format = CImageInfo::FORMAT_UNDEFINED;
6813 pCommand->m_pImage->m_pData = nullptr;
6814 }
6815
6816 return true;
6817 }
6818
6819 void Cmd_RenderTex3D_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderTex3D *pCommand)
6820 {
6821 bool IsTextured = GetIsTextured(State: pCommand->m_State);
6822 if(IsTextured)
6823 {
6824 ExecBuffer.m_aDescriptors[0] = m_vTextures[pCommand->m_State.m_Texture].m_VKStandard3DTexturedDescrSet;
6825 }
6826
6827 ExecBuffer.m_IndexBuffer = m_IndexBuffer;
6828
6829 ExecBuffer.m_EstimatedRenderCallCount = 1;
6830
6831 ExecBufferFillDynamicStates(State: pCommand->m_State, ExecBuffer);
6832 }
6833
6834 [[nodiscard]] bool Cmd_RenderTex3D(const CCommandBuffer::SCommand_RenderTex3D *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
6835 {
6836 return RenderStandard<CCommandBuffer::SVertexTex3DStream, true>(ExecBuffer, State: pCommand->m_State, PrimType: pCommand->m_PrimType, pVertices: pCommand->m_pVertices, PrimitiveCount: pCommand->m_PrimCount);
6837 }
6838
6839 void Cmd_Update_Viewport_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_Update_Viewport *pCommand)
6840 {
6841 ExecBuffer.m_EstimatedRenderCallCount = 0;
6842 }
6843
6844 [[nodiscard]] bool Cmd_Update_Viewport(const CCommandBuffer::SCommand_Update_Viewport *pCommand)
6845 {
6846 if(pCommand->m_ByResize)
6847 {
6848 if(IsVerbose())
6849 {
6850 log_debug("gfx/vulkan", "Got resize event.");
6851 }
6852 m_CanvasWidth = (uint32_t)pCommand->m_Width;
6853 m_CanvasHeight = (uint32_t)pCommand->m_Height;
6854#ifndef CONF_PLATFORM_MACOS
6855 m_RecreateSwapChain = true;
6856#endif
6857 }
6858 else
6859 {
6860 auto Viewport = m_VKSwapImgAndViewportExtent.GetPresentedImageViewport();
6861 if(pCommand->m_X != 0 || pCommand->m_Y != 0 || (uint32_t)pCommand->m_Width != Viewport.width || (uint32_t)pCommand->m_Height != Viewport.height)
6862 {
6863 m_HasDynamicViewport = true;
6864
6865 // convert viewport from OGL to vulkan
6866 int32_t ViewportY = (int32_t)Viewport.height - ((int32_t)pCommand->m_Y + (int32_t)pCommand->m_Height);
6867 uint32_t ViewportH = (int32_t)pCommand->m_Height;
6868 m_DynamicViewportOffset = {.x: (int32_t)pCommand->m_X, .y: ViewportY};
6869 m_DynamicViewportSize = {.width: (uint32_t)pCommand->m_Width, .height: ViewportH};
6870 }
6871 else
6872 {
6873 m_HasDynamicViewport = false;
6874 }
6875 }
6876
6877 return true;
6878 }
6879
6880 [[nodiscard]] bool Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand)
6881 {
6882 if(IsVerbose())
6883 {
6884 log_info("gfx/vulkan", "Queueing swap chain recreation because V-Sync was changed.");
6885 }
6886 m_RecreateSwapChain = true;
6887 *pCommand->m_pRetOk = true;
6888
6889 return true;
6890 }
6891
6892 [[nodiscard]] bool Cmd_MultiSampling(const CCommandBuffer::SCommand_MultiSampling *pCommand)
6893 {
6894 if(IsVerbose())
6895 {
6896 log_info("gfx/vulkan", "Queueing swap chain recreation because multi sampling was changed.");
6897 }
6898 m_RecreateSwapChain = true;
6899
6900 uint32_t MSCount = (std::min(a: pCommand->m_RequestedMultiSamplingCount, b: (uint32_t)GetMaxSampleCount()) & 0xFFFFFFFE); // ignore the uneven bits
6901 m_NextMultiSamplingCount = MSCount;
6902
6903 *pCommand->m_pRetMultiSamplingCount = MSCount;
6904 *pCommand->m_pRetOk = true;
6905
6906 return true;
6907 }
6908
6909 [[nodiscard]] bool Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand)
6910 {
6911 return NextFrame();
6912 }
6913
6914 [[nodiscard]] bool Cmd_CreateBufferObject(const CCommandBuffer::SCommand_CreateBufferObject *pCommand)
6915 {
6916 bool IsOneFrameBuffer = (pCommand->m_Flags & IGraphics::EBufferObjectCreateFlags::BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT) != 0;
6917 if(!CreateBufferObject(BufferIndex: (size_t)pCommand->m_BufferIndex, pUploadData: pCommand->m_pUploadData, BufferDataSize: (VkDeviceSize)pCommand->m_DataSize, IsOneFrameBuffer))
6918 return false;
6919 if(pCommand->m_DeletePointer)
6920 free(ptr: pCommand->m_pUploadData);
6921
6922 return true;
6923 }
6924
6925 [[nodiscard]] bool Cmd_UpdateBufferObject(const CCommandBuffer::SCommand_UpdateBufferObject *pCommand)
6926 {
6927 size_t BufferIndex = (size_t)pCommand->m_BufferIndex;
6928 bool DeletePointer = pCommand->m_DeletePointer;
6929 VkDeviceSize Offset = (VkDeviceSize)((intptr_t)pCommand->m_pOffset);
6930 void *pUploadData = pCommand->m_pUploadData;
6931 VkDeviceSize DataSize = (VkDeviceSize)pCommand->m_DataSize;
6932
6933 SMemoryBlock<STAGING_BUFFER_CACHE_ID> StagingBuffer;
6934 if(!GetStagingBuffer(ResBlock&: StagingBuffer, pBufferData: pUploadData, RequiredSize: DataSize))
6935 return false;
6936
6937 const auto &MemBlock = m_vBufferObjects[BufferIndex].m_BufferObject.m_Mem;
6938 VkBuffer VertexBuffer = MemBlock.m_Buffer;
6939 if(!MemoryBarrier(Buffer: VertexBuffer, Offset: Offset + MemBlock.m_HeapData.m_OffsetToAlign, Size: DataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: true))
6940 return false;
6941 if(!CopyBuffer(SrcBuffer: StagingBuffer.m_Buffer, DstBuffer: VertexBuffer, SrcOffset: StagingBuffer.m_HeapData.m_OffsetToAlign, DstOffset: Offset + MemBlock.m_HeapData.m_OffsetToAlign, CopySize: DataSize))
6942 return false;
6943 if(!MemoryBarrier(Buffer: VertexBuffer, Offset: Offset + MemBlock.m_HeapData.m_OffsetToAlign, Size: DataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: false))
6944 return false;
6945
6946 UploadAndFreeStagingMemBlock(Block&: StagingBuffer);
6947
6948 if(DeletePointer)
6949 free(ptr: pUploadData);
6950
6951 return true;
6952 }
6953
6954 [[nodiscard]] bool Cmd_RecreateBufferObject(const CCommandBuffer::SCommand_RecreateBufferObject *pCommand)
6955 {
6956 DeleteBufferObject(BufferIndex: (size_t)pCommand->m_BufferIndex);
6957 bool IsOneFrameBuffer = (pCommand->m_Flags & IGraphics::EBufferObjectCreateFlags::BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT) != 0;
6958 return CreateBufferObject(BufferIndex: (size_t)pCommand->m_BufferIndex, pUploadData: pCommand->m_pUploadData, BufferDataSize: (VkDeviceSize)pCommand->m_DataSize, IsOneFrameBuffer);
6959 }
6960
6961 [[nodiscard]] bool Cmd_CopyBufferObject(const CCommandBuffer::SCommand_CopyBufferObject *pCommand)
6962 {
6963 size_t ReadBufferIndex = (size_t)pCommand->m_ReadBufferIndex;
6964 size_t WriteBufferIndex = (size_t)pCommand->m_WriteBufferIndex;
6965 auto &ReadMemBlock = m_vBufferObjects[ReadBufferIndex].m_BufferObject.m_Mem;
6966 auto &WriteMemBlock = m_vBufferObjects[WriteBufferIndex].m_BufferObject.m_Mem;
6967 VkBuffer ReadBuffer = ReadMemBlock.m_Buffer;
6968 VkBuffer WriteBuffer = WriteMemBlock.m_Buffer;
6969
6970 VkDeviceSize DataSize = (VkDeviceSize)pCommand->m_CopySize;
6971 VkDeviceSize ReadOffset = (VkDeviceSize)pCommand->m_ReadOffset + ReadMemBlock.m_HeapData.m_OffsetToAlign;
6972 VkDeviceSize WriteOffset = (VkDeviceSize)pCommand->m_WriteOffset + WriteMemBlock.m_HeapData.m_OffsetToAlign;
6973
6974 if(!MemoryBarrier(Buffer: ReadBuffer, Offset: ReadOffset, Size: DataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: true))
6975 return false;
6976 if(!MemoryBarrier(Buffer: WriteBuffer, Offset: WriteOffset, Size: DataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: true))
6977 return false;
6978 if(!CopyBuffer(SrcBuffer: ReadBuffer, DstBuffer: WriteBuffer, SrcOffset: ReadOffset, DstOffset: WriteOffset, CopySize: DataSize))
6979 return false;
6980 if(!MemoryBarrier(Buffer: WriteBuffer, Offset: WriteOffset, Size: DataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: false))
6981 return false;
6982 if(!MemoryBarrier(Buffer: ReadBuffer, Offset: ReadOffset, Size: DataSize, BufferAccessType: VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT, BeforeCommand: false))
6983 return false;
6984
6985 return true;
6986 }
6987
6988 [[nodiscard]] bool Cmd_DeleteBufferObject(const CCommandBuffer::SCommand_DeleteBufferObject *pCommand)
6989 {
6990 size_t BufferIndex = (size_t)pCommand->m_BufferIndex;
6991 DeleteBufferObject(BufferIndex);
6992
6993 return true;
6994 }
6995
6996 [[nodiscard]] bool Cmd_CreateBufferContainer(const CCommandBuffer::SCommand_CreateBufferContainer *pCommand)
6997 {
6998 size_t ContainerIndex = (size_t)pCommand->m_BufferContainerIndex;
6999 while(ContainerIndex >= m_vBufferContainers.size())
7000 m_vBufferContainers.resize(sz: (m_vBufferContainers.size() * 2) + 1);
7001
7002 m_vBufferContainers[ContainerIndex].m_BufferObjectIndex = pCommand->m_VertBufferBindingIndex;
7003
7004 return true;
7005 }
7006
7007 [[nodiscard]] bool Cmd_UpdateBufferContainer(const CCommandBuffer::SCommand_UpdateBufferContainer *pCommand)
7008 {
7009 size_t ContainerIndex = (size_t)pCommand->m_BufferContainerIndex;
7010 m_vBufferContainers[ContainerIndex].m_BufferObjectIndex = pCommand->m_VertBufferBindingIndex;
7011
7012 return true;
7013 }
7014
7015 [[nodiscard]] bool Cmd_DeleteBufferContainer(const CCommandBuffer::SCommand_DeleteBufferContainer *pCommand)
7016 {
7017 size_t ContainerIndex = (size_t)pCommand->m_BufferContainerIndex;
7018 bool DeleteAllBO = pCommand->m_DestroyAllBO;
7019 if(DeleteAllBO)
7020 {
7021 size_t BufferIndex = (size_t)m_vBufferContainers[ContainerIndex].m_BufferObjectIndex;
7022 DeleteBufferObject(BufferIndex);
7023 }
7024
7025 return true;
7026 }
7027
7028 [[nodiscard]] bool Cmd_IndicesRequiredNumNotify(const CCommandBuffer::SCommand_IndicesRequiredNumNotify *pCommand)
7029 {
7030 size_t IndicesCount = pCommand->m_RequiredIndicesNum;
7031 if(m_CurRenderIndexPrimitiveCount < IndicesCount / 6)
7032 {
7033 m_vvFrameDelayedBufferCleanup[m_CurImageIndex].push_back(x: {.m_Buffer: m_RenderIndexBuffer, .m_Mem: m_RenderIndexBufferMemory});
7034 std::vector<uint32_t> vIndices(IndicesCount);
7035 uint32_t Primq = 0;
7036 for(size_t i = 0; i < IndicesCount; i += 6)
7037 {
7038 vIndices[i] = Primq;
7039 vIndices[i + 1] = Primq + 1;
7040 vIndices[i + 2] = Primq + 2;
7041 vIndices[i + 3] = Primq;
7042 vIndices[i + 4] = Primq + 2;
7043 vIndices[i + 5] = Primq + 3;
7044 Primq += 4;
7045 }
7046 if(!CreateIndexBuffer(pData: vIndices.data(), DataSize: vIndices.size() * sizeof(uint32_t), Buffer&: m_RenderIndexBuffer, Memory&: m_RenderIndexBufferMemory))
7047 return false;
7048 m_CurRenderIndexPrimitiveCount = IndicesCount / 6;
7049 }
7050
7051 return true;
7052 }
7053
7054 void Cmd_RenderTileLayer_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderTileLayer *pCommand)
7055 {
7056 RenderTileLayer_FillExecuteBuffer(ExecBuffer, DrawCalls: pCommand->m_IndicesDrawNum, State: pCommand->m_State, BufferContainerIndex: pCommand->m_BufferContainerIndex);
7057 }
7058
7059 [[nodiscard]] bool Cmd_RenderTileLayer(const CCommandBuffer::SCommand_RenderTileLayer *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
7060 {
7061 vec2 Scale{};
7062 vec2 Off{};
7063 return RenderTileLayer(ExecBuffer, State: pCommand->m_State, IsBorder: false, Color: pCommand->m_Color, Scale, Off, IndicesDrawNum: (size_t)pCommand->m_IndicesDrawNum, pIndicesOffsets: pCommand->m_pIndicesOffsets, pDrawCount: pCommand->m_pDrawCount);
7064 }
7065
7066 void Cmd_RenderBorderTile_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderBorderTile *pCommand)
7067 {
7068 RenderTileLayer_FillExecuteBuffer(ExecBuffer, DrawCalls: 1, State: pCommand->m_State, BufferContainerIndex: pCommand->m_BufferContainerIndex);
7069 }
7070
7071 [[nodiscard]] bool Cmd_RenderBorderTile(const CCommandBuffer::SCommand_RenderBorderTile *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
7072 {
7073 vec2 Scale = pCommand->m_Scale;
7074 vec2 Off = pCommand->m_Offset;
7075 unsigned int DrawNum = pCommand->m_DrawNum * 6;
7076 return RenderTileLayer(ExecBuffer, State: pCommand->m_State, IsBorder: true, Color: pCommand->m_Color, Scale, Off, IndicesDrawNum: 1, pIndicesOffsets: &pCommand->m_pIndicesOffset, pDrawCount: &DrawNum);
7077 }
7078
7079 void Cmd_RenderQuadLayer_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderQuadLayer *pCommand)
7080 {
7081 size_t BufferContainerIndex = (size_t)pCommand->m_BufferContainerIndex;
7082 size_t BufferObjectIndex = (size_t)m_vBufferContainers[BufferContainerIndex].m_BufferObjectIndex;
7083 const auto &BufferObject = m_vBufferObjects[BufferObjectIndex];
7084
7085 ExecBuffer.m_Buffer = BufferObject.m_CurBuffer;
7086 ExecBuffer.m_BufferOff = BufferObject.m_CurBufferOffset;
7087
7088 bool IsTextured = GetIsTextured(State: pCommand->m_State);
7089 if(IsTextured)
7090 {
7091 size_t AddressModeIndex = GetAddressModeIndex(State: pCommand->m_State);
7092 ExecBuffer.m_aDescriptors[0] = m_vTextures[pCommand->m_State.m_Texture].m_aVKStandardTexturedDescrSets[AddressModeIndex];
7093 }
7094
7095 ExecBuffer.m_IndexBuffer = m_RenderIndexBuffer;
7096
7097 ExecBuffer.m_EstimatedRenderCallCount = ((pCommand->m_QuadNum - 1) / GRAPHICS_MAX_QUADS_RENDER_COUNT) + 1;
7098
7099 ExecBufferFillDynamicStates(State: pCommand->m_State, ExecBuffer);
7100 }
7101
7102 [[nodiscard]] bool Cmd_RenderQuadLayer(const CCommandBuffer::SCommand_RenderQuadLayer *pCommand, SRenderCommandExecuteBuffer &ExecBuffer, bool Grouped)
7103 {
7104 std::array<float, (size_t)4 * 2> m;
7105 GetStateMatrix(State: pCommand->m_State, Matrix&: m);
7106
7107 bool CanBeGrouped = Grouped || pCommand->m_QuadNum == 1;
7108
7109 bool IsTextured;
7110 size_t BlendModeIndex;
7111 size_t DynamicIndex;
7112 size_t AddressModeIndex;
7113 GetStateIndices(ExecBuffer, State: pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
7114 auto &PipeLayout = GetPipeLayout(Container&: CanBeGrouped ? m_QuadGroupedPipeline : m_QuadPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7115 auto &PipeLine = GetPipeline(Container&: CanBeGrouped ? m_QuadGroupedPipeline : m_QuadPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7116
7117 VkCommandBuffer *pCommandBuffer;
7118 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
7119 return false;
7120 auto &CommandBuffer = *pCommandBuffer;
7121
7122 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State: pCommand->m_State);
7123
7124 std::array<VkBuffer, 1> aVertexBuffers = {ExecBuffer.m_Buffer};
7125 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)ExecBuffer.m_BufferOff};
7126 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
7127
7128 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: 0, indexType: VK_INDEX_TYPE_UINT32);
7129
7130 if(IsTextured)
7131 {
7132 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7133 }
7134
7135 uint32_t DrawCount = (uint32_t)pCommand->m_QuadNum;
7136
7137 if(CanBeGrouped)
7138 {
7139 SUniformQuadGroupedGPos PushConstantVertex;
7140 mem_copy(dest: &PushConstantVertex.m_BOPush, source: &pCommand->m_pQuadInfo[0], size: sizeof(PushConstantVertex.m_BOPush));
7141
7142 mem_copy(dest: PushConstantVertex.m_aPos, source: m.data(), size: sizeof(PushConstantVertex.m_aPos));
7143 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, offset: 0, size: sizeof(SUniformQuadGroupedGPos), pValues: &PushConstantVertex);
7144
7145 VkDeviceSize IndexOffset = (VkDeviceSize)((ptrdiff_t)(pCommand->m_QuadOffset) * 6);
7146 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(DrawCount * 6), instanceCount: 1, firstIndex: IndexOffset, vertexOffset: 0, firstInstance: 0);
7147 }
7148 else
7149 {
7150 SUniformQuadGPos PushConstantVertex;
7151 mem_copy(dest: PushConstantVertex.m_aPos, source: m.data(), size: sizeof(PushConstantVertex.m_aPos));
7152 PushConstantVertex.m_QuadOffset = pCommand->m_QuadOffset;
7153
7154 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: sizeof(PushConstantVertex), pValues: &PushConstantVertex);
7155
7156 size_t RenderOffset = 0;
7157 while(DrawCount > 0)
7158 {
7159 uint32_t RealDrawCount = (DrawCount > GRAPHICS_MAX_QUADS_RENDER_COUNT ? GRAPHICS_MAX_QUADS_RENDER_COUNT : DrawCount);
7160 VkDeviceSize IndexOffset = (VkDeviceSize)((ptrdiff_t)(pCommand->m_QuadOffset + RenderOffset) * 6);
7161
7162 // create uniform buffer
7163 SDeviceDescriptorSet UniDescrSet;
7164 if(!GetUniformBufferObject(RenderThreadIndex: ExecBuffer.m_ThreadIndex, RequiresSharedStagesDescriptor: true, DescrSet&: UniDescrSet, ParticleCount: RealDrawCount, pData: (const float *)(pCommand->m_pQuadInfo + RenderOffset), DataSize: RealDrawCount * sizeof(SQuadRenderInfo)))
7165 return false;
7166
7167 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: IsTextured ? 1 : 0, descriptorSetCount: 1, pDescriptorSets: &UniDescrSet.m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7168 if(RenderOffset > 0)
7169 {
7170 int32_t QuadOffset = pCommand->m_QuadOffset + RenderOffset;
7171 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: sizeof(SUniformQuadGPos) - sizeof(int32_t), size: sizeof(int32_t), pValues: &QuadOffset);
7172 }
7173
7174 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(RealDrawCount * 6), instanceCount: 1, firstIndex: IndexOffset, vertexOffset: 0, firstInstance: 0);
7175 RenderOffset += RealDrawCount;
7176 DrawCount -= RealDrawCount;
7177 }
7178 }
7179
7180 return true;
7181 }
7182
7183 void Cmd_RenderText_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderText *pCommand)
7184 {
7185 size_t BufferContainerIndex = (size_t)pCommand->m_BufferContainerIndex;
7186 size_t BufferObjectIndex = (size_t)m_vBufferContainers[BufferContainerIndex].m_BufferObjectIndex;
7187 const auto &BufferObject = m_vBufferObjects[BufferObjectIndex];
7188
7189 ExecBuffer.m_Buffer = BufferObject.m_CurBuffer;
7190 ExecBuffer.m_BufferOff = BufferObject.m_CurBufferOffset;
7191
7192 ExecBuffer.m_aDescriptors[0] = m_vTextures[pCommand->m_TextTextureIndex].m_VKTextDescrSet;
7193
7194 ExecBuffer.m_IndexBuffer = m_RenderIndexBuffer;
7195
7196 ExecBuffer.m_EstimatedRenderCallCount = 1;
7197
7198 ExecBufferFillDynamicStates(State: pCommand->m_State, ExecBuffer);
7199 }
7200
7201 [[nodiscard]] bool Cmd_RenderText(const CCommandBuffer::SCommand_RenderText *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
7202 {
7203 std::array<float, (size_t)4 * 2> m;
7204 GetStateMatrix(State: pCommand->m_State, Matrix&: m);
7205
7206 bool IsTextured;
7207 size_t BlendModeIndex;
7208 size_t DynamicIndex;
7209 size_t AddressModeIndex;
7210 GetStateIndices(ExecBuffer, State: pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
7211 IsTextured = true; // text is always textured
7212 auto &PipeLayout = GetPipeLayout(Container&: m_TextPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7213 auto &PipeLine = GetPipeline(Container&: m_TextPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7214
7215 VkCommandBuffer *pCommandBuffer;
7216 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
7217 return false;
7218 auto &CommandBuffer = *pCommandBuffer;
7219
7220 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State: pCommand->m_State);
7221
7222 std::array<VkBuffer, 1> aVertexBuffers = {ExecBuffer.m_Buffer};
7223 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)ExecBuffer.m_BufferOff};
7224 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
7225
7226 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: 0, indexType: VK_INDEX_TYPE_UINT32);
7227
7228 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7229
7230 SUniformGTextPos PosTexSizeConstant;
7231 mem_copy(dest: PosTexSizeConstant.m_aPos, source: m.data(), size: m.size() * sizeof(float));
7232 PosTexSizeConstant.m_TextureSize = pCommand->m_TextureSize;
7233
7234 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: sizeof(SUniformGTextPos), pValues: &PosTexSizeConstant);
7235
7236 SUniformTextFragment FragmentConstants;
7237
7238 FragmentConstants.m_Constants.m_TextColor = pCommand->m_TextColor;
7239 FragmentConstants.m_Constants.m_TextOutlineColor = pCommand->m_TextOutlineColor;
7240 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, offset: sizeof(SUniformGTextPos) + sizeof(SUniformTextGFragmentOffset), size: sizeof(SUniformTextFragment), pValues: &FragmentConstants);
7241
7242 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(pCommand->m_DrawNum), instanceCount: 1, firstIndex: 0, vertexOffset: 0, firstInstance: 0);
7243
7244 return true;
7245 }
7246
7247 void BufferContainer_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SState &State, size_t BufferContainerIndex, size_t DrawCalls)
7248 {
7249 size_t BufferObjectIndex = (size_t)m_vBufferContainers[BufferContainerIndex].m_BufferObjectIndex;
7250 const auto &BufferObject = m_vBufferObjects[BufferObjectIndex];
7251
7252 ExecBuffer.m_Buffer = BufferObject.m_CurBuffer;
7253 ExecBuffer.m_BufferOff = BufferObject.m_CurBufferOffset;
7254
7255 bool IsTextured = GetIsTextured(State);
7256 if(IsTextured)
7257 {
7258 size_t AddressModeIndex = GetAddressModeIndex(State);
7259 ExecBuffer.m_aDescriptors[0] = m_vTextures[State.m_Texture].m_aVKStandardTexturedDescrSets[AddressModeIndex];
7260 }
7261
7262 ExecBuffer.m_IndexBuffer = m_RenderIndexBuffer;
7263
7264 ExecBuffer.m_EstimatedRenderCallCount = DrawCalls;
7265
7266 ExecBufferFillDynamicStates(State, ExecBuffer);
7267 }
7268
7269 void Cmd_RenderQuadContainer_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderQuadContainer *pCommand)
7270 {
7271 BufferContainer_FillExecuteBuffer(ExecBuffer, State: pCommand->m_State, BufferContainerIndex: (size_t)pCommand->m_BufferContainerIndex, DrawCalls: 1);
7272 }
7273
7274 [[nodiscard]] bool Cmd_RenderQuadContainer(const CCommandBuffer::SCommand_RenderQuadContainer *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
7275 {
7276 std::array<float, (size_t)4 * 2> m;
7277 GetStateMatrix(State: pCommand->m_State, Matrix&: m);
7278
7279 bool IsTextured;
7280 size_t BlendModeIndex;
7281 size_t DynamicIndex;
7282 size_t AddressModeIndex;
7283 GetStateIndices(ExecBuffer, State: pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
7284 auto &PipeLayout = GetStandardPipeLayout(IsLineGeometry: false, IsTextured, BlendModeIndex, DynamicIndex);
7285 auto &PipeLine = GetStandardPipe(IsLineGeometry: false, IsTextured, BlendModeIndex, DynamicIndex);
7286
7287 VkCommandBuffer *pCommandBuffer;
7288 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
7289 return false;
7290 auto &CommandBuffer = *pCommandBuffer;
7291
7292 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State: pCommand->m_State);
7293
7294 std::array<VkBuffer, 1> aVertexBuffers = {ExecBuffer.m_Buffer};
7295 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)ExecBuffer.m_BufferOff};
7296 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
7297
7298 VkDeviceSize IndexOffset = (VkDeviceSize)((ptrdiff_t)pCommand->m_pOffset);
7299
7300 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: IndexOffset, indexType: VK_INDEX_TYPE_UINT32);
7301
7302 if(IsTextured)
7303 {
7304 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7305 }
7306
7307 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: sizeof(SUniformGPos), pValues: m.data());
7308
7309 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(pCommand->m_DrawNum), instanceCount: 1, firstIndex: 0, vertexOffset: 0, firstInstance: 0);
7310
7311 return true;
7312 }
7313
7314 void Cmd_RenderQuadContainerEx_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand)
7315 {
7316 BufferContainer_FillExecuteBuffer(ExecBuffer, State: pCommand->m_State, BufferContainerIndex: (size_t)pCommand->m_BufferContainerIndex, DrawCalls: 1);
7317 }
7318
7319 [[nodiscard]] bool Cmd_RenderQuadContainerEx(const CCommandBuffer::SCommand_RenderQuadContainerEx *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
7320 {
7321 std::array<float, (size_t)4 * 2> m;
7322 GetStateMatrix(State: pCommand->m_State, Matrix&: m);
7323
7324 bool IsRotationless = !(pCommand->m_Rotation != 0);
7325 bool IsTextured;
7326 size_t BlendModeIndex;
7327 size_t DynamicIndex;
7328 size_t AddressModeIndex;
7329 GetStateIndices(ExecBuffer, State: pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
7330 auto &PipeLayout = GetPipeLayout(Container&: IsRotationless ? m_PrimExRotationlessPipeline : m_PrimExPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7331 auto &PipeLine = GetPipeline(Container&: IsRotationless ? m_PrimExRotationlessPipeline : m_PrimExPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7332
7333 VkCommandBuffer *pCommandBuffer;
7334 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
7335 return false;
7336 auto &CommandBuffer = *pCommandBuffer;
7337
7338 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State: pCommand->m_State);
7339
7340 std::array<VkBuffer, 1> aVertexBuffers = {ExecBuffer.m_Buffer};
7341 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)ExecBuffer.m_BufferOff};
7342 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
7343
7344 VkDeviceSize IndexOffset = (VkDeviceSize)((ptrdiff_t)pCommand->m_pOffset);
7345
7346 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: IndexOffset, indexType: VK_INDEX_TYPE_UINT32);
7347
7348 if(IsTextured)
7349 {
7350 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7351 }
7352
7353 SUniformPrimExGVertColor PushConstantColor;
7354 SUniformPrimExGPos PushConstantVertex;
7355 size_t VertexPushConstantSize = sizeof(PushConstantVertex);
7356
7357 PushConstantColor = pCommand->m_VertexColor;
7358 mem_copy(dest: PushConstantVertex.m_aPos, source: m.data(), size: sizeof(PushConstantVertex.m_aPos));
7359
7360 if(!IsRotationless)
7361 {
7362 PushConstantVertex.m_Rotation = pCommand->m_Rotation;
7363 PushConstantVertex.m_Center = {pCommand->m_Center.x, pCommand->m_Center.y};
7364 }
7365 else
7366 {
7367 VertexPushConstantSize = sizeof(SUniformPrimExGPosRotationless);
7368 }
7369
7370 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: VertexPushConstantSize, pValues: &PushConstantVertex);
7371 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, offset: sizeof(SUniformPrimExGPos) + sizeof(SUniformPrimExGVertColorAlign), size: sizeof(PushConstantColor), pValues: &PushConstantColor);
7372
7373 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(pCommand->m_DrawNum), instanceCount: 1, firstIndex: 0, vertexOffset: 0, firstInstance: 0);
7374
7375 return true;
7376 }
7377
7378 void Cmd_RenderQuadContainerAsSpriteMultiple_FillExecuteBuffer(SRenderCommandExecuteBuffer &ExecBuffer, const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand)
7379 {
7380 BufferContainer_FillExecuteBuffer(ExecBuffer, State: pCommand->m_State, BufferContainerIndex: (size_t)pCommand->m_BufferContainerIndex, DrawCalls: ((pCommand->m_DrawCount - 1) / GRAPHICS_MAX_PARTICLES_RENDER_COUNT) + 1);
7381 }
7382
7383 [[nodiscard]] bool Cmd_RenderQuadContainerAsSpriteMultiple(const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *pCommand, SRenderCommandExecuteBuffer &ExecBuffer)
7384 {
7385 std::array<float, (size_t)4 * 2> m;
7386 GetStateMatrix(State: pCommand->m_State, Matrix&: m);
7387
7388 bool CanBePushed = pCommand->m_DrawCount <= 1;
7389
7390 bool IsTextured;
7391 size_t BlendModeIndex;
7392 size_t DynamicIndex;
7393 size_t AddressModeIndex;
7394 GetStateIndices(ExecBuffer, State: pCommand->m_State, IsTextured, BlendModeIndex, DynamicIndex, AddressModeIndex);
7395 auto &PipeLayout = GetPipeLayout(Container&: CanBePushed ? m_SpriteMultiPushPipeline : m_SpriteMultiPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7396 auto &PipeLine = GetPipeline(Container&: CanBePushed ? m_SpriteMultiPushPipeline : m_SpriteMultiPipeline, IsTextured, BlendModeIndex, DynamicIndex);
7397
7398 VkCommandBuffer *pCommandBuffer;
7399 if(!GetGraphicCommandBuffer(pDrawCommandBuffer&: pCommandBuffer, RenderThreadIndex: ExecBuffer.m_ThreadIndex))
7400 return false;
7401 auto &CommandBuffer = *pCommandBuffer;
7402
7403 BindPipeline(RenderThreadIndex: ExecBuffer.m_ThreadIndex, CommandBuffer, ExecBuffer, BindingPipe&: PipeLine, State: pCommand->m_State);
7404
7405 std::array<VkBuffer, 1> aVertexBuffers = {ExecBuffer.m_Buffer};
7406 std::array<VkDeviceSize, 1> aOffsets = {(VkDeviceSize)ExecBuffer.m_BufferOff};
7407 vkCmdBindVertexBuffers(commandBuffer: CommandBuffer, firstBinding: 0, bindingCount: 1, pBuffers: aVertexBuffers.data(), pOffsets: aOffsets.data());
7408
7409 VkDeviceSize IndexOffset = (VkDeviceSize)((ptrdiff_t)pCommand->m_pOffset);
7410 vkCmdBindIndexBuffer(commandBuffer: CommandBuffer, buffer: ExecBuffer.m_IndexBuffer, offset: IndexOffset, indexType: VK_INDEX_TYPE_UINT32);
7411
7412 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 0, descriptorSetCount: 1, pDescriptorSets: &ExecBuffer.m_aDescriptors[0].m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7413
7414 if(CanBePushed)
7415 {
7416 SUniformSpriteMultiPushGVertColor PushConstantColor;
7417 SUniformSpriteMultiPushGPos PushConstantVertex;
7418
7419 PushConstantColor = pCommand->m_VertexColor;
7420
7421 mem_copy(dest: PushConstantVertex.m_aPos, source: m.data(), size: sizeof(PushConstantVertex.m_aPos));
7422 PushConstantVertex.m_Center = pCommand->m_Center;
7423
7424 for(size_t i = 0; i < pCommand->m_DrawCount; ++i)
7425 PushConstantVertex.m_aPSR[i] = vec4(pCommand->m_pRenderInfo[i].m_Pos.x, pCommand->m_pRenderInfo[i].m_Pos.y, pCommand->m_pRenderInfo[i].m_Scale, pCommand->m_pRenderInfo[i].m_Rotation);
7426
7427 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: sizeof(SUniformSpriteMultiPushGPosBase) + sizeof(vec4) * pCommand->m_DrawCount, pValues: &PushConstantVertex);
7428 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, offset: sizeof(SUniformSpriteMultiPushGPos), size: sizeof(PushConstantColor), pValues: &PushConstantColor);
7429 }
7430 else
7431 {
7432 SUniformSpriteMultiGVertColor PushConstantColor;
7433 SUniformSpriteMultiGPos PushConstantVertex;
7434
7435 PushConstantColor = pCommand->m_VertexColor;
7436
7437 mem_copy(dest: PushConstantVertex.m_aPos, source: m.data(), size: sizeof(PushConstantVertex.m_aPos));
7438 PushConstantVertex.m_Center = pCommand->m_Center;
7439
7440 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_VERTEX_BIT, offset: 0, size: sizeof(PushConstantVertex), pValues: &PushConstantVertex);
7441 vkCmdPushConstants(commandBuffer: CommandBuffer, layout: PipeLayout, stageFlags: VK_SHADER_STAGE_FRAGMENT_BIT, offset: sizeof(SUniformSpriteMultiGPos) + sizeof(SUniformSpriteMultiGVertColorAlign), size: sizeof(PushConstantColor), pValues: &PushConstantColor);
7442 }
7443
7444 const int RSPCount = 512;
7445 int DrawCount = pCommand->m_DrawCount;
7446 size_t RenderOffset = 0;
7447
7448 while(DrawCount > 0)
7449 {
7450 int UniformCount = (DrawCount > RSPCount ? RSPCount : DrawCount);
7451
7452 if(!CanBePushed)
7453 {
7454 // create uniform buffer
7455 SDeviceDescriptorSet UniDescrSet;
7456 if(!GetUniformBufferObject(RenderThreadIndex: ExecBuffer.m_ThreadIndex, RequiresSharedStagesDescriptor: false, DescrSet&: UniDescrSet, ParticleCount: UniformCount, pData: (const float *)(pCommand->m_pRenderInfo + RenderOffset), DataSize: UniformCount * sizeof(IGraphics::SRenderSpriteInfo)))
7457 return false;
7458
7459 vkCmdBindDescriptorSets(commandBuffer: CommandBuffer, pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS, layout: PipeLayout, firstSet: 1, descriptorSetCount: 1, pDescriptorSets: &UniDescrSet.m_Descriptor, dynamicOffsetCount: 0, pDynamicOffsets: nullptr);
7460 }
7461
7462 vkCmdDrawIndexed(commandBuffer: CommandBuffer, indexCount: static_cast<uint32_t>(pCommand->m_DrawNum), instanceCount: UniformCount, firstIndex: 0, vertexOffset: 0, firstInstance: 0);
7463
7464 RenderOffset += RSPCount;
7465 DrawCount -= RSPCount;
7466 }
7467
7468 return true;
7469 }
7470
7471 [[nodiscard]] bool Cmd_WindowCreateNtf(const CCommandBuffer::SCommand_WindowCreateNtf *pCommand)
7472 {
7473 if(IsVerbose())
7474 {
7475 log_debug("gfx/vulkan", "Creating new surface.");
7476 }
7477 m_pWindow = SDL_GetWindowFromID(id: pCommand->m_WindowId);
7478 if(m_RenderingPaused)
7479 {
7480#ifdef CONF_PLATFORM_ANDROID
7481 if(!CreateSurface(m_pWindow))
7482 return false;
7483 m_RecreateSwapChain = true;
7484#endif
7485 m_RenderingPaused = false;
7486 if(!PureMemoryFrame())
7487 return false;
7488 if(!PrepareFrame())
7489 return false;
7490 }
7491
7492 return true;
7493 }
7494
7495 [[nodiscard]] bool Cmd_WindowDestroyNtf(const CCommandBuffer::SCommand_WindowDestroyNtf *pCommand)
7496 {
7497 if(IsVerbose())
7498 {
7499 log_debug("gfx/vulkan", "Surface got destroyed.");
7500 }
7501 if(!m_RenderingPaused)
7502 {
7503 if(!WaitFrame())
7504 return false;
7505 m_RenderingPaused = true;
7506 vkDeviceWaitIdle(device: m_VKDevice);
7507#ifdef CONF_PLATFORM_ANDROID
7508 CleanupVulkanSwapChain(true);
7509#endif
7510 }
7511
7512 return true;
7513 }
7514
7515 [[nodiscard]] bool Cmd_PreInit(const CCommandProcessorFragment_GLBase::SCommand_PreInit *pCommand)
7516 {
7517 m_pGpuList = pCommand->m_pGpuList;
7518 if(InitVulkanSDL(pWindow: pCommand->m_pWindow, CanvasWidth: pCommand->m_Width, CanvasHeight: pCommand->m_Height, pRendererString: pCommand->m_pRendererString, pVendorString: pCommand->m_pVendorString, pVersionString: pCommand->m_pVersionString) != 0)
7519 {
7520 m_VKInstance = VK_NULL_HANDLE;
7521 }
7522
7523 RegisterCommands();
7524
7525 m_ThreadCount = g_Config.m_GfxRenderThreadCount;
7526 if(m_ThreadCount <= 1)
7527 m_ThreadCount = 1;
7528 else
7529 {
7530 m_ThreadCount = std::clamp<decltype(m_ThreadCount)>(val: m_ThreadCount, lo: 3, hi: std::max<decltype(m_ThreadCount)>(a: 3, b: std::thread::hardware_concurrency()));
7531 }
7532
7533 // start threads
7534 dbg_assert(m_ThreadCount != 2, "Either use 1 main thread or at least 2 extra rendering threads.");
7535 if(m_ThreadCount > 1)
7536 {
7537 m_vvThreadCommandLists.resize(sz: m_ThreadCount - 1);
7538 m_vThreadHelperHadCommands.resize(sz: m_ThreadCount - 1, c: false);
7539 for(auto &ThreadCommandList : m_vvThreadCommandLists)
7540 {
7541 ThreadCommandList.reserve(n: 256);
7542 }
7543
7544 m_vpRenderThreads.reserve(n: m_ThreadCount - 1);
7545 for(size_t i = 0; i < m_ThreadCount - 1; ++i)
7546 {
7547 auto *pRenderThread = new SRenderThread();
7548 std::unique_lock<std::mutex> Lock(pRenderThread->m_Mutex);
7549 m_vpRenderThreads.emplace_back(args&: pRenderThread);
7550 pRenderThread->m_Thread = std::thread([this, i]() { RunThread(ThreadIndex: i); });
7551 // wait until thread started
7552 pRenderThread->m_Cond.wait(lock&: Lock, p: [pRenderThread]() -> bool { return pRenderThread->m_Started; });
7553 }
7554 }
7555
7556 return true;
7557 }
7558
7559 [[nodiscard]] bool Cmd_PostShutdown(const CCommandProcessorFragment_GLBase::SCommand_PostShutdown *pCommand)
7560 {
7561 for(size_t i = 0; i < m_ThreadCount - 1; ++i)
7562 {
7563 auto *pThread = m_vpRenderThreads[i].get();
7564 {
7565 std::unique_lock<std::mutex> Lock(pThread->m_Mutex);
7566 pThread->m_Finished = true;
7567 pThread->m_Cond.notify_one();
7568 }
7569 pThread->m_Thread.join();
7570 }
7571 m_vpRenderThreads.clear();
7572 m_vvThreadCommandLists.clear();
7573 m_vThreadHelperHadCommands.clear();
7574
7575 m_ThreadCount = 1;
7576
7577 CleanupVulkanSDL();
7578
7579 return true;
7580 }
7581
7582 void StartCommands(size_t CommandCount, size_t EstimatedRenderCallCount) override
7583 {
7584 m_CommandsInPipe = CommandCount;
7585 m_RenderCallsInPipe = EstimatedRenderCallCount;
7586 m_CurCommandInPipe = 0;
7587 m_CurRenderCallCountInPipe = 0;
7588 }
7589
7590 void EndCommands() override
7591 {
7592 FinishRenderThreads();
7593 m_CommandsInPipe = 0;
7594 m_RenderCallsInPipe = 0;
7595 }
7596
7597 /****************
7598 * RENDER THREADS
7599 *****************/
7600
7601 void RunThread(size_t ThreadIndex)
7602 {
7603 auto *pThread = m_vpRenderThreads[ThreadIndex].get();
7604 std::unique_lock<std::mutex> Lock(pThread->m_Mutex);
7605 pThread->m_Started = true;
7606 pThread->m_Cond.notify_one();
7607
7608 while(!pThread->m_Finished)
7609 {
7610 pThread->m_Cond.wait(lock&: Lock, p: [pThread]() -> bool { return pThread->m_IsRendering || pThread->m_Finished; });
7611 pThread->m_Cond.notify_one();
7612
7613 // set this to true, if you want to benchmark the render thread times
7614 static constexpr bool BENCHMARK_RENDER_THREADS = false;
7615 std::chrono::nanoseconds ThreadRenderTime = 0ns;
7616 if(IsVerbose() && BENCHMARK_RENDER_THREADS)
7617 {
7618 ThreadRenderTime = time_get_nanoseconds();
7619 }
7620
7621 if(!pThread->m_Finished)
7622 {
7623 bool HasErrorFromCmd = false;
7624 for(auto &NextCmd : m_vvThreadCommandLists[ThreadIndex])
7625 {
7626 if(!m_aCommandCallbacks[CommandBufferCMDOff(CommandBufferCMD: NextCmd.m_Command)].m_CommandCB(NextCmd.m_pRawCommand, NextCmd))
7627 {
7628 // an error occurred, the thread will not continue execution
7629 HasErrorFromCmd = true;
7630 break;
7631 }
7632 }
7633 m_vvThreadCommandLists[ThreadIndex].clear();
7634
7635 if(!HasErrorFromCmd && m_vvUsedThreadDrawCommandBuffer[ThreadIndex + 1][m_CurImageIndex])
7636 {
7637 auto &GraphicThreadCommandBuffer = m_vvThreadDrawCommandBuffers[ThreadIndex + 1][m_CurImageIndex];
7638 vkEndCommandBuffer(commandBuffer: GraphicThreadCommandBuffer);
7639 }
7640 }
7641
7642 if(IsVerbose() && BENCHMARK_RENDER_THREADS)
7643 {
7644 log_debug("gfx/vulkan", "Render thread %" PRIzu " took %" PRId64 " ns to finish.", ThreadIndex, (int64_t)(time_get_nanoseconds() - ThreadRenderTime).count());
7645 }
7646
7647 pThread->m_IsRendering = false;
7648 }
7649 }
7650};
7651
7652CCommandProcessorFragment_GLBase *CreateVulkanCommandProcessorFragment()
7653{
7654 return new CCommandProcessorFragment_Vulkan();
7655}
7656
7657#endif
7658