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