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