KiCad PCB EDA Suite
Loading...
Searching...
No Matches
rhi_vulkan_device.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
22
23#include <wx/dynlib.h>
24
25// Each window system is compiled in only when the build found its development files
26#ifdef RHI_VULKAN_HAS_X11
27#define VK_USE_PLATFORM_XLIB_KHR
28#define VK_USE_PLATFORM_XCB_KHR
29#include <X11/Xlib.h>
30#include <xcb/xcb.h>
31#endif
32
33#ifdef RHI_VULKAN_HAS_WAYLAND
34#define VK_USE_PLATFORM_WAYLAND_KHR
35#include <wayland-client.h>
36#endif
37
38#ifdef _WIN32
39#define VK_USE_PLATFORM_WIN32_KHR
40#endif
41
42#include <vulkan/vulkan.h>
43#include <trace_helpers.h>
44#include <core/profile.h>
45
46#include <array>
47#include <cstdio>
48#include <cstdlib>
49
50// Windows headers pulled in by VK_USE_PLATFORM_WIN32_KHR re-define ERROR,
51// which collides with RHI_FRAME_OP_RESULT::ERROR in rhi_device.h.
52#undef ERROR
53
54#define VMA_STATIC_VULKAN_FUNCTIONS 0
55#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
56#include <vk_mem_alloc.h>
57
58#include <wx/log.h>
59
60#include <magic_enum.hpp>
61
62#include <present_blit_vert.h>
63#include <present_blit_frag.h>
64
65#include <atomic>
66#include <chrono>
67#include <cstring>
68#include <deque>
69#include <mutex>
70#include <optional>
71#include <string>
72#include <thread>
73#include <unordered_map>
74#include <vector>
75
76
77static const char* VkResultToString( VkResult aResult )
78{
79 switch( aResult )
80 {
81 case VK_SUCCESS: return "VK_SUCCESS";
82 case VK_NOT_READY: return "VK_NOT_READY";
83 case VK_TIMEOUT: return "VK_TIMEOUT";
84 case VK_EVENT_SET: return "VK_EVENT_SET";
85 case VK_EVENT_RESET: return "VK_EVENT_RESET";
86 case VK_INCOMPLETE: return "VK_INCOMPLETE";
87 case VK_ERROR_OUT_OF_HOST_MEMORY:return "VK_ERROR_OUT_OF_HOST_MEMORY";
88 case VK_ERROR_OUT_OF_DEVICE_MEMORY:return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
89 case VK_ERROR_INITIALIZATION_FAILED:return "VK_ERROR_INITIALIZATION_FAILED";
90 case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST";
91 case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
92 case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
93 case VK_ERROR_EXTENSION_NOT_PRESENT:return "VK_ERROR_EXTENSION_NOT_PRESENT";
94 case VK_ERROR_FEATURE_NOT_PRESENT:return "VK_ERROR_FEATURE_NOT_PRESENT";
95 case VK_ERROR_INCOMPATIBLE_DRIVER:return "VK_ERROR_INCOMPATIBLE_DRIVER";
96 case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR";
97 case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
98 case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR";
99 case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR";
100 default: return "VK_RESULT_UNKNOWN";
101 }
102}
103
104
105// Whole milliseconds a timer has run, as the device traces print them
106static long long VkElapsedMs( PROF_TIMER& aTimer )
107{
108 return static_cast<long long>( aTimer.msecs() );
109}
110
111#ifdef VK_USE_PLATFORM_XLIB_KHR
112// The render thread's own connection to the X server, through xcb or Xlib as the surface was made
113struct X11_CONNECTION_OWNER
114{
115 xcb_connection_t* xcb = nullptr;
116 Display* xlib = nullptr;
117 int screen = 0;
118
119 // True when a compositing manager owns this screen's _NET_WM_CM_Sn selection
120 bool HasCompositor() const
121 {
122 const std::string selection = "_NET_WM_CM_S" + std::to_string( screen );
123
124 if( xcb )
125 {
126 xcb_intern_atom_reply_t* atomReply = xcb_intern_atom_reply(
127 xcb, xcb_intern_atom( xcb, 1, selection.size(), selection.c_str() ), nullptr );
128
129 if( !atomReply )
130 return false;
131
132 const xcb_atom_t atom = atomReply->atom;
133 free( atomReply );
134
135 if( atom == XCB_ATOM_NONE )
136 return false;
137
138 xcb_get_selection_owner_reply_t* ownerReply =
139 xcb_get_selection_owner_reply( xcb, xcb_get_selection_owner( xcb, atom ), nullptr );
140
141 if( !ownerReply )
142 return false;
143
144 const bool owned = ownerReply->owner != XCB_WINDOW_NONE;
145 free( ownerReply );
146 return owned;
147 }
148
149 if( xlib )
150 {
151 const Atom atom = XInternAtom( xlib, selection.c_str(), True );
152 return atom != None && XGetSelectionOwner( xlib, atom ) != None;
153 }
154
155 return false;
156 }
157
158 void Close()
159 {
160 if( xcb )
161 xcb_disconnect( xcb );
162
163 if( xlib )
164 XCloseDisplay( xlib );
165
166 xcb = nullptr;
167 xlib = nullptr;
168 }
169};
170#endif
171
172static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback(
173 VkDebugUtilsMessageSeverityFlagBitsEXT aSeverity,
174 VkDebugUtilsMessageTypeFlagsEXT aType,
175 const VkDebugUtilsMessengerCallbackDataEXT* aCallbackData,
176 void* aUserData );
177
178
179// Dynamically loaded Vulkan function pointers
181{
182 // Loader / instance level
183 PFN_vkGetInstanceProcAddr GetInstanceProcAddr = nullptr;
184 PFN_vkCreateInstance CreateInstance = nullptr;
185 PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties = nullptr;
186 PFN_vkEnumerateInstanceLayerProperties EnumerateInstanceLayerProperties = nullptr;
187 PFN_vkDestroyInstance DestroyInstance = nullptr;
188 PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices = nullptr;
189 PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties = nullptr;
190 PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties = nullptr;
191 PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2 = nullptr;
192 PFN_vkGetPhysicalDeviceFeatures2 GetPhysicalDeviceFeatures2 = nullptr;
193 PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties = nullptr;
194 PFN_vkCreateDevice CreateDevice = nullptr;
195 PFN_vkGetDeviceProcAddr GetDeviceProcAddr = nullptr;
196
197 // Device level
198 PFN_vkDestroyDevice DestroyDevice = nullptr;
199 PFN_vkGetDeviceQueue GetDeviceQueue = nullptr;
200 PFN_vkDeviceWaitIdle DeviceWaitIdle = nullptr;
201 PFN_vkCreateImageView CreateImageView = nullptr;
202 PFN_vkDestroyImageView DestroyImageView = nullptr;
203 PFN_vkCreateSampler CreateSampler = nullptr;
204 PFN_vkDestroySampler DestroySampler = nullptr;
205 PFN_vkCreateCommandPool CreateCommandPool = nullptr;
206 PFN_vkDestroyCommandPool DestroyCommandPool = nullptr;
207 PFN_vkAllocateCommandBuffers AllocateCommandBuffers = nullptr;
208 PFN_vkFreeCommandBuffers FreeCommandBuffers = nullptr;
209 PFN_vkBeginCommandBuffer BeginCommandBuffer = nullptr;
210 PFN_vkEndCommandBuffer EndCommandBuffer = nullptr;
211 PFN_vkCmdPipelineBarrier CmdPipelineBarrier = nullptr;
212 PFN_vkCmdCopyBuffer CmdCopyBuffer = nullptr;
213 PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage = nullptr;
214 PFN_vkQueueSubmit QueueSubmit = nullptr;
215 PFN_vkQueueWaitIdle QueueWaitIdle = nullptr;
216 PFN_vkCreateShaderModule CreateShaderModule = nullptr;
217 PFN_vkDestroyShaderModule DestroyShaderModule = nullptr;
218 PFN_vkCreatePipelineLayout CreatePipelineLayout = nullptr;
219 PFN_vkDestroyPipelineLayout DestroyPipelineLayout = nullptr;
220 PFN_vkCreatePipelineCache CreatePipelineCache = nullptr;
221 PFN_vkDestroyPipelineCache DestroyPipelineCache = nullptr;
222 PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines = nullptr;
223 PFN_vkCreateComputePipelines CreateComputePipelines = nullptr;
224 PFN_vkCmdDispatch CmdDispatch = nullptr;
225 PFN_vkDestroyPipeline DestroyPipeline = nullptr;
226 PFN_vkCreateRenderPass CreateRenderPass = nullptr;
227 PFN_vkDestroyRenderPass DestroyRenderPass = nullptr;
228 PFN_vkCreateFramebuffer CreateFramebuffer = nullptr;
229 PFN_vkDestroyFramebuffer DestroyFramebuffer = nullptr;
230 PFN_vkCmdBeginRenderPass CmdBeginRenderPass = nullptr;
231 PFN_vkCmdEndRenderPass CmdEndRenderPass = nullptr;
232 PFN_vkCmdClearColorImage CmdClearColorImage = nullptr;
233 PFN_vkCmdCopyImage CmdCopyImage = nullptr;
234 PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer = nullptr;
235 PFN_vkCmdSetViewport CmdSetViewport = nullptr;
236 PFN_vkCmdSetScissor CmdSetScissor = nullptr;
237 PFN_vkCmdBindPipeline CmdBindPipeline = nullptr;
238 PFN_vkCmdPushConstants CmdPushConstants = nullptr;
239 PFN_vkCmdBindVertexBuffers CmdBindVertexBuffers = nullptr;
240 PFN_vkCmdBindIndexBuffer CmdBindIndexBuffer = nullptr;
241 PFN_vkCmdDraw CmdDraw = nullptr;
242 PFN_vkCmdDrawIndexed CmdDrawIndexed = nullptr;
243 PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets = nullptr;
244 PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout = nullptr;
245 PFN_vkDestroyDescriptorSetLayout DestroyDescriptorSetLayout = nullptr;
246 PFN_vkCreateDescriptorPool CreateDescriptorPool = nullptr;
247 PFN_vkDestroyDescriptorPool DestroyDescriptorPool = nullptr;
248 PFN_vkAllocateDescriptorSets AllocateDescriptorSets = nullptr;
249 PFN_vkFreeDescriptorSets FreeDescriptorSets = nullptr;
250 PFN_vkUpdateDescriptorSets UpdateDescriptorSets = nullptr;
251 PFN_vkCreateFence CreateFence = nullptr;
252 PFN_vkDestroyFence DestroyFence = nullptr;
253 PFN_vkWaitForFences WaitForFences = nullptr;
254 PFN_vkResetFences ResetFences = nullptr;
255 PFN_vkAcquireNextImageKHR AcquireNextImageKHR = nullptr;
256 PFN_vkQueuePresentKHR QueuePresentKHR = nullptr;
257 PFN_vkWaitForPresentKHR WaitForPresentKHR = nullptr;
258 PFN_vkCreateSwapchainKHR CreateSwapchainKHR = nullptr;
259 PFN_vkDestroySwapchainKHR DestroySwapchainKHR = nullptr;
260 PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR = nullptr;
261 PFN_vkCreateSemaphore CreateSemaphore = nullptr;
262 PFN_vkDestroySemaphore DestroySemaphore = nullptr;
263
264 // Instance level surface functions
265 PFN_vkDestroySurfaceKHR DestroySurfaceKHR = nullptr;
266 PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR = nullptr;
267 PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr;
268 PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR = nullptr;
269 PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR = nullptr;
270
271#ifdef VK_USE_PLATFORM_XLIB_KHR
272 PFN_vkCreateXlibSurfaceKHR CreateXlibSurfaceKHR = nullptr;
273#endif
274
275#ifdef VK_USE_PLATFORM_XCB_KHR
276 PFN_vkCreateXcbSurfaceKHR CreateXcbSurfaceKHR = nullptr;
277#endif
278
279#ifdef VK_USE_PLATFORM_WAYLAND_KHR
280 PFN_vkCreateWaylandSurfaceKHR CreateWaylandSurfaceKHR = nullptr;
281#endif
282
283#ifdef VK_USE_PLATFORM_WIN32_KHR
284 PFN_vkCreateWin32SurfaceKHR CreateWin32SurfaceKHR = nullptr;
285#endif
286
287 PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT = nullptr;
288 PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT = nullptr;
289};
290
291
292// One attachment of a render pass made by RHI_VULKAN_DEVICE::makeRenderPass()
294{
295 VkFormat format;
296 VkAttachmentLoadOp loadOp;
297 VkAttachmentStoreOp storeOp;
298 VkImageLayout initialLayout;
299 VkImageLayout finalLayout;
300};
301
302
304{
305public:
306 RHI_VULKAN_DEVICE( bool enableValidation );
307 ~RHI_VULKAN_DEVICE() override;
308
309 const char* GetName() const override;
310 RHI_BACKEND GetBackend() const override;
311 uint32_t MaxTextureSize() const override { return m_maxTextureSize; }
312 bool IsInitialized() const override;
313
314 bool SupportsCompute() const override;
317 void DestroyComputePipeline( RHI_ComputePipelineHandle aHandle ) override;
319 RHI_COMPUTE_RESULT ExecuteCompute( const RHI_COMPUTE_JOB& aJob ) override;
320
322 bool RealizeBuffer( RHI_BufferHandle handle, const RHI_BUFFER_DESC& desc ) override;
323 void DestroyBuffer( RHI_BufferHandle handle ) override;
324 void UpdateBuffer( RHI_BufferHandle handle, const void* data,
325 size_t sizeBytes, size_t offset = 0 ) override;
326
328 bool RealizeTexture( RHI_TextureHandle handle, const RHI_TEXTURE_DESC& desc ) override;
329 void DestroyTexture( RHI_TextureHandle handle ) override;
330 void UploadTexture( RHI_TextureHandle handle, const void* data,
331 size_t sizeBytes ) override;
332
333 RHI_ShaderHandle CreateShader( const RHI_SHADER_DESC& desc ) override;
334 void DestroyShader( RHI_ShaderHandle handle ) override;
335
337 void DestroyPipeline( RHI_PipelineHandle handle ) override;
338
340 const RHI_BIND_GROUP_LAYOUT_DESC& desc ) override;
343 void DestroyBindGroup( RHI_BindGroupHandle handle ) override;
344 void BindGroup( uint32_t groupIndex, RHI_BindGroupHandle group ) override;
345
347 void DestroyRenderPass( RHI_RenderPassHandle handle ) override;
349 void DestroyFramebuffer( RHI_FramebufferHandle handle ) override;
352 uint32_t attachmentIndex = 0 ) const override;
353
354 bool BeginRenderPass( const RHI_RENDER_PASS_BEGIN_DESC& desc ) override;
355 bool EndRenderPass() override;
356
357 void SetViewport( const RHI_VIEWPORT& viewport ) override;
358 void SetScissor( const RHI_SCISSOR_RECT& scissor ) override;
359 void BindPipeline( RHI_PipelineHandle pipeline ) override;
360 void SetPushConstants( const void* data, size_t sizeBytes ) override;
361 void BindVertexBuffer( uint32_t slot, RHI_BufferHandle buffer ) override;
362 void BindIndexBuffer( RHI_BufferHandle buffer ) override;
363
364 void Draw( uint32_t vertexCount, uint32_t firstVertex = 0 ) override;
365 void DrawInstanced( uint32_t indexCount, uint32_t instanceCount,
366 uint32_t firstIndex = 0, int32_t vertexOffset = 0,
367 uint32_t firstInstance = 0 ) override;
368 void DrawIndexedInstanced( uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex = 0,
369 int32_t vertexOffset = 0, uint32_t firstInstance = 0 ) override;
370
371 RHI_SurfaceHandle CreateSurface( const RHI_SURFACE_DESC& desc ) override;
372 void DestroySurface( RHI_SurfaceHandle handle ) override;
374 bool ResizeSwapchain( RHI_SwapchainHandle handle, uint32_t width,
375 uint32_t height, float dpiScale ) override;
376 void DestroySwapchain( RHI_SwapchainHandle handle ) override;
377 bool WaitForPresentSlot( RHI_SwapchainHandle aSwapchain, std::chrono::milliseconds aTimeout,
378 int aAllowed ) override;
380 bool PresentWaitsForDisplay( RHI_SwapchainHandle aSwapchain ) const override;
381 bool PresentReplacesQueued( RHI_SwapchainHandle aSwapchain ) const override;
383 const RHI_SCISSOR_RECT* aSourceRect = nullptr ) override;
384
386 uint32_t attachmentIndex,
387 void* buffer, size_t bufferSize ) override;
389 uint32_t aSourceX = 0, uint32_t aSourceY = 0 ) override;
390
391 void BeginUploadBatch() override;
392 void EndUploadBatch() override;
393
394private:
395 bool InitLoader();
396 bool CreateVkInstance( bool enableValidation );
398 bool CreateLogicalDevice();
399 void LoadDeviceFunctions();
400 bool InitVMA();
401 bool InitCommandPool();
403
404 void* LoadVulkanFunction( const char* name );
405 void* LoadInstanceFunction( const char* name );
406
407 // A single-subpass render pass drawing into aColor and, when given, aDepth
408 VkRenderPass makeRenderPass( const std::vector<VK_ATTACHMENT>& aColor,
409 const std::optional<VK_ATTACHMENT>& aDepth );
410 void* LoadDeviceFunction( const char* name );
411
412 static VkBufferUsageFlags TranslateBufferUsage( RHI_BUFFER_TYPE type );
413 static VkFormat TranslateTextureFormat( RHI_TEXTURE_FORMAT format );
414 static VkFilter TranslateFilter( RHI_FILTER filter );
415
416 // Every command buffer of the device pool goes through these steps under the device mutex
417 bool allocateCommandBuffer( VkCommandBuffer& aCmd );
418 VkResult beginOneTimeCommands( VkCommandBuffer aCmd );
419 VkResult submitCommands( VkCommandBuffer aCmd );
420
421 // The batch's command buffer on the thread that owns the batch, otherwise a one-shot buffer under aLock
422 VkCommandBuffer beginUploadCommands( std::unique_lock<std::recursive_mutex>& aLock );
423
424 // Returns true once the copy has run and the caller's staging memory is free again. A batch
425 // holds that memory until EndUploadBatch().
426 bool endUploadCommands( VkCommandBuffer aCmd, const std::unique_lock<std::recursive_mutex>& aLock );
427
428 // Returns true once the commands have finished on the GPU
429 bool SubmitOneShot( VkCommandBuffer cmd );
430 VkCommandBuffer EnsureUploadCmdBuf();
431 bool CurrentThreadOwnsUploadBatch() const;
432
434 {
436 VmaAllocation allocation = VK_NULL_HANDLE;
437 void* mappedPtr = nullptr;
438 size_t size = 0;
439 bool mappable = false;
441
442 // Serial of the last submitted pass that reads this buffer, see SyncSubmittedPasses()
443 uint64_t lastUseSerial = 0;
444 };
445
447 {
449 VmaAllocation allocation = VK_NULL_HANDLE;
452 uint32_t width = 0;
453 uint32_t height = 0;
455 bool ownedByFBO = false;
456 VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED;
457 };
458
459 wxDynamicLibrary m_vulkanLib;
461 VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE;
465 uint32_t m_graphicsQueueFamily = UINT32_MAX;
468
469 // Queues and command pools need external synchronization, and the main and render threads share this device.
470 // Recursive because EndUploadBatch() holds it while calling SubmitOneShot().
471 std::recursive_mutex m_deviceMutex;
472
475
476 // Every Vulkan device takes 2D images at least this large
477 uint32_t m_maxTextureSize = 4096;
478
479 VkPhysicalDeviceLimits m_deviceLimits{};
480 bool m_computeQueue = false;
483
485 std::string m_deviceName;
486 bool m_initialized = false;
487
488 // Tracked by thread id so main-thread uploads take the locked one-shot path while the render
489 // thread is inside its own batch
490 std::atomic<std::thread::id> m_uploadBatchOwner{ std::thread::id{} };
492
493 // Kept between uploads because mapping fresh pages for the tens of megabytes a rebuilt scene
494 // uploads costs more than the copy
496 {
498 VmaAllocation allocation = VK_NULL_HANDLE;
499 void* mapped = nullptr;
500 size_t size = 0;
501 bool inUse = false;
502 };
503
504 static constexpr size_t STAGING_MIN_BYTES = 64 * 1024;
505 static constexpr size_t STAGING_POOL_BYTES = 256 * 1024 * 1024;
506
507 std::vector<StagingBuffer> m_stagingPool;
508 std::vector<size_t> m_batchPooledStaging;
509
510 int AcquireStaging( size_t aSizeBytes );
511 void ReleaseStaging( int aIndex );
512
513 // Copy aData into a pooled staging buffer and return its index, or -1. aBuffer receives the buffer.
514 int stageData( const void* aData, size_t aSizeBytes, VkBuffer& aBuffer );
515
516 // End upload commands that read a staging buffer, which is released once they are submitted
517 void endStagedUpload( VkCommandBuffer aCmd, std::unique_lock<std::recursive_mutex>& aLock, int aStaging );
518 void DestroyStagingPool();
519
521 std::unordered_map<uint32_t, BufferAllocation> m_buffers;
522
524 std::unordered_map<uint32_t, TextureAllocation> m_textures;
525
527 {
528 VkShaderModule module = VK_NULL_HANDLE;
529 };
530
532 std::unordered_map<uint32_t, ShaderAllocation> m_shaders;
533
535 {
537 VkPipelineLayout layout = VK_NULL_HANDLE;
539 std::vector<VkDescriptorSetLayout> dsLayouts;
540 };
541
543 std::unordered_map<uint32_t, PipelineAllocation> m_pipelines;
544
546 {
548 RHI_COMPUTE_PIPELINE_DESC desc; // shader blob cleared, kept for job validation
549 };
550
552 std::unordered_map<uint32_t, ComputePipelineAllocation> m_computePipelines;
553
559
561 std::unordered_map<uint32_t, BindGroupLayoutAllocation> m_bindGroupLayouts;
562
564 {
565 VkDescriptorPool pool = VK_NULL_HANDLE;
566 uint32_t allocatedSets = 0;
567 uint32_t maxSets = 128;
568 };
569
570 std::vector<DescriptorPoolEntry> m_descriptorPools;
571
572 VkDescriptorPool AcquireDescriptorPool();
573 void ReleaseDescriptorSet( VkDescriptorPool pool,
574 VkDescriptorSet dset = VK_NULL_HANDLE );
575 void TransitionTexture( VkCommandBuffer cmd, TextureAllocation& tex,
576 VkImageLayout newLayout,
577 VkPipelineStageFlags srcStage,
578 VkPipelineStageFlags dstStage );
579
581 {
582 VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
583 VkDescriptorPool pool = VK_NULL_HANDLE;
585
586 // Pool indices of the buffers this group binds, marked in use when the group is bound
587 std::vector<uint32_t> bufferIndices;
588 };
589
591 std::unordered_map<uint32_t, BindGroupAllocation> m_bindGroups;
592
594 {
596 std::vector<RHI_COLOR_ATTACHMENT_DESC> colorDescs;
598 };
599
601 std::unordered_map<uint32_t, RenderPassAllocation> m_renderPasses;
602
604 {
607 std::vector<VkImage> colorImages;
608 std::vector<VmaAllocation> colorAllocations;
609 std::vector<VkImageView> colorViews;
613 bool depthRendered = false; // left in attachment layout
614 std::vector<RHI_TEXTURE_FORMAT> colorFormats;
615 std::vector<RHI_TextureHandle> colorTextureHandles;
616 uint32_t width = 0;
617 uint32_t height = 0;
618 };
619
621 std::unordered_map<uint32_t, FramebufferAllocation> m_framebuffers;
622
623 // Destroy a framebuffer's Vulkan objects and images, leaving its texture handles alone
625 void destroyTexture( TextureAllocation& aTexture );
626
627 // A single-level 2D image in device memory. Returns false and makes nothing on failure.
628 bool createImage2D( VkFormat aFormat, uint32_t aWidth, uint32_t aHeight, VkImageUsageFlags aUsage,
629 bool aDedicated, VkImage& aImage, VmaAllocation& aAllocation );
630
631 // A view of all of a 2D image, or VK_NULL_HANDLE
632 VkImageView createView( VkImage aImage, VkFormat aFormat, VkImageAspectFlags aAspect );
633
634 // A sampler clamping to the edge, or VK_NULL_HANDLE
635 VkSampler createSampler( VkFilter aFilter );
636
637 // A pass's command buffer, reused when one has come back from the queue
638 bool acquirePassCmd( VkCommandBuffer& aCmd );
639
640 // Submit a pass's ended command buffer without waiting. A failed submit returns it for reuse.
641 VkResult submitPassCmd( VkCommandBuffer aCmd );
642
643 // The descriptor set layout of a bind group layout, or VK_NULL_HANDLE
644 VkDescriptorSetLayout createSetLayout( const RHI_BIND_GROUP_LAYOUT_DESC& aDesc );
645
646 // A graphics pipeline with one viewport, no culling and no multisampling, the rest as given
647 VkResult buildPipeline( VkShaderModule aVertex, VkShaderModule aFragment,
648 const VkPipelineVertexInputStateCreateInfo& aVertexInput,
649 VkPrimitiveTopology aTopology,
650 const std::vector<VkPipelineColorBlendAttachmentState>& aBlend,
651 const VkPipelineDepthStencilStateCreateInfo& aDepthStencil,
652 const std::vector<VkDynamicState>& aDynamicStates, VkPipelineLayout aLayout,
653 VkRenderPass aRenderPass, VkPipeline& aPipeline );
654 void destroyPipeline( PipelineAllocation& aPipeline );
655
657 {
658 VkSurfaceKHR surface = VK_NULL_HANDLE;
659 bool wayland = false;
660#ifdef VK_USE_PLATFORM_XLIB_KHR
661 X11_CONNECTION_OWNER x11Owner;
662#endif
663 };
664
666 std::unordered_map<uint32_t, SurfaceAllocation> m_surfaces;
667
669 {
670 VkSwapchainKHR swapchain = VK_NULL_HANDLE;
672 VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
673 VkColorSpaceKHR colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
674 VkExtent2D extent = { 0, 0 };
675 VkExtent2D requestedExtent = { 0, 0 };
676 float dpiScale = 1.0f;
677 VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
678 std::vector<VkImage> images;
679 std::vector<VkImageView> imageViews;
682
683 // One per image because the fence does not show that a present has stopped waiting on its semaphore
684 std::vector<VkSemaphore> renderFinished;
685 uint64_t lastPresentId = 0; // id of the last present, see WaitForPresentSlot
686 uint32_t currentImageIndex = 0;
688 bool needsResize = false;
690 std::chrono::steady_clock::time_point nextSurfaceExtentCheck{};
691 VkExtent2D lastLoggedExtent = { 0, 0 };
693
694 // Objects of the present blit
696 std::vector<VkFramebuffer> presentFramebuffers;
697 VkDescriptorPool presentDescPool = VK_NULL_HANDLE;
699 };
700
702 std::unordered_map<uint32_t, SwapchainAllocation> m_swapchains;
703
704 void ExecuteDeferredReleases() override;
706
707 bool FindPresentQueueFamily( VkSurfaceKHR vkSurface, uint32_t& outFamily );
708 VkPresentModeKHR ChoosePresentMode( const SurfaceAllocation& aSurface );
710 void DestroySwapchainResources( SwapchainAllocation& swa, bool aKeepSwapchain = false );
711
712 // Destroy everything a swapchain owns, the sync objects included
714
716 {
717 VkSwapchainKHR swapchain = VK_NULL_HANDLE;
718 VkResult result = VK_ERROR_INITIALIZATION_FAILED;
719 uint32_t imageCount = 0; // the count first asked for
720 VkPresentModeKHR preferredMode = VK_PRESENT_MODE_FIFO_KHR;
721 VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; // the mode that succeeded
722 };
723
724 // Create the VkSwapchainKHR of a window surface, retrying with FIFO and then with the minimum
725 // image count when the driver refuses what was asked for
726 VkSwapchainTry createVkSwapchain( const SurfaceAllocation& aSurface, const VkSurfaceCapabilitiesKHR& aCaps,
727 VkSurfaceFormatKHR aFormat, VkExtent2D aExtent, VkSwapchainKHR aOld,
728 bool aResize );
729
730
731 // The present draws the compositor output into the swapchain image instead of using a transfer copy
732 // Kept per format until device teardown since another swapchain's submitted frame may still use one
733 std::unordered_map<VkFormat, VkPipeline> m_presentBlitPipelines;
735 VkDescriptorSetLayout m_presentBlitDSLayout = VK_NULL_HANDLE;
736
737 // Returns VK_NULL_HANDLE when the pipeline cannot be built
738 VkPipeline EnsurePresentBlit( VkFormat aSwapchainFormat );
739 void DestroyPresentBlit();
740
741 // Active render pass state
742 VkCommandBuffer m_currentCmd = VK_NULL_HANDLE;
743
744 // Passes are submitted without waiting, so their command buffers are held here until the queue is
745 // next known idle
746 std::vector<VkCommandBuffer> m_submittedCmds;
747 std::vector<VkCommandBuffer> m_freeCmds;
748 uint64_t m_submitSerial = 0;
749 uint64_t m_syncedSerial = 0;
750
752 void OnQueueIdle();
753 void SyncSubmittedPasses();
754 bool m_renderPassActive = false;
755 bool m_presentWait = false; // presents carry ids that can be waited for
757};
758
759
760// ---- Dynamic loader ----
761
763{
764 if( !m_vulkanLib.IsLoaded() )
765 return nullptr;
766
767 return m_vulkanLib.RawGetSymbol( wxString::FromAscii( name ) );
768}
769
770
772{
773 if( m_vk.GetInstanceProcAddr && m_instance )
774 return reinterpret_cast<void*>( m_vk.GetInstanceProcAddr( m_instance, name ) );
775
776 return nullptr;
777}
778
779
781{
782 if( m_vk.GetDeviceProcAddr && m_device )
783 return reinterpret_cast<void*>( m_vk.GetDeviceProcAddr( m_device, name ) );
784
785 return nullptr;
786}
787
788
790{
791 // Quiet, as a missing loader only means this canvas is unavailable
792 const int flags = wxDL_VERBATIM | wxDL_LAZY | wxDL_QUIET;
793
794#ifdef _WIN32
795 m_vulkanLib.Load( wxS( "vulkan-1.dll" ), flags );
796#else
797 if( !m_vulkanLib.Load( wxS( "libvulkan.so.1" ), flags ) )
798 m_vulkanLib.Load( wxS( "libvulkan.so" ), flags );
799#endif
800
801 m_vk.GetInstanceProcAddr =
802 reinterpret_cast<PFN_vkGetInstanceProcAddr>( LoadVulkanFunction( "vkGetInstanceProcAddr" ) );
803
804 if( !m_vk.GetInstanceProcAddr )
805 return false;
806
807 auto loadGlobal = [this]( const char* aName )
808 {
809 return reinterpret_cast<void*>( m_vk.GetInstanceProcAddr( VK_NULL_HANDLE, aName ) );
810 };
811
812 m_vk.CreateInstance = reinterpret_cast<PFN_vkCreateInstance>( loadGlobal( "vkCreateInstance" ) );
813 m_vk.EnumerateInstanceLayerProperties = reinterpret_cast<PFN_vkEnumerateInstanceLayerProperties>(
814 loadGlobal( "vkEnumerateInstanceLayerProperties" ) );
815 m_vk.EnumerateInstanceExtensionProperties = reinterpret_cast<PFN_vkEnumerateInstanceExtensionProperties>(
816 loadGlobal( "vkEnumerateInstanceExtensionProperties" ) );
817
818 return m_vk.CreateInstance != nullptr;
819}
820
821
822bool RHI_VULKAN_DEVICE::CreateVkInstance( bool enableValidation )
823{
824 VkApplicationInfo appInfo{};
825 appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
826 appInfo.pApplicationName = "KiCad";
827 appInfo.applicationVersion = VK_MAKE_VERSION( 1, 0, 0 );
828 appInfo.pEngineName = "KiCad RHI";
829 appInfo.engineVersion = VK_MAKE_VERSION( 1, 0, 0 );
830 appInfo.apiVersion = VK_API_VERSION_1_1;
831
832 std::vector<const char*> layers;
833
834 if( enableValidation && m_vk.EnumerateInstanceLayerProperties )
835 {
836 uint32_t layerCount = 0;
837 m_vk.EnumerateInstanceLayerProperties( &layerCount, nullptr );
838 std::vector<VkLayerProperties> available( layerCount );
839 m_vk.EnumerateInstanceLayerProperties( &layerCount, available.data() );
840
841 for( const auto& layer : available )
842 {
843 if( std::strcmp( layer.layerName, "VK_LAYER_KHRONOS_validation" ) == 0 )
844 {
845 layers.push_back( "VK_LAYER_KHRONOS_validation" );
846 break;
847 }
848 }
849 }
850
851 if( enableValidation && layers.empty() )
852 {
853 wxLogTrace( traceRhi, wxS( "VulkanDevice: validation requested but "
854 "VK_LAYER_KHRONOS_validation not available" ) );
855 }
856
857 std::vector<const char*> extensions;
858 extensions.push_back( VK_KHR_SURFACE_EXTENSION_NAME );
859
860#ifdef VK_USE_PLATFORM_XLIB_KHR
861 extensions.push_back( VK_KHR_XLIB_SURFACE_EXTENSION_NAME );
862#endif
863
864#ifdef VK_USE_PLATFORM_XCB_KHR
865 extensions.push_back( VK_KHR_XCB_SURFACE_EXTENSION_NAME );
866#endif
867
868#ifdef VK_USE_PLATFORM_WAYLAND_KHR
869 extensions.push_back( VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME );
870#endif
871
872#ifdef VK_USE_PLATFORM_WIN32_KHR
873 extensions.push_back( VK_KHR_WIN32_SURFACE_EXTENSION_NAME );
874#endif
875
876 bool debugUtilsAvailable = false;
877
878 if( enableValidation && m_vk.EnumerateInstanceExtensionProperties )
879 {
880 uint32_t extCount = 0;
881 m_vk.EnumerateInstanceExtensionProperties( nullptr, &extCount, nullptr );
882 std::vector<VkExtensionProperties> availExts( extCount );
883 m_vk.EnumerateInstanceExtensionProperties( nullptr, &extCount, availExts.data() );
884
885 for( const auto& ext : availExts )
886 {
887 if( std::strcmp( ext.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME ) == 0 )
888 {
889 extensions.push_back( VK_EXT_DEBUG_UTILS_EXTENSION_NAME );
890 debugUtilsAvailable = true;
891 break;
892 }
893 }
894 }
895
896 if( enableValidation && !debugUtilsAvailable )
897 {
898 wxLogTrace( traceRhi, wxS( "VulkanDevice: validation requested but "
899 "VK_EXT_debug_utils not available" ) );
900 }
901
902 for( const auto& ext : extensions )
903 wxLogTrace( traceRhi, wxS( "VulkanDevice: requesting instance extension %s" ), ext );
904
905 for( const auto& layer : layers )
906 wxLogTrace( traceRhi, wxS( "VulkanDevice: requesting layer %s" ), layer );
907
908 VkInstanceCreateInfo createInfo{};
909 createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
910 createInfo.pApplicationInfo = &appInfo;
911 createInfo.enabledLayerCount = static_cast<uint32_t>( layers.size() );
912 createInfo.ppEnabledLayerNames = layers.empty() ? nullptr : layers.data();
913 createInfo.enabledExtensionCount = static_cast<uint32_t>( extensions.size() );
914 createInfo.ppEnabledExtensionNames = extensions.data();
915
916 VkResult result = m_vk.CreateInstance( &createInfo, nullptr, &m_instance );
917
918 if( result != VK_SUCCESS )
919 {
920 wxLogTrace( traceRhi, wxS( "VulkanDevice: vkCreateInstance failed (%d)" ),
921 static_cast<int>( result ) );
922 return false;
923 }
924
925#define LOAD_INST_FN( fn ) \
926 m_vk.fn = reinterpret_cast<PFN_vk##fn>( LoadInstanceFunction( "vk" #fn ) )
927
928 LOAD_INST_FN( DestroyInstance );
929 LOAD_INST_FN( EnumeratePhysicalDevices );
930 LOAD_INST_FN( EnumerateDeviceExtensionProperties );
931 LOAD_INST_FN( GetPhysicalDeviceProperties );
932 LOAD_INST_FN( GetPhysicalDeviceProperties2 );
933 LOAD_INST_FN( GetPhysicalDeviceFeatures2 );
934 LOAD_INST_FN( GetPhysicalDeviceQueueFamilyProperties );
935 LOAD_INST_FN( CreateDevice );
936 LOAD_INST_FN( GetDeviceProcAddr );
937 LOAD_INST_FN( DestroySurfaceKHR );
938 LOAD_INST_FN( GetPhysicalDeviceSurfaceSupportKHR );
939 LOAD_INST_FN( GetPhysicalDeviceSurfaceCapabilitiesKHR );
940 LOAD_INST_FN( GetPhysicalDeviceSurfaceFormatsKHR );
941 LOAD_INST_FN( GetPhysicalDeviceSurfacePresentModesKHR );
942
943#ifdef VK_USE_PLATFORM_XLIB_KHR
944 LOAD_INST_FN( CreateXlibSurfaceKHR );
945#endif
946
947#ifdef VK_USE_PLATFORM_XCB_KHR
948 LOAD_INST_FN( CreateXcbSurfaceKHR );
949#endif
950
951#ifdef VK_USE_PLATFORM_WAYLAND_KHR
952 LOAD_INST_FN( CreateWaylandSurfaceKHR );
953#endif
954
955#ifdef VK_USE_PLATFORM_WIN32_KHR
956 LOAD_INST_FN( CreateWin32SurfaceKHR );
957#endif
958
959 if( debugUtilsAvailable )
960 {
961 LOAD_INST_FN( CreateDebugUtilsMessengerEXT );
962 LOAD_INST_FN( DestroyDebugUtilsMessengerEXT );
963
964 if( m_vk.CreateDebugUtilsMessengerEXT )
965 {
966 VkDebugUtilsMessengerCreateInfoEXT dbgInfo{};
967 dbgInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
968 dbgInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
969 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
970 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
971 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
972 dbgInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
973 | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
974 | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
975 dbgInfo.pfnUserCallback = VulkanDebugCallback;
976
977 VkResult dbgResult = m_vk.CreateDebugUtilsMessengerEXT(
978 m_instance, &dbgInfo, nullptr, &m_debugMessenger );
979
980 wxLogTrace( traceRhi, wxS( "VulkanDevice: debug messenger %s" ),
981 dbgResult == VK_SUCCESS ? "created" : "FAILED" );
982 }
983 }
984
985#undef LOAD_INST_FN
986
987 return true;
988}
989
990
992{
993 if( !m_vk.EnumeratePhysicalDevices )
994 return false;
995
996 uint32_t deviceCount = 0;
997 m_vk.EnumeratePhysicalDevices( m_instance, &deviceCount, nullptr );
998
999 if( deviceCount == 0 )
1000 return false;
1001
1002 std::vector<VkPhysicalDevice> devices( deviceCount );
1003 m_vk.EnumeratePhysicalDevices( m_instance, &deviceCount, devices.data() );
1004
1005 // Prefer discrete GPU, fall back to integrated, then any
1006 VkPhysicalDevice discrete = VK_NULL_HANDLE;
1007 VkPhysicalDevice integrated = VK_NULL_HANDLE;
1008
1009 for( VkPhysicalDevice dev : devices )
1010 {
1011 VkPhysicalDeviceProperties props;
1012 m_vk.GetPhysicalDeviceProperties( dev, &props );
1013
1014 uint32_t queueFamilyCount = 0;
1015 m_vk.GetPhysicalDeviceQueueFamilyProperties( dev, &queueFamilyCount, nullptr );
1016 std::vector<VkQueueFamilyProperties> queueFamilies( queueFamilyCount );
1017 m_vk.GetPhysicalDeviceQueueFamilyProperties( dev, &queueFamilyCount,
1018 queueFamilies.data() );
1019
1020 bool hasGraphics = false;
1021
1022 for( const auto& qf : queueFamilies )
1023 {
1024 if( qf.queueFlags & VK_QUEUE_GRAPHICS_BIT )
1025 {
1026 hasGraphics = true;
1027 break;
1028 }
1029 }
1030
1031 if( !hasGraphics )
1032 continue;
1033
1034 if( props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && !discrete )
1035 discrete = dev;
1036 else if( props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU && !integrated )
1037 integrated = dev;
1038 }
1039
1040 if( discrete )
1041 m_physicalDevice = discrete;
1042 else if( integrated )
1043 m_physicalDevice = integrated;
1044 else
1045 m_physicalDevice = devices[0];
1046
1048 {
1049 VkPhysicalDeviceProperties props;
1050 m_vk.GetPhysicalDeviceProperties( m_physicalDevice, &props );
1051 wxLogTrace( traceRhi, wxS( "VulkanDevice: selected GPU '%s' (type=%d) from %u device(s)" ),
1052 props.deviceName, static_cast<int>( props.deviceType ), deviceCount );
1053 }
1054
1056}
1057
1058
1060{
1061 if( !m_vk.CreateDevice || !m_physicalDevice )
1062 return false;
1063
1064 uint32_t queueFamilyCount = 0;
1065 m_vk.GetPhysicalDeviceQueueFamilyProperties( m_physicalDevice, &queueFamilyCount, nullptr );
1066 std::vector<VkQueueFamilyProperties> queueFamilies( queueFamilyCount );
1067 m_vk.GetPhysicalDeviceQueueFamilyProperties( m_physicalDevice, &queueFamilyCount,
1068 queueFamilies.data() );
1069
1070 m_graphicsQueueFamily = UINT32_MAX;
1071
1072 for( uint32_t i = 0; i < queueFamilyCount; i++ )
1073 {
1074 if( queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT )
1075 {
1077 break;
1078 }
1079 }
1080
1081 if( m_graphicsQueueFamily == UINT32_MAX )
1082 return false;
1083
1084 m_computeQueue = ( queueFamilies[m_graphicsQueueFamily].queueFlags & VK_QUEUE_COMPUTE_BIT ) != 0;
1085
1086 float queuePriority = 1.0f;
1087 VkDeviceQueueCreateInfo queueInfo{};
1088 queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
1089 queueInfo.queueFamilyIndex = m_graphicsQueueFamily;
1090 queueInfo.queueCount = 1;
1091 queueInfo.pQueuePriorities = &queuePriority;
1092
1093 VkPhysicalDeviceFeatures features{};
1094
1095 std::vector<const char*> deviceExtensions;
1096
1097 // Requesting an unadvertised extension fails device creation, which would rule out offscreen
1098 // rendering on headless or software Vulkan without WSI
1099 bool swapchainSupported = true; // assume supported when enumeration is unavailable
1100
1101 std::vector<VkExtensionProperties> avail;
1102
1103 auto has = [&]( const char* aName )
1104 {
1105 return std::any_of( avail.begin(), avail.end(),
1106 [&]( const VkExtensionProperties& e )
1107 {
1108 return std::strcmp( e.extensionName, aName ) == 0;
1109 } );
1110 };
1111
1112 if( m_vk.EnumerateDeviceExtensionProperties )
1113 {
1114 uint32_t extCount = 0;
1115 m_vk.EnumerateDeviceExtensionProperties( m_physicalDevice, nullptr, &extCount, nullptr );
1116
1117 avail.resize( extCount );
1118 m_vk.EnumerateDeviceExtensionProperties( m_physicalDevice, nullptr, &extCount, avail.data() );
1119
1120 swapchainSupported = has( VK_KHR_SWAPCHAIN_EXTENSION_NAME );
1121 }
1122
1123 if( swapchainSupported )
1124 deviceExtensions.push_back( VK_KHR_SWAPCHAIN_EXTENSION_NAME );
1125 else
1126 wxLogTrace( traceRhi, wxS( "VulkanDevice: swapchain extension unsupported; offscreen only" ) );
1127
1128 // Waiting for a present to reach the display needs present ids
1129 VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures{};
1130 VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures{};
1131
1132 presentIdFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
1133 presentWaitFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR;
1134 presentIdFeatures.pNext = &presentWaitFeatures;
1135
1136 if( swapchainSupported && m_vk.GetPhysicalDeviceFeatures2 && has( VK_KHR_PRESENT_ID_EXTENSION_NAME )
1137 && has( VK_KHR_PRESENT_WAIT_EXTENSION_NAME ) )
1138 {
1139 VkPhysicalDeviceFeatures2 query{};
1140 query.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
1141 query.pNext = &presentIdFeatures;
1142 m_vk.GetPhysicalDeviceFeatures2( m_physicalDevice, &query );
1143
1144 m_presentWait = presentIdFeatures.presentId && presentWaitFeatures.presentWait;
1145 }
1146
1147 if( m_presentWait )
1148 {
1149 deviceExtensions.push_back( VK_KHR_PRESENT_ID_EXTENSION_NAME );
1150 deviceExtensions.push_back( VK_KHR_PRESENT_WAIT_EXTENSION_NAME );
1151 }
1152
1153 wxLogTrace( traceRhi, wxS( "VulkanDevice: present wait %s" ), m_presentWait ? "on" : "off (fence)" );
1154
1155 VkDeviceCreateInfo deviceInfo{};
1156 deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
1157 deviceInfo.pNext = m_presentWait ? &presentIdFeatures : nullptr;
1158 deviceInfo.queueCreateInfoCount = 1;
1159 deviceInfo.pQueueCreateInfos = &queueInfo;
1160 deviceInfo.pEnabledFeatures = &features;
1161 deviceInfo.enabledExtensionCount = static_cast<uint32_t>( deviceExtensions.size() );
1162 deviceInfo.ppEnabledExtensionNames = deviceExtensions.data();
1163
1164 VkResult result = m_vk.CreateDevice( m_physicalDevice, &deviceInfo, nullptr, &m_device );
1165
1166 if( result != VK_SUCCESS )
1167 return false;
1168
1170
1171 if( m_vk.GetDeviceQueue )
1172 m_vk.GetDeviceQueue( m_device, m_graphicsQueueFamily, 0, &m_graphicsQueue );
1173
1174 return true;
1175}
1176
1177
1179{
1180#define LOAD_DEV_FN( fn ) \
1181 m_vk.fn = reinterpret_cast<PFN_vk##fn>( LoadDeviceFunction( "vk" #fn ) )
1182
1183 LOAD_DEV_FN( DestroyDevice );
1184 LOAD_DEV_FN( GetDeviceQueue );
1185 LOAD_DEV_FN( DeviceWaitIdle );
1186 LOAD_DEV_FN( CreateImageView );
1187 LOAD_DEV_FN( DestroyImageView );
1188 LOAD_DEV_FN( CreateSampler );
1189 LOAD_DEV_FN( DestroySampler );
1190 LOAD_DEV_FN( CreateCommandPool );
1191 LOAD_DEV_FN( DestroyCommandPool );
1192 LOAD_DEV_FN( AllocateCommandBuffers );
1193 LOAD_DEV_FN( FreeCommandBuffers );
1194 LOAD_DEV_FN( BeginCommandBuffer );
1195 LOAD_DEV_FN( EndCommandBuffer );
1196 LOAD_DEV_FN( CmdPipelineBarrier );
1197 LOAD_DEV_FN( CmdCopyBuffer );
1198 LOAD_DEV_FN( CmdCopyBufferToImage );
1199 LOAD_DEV_FN( QueueSubmit );
1200 LOAD_DEV_FN( QueueWaitIdle );
1201 LOAD_DEV_FN( CreateShaderModule );
1202 LOAD_DEV_FN( DestroyShaderModule );
1203 LOAD_DEV_FN( CreatePipelineLayout );
1204 LOAD_DEV_FN( DestroyPipelineLayout );
1205 LOAD_DEV_FN( CreatePipelineCache );
1206 LOAD_DEV_FN( DestroyPipelineCache );
1207 LOAD_DEV_FN( CreateGraphicsPipelines );
1208 LOAD_DEV_FN( CreateComputePipelines );
1209 LOAD_DEV_FN( CmdDispatch );
1215 LOAD_DEV_FN( CmdBeginRenderPass );
1216 LOAD_DEV_FN( CmdEndRenderPass );
1217 LOAD_DEV_FN( CmdClearColorImage );
1218 LOAD_DEV_FN( CmdCopyImage );
1219 LOAD_DEV_FN( CmdCopyImageToBuffer );
1220 LOAD_DEV_FN( CmdSetViewport );
1221 LOAD_DEV_FN( CmdSetScissor );
1222 LOAD_DEV_FN( CmdBindPipeline );
1223 LOAD_DEV_FN( CmdPushConstants );
1224 LOAD_DEV_FN( CmdBindVertexBuffers );
1225 LOAD_DEV_FN( CmdBindIndexBuffer );
1226 LOAD_DEV_FN( CmdDraw );
1227 LOAD_DEV_FN( CmdDrawIndexed );
1228 LOAD_DEV_FN( CmdBindDescriptorSets );
1229 LOAD_DEV_FN( CreateDescriptorSetLayout );
1230 LOAD_DEV_FN( DestroyDescriptorSetLayout );
1231 LOAD_DEV_FN( CreateDescriptorPool );
1232 LOAD_DEV_FN( DestroyDescriptorPool );
1233 LOAD_DEV_FN( AllocateDescriptorSets );
1234 LOAD_DEV_FN( FreeDescriptorSets );
1235 LOAD_DEV_FN( UpdateDescriptorSets );
1236 LOAD_DEV_FN( CreateFence );
1237 LOAD_DEV_FN( DestroyFence );
1238 LOAD_DEV_FN( WaitForFences );
1239 LOAD_DEV_FN( ResetFences );
1240 LOAD_DEV_FN( AcquireNextImageKHR );
1241 LOAD_DEV_FN( QueuePresentKHR );
1242
1243 if( m_presentWait )
1244 {
1245 LOAD_DEV_FN( WaitForPresentKHR );
1246 m_presentWait = m_vk.WaitForPresentKHR != nullptr;
1247 }
1248 LOAD_DEV_FN( CreateSwapchainKHR );
1249 LOAD_DEV_FN( DestroySwapchainKHR );
1250 LOAD_DEV_FN( GetSwapchainImagesKHR );
1251 LOAD_DEV_FN( CreateSemaphore );
1252 LOAD_DEV_FN( DestroySemaphore );
1253
1254#undef LOAD_DEV_FN
1255}
1256
1257
1259{
1260 VkPhysicalDeviceProperties props;
1261 m_vk.GetPhysicalDeviceProperties( m_physicalDevice, &props );
1262
1263 m_deviceName = std::string( "Vulkan (" ) + props.deviceName + ")";
1264 m_maxTextureSize = props.limits.maxImageDimension2D;
1265 m_deviceLimits = props.limits;
1266
1267 if( m_vk.GetPhysicalDeviceProperties2 )
1268 {
1269 VkPhysicalDeviceSubgroupProperties subgroup{};
1270 subgroup.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
1271 VkPhysicalDeviceProperties2 properties{};
1272 properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
1273 properties.pNext = &subgroup;
1274 m_vk.GetPhysicalDeviceProperties2( m_physicalDevice, &properties );
1275
1276 if( subgroup.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT )
1277 m_computeSubgroupSize = subgroup.subgroupSize;
1278 }
1279}
1280
1281
1283{
1284 VmaVulkanFunctions vmaFuncs{};
1285 vmaFuncs.vkGetInstanceProcAddr = m_vk.GetInstanceProcAddr;
1286 vmaFuncs.vkGetDeviceProcAddr = m_vk.GetDeviceProcAddr;
1287
1288 VmaAllocatorCreateInfo allocatorInfo{};
1289 allocatorInfo.vulkanApiVersion = VK_API_VERSION_1_1;
1290 allocatorInfo.physicalDevice = m_physicalDevice;
1291 allocatorInfo.device = m_device;
1292 allocatorInfo.instance = m_instance;
1293 allocatorInfo.pVulkanFunctions = &vmaFuncs;
1294
1295 return vmaCreateAllocator( &allocatorInfo, &m_allocator ) == VK_SUCCESS;
1296}
1297
1298
1300{
1301 switch( type )
1302 {
1303 case RHI_BUFFER_TYPE::VERTEX: return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
1304 | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1305 case RHI_BUFFER_TYPE::INDEX: return VK_BUFFER_USAGE_INDEX_BUFFER_BIT
1306 | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1308 return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1309 case RHI_BUFFER_TYPE::UNIFORM: return VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
1310 | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1311 }
1312
1313 return VK_BUFFER_USAGE_TRANSFER_DST_BIT;
1314}
1315
1316
1318{
1319 switch( format )
1320 {
1321 case RHI_TEXTURE_FORMAT::RGBA8: return VK_FORMAT_R8G8B8A8_UNORM;
1322 }
1323
1324 return VK_FORMAT_R8G8B8A8_UNORM;
1325}
1326
1327
1328static VkFormat RhiDefaultDepthFormat()
1329{
1330 return VK_FORMAT_D32_SFLOAT;
1331}
1332
1333
1335{
1336 switch( filter )
1337 {
1338 case RHI_FILTER::BILINEAR: return VK_FILTER_LINEAR;
1339 }
1340
1341 return VK_FILTER_LINEAR;
1342}
1343
1344
1345static VkBlendFactor TranslateBlendFactor( RHI_BLEND_FACTOR factor )
1346{
1347 switch( factor )
1348 {
1349 case RHI_BLEND_FACTOR::ZERO: return VK_BLEND_FACTOR_ZERO;
1350 case RHI_BLEND_FACTOR::ONE: return VK_BLEND_FACTOR_ONE;
1351 case RHI_BLEND_FACTOR::SRC_ALPHA: return VK_BLEND_FACTOR_SRC_ALPHA;
1352 case RHI_BLEND_FACTOR::ONE_MINUS_SRC_ALPHA: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1353 }
1354
1355 return VK_BLEND_FACTOR_ONE;
1356}
1357
1358
1359static VkBlendOp TranslateBlendOp( RHI_BLEND_OP op )
1360{
1361 switch( op )
1362 {
1363 case RHI_BLEND_OP::ADD: return VK_BLEND_OP_ADD;
1364 }
1365
1366 return VK_BLEND_OP_ADD;
1367}
1368
1369
1370static VkCompareOp TranslateCompareOp( RHI_COMPARE_OP op )
1371{
1372 switch( op )
1373 {
1374 case RHI_COMPARE_OP::LESS: return VK_COMPARE_OP_LESS;
1375 case RHI_COMPARE_OP::LESS_EQUAL: return VK_COMPARE_OP_LESS_OR_EQUAL;
1376 }
1377
1378 return VK_COMPARE_OP_LESS;
1379}
1380
1381
1383{
1384 switch( format )
1385 {
1386 case RHI_VERTEX_FORMAT::FLOAT32X2: return VK_FORMAT_R32G32_SFLOAT;
1387 case RHI_VERTEX_FORMAT::FLOAT32X4: return VK_FORMAT_R32G32B32A32_SFLOAT;
1388 case RHI_VERTEX_FORMAT::FLOAT16X2: return VK_FORMAT_R16G16_SFLOAT;
1389 case RHI_VERTEX_FORMAT::FLOAT16X4: return VK_FORMAT_R16G16B16A16_SFLOAT;
1390 case RHI_VERTEX_FORMAT::UINT8X4_NORM: return VK_FORMAT_R8G8B8A8_UNORM;
1391 case RHI_VERTEX_FORMAT::UINT16X2: return VK_FORMAT_R16G16_UINT;
1392 }
1393
1394 return VK_FORMAT_R32G32B32A32_SFLOAT;
1395}
1396
1397
1398static VkPrimitiveTopology TranslateTopology( RHI_PRIMITIVE_TOPOLOGY topology )
1399{
1400 switch( topology )
1401 {
1402 case RHI_PRIMITIVE_TOPOLOGY::TRIANGLE_LIST: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1403 }
1404
1405 return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
1406}
1407
1408
1409static VkDynamicState TranslateDynamicState( RHI_DYNAMIC_STATE state )
1410{
1411 switch( state )
1412 {
1413 case RHI_DYNAMIC_STATE::VIEWPORT: return VK_DYNAMIC_STATE_VIEWPORT;
1414 case RHI_DYNAMIC_STATE::SCISSOR: return VK_DYNAMIC_STATE_SCISSOR;
1415 }
1416
1417 return VK_DYNAMIC_STATE_VIEWPORT;
1418}
1419
1420
1422{
1423 if( !m_vk.CreateCommandPool )
1424 return false;
1425
1426 VkCommandPoolCreateInfo poolInfo{};
1427 poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
1428 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
1429 poolInfo.queueFamilyIndex = m_graphicsQueueFamily;
1430
1431 return m_vk.CreateCommandPool( m_device, &poolInfo, nullptr, &m_commandPool ) == VK_SUCCESS;
1432}
1433
1434
1435bool RHI_VULKAN_DEVICE::allocateCommandBuffer( VkCommandBuffer& aCmd )
1436{
1437 VkCommandBufferAllocateInfo allocInfo{};
1438 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
1439 allocInfo.commandPool = m_commandPool;
1440 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
1441 allocInfo.commandBufferCount = 1;
1442
1443 return m_vk.AllocateCommandBuffers( m_device, &allocInfo, &aCmd ) == VK_SUCCESS;
1444}
1445
1446
1447VkResult RHI_VULKAN_DEVICE::beginOneTimeCommands( VkCommandBuffer aCmd )
1448{
1449 VkCommandBufferBeginInfo beginInfo{};
1450 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
1451 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
1452
1453 return m_vk.BeginCommandBuffer( aCmd, &beginInfo );
1454}
1455
1456
1457VkResult RHI_VULKAN_DEVICE::submitCommands( VkCommandBuffer aCmd )
1458{
1459 VkSubmitInfo submitInfo{};
1460 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
1461 submitInfo.commandBufferCount = 1;
1462 submitInfo.pCommandBuffers = &aCmd;
1463
1464 return m_vk.QueueSubmit( m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE );
1465}
1466
1467
1468bool RHI_VULKAN_DEVICE::SubmitOneShot( VkCommandBuffer cmd )
1469{
1470 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1471
1472 bool ok = false;
1473
1474 if( m_vk.EndCommandBuffer( cmd ) == VK_SUCCESS && submitCommands( cmd ) == VK_SUCCESS )
1475 {
1476 ok = m_vk.QueueWaitIdle( m_graphicsQueue ) == VK_SUCCESS;
1477
1478 if( ok )
1479 OnQueueIdle();
1480 }
1481 else
1482 {
1483 wxLogTrace( traceRhi, wxS( "VulkanDevice: one-shot command submit failed" ) );
1484 }
1485
1486 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
1487 return ok;
1488}
1489
1490
1491bool RHI_VULKAN_DEVICE::acquirePassCmd( VkCommandBuffer& aCmd )
1492{
1493 if( m_freeCmds.empty() )
1494 return allocateCommandBuffer( aCmd );
1495
1496 aCmd = m_freeCmds.back();
1497 m_freeCmds.pop_back();
1498 return true;
1499}
1500
1501
1502VkResult RHI_VULKAN_DEVICE::submitPassCmd( VkCommandBuffer aCmd )
1503{
1504 const VkResult result = submitCommands( aCmd );
1505
1506 if( result != VK_SUCCESS )
1507 {
1508 m_freeCmds.push_back( aCmd );
1509 return result;
1510 }
1511
1512 m_submittedCmds.push_back( aCmd );
1514 return result;
1515}
1516
1517
1519{
1520 m_freeCmds.insert( m_freeCmds.end(), m_submittedCmds.begin(), m_submittedCmds.end() );
1521 m_submittedCmds.clear();
1523}
1524
1525
1527{
1528 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1529
1531 return;
1532
1533 VkResult idleResult = m_vk.QueueWaitIdle( m_graphicsQueue );
1534
1535 if( idleResult != VK_SUCCESS )
1536 wxLogTrace( traceRhi, wxS( "VulkanDevice: SyncSubmittedPasses idle=%d" ), idleResult );
1537
1538 // On device loss the command buffers are dead either way, so recycling them is still right
1539 OnQueueIdle();
1540}
1541
1542
1544{
1545 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1546
1548 return m_uploadCmdBuf;
1549
1551 {
1553 return VK_NULL_HANDLE;
1554 }
1555
1557
1558 return m_uploadCmdBuf;
1559}
1560
1561
1562VkCommandBuffer RHI_VULKAN_DEVICE::beginUploadCommands( std::unique_lock<std::recursive_mutex>& aLock )
1563{
1565 return EnsureUploadCmdBuf();
1566
1567 // Held from the allocation to the end of the submit so the render thread cannot race this
1568 // one on the shared command pool and queue
1569 aLock.lock();
1570
1571 VkCommandBuffer cmd = VK_NULL_HANDLE;
1572
1573 if( !allocateCommandBuffer( cmd ) )
1574 return VK_NULL_HANDLE;
1575
1576 beginOneTimeCommands( cmd );
1577 return cmd;
1578}
1579
1580
1581bool RHI_VULKAN_DEVICE::endUploadCommands( VkCommandBuffer aCmd,
1582 const std::unique_lock<std::recursive_mutex>& aLock )
1583{
1584 if( !aLock.owns_lock() )
1585 return false;
1586
1587 SubmitOneShot( aCmd );
1588 return true;
1589}
1590
1591
1593{
1594 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1595
1596 m_uploadBatchOwner.store( std::this_thread::get_id(), std::memory_order_release );
1598}
1599
1600
1602{
1603 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1604
1605 m_uploadBatchOwner.store( std::thread::id{}, std::memory_order_release );
1606
1608 {
1611 }
1612
1613 for( size_t index : m_batchPooledStaging )
1614 ReleaseStaging( static_cast<int>( index ) );
1615
1616 m_batchPooledStaging.clear();
1617}
1618
1619
1621{
1622 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1623
1624 int best = -1;
1625
1626 for( size_t i = 0; i < m_stagingPool.size(); ++i )
1627 {
1628 const StagingBuffer& candidate = m_stagingPool[i];
1629
1630 if( candidate.inUse || candidate.size < aSizeBytes )
1631 continue;
1632
1633 if( best < 0 || candidate.size < m_stagingPool[best].size )
1634 best = static_cast<int>( i );
1635 }
1636
1637 if( best >= 0 )
1638 {
1639 m_stagingPool[best].inUse = true;
1640 return best;
1641 }
1642
1643 // Round up so a scene that grows a little each frame keeps fitting the same buffer
1644 size_t size = STAGING_MIN_BYTES;
1645
1646 while( size < aSizeBytes )
1647 size *= 2;
1648
1649 VkBufferCreateInfo stagingInfo{};
1650 stagingInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
1651 stagingInfo.size = size;
1652 stagingInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
1653
1654 VmaAllocationCreateInfo stagingAllocInfo{};
1655 stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
1656 stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
1657
1658 StagingBuffer staging;
1659 VmaAllocationInfo mapInfo{};
1660
1661 if( vmaCreateBuffer( m_allocator, &stagingInfo, &stagingAllocInfo, &staging.buffer,
1662 &staging.allocation, &mapInfo ) != VK_SUCCESS )
1663 {
1664 return -1;
1665 }
1666
1667 staging.mapped = mapInfo.pMappedData;
1668 staging.size = size;
1669 staging.inUse = true;
1670
1671 // Reuse a dead slot so indices held by an open batch stay valid
1672 for( size_t i = 0; i < m_stagingPool.size(); ++i )
1673 {
1674 if( m_stagingPool[i].buffer == VK_NULL_HANDLE )
1675 {
1676 m_stagingPool[i] = staging;
1677 return static_cast<int>( i );
1678 }
1679 }
1680
1681 m_stagingPool.push_back( staging );
1682 return static_cast<int>( m_stagingPool.size() - 1 );
1683}
1684
1685
1686int RHI_VULKAN_DEVICE::stageData( const void* aData, size_t aSizeBytes, VkBuffer& aBuffer )
1687{
1688 const int index = AcquireStaging( aSizeBytes );
1689
1690 if( index < 0 )
1691 return -1;
1692
1693 // Copy the handle out. The pool vector may grow under another thread's acquire.
1694 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1695 const StagingBuffer& staging = m_stagingPool[index];
1696
1697 aBuffer = staging.buffer;
1698 std::memcpy( staging.mapped, aData, aSizeBytes );
1699 vmaFlushAllocation( m_allocator, staging.allocation, 0, aSizeBytes );
1700 return index;
1701}
1702
1703
1704void RHI_VULKAN_DEVICE::endStagedUpload( VkCommandBuffer aCmd, std::unique_lock<std::recursive_mutex>& aLock,
1705 int aStaging )
1706{
1707 if( endUploadCommands( aCmd, aLock ) )
1708 ReleaseStaging( aStaging );
1709 else
1710 m_batchPooledStaging.push_back( static_cast<size_t>( aStaging ) );
1711}
1712
1713
1715{
1716 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
1717
1718 if( aIndex < 0 || aIndex >= static_cast<int>( m_stagingPool.size() ) )
1719 return;
1720
1721 m_stagingPool[aIndex].inUse = false;
1722
1723 size_t total = 0;
1724
1725 for( const StagingBuffer& staging : m_stagingPool )
1726 total += staging.size;
1727
1728 // Drop idle buffers, largest first, once a burst has left the pool over its budget
1729 while( total > STAGING_POOL_BYTES )
1730 {
1731 int largest = -1;
1732
1733 for( size_t i = 0; i < m_stagingPool.size(); ++i )
1734 {
1735 const StagingBuffer& candidate = m_stagingPool[i];
1736
1737 if( candidate.inUse || candidate.buffer == VK_NULL_HANDLE )
1738 continue;
1739
1740 if( largest < 0 || candidate.size > m_stagingPool[largest].size )
1741 largest = static_cast<int>( i );
1742 }
1743
1744 if( largest < 0 )
1745 break;
1746
1747 StagingBuffer& victim = m_stagingPool[largest];
1748 vmaDestroyBuffer( m_allocator, victim.buffer, victim.allocation );
1749 total -= victim.size;
1750 victim = StagingBuffer{};
1751 }
1752}
1753
1754
1756{
1757 for( StagingBuffer& staging : m_stagingPool )
1758 {
1759 if( m_allocator && staging.buffer != VK_NULL_HANDLE )
1760 vmaDestroyBuffer( m_allocator, staging.buffer, staging.allocation );
1761 }
1762
1763 m_stagingPool.clear();
1764 m_batchPooledStaging.clear();
1765}
1766
1767
1769{
1770 return m_uploadBatchOwner.load( std::memory_order_acquire )
1771 == std::this_thread::get_id();
1772}
1773
1774
1775static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback(
1776 VkDebugUtilsMessageSeverityFlagBitsEXT aSeverity,
1777 VkDebugUtilsMessageTypeFlagsEXT aType,
1778 const VkDebugUtilsMessengerCallbackDataEXT* aCallbackData,
1779 void* )
1780{
1781 const char* severityStr = "UNKNOWN";
1782
1783 if( aSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT )
1784 severityStr = "ERROR";
1785 else if( aSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT )
1786 severityStr = "WARNING";
1787 else if( aSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT )
1788 severityStr = "INFO";
1789 else if( aSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT )
1790 severityStr = "VERBOSE";
1791
1792 const char* typeStr = "GENERAL";
1793
1794 if( aType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT )
1795 typeStr = "VALIDATION";
1796 else if( aType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT )
1797 typeStr = "PERFORMANCE";
1798
1799 wxLogTrace( traceRhi, wxS( "Vulkan %s [%s]: %s" ),
1800 severityStr, typeStr, aCallbackData->pMessage );
1801
1802 return VK_FALSE;
1803}
1804
1805
1807{
1808 wxLogTrace( traceRhi, wxS( "VulkanDevice: init (validation=%s)" ),
1809 enableValidation ? "ON" : "OFF" );
1810
1811 if( !InitLoader() )
1812 {
1813 wxLogTrace( traceRhi, wxS( "VulkanDevice: InitLoader FAILED" ) );
1814 return;
1815 }
1816
1817 if( !CreateVkInstance( enableValidation ) )
1818 {
1819 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateVkInstance FAILED" ) );
1820 return;
1821 }
1822
1823 if( !SelectPhysicalDevice() )
1824 {
1825 wxLogTrace( traceRhi, wxS( "VulkanDevice: SelectPhysicalDevice FAILED" ) );
1826 return;
1827 }
1828
1829 if( !CreateLogicalDevice() )
1830 {
1831 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateLogicalDevice FAILED" ) );
1832 return;
1833 }
1834
1835 if( !InitVMA() )
1836 {
1837 wxLogTrace( traceRhi, wxS( "VulkanDevice: InitVMA FAILED" ) );
1838 return;
1839 }
1840
1841 if( !InitCommandPool() )
1842 {
1843 wxLogTrace( traceRhi, wxS( "VulkanDevice: InitCommandPool FAILED" ) );
1844 return;
1845 }
1846
1847 if( m_vk.CreatePipelineCache )
1848 {
1849 VkPipelineCacheCreateInfo cacheInfo{};
1850 cacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
1851 m_vk.CreatePipelineCache( m_device, &cacheInfo, nullptr, &m_pipelineCache );
1852 }
1853
1855 m_initialized = true;
1856
1857 wxLogTrace( traceRhi, wxS( "VulkanDevice: initialized OK (%s)" ), m_deviceName );
1858}
1859
1860
1862{
1863 if( m_device )
1864 {
1865 m_vk.DeviceWaitIdle( m_device );
1866
1867 // Command pool destruction frees all allocated command buffers implicitly
1869
1871 m_vk.DestroyPipelineCache( m_device, m_pipelineCache, nullptr );
1872 m_vk.DestroyCommandPool( m_device, m_commandPool, nullptr );
1873
1874 m_deferredReleaseQueue.FlushAll(
1875 [this]( const RHI_DEFERRED_RELEASE_ENTRY& entry )
1876 {
1877 ExecuteDeferredRelease( entry );
1878 } );
1879
1881
1882 // Destroy swapchains before surfaces
1883 for( auto& [index, swa] : m_swapchains )
1885
1886 for( auto& [index, sa] : m_surfaces )
1887 {
1888 m_vk.DestroySurfaceKHR( m_instance, sa.surface, nullptr );
1889
1890#ifdef VK_USE_PLATFORM_XLIB_KHR
1891 // As in DestroySurface, close the X11 connection this surface opened
1892 sa.x11Owner.Close();
1893#endif
1894 }
1895
1896 // A framebuffer's color textures are left to it, so it goes before them
1897 for( auto& [index, fb] : m_framebuffers )
1899
1900 for( auto& [index, rp] : m_renderPasses )
1901 m_vk.DestroyRenderPass( m_device, rp.renderPass, nullptr );
1902
1903 for( auto& poolEntry : m_descriptorPools )
1904 m_vk.DestroyDescriptorPool( m_device, poolEntry.pool, nullptr );
1905
1906 for( auto& [index, bgl] : m_bindGroupLayouts )
1907 m_vk.DestroyDescriptorSetLayout( m_device, bgl.layout, nullptr );
1908
1909 for( auto& [index, pipe] : m_pipelines )
1910 destroyPipeline( pipe );
1911
1912 for( auto& [index, pipe] : m_computePipelines )
1913 destroyPipeline( pipe.native );
1914
1915 for( auto& [index, shader] : m_shaders )
1916 m_vk.DestroyShaderModule( m_device, shader.module, nullptr );
1917
1918 for( auto& [index, tex] : m_textures )
1919 destroyTexture( tex );
1920
1921 if( m_allocator )
1922 {
1923 for( auto& [index, buf] : m_buffers )
1924 vmaDestroyBuffer( m_allocator, buf.buffer, buf.allocation );
1925
1926 vmaDestroyAllocator( m_allocator );
1927 }
1928
1929 m_vk.DestroyDevice( m_device, nullptr );
1930 }
1931
1932 if( m_debugMessenger )
1933 m_vk.DestroyDebugUtilsMessengerEXT( m_instance, m_debugMessenger, nullptr );
1934
1935 if( m_instance )
1936 m_vk.DestroyInstance( m_instance, nullptr );
1937
1938 m_vulkanLib.Unload();
1939
1940 wxLogTrace( traceRhi, wxS( "VulkanDevice: destroyed" ) );
1941}
1942
1943
1944bool RHI_VULKAN_DEVICE::createImage2D( VkFormat aFormat, uint32_t aWidth, uint32_t aHeight,
1945 VkImageUsageFlags aUsage, bool aDedicated, VkImage& aImage,
1946 VmaAllocation& aAllocation )
1947{
1948 VkImageCreateInfo imageInfo{};
1949 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
1950 imageInfo.imageType = VK_IMAGE_TYPE_2D;
1951 imageInfo.format = aFormat;
1952 imageInfo.extent = { aWidth, aHeight, 1 };
1953 imageInfo.mipLevels = 1;
1954 imageInfo.arrayLayers = 1;
1955 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
1956 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
1957 imageInfo.usage = aUsage;
1958 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
1959 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
1960
1961 VmaAllocationCreateInfo allocInfo{};
1962 allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
1963
1964 if( aDedicated )
1965 allocInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
1966
1967 return vmaCreateImage( m_allocator, &imageInfo, &allocInfo, &aImage, &aAllocation, nullptr ) == VK_SUCCESS;
1968}
1969
1970
1971VkImageView RHI_VULKAN_DEVICE::createView( VkImage aImage, VkFormat aFormat, VkImageAspectFlags aAspect )
1972{
1973 VkImageViewCreateInfo viewInfo{};
1974 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
1975 viewInfo.image = aImage;
1976 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
1977 viewInfo.format = aFormat;
1978 viewInfo.subresourceRange.aspectMask = aAspect;
1979 viewInfo.subresourceRange.levelCount = 1;
1980 viewInfo.subresourceRange.layerCount = 1;
1981
1982 VkImageView view = VK_NULL_HANDLE;
1983
1984 if( m_vk.CreateImageView( m_device, &viewInfo, nullptr, &view ) != VK_SUCCESS )
1985 return VK_NULL_HANDLE;
1986
1987 return view;
1988}
1989
1990
1991VkSampler RHI_VULKAN_DEVICE::createSampler( VkFilter aFilter )
1992{
1993 VkSamplerCreateInfo samplerInfo{};
1994 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
1995 samplerInfo.magFilter = aFilter;
1996 samplerInfo.minFilter = aFilter;
1997 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
1998 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
1999 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2000 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
2001
2002 VkSampler sampler = VK_NULL_HANDLE;
2003
2004 if( m_vk.CreateSampler( m_device, &samplerInfo, nullptr, &sampler ) != VK_SUCCESS )
2005 return VK_NULL_HANDLE;
2006
2007 return sampler;
2008}
2009
2010
2012{
2013 m_vk.DestroySampler( m_device, aTexture.sampler, nullptr );
2014
2015 // A framebuffer's color texture shares its image and view, which go with the framebuffer
2016 if( aTexture.ownedByFBO )
2017 return;
2018
2019 m_vk.DestroyImageView( m_device, aTexture.imageView, nullptr );
2020
2021 if( m_allocator )
2022 vmaDestroyImage( m_allocator, aTexture.image, aTexture.allocation );
2023}
2024
2025
2027{
2028 m_vk.DestroyPipeline( m_device, aPipeline.pipeline, nullptr );
2029 m_vk.DestroyPipelineLayout( m_device, aPipeline.layout, nullptr );
2030
2031 for( VkDescriptorSetLayout dsLayout : aPipeline.dsLayouts )
2032 m_vk.DestroyDescriptorSetLayout( m_device, dsLayout, nullptr );
2033
2034 m_vk.DestroyRenderPass( m_device, aPipeline.renderPass, nullptr );
2035}
2036
2037
2039{
2040 m_vk.DestroyFramebuffer( m_device, aFb.framebuffer, nullptr );
2041 m_vk.DestroyRenderPass( m_device, aFb.internalRenderPass, nullptr );
2042 m_vk.DestroyImageView( m_device, aFb.depthView, nullptr );
2043
2044 for( VkImageView view : aFb.colorViews )
2045 m_vk.DestroyImageView( m_device, view, nullptr );
2046
2047 if( m_allocator )
2048 {
2049 vmaDestroyImage( m_allocator, aFb.depthImage, aFb.depthAllocation );
2050
2051 for( size_t i = 0; i < aFb.colorImages.size(); i++ )
2052 vmaDestroyImage( m_allocator, aFb.colorImages[i], aFb.colorAllocations[i] );
2053 }
2054
2055 aFb = FramebufferAllocation();
2056}
2057
2058
2060{
2061 // Retirement counts CPU frames, and submitted passes may still be reading these resources
2062 if( m_deferredReleaseQueue.HasRetired( m_frameCounter ) )
2064
2065 m_deferredReleaseQueue.ProcessRetired( m_frameCounter,
2066 [this]( const RHI_DEFERRED_RELEASE_ENTRY& entry )
2067 {
2068 ExecuteDeferredRelease( entry );
2069 } );
2070}
2071
2072
2074{
2075 uint32_t idx = aEntry.handleValue & RHI_HANDLE_INDEX_MASK;
2076
2077 switch( aEntry.type )
2078 {
2080 {
2081 auto it = m_buffers.find( idx );
2082
2083 if( it != m_buffers.end() )
2084 {
2085 vmaDestroyBuffer( m_allocator, it->second.buffer, it->second.allocation );
2086 m_buffers.erase( it );
2087 }
2088
2089 m_bufferPool.Release( RHI_BufferHandle{ aEntry.handleValue } );
2090 break;
2091 }
2092
2094 {
2095 auto it = m_textures.find( idx );
2096
2097 if( it != m_textures.end() )
2098 {
2099 destroyTexture( it->second );
2100 m_textures.erase( it );
2101 }
2102
2103 m_texturePool.Release( RHI_TextureHandle{ aEntry.handleValue } );
2104 break;
2105 }
2106
2108 {
2109 auto it = m_shaders.find( idx );
2110
2111 if( it != m_shaders.end() )
2112 {
2113 m_vk.DestroyShaderModule( m_device, it->second.module, nullptr );
2114 m_shaders.erase( it );
2115 }
2116
2117 m_shaderPool.Release( RHI_ShaderHandle{ aEntry.handleValue } );
2118 break;
2119 }
2120
2122 {
2123 auto it = m_pipelines.find( idx );
2124
2125 if( it != m_pipelines.end() )
2126 {
2127 destroyPipeline( it->second );
2128 m_pipelines.erase( it );
2129 }
2130
2131 m_pipelinePool.Release( RHI_PipelineHandle{ aEntry.handleValue } );
2132 break;
2133 }
2134
2136 {
2137 auto it = m_bindGroupLayouts.find( idx );
2138
2139 if( it != m_bindGroupLayouts.end() )
2140 {
2141 m_vk.DestroyDescriptorSetLayout( m_device, it->second.layout, nullptr );
2142 m_bindGroupLayouts.erase( it );
2143 }
2144
2146 break;
2147 }
2148
2150 {
2151 auto it = m_bindGroups.find( idx );
2152
2153 if( it != m_bindGroups.end() )
2154 {
2155 ReleaseDescriptorSet( it->second.pool, it->second.descriptorSet );
2156 m_bindGroups.erase( it );
2157 }
2158
2159 m_bindGroupPool.Release( RHI_BindGroupHandle{ aEntry.handleValue } );
2160 break;
2161 }
2162
2164 {
2165 auto it = m_renderPasses.find( idx );
2166
2167 if( it != m_renderPasses.end() )
2168 {
2169 m_vk.DestroyRenderPass( m_device, it->second.renderPass, nullptr );
2170 m_renderPasses.erase( it );
2171 }
2172
2174 break;
2175 }
2176
2178 {
2179 auto it = m_framebuffers.find( idx );
2180
2181 if( it != m_framebuffers.end() )
2182 {
2183 FramebufferAllocation& fba = it->second;
2184
2185 for( RHI_TextureHandle texHandle : fba.colorTextureHandles )
2186 {
2187 if( !texHandle )
2188 continue;
2189
2190 // FBO color textures own their sampler, which would otherwise leak until device teardown
2191 auto texIt = m_textures.find( RhiHandleIndex( texHandle ) );
2192
2193 if( texIt != m_textures.end() )
2194 {
2195 destroyTexture( texIt->second );
2196 m_textures.erase( texIt );
2197 }
2198
2199 m_texturePool.Release( texHandle );
2200 }
2201
2203 m_framebuffers.erase( it );
2204 }
2205
2207 break;
2208 }
2209 }
2210}
2211
2212
2214{
2215 return m_deviceName.c_str();
2216}
2217
2218
2223
2224
2226{
2227 return m_initialized;
2228}
2229
2230
2231// ---- Buffer operations (VMA-backed) ----
2232
2237
2238
2240{
2241 if( !handle )
2242 return false;
2243
2244 // Release the reserved slot on every failure so a create command that cannot realize does not leak it
2245 if( desc.sizeBytes == 0 || !m_allocator )
2246 {
2247 m_bufferPool.Release( handle );
2248 return false;
2249 }
2250
2251 VkBufferCreateInfo bufInfo{};
2252 bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
2253 bufInfo.size = desc.sizeBytes;
2254 bufInfo.usage = TranslateBufferUsage( desc.type );
2255 bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2256
2257 VmaAllocationCreateInfo allocInfo{};
2258 allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
2259
2260 if( desc.mappable )
2261 {
2262 allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT
2263 | VMA_ALLOCATION_CREATE_MAPPED_BIT;
2264 }
2265
2266 VkBuffer buffer = VK_NULL_HANDLE;
2267 VmaAllocation allocation = VK_NULL_HANDLE;
2268 VmaAllocationInfo allocationInfo{};
2269
2270 VkResult result = vmaCreateBuffer( m_allocator, &bufInfo, &allocInfo,
2271 &buffer, &allocation, &allocationInfo );
2272
2273 if( result != VK_SUCCESS )
2274 {
2275 m_bufferPool.Release( handle );
2276 return false;
2277 }
2278
2279 uint32_t idx = RhiHandleIndex( handle );
2280
2281 BufferAllocation& ba = m_buffers[idx];
2282 ba.buffer = buffer;
2283 ba.allocation = allocation;
2284 ba.mappedPtr = desc.mappable ? allocationInfo.pMappedData : nullptr;
2285 ba.size = desc.sizeBytes;
2286 ba.mappable = desc.mappable;
2287 ba.type = desc.type;
2288
2289 return true;
2290}
2291
2292
2300
2301
2303 size_t sizeBytes, size_t offset )
2304{
2305 if( !m_bufferPool.IsValid( handle ) || !data || sizeBytes == 0 )
2306 return;
2307
2308 uint32_t idx = RhiHandleIndex( handle );
2309 auto it = m_buffers.find( idx );
2310
2311 if( it == m_buffers.end() )
2312 return;
2313
2314 if( !RhiRangeWithinBuffer( offset, sizeBytes, it->second.size ) )
2315 return;
2316
2317 if( it->second.mappable && it->second.mappedPtr )
2318 {
2319 // A CPU write lands immediately, so a pass still queued on the GPU would read the
2320 // new contents. Staged copies below are ordered on the queue and need no wait.
2321 if( it->second.lastUseSerial > m_syncedSerial )
2323
2324 std::memcpy( static_cast<uint8_t*>( it->second.mappedPtr ) + offset, data, sizeBytes );
2325 vmaFlushAllocation( m_allocator, it->second.allocation, offset, sizeBytes );
2326 return;
2327 }
2328
2329 VkBuffer stagingBuffer = VK_NULL_HANDLE;
2330 const int stagingIndex = stageData( data, sizeBytes, stagingBuffer );
2331
2332 if( stagingIndex < 0 )
2333 return;
2334
2335 std::unique_lock<std::recursive_mutex> oneShotLock( m_deviceMutex, std::defer_lock );
2336 VkCommandBuffer cmd = beginUploadCommands( oneShotLock );
2337
2338 if( cmd == VK_NULL_HANDLE )
2339 {
2340 ReleaseStaging( stagingIndex );
2341 return;
2342 }
2343
2344 VkBufferCopy copyRegion{};
2345 copyRegion.srcOffset = 0;
2346 copyRegion.dstOffset = offset;
2347 copyRegion.size = sizeBytes;
2348
2349 m_vk.CmdCopyBuffer( cmd, stagingBuffer, it->second.buffer, 1, &copyRegion );
2350 endStagedUpload( cmd, oneShotLock, stagingIndex );
2351}
2352
2353
2354// ---- Texture operations (VMA-backed) ----
2355
2360
2361
2363{
2364 if( !handle )
2365 return false;
2366
2367 if( desc.width == 0 || desc.height == 0 || !m_allocator )
2368 {
2369 m_texturePool.Release( handle );
2370 return false;
2371 }
2372
2373 const VkFormat vkFormat = TranslateTextureFormat( desc.format );
2375
2376 if( createImage2D( vkFormat, desc.width, desc.height, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
2377 false, ta.image, ta.allocation ) )
2378 {
2379 ta.imageView = createView( ta.image, vkFormat, VK_IMAGE_ASPECT_COLOR_BIT );
2380
2381 if( ta.imageView )
2383 }
2384
2385 if( !ta.sampler )
2386 {
2387 destroyTexture( ta );
2388 m_texturePool.Release( handle );
2389 return false;
2390 }
2391
2392 ta.width = desc.width;
2393 ta.height = desc.height;
2394 ta.format = desc.format;
2395
2396 m_textures[RhiHandleIndex( handle )] = ta;
2397
2398 return true;
2399}
2400
2401
2406
2407
2409 size_t sizeBytes )
2410{
2411 if( !m_texturePool.IsValid( handle ) || !data || sizeBytes == 0 || !m_allocator )
2412 return;
2413
2414 uint32_t idx = RhiHandleIndex( handle );
2415 auto it = m_textures.find( idx );
2416
2417 if( it == m_textures.end() )
2418 return;
2419
2420 TextureAllocation& tex = it->second;
2421 uint32_t bpp = RhiTextureBytesPerPixel( tex.format );
2422 size_t expected = static_cast<size_t>( tex.width ) * tex.height * bpp;
2423
2424 if( sizeBytes < expected )
2425 return;
2426
2427 VkBuffer stagingBuffer = VK_NULL_HANDLE;
2428 const int stagingIndex = stageData( data, expected, stagingBuffer );
2429
2430 if( stagingIndex < 0 )
2431 return;
2432
2433 std::unique_lock<std::recursive_mutex> oneShotLock( m_deviceMutex, std::defer_lock );
2434 VkCommandBuffer cmd = beginUploadCommands( oneShotLock );
2435
2436 if( cmd == VK_NULL_HANDLE )
2437 {
2438 ReleaseStaging( stagingIndex );
2439 return;
2440 }
2441
2442 TransitionTexture( cmd, tex, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
2443 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT );
2444
2445 VkBufferImageCopy region{};
2446 region.bufferOffset = 0;
2447 region.bufferRowLength = 0;
2448 region.bufferImageHeight = 0;
2449 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
2450 region.imageSubresource.mipLevel = 0;
2451 region.imageSubresource.baseArrayLayer = 0;
2452 region.imageSubresource.layerCount = 1;
2453 region.imageOffset = { 0, 0, 0 };
2454 region.imageExtent = { tex.width, tex.height, 1 };
2455
2456 m_vk.CmdCopyBufferToImage( cmd, stagingBuffer, tex.image,
2457 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region );
2458
2459 TransitionTexture( cmd, tex, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
2460 VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT );
2461
2462 endStagedUpload( cmd, oneShotLock, stagingIndex );
2463}
2464
2465
2466// ---- Shader and pipeline operations ----
2467
2469{
2470 if( !desc.bytecode || desc.sizeBytes == 0 || !m_vk.CreateShaderModule )
2471 return {};
2472
2473 // SPIR-V must be a multiple of 4 bytes
2474 if( desc.sizeBytes % 4 != 0 )
2475 return {};
2476
2477 VkShaderModuleCreateInfo moduleInfo{};
2478 moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
2479 moduleInfo.codeSize = desc.sizeBytes;
2480 moduleInfo.pCode = reinterpret_cast<const uint32_t*>( desc.bytecode );
2481
2482 VkShaderModule module = VK_NULL_HANDLE;
2483 VkResult result = m_vk.CreateShaderModule( m_device, &moduleInfo, nullptr, &module );
2484
2485 if( result != VK_SUCCESS )
2486 {
2487 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateShaderModule FAILED %s(%d) bytes=%zu" ),
2488 VkResultToString( result ), static_cast<int>( result ), desc.sizeBytes );
2489 return {};
2490 }
2491
2492 RHI_ShaderHandle handle = m_shaderPool.Allocate();
2493
2494 if( !handle )
2495 {
2496 m_vk.DestroyShaderModule( m_device, module, nullptr );
2497 return {};
2498 }
2499
2500 uint32_t idx = RhiHandleIndex( handle );
2501
2502 ShaderAllocation& sa = m_shaders[idx];
2503 sa.module = module;
2504
2505 return handle;
2506}
2507
2508
2513
2514
2515static VkAccessFlags LayoutToAccessMask( VkImageLayout layout )
2516{
2517 switch( layout )
2518 {
2519 case VK_IMAGE_LAYOUT_UNDEFINED: return 0;
2520 case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: return VK_ACCESS_TRANSFER_WRITE_BIT;
2521 case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: return VK_ACCESS_TRANSFER_READ_BIT;
2522 case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: return VK_ACCESS_SHADER_READ_BIT;
2523 case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
2524 case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
2525 case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: return 0;
2526 default: return 0;
2527 }
2528}
2529
2530
2531// A barrier over the one mip level and array layer of an image
2532static VkImageMemoryBarrier imageBarrier( VkImage aImage, VkImageAspectFlags aAspect, VkImageLayout aOld,
2533 VkImageLayout aNew, VkAccessFlags aSrcAccess, VkAccessFlags aDstAccess )
2534{
2535 VkImageMemoryBarrier barrier{};
2536 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
2537 barrier.srcAccessMask = aSrcAccess;
2538 barrier.dstAccessMask = aDstAccess;
2539 barrier.oldLayout = aOld;
2540 barrier.newLayout = aNew;
2541 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2542 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
2543 barrier.image = aImage;
2544 barrier.subresourceRange = { aAspect, 0, 1, 0, 1 };
2545 return barrier;
2546}
2547
2548
2549static VkDescriptorType TranslateBindingType( RHI_BINDING_TYPE type )
2550{
2551 switch( type )
2552 {
2553 case RHI_BINDING_TYPE::UNIFORM_BUFFER: return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
2555 case RHI_BINDING_TYPE::STORAGE_BUFFER_READONLY: return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
2556 case RHI_BINDING_TYPE::COMBINED_TEXTURE_SAMPLER: return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
2557 }
2558
2559 return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
2560}
2561
2562
2563static VkShaderStageFlags TranslateShaderStage( RHI_SHADER_STAGE stages )
2564{
2565 VkShaderStageFlags flags = 0;
2566
2568 flags |= VK_SHADER_STAGE_VERTEX_BIT;
2569
2571 flags |= VK_SHADER_STAGE_FRAGMENT_BIT;
2572
2574 flags |= VK_SHADER_STAGE_COMPUTE_BIT;
2575
2576 return flags;
2577}
2578
2579
2580// A swapchain image drawn whole and then presented
2581static VK_ATTACHMENT presentAttachment( VkFormat aFormat )
2582{
2583 return { aFormat, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_UNDEFINED,
2584 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR };
2585}
2586
2587
2588VkRenderPass RHI_VULKAN_DEVICE::makeRenderPass( const std::vector<VK_ATTACHMENT>& aColor,
2589 const std::optional<VK_ATTACHMENT>& aDepth )
2590{
2591 std::vector<VkAttachmentDescription> attachments;
2592 std::vector<VkAttachmentReference> colorRefs;
2593
2594 auto describe = []( const VK_ATTACHMENT& aAttachment )
2595 {
2596 VkAttachmentDescription att{};
2597 att.format = aAttachment.format;
2598 att.samples = VK_SAMPLE_COUNT_1_BIT;
2599 att.loadOp = aAttachment.loadOp;
2600 att.storeOp = aAttachment.storeOp;
2601 att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
2602 att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
2603 att.initialLayout = aAttachment.initialLayout;
2604 att.finalLayout = aAttachment.finalLayout;
2605 return att;
2606 };
2607
2608 for( const VK_ATTACHMENT& color : aColor )
2609 {
2610 const uint32_t index = static_cast<uint32_t>( attachments.size() );
2611
2612 colorRefs.push_back( { index, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL } );
2613 attachments.push_back( describe( color ) );
2614 }
2615
2616 const VkAttachmentReference depthRef{ static_cast<uint32_t>( attachments.size() ),
2617 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
2618
2619 if( aDepth )
2620 attachments.push_back( describe( *aDepth ) );
2621
2622 VkSubpassDescription subpass{};
2623 subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
2624 subpass.colorAttachmentCount = static_cast<uint32_t>( colorRefs.size() );
2625 subpass.pColorAttachments = colorRefs.data();
2626
2627 if( aDepth )
2628 subpass.pDepthStencilAttachment = &depthRef;
2629
2630 VkRenderPassCreateInfo rpInfo{};
2631 rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
2632 rpInfo.attachmentCount = static_cast<uint32_t>( attachments.size() );
2633 rpInfo.pAttachments = attachments.data();
2634 rpInfo.subpassCount = 1;
2635 rpInfo.pSubpasses = &subpass;
2636
2637 VkRenderPass renderPass = VK_NULL_HANDLE;
2638
2639 if( m_vk.CreateRenderPass( m_device, &rpInfo, nullptr, &renderPass ) != VK_SUCCESS )
2640 return VK_NULL_HANDLE;
2641
2642 return renderPass;
2643}
2644
2645
2647{
2648 PROF_TIMER createTimer;
2649
2650 if( !m_vk.CreateGraphicsPipelines || !m_vk.CreatePipelineLayout || !m_vk.CreateRenderPass )
2651 return {};
2652
2653 if( !desc.vertexShader || !desc.fragmentShader )
2654 return {};
2655
2656 if( !m_shaderPool.IsValid( desc.vertexShader ) || !m_shaderPool.IsValid( desc.fragmentShader ) )
2657 return {};
2658
2659 auto vertIt = m_shaders.find( RhiHandleIndex( desc.vertexShader ) );
2660 auto fragIt = m_shaders.find( RhiHandleIndex( desc.fragmentShader ) );
2661
2662 if( vertIt == m_shaders.end() || fragIt == m_shaders.end() )
2663 return {};
2664
2665 const bool useDepthStencil = RhiPipelineUsesDepthStencil( desc );
2666
2667 std::optional<VK_ATTACHMENT> depth;
2668
2669 if( useDepthStencil )
2670 {
2671 depth = VK_ATTACHMENT{ RhiDefaultDepthFormat(), VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_DONT_CARE,
2672 VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
2673 }
2674
2675 const VkRenderPass renderPass =
2676 makeRenderPass( { { TranslateTextureFormat( desc.colorFormat ), VK_ATTACHMENT_LOAD_OP_CLEAR,
2677 VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_UNDEFINED,
2678 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL } },
2679 depth );
2680
2681 if( !renderPass )
2682 return {};
2683
2684 std::vector<VkDescriptorSetLayout> dsLayouts;
2685
2686 // The pipeline owns these until they move into its PipelineAllocation
2687 auto destroyDsLayouts = [&]()
2688 {
2689 for( VkDescriptorSetLayout dsLayout : dsLayouts )
2690 m_vk.DestroyDescriptorSetLayout( m_device, dsLayout, nullptr );
2691 };
2692
2693 for( const auto& bglDesc : desc.bindGroupLayouts )
2694 {
2695 const VkDescriptorSetLayout dsLayout = createSetLayout( bglDesc );
2696
2697 if( !dsLayout )
2698 {
2699 destroyDsLayouts();
2700 m_vk.DestroyRenderPass( m_device, renderPass, nullptr );
2701 return {};
2702 }
2703
2704 dsLayouts.push_back( dsLayout );
2705 }
2706
2707 VkPushConstantRange pushRange{};
2708 pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
2709 pushRange.offset = 0;
2710 pushRange.size = RHI_MAX_PUSH_CONSTANT_BYTES;
2711
2712 VkPipelineLayoutCreateInfo layoutInfo{};
2713 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
2714 layoutInfo.setLayoutCount = static_cast<uint32_t>( dsLayouts.size() );
2715 layoutInfo.pSetLayouts = dsLayouts.empty() ? nullptr : dsLayouts.data();
2716 layoutInfo.pushConstantRangeCount = 1;
2717 layoutInfo.pPushConstantRanges = &pushRange;
2718
2719 VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
2720
2721 if( m_vk.CreatePipelineLayout( m_device, &layoutInfo, nullptr, &pipelineLayout ) != VK_SUCCESS )
2722 {
2723 destroyDsLayouts();
2724 m_vk.DestroyRenderPass( m_device, renderPass, nullptr );
2725 return {};
2726 }
2727
2728 std::vector<VkVertexInputBindingDescription> bindings;
2729 std::vector<VkVertexInputAttributeDescription> attributes;
2730
2731 for( uint32_t b = 0; b < desc.vertexBuffers.size(); b++ )
2732 {
2733 const auto& vb = desc.vertexBuffers[b];
2734
2735 VkVertexInputBindingDescription binding{};
2736 binding.binding = b;
2737 binding.stride = vb.stride;
2738 binding.inputRate = ( vb.stepMode == RHI_VERTEX_STEP_MODE::INSTANCE )
2739 ? VK_VERTEX_INPUT_RATE_INSTANCE
2740 : VK_VERTEX_INPUT_RATE_VERTEX;
2741 bindings.push_back( binding );
2742
2743 for( const auto& attr : vb.attributes )
2744 {
2745 VkVertexInputAttributeDescription a{};
2746 a.location = attr.location;
2747 a.binding = b;
2748 a.format = TranslateVertexFormat( attr.format );
2749 a.offset = attr.offset;
2750 attributes.push_back( a );
2751 }
2752 }
2753
2754 VkPipelineVertexInputStateCreateInfo vertexInput{};
2755 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
2756 vertexInput.vertexBindingDescriptionCount = static_cast<uint32_t>( bindings.size() );
2757 vertexInput.pVertexBindingDescriptions = bindings.data();
2758 vertexInput.vertexAttributeDescriptionCount = static_cast<uint32_t>( attributes.size() );
2759 vertexInput.pVertexAttributeDescriptions = attributes.data();
2760
2761 std::vector<VkPipelineColorBlendAttachmentState> blendAttachments;
2762
2763 if( desc.blendStates.empty() )
2764 {
2765 VkPipelineColorBlendAttachmentState defaultBlend{};
2766 defaultBlend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
2767 | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
2768 blendAttachments.push_back( defaultBlend );
2769 }
2770 else
2771 {
2772 for( const auto& bs : desc.blendStates )
2773 {
2774 VkPipelineColorBlendAttachmentState vkBlend{};
2775 vkBlend.blendEnable = bs.enabled ? VK_TRUE : VK_FALSE;
2776 vkBlend.srcColorBlendFactor = TranslateBlendFactor( bs.srcColor );
2777 vkBlend.dstColorBlendFactor = TranslateBlendFactor( bs.dstColor );
2778 vkBlend.colorBlendOp = TranslateBlendOp( bs.colorOp );
2779 vkBlend.srcAlphaBlendFactor = TranslateBlendFactor( bs.srcAlpha );
2780 vkBlend.dstAlphaBlendFactor = TranslateBlendFactor( bs.dstAlpha );
2781 vkBlend.alphaBlendOp = TranslateBlendOp( bs.alphaOp );
2782 vkBlend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
2783 | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
2784 blendAttachments.push_back( vkBlend );
2785 }
2786 }
2787
2788 VkPipelineDepthStencilStateCreateInfo depthStencil{};
2789 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
2790 depthStencil.depthTestEnable = desc.depthStencil.depthTestEnable ? VK_TRUE : VK_FALSE;
2791 depthStencil.depthWriteEnable = desc.depthStencil.depthWriteEnable ? VK_TRUE : VK_FALSE;
2792 depthStencil.depthCompareOp = TranslateCompareOp( desc.depthStencil.depthCompareOp );
2793 depthStencil.stencilTestEnable = VK_FALSE;
2794
2795 // Viewport and scissor are always dynamic
2796 std::vector<VkDynamicState> dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT,
2797 VK_DYNAMIC_STATE_SCISSOR };
2798
2799 for( auto ds : desc.dynamicStates )
2800 {
2801 VkDynamicState vkDs = TranslateDynamicState( ds );
2802
2803 if( vkDs != VK_DYNAMIC_STATE_VIEWPORT && vkDs != VK_DYNAMIC_STATE_SCISSOR )
2804 dynamicStates.push_back( vkDs );
2805 }
2806
2807 VkPipeline pipeline = VK_NULL_HANDLE;
2808 const VkResult result = buildPipeline( vertIt->second.module, fragIt->second.module, vertexInput,
2809 TranslateTopology( desc.topology ), blendAttachments, depthStencil,
2810 dynamicStates, pipelineLayout, renderPass, pipeline );
2811
2812 if( result != VK_SUCCESS )
2813 {
2814 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateGraphicsPipelines FAILED %s(%d)" ),
2815 VkResultToString( result ), static_cast<int>( result ) );
2816 m_vk.DestroyPipelineLayout( m_device, pipelineLayout, nullptr );
2817 m_vk.DestroyRenderPass( m_device, renderPass, nullptr );
2818 destroyDsLayouts();
2819 return {};
2820 }
2821
2822 RHI_PipelineHandle handle = m_pipelinePool.Allocate();
2823
2824 if( !handle )
2825 {
2826 m_vk.DestroyPipeline( m_device, pipeline, nullptr );
2827 m_vk.DestroyPipelineLayout( m_device, pipelineLayout, nullptr );
2828 m_vk.DestroyRenderPass( m_device, renderPass, nullptr );
2829 destroyDsLayouts();
2830 return {};
2831 }
2832
2833 uint32_t idx = RhiHandleIndex( handle );
2834
2836 pa.pipeline = pipeline;
2837 pa.layout = pipelineLayout;
2838 pa.renderPass = renderPass;
2839 pa.dsLayouts = std::move( dsLayouts );
2840
2841 const double ms = createTimer.msecs();
2843
2844 if( ms > 10.0 )
2845 {
2846 wxLogTrace( traceRhi, wxS( "%s: CreatePipeline took %.1fms (total %.1fms)" ),
2848 }
2849
2850 return handle;
2851}
2852
2853
2858
2859
2860VkResult RHI_VULKAN_DEVICE::buildPipeline( VkShaderModule aVertex, VkShaderModule aFragment,
2861 const VkPipelineVertexInputStateCreateInfo& aVertexInput,
2862 VkPrimitiveTopology aTopology,
2863 const std::vector<VkPipelineColorBlendAttachmentState>& aBlend,
2864 const VkPipelineDepthStencilStateCreateInfo& aDepthStencil,
2865 const std::vector<VkDynamicState>& aDynamicStates,
2866 VkPipelineLayout aLayout, VkRenderPass aRenderPass,
2867 VkPipeline& aPipeline )
2868{
2869 VkPipelineShaderStageCreateInfo stages[2] = {};
2870 stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
2871 stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
2872 stages[0].module = aVertex;
2873 stages[0].pName = "main";
2874 stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
2875 stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
2876 stages[1].module = aFragment;
2877 stages[1].pName = "main";
2878
2879 VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
2880 inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
2881 inputAssembly.topology = aTopology;
2882
2883 // Viewport and scissor are dynamic, so only the counts are set here
2884 VkPipelineViewportStateCreateInfo viewportState{};
2885 viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
2886 viewportState.viewportCount = 1;
2887 viewportState.scissorCount = 1;
2888
2889 VkPipelineRasterizationStateCreateInfo rasterizer{};
2890 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
2891 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
2892 rasterizer.cullMode = VK_CULL_MODE_NONE;
2893 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
2894 rasterizer.lineWidth = 1.0f;
2895
2896 VkPipelineMultisampleStateCreateInfo multisample{};
2897 multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
2898 multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
2899
2900 VkPipelineColorBlendStateCreateInfo blendState{};
2901 blendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
2902 blendState.attachmentCount = static_cast<uint32_t>( aBlend.size() );
2903 blendState.pAttachments = aBlend.data();
2904
2905 VkPipelineDynamicStateCreateInfo dynamicStateInfo{};
2906 dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
2907 dynamicStateInfo.dynamicStateCount = static_cast<uint32_t>( aDynamicStates.size() );
2908 dynamicStateInfo.pDynamicStates = aDynamicStates.data();
2909
2910 VkGraphicsPipelineCreateInfo pipelineInfo{};
2911 pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
2912 pipelineInfo.stageCount = 2;
2913 pipelineInfo.pStages = stages;
2914 pipelineInfo.pVertexInputState = &aVertexInput;
2915 pipelineInfo.pInputAssemblyState = &inputAssembly;
2916 pipelineInfo.pViewportState = &viewportState;
2917 pipelineInfo.pRasterizationState = &rasterizer;
2918 pipelineInfo.pMultisampleState = &multisample;
2919 pipelineInfo.pColorBlendState = &blendState;
2920 pipelineInfo.pDepthStencilState = &aDepthStencil;
2921 pipelineInfo.pDynamicState = &dynamicStateInfo;
2922 pipelineInfo.layout = aLayout;
2923 pipelineInfo.renderPass = aRenderPass;
2924
2925 return m_vk.CreateGraphicsPipelines( m_device, m_pipelineCache, 1, &pipelineInfo, nullptr, &aPipeline );
2926}
2927
2928
2930{
2931 std::vector<VkDescriptorSetLayoutBinding> bindings;
2932
2933 for( const RHI_BINDING_ENTRY& entry : aDesc.entries )
2934 {
2935 bindings.push_back( { entry.binding, TranslateBindingType( entry.type ), 1,
2936 TranslateShaderStage( entry.stages ), nullptr } );
2937 }
2938
2939 VkDescriptorSetLayoutCreateInfo layoutInfo{};
2940 layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
2941 layoutInfo.bindingCount = static_cast<uint32_t>( bindings.size() );
2942 layoutInfo.pBindings = bindings.data();
2943
2944 VkDescriptorSetLayout layout = VK_NULL_HANDLE;
2945
2946 if( m_vk.CreateDescriptorSetLayout( m_device, &layoutInfo, nullptr, &layout ) != VK_SUCCESS )
2947 return VK_NULL_HANDLE;
2948
2949 return layout;
2950}
2951
2952
2954{
2955 return m_initialized && m_computeQueue && m_vk.CreateComputePipelines && m_vk.CmdDispatch;
2956}
2957
2958
2960{
2961 if( !SupportsCompute() )
2962 return {};
2963
2965
2966 for( size_t i = 0; i < 3; ++i )
2967 {
2968 limits.maxWorkgroupSize[i] = m_deviceLimits.maxComputeWorkGroupSize[i];
2969 limits.maxDispatchGroups[i] = m_deviceLimits.maxComputeWorkGroupCount[i];
2970 }
2971
2972 limits.maxWorkgroupInvocations = m_deviceLimits.maxComputeWorkGroupInvocations;
2973 limits.maxSharedMemoryBytes = m_deviceLimits.maxComputeSharedMemorySize;
2974 limits.maxStorageBufferRange = m_deviceLimits.maxStorageBufferRange;
2975 limits.minStorageBufferOffsetAlignment = m_deviceLimits.minStorageBufferOffsetAlignment;
2976 return limits;
2977}
2978
2979
2981{
2982 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
2983
2984 size_t bindings = 0;
2985
2986 for( const RHI_BIND_GROUP_LAYOUT_DESC& layout : aDesc.bindGroupLayouts )
2987 bindings += layout.entries.size();
2988
2989 // Exceeding a descriptor limit is undefined behaviour rather than a reported error
2991 || aDesc.workgroupX > m_deviceLimits.maxComputeWorkGroupSize[0]
2992 || aDesc.workgroupY > m_deviceLimits.maxComputeWorkGroupSize[1]
2993 || uint64_t( aDesc.workgroupX ) * aDesc.workgroupY > m_deviceLimits.maxComputeWorkGroupInvocations
2994 || aDesc.pushConstantBytes > m_deviceLimits.maxPushConstantsSize
2995 || bindings > m_deviceLimits.maxPerStageDescriptorStorageBuffers
2996 || bindings > m_deviceLimits.maxPerStageResources )
2997 {
2998 return {};
2999 }
3000
3002
3003 if( !shader )
3004 return {};
3005
3006 ComputePipelineAllocation allocation;
3007 allocation.desc = aDesc;
3008 allocation.desc.shader = {};
3009
3010 auto fail = [&]() -> RHI_ComputePipelineHandle
3011 {
3012 destroyPipeline( allocation.native );
3013 DestroyShader( shader );
3014 return {};
3015 };
3016
3017 for( const auto& layout : aDesc.bindGroupLayouts )
3018 {
3019 VkDescriptorSetLayout native = createSetLayout( layout );
3020
3021 if( !native )
3022 return fail();
3023
3024 allocation.native.dsLayouts.push_back( native );
3025 }
3026
3027 VkPushConstantRange pushRange{};
3028 pushRange.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
3029 pushRange.size = aDesc.pushConstantBytes;
3030 VkPipelineLayoutCreateInfo layoutInfo{};
3031 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
3032 layoutInfo.setLayoutCount = static_cast<uint32_t>( allocation.native.dsLayouts.size() );
3033 layoutInfo.pSetLayouts = allocation.native.dsLayouts.data();
3034 layoutInfo.pushConstantRangeCount = aDesc.pushConstantBytes ? 1 : 0;
3035 layoutInfo.pPushConstantRanges = aDesc.pushConstantBytes ? &pushRange : nullptr;
3036
3037 if( m_vk.CreatePipelineLayout( m_device, &layoutInfo, nullptr, &allocation.native.layout ) != VK_SUCCESS )
3038 return fail();
3039
3040 std::vector<uint32_t> values{ aDesc.workgroupX, aDesc.workgroupY };
3041 std::vector<VkSpecializationMapEntry> entries{ { RHI_SPEC_ID_WORKGROUP_X, 0, sizeof( uint32_t ) },
3042 { RHI_SPEC_ID_WORKGROUP_Y, sizeof( uint32_t ),
3043 sizeof( uint32_t ) } };
3044
3045 for( const auto& constant : aDesc.constants )
3046 {
3047 entries.push_back(
3048 { constant.id, static_cast<uint32_t>( values.size() * sizeof( uint32_t ) ), sizeof( uint32_t ) } );
3049 values.push_back( constant.value );
3050 }
3051
3052 VkSpecializationInfo specialization{};
3053 specialization.mapEntryCount = static_cast<uint32_t>( entries.size() );
3054 specialization.pMapEntries = entries.data();
3055 specialization.dataSize = values.size() * sizeof( uint32_t );
3056 specialization.pData = values.data();
3057 VkComputePipelineCreateInfo pipelineInfo{};
3058 pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
3059 pipelineInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
3060 pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
3061 pipelineInfo.stage.module = m_shaders.at( RhiHandleIndex( shader ) ).module;
3062 pipelineInfo.stage.pName = "main";
3063 pipelineInfo.stage.pSpecializationInfo = &specialization;
3064 pipelineInfo.layout = allocation.native.layout;
3065
3066 if( m_vk.CreateComputePipelines( m_device, m_pipelineCache, 1, &pipelineInfo, nullptr, &allocation.native.pipeline )
3067 != VK_SUCCESS )
3068 {
3069 return fail();
3070 }
3071
3073
3074 if( !handle )
3075 return fail();
3076
3077 DestroyShader( shader );
3078 m_computePipelines.emplace( RhiHandleIndex( handle ), std::move( allocation ) );
3079 return handle;
3080}
3081
3082
3084{
3085 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3086
3087 if( !m_computePipelinePool.IsValid( aHandle ) )
3088 return;
3089
3090 auto it = m_computePipelines.find( RhiHandleIndex( aHandle ) );
3091
3092 if( it != m_computePipelines.end() )
3093 {
3094 // Compute submissions finish before returning, so no frame retirement is needed
3095 destroyPipeline( it->second.native );
3096 m_computePipelines.erase( it );
3097 }
3098
3099 m_computePipelinePool.Release( aHandle );
3100}
3101
3102
3104{
3105 if( !m_computePipelinePool.IsValid( aHandle )
3106 || m_computePipelines.find( RhiHandleIndex( aHandle ) ) == m_computePipelines.end() )
3107 {
3108 return {};
3109 }
3110
3111 return { m_deviceLimits.maxComputeWorkGroupInvocations, m_computeSubgroupSize };
3112}
3113
3114
3116{
3117 using OP = RHI_COMPUTE_JOB::OP;
3118
3119 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3120
3122
3123 lookup.pipeline = [this]( RHI_ComputePipelineHandle aHandle ) -> const RHI_COMPUTE_PIPELINE_DESC*
3124 {
3125 auto it = m_computePipelines.find( RhiHandleIndex( aHandle ) );
3126 return m_computePipelinePool.IsValid( aHandle ) && it != m_computePipelines.end() ? &it->second.desc : nullptr;
3127 };
3128
3129 lookup.groupLayout = [this]( RHI_BindGroupHandle aHandle ) -> const RHI_BIND_GROUP_LAYOUT_DESC*
3130 {
3131 auto group = m_bindGroups.find( RhiHandleIndex( aHandle ) );
3132
3133 if( !m_bindGroupPool.IsValid( aHandle ) || group == m_bindGroups.end() )
3134 return nullptr;
3135
3136 auto layout = m_bindGroupLayouts.find( RhiHandleIndex( group->second.layout ) );
3137 return layout != m_bindGroupLayouts.end() ? &layout->second.desc : nullptr;
3138 };
3139
3140 lookup.storageRange = [this]( RHI_BufferHandle aHandle, size_t aOffset, size_t aBytes )
3141 {
3142 auto it = m_buffers.find( RhiHandleIndex( aHandle ) );
3143 return m_bufferPool.IsValid( aHandle ) && it != m_buffers.end() && it->second.type == RHI_BUFFER_TYPE::STORAGE
3144 && RhiRangeWithinBuffer( aOffset, aBytes, it->second.size );
3145 };
3146
3147 if( !SupportsCompute() || !RhiValidateComputeJob( aJob, ComputeDeviceLimits(), lookup ) )
3149
3150 struct READBACK
3151 {
3152 VkBuffer buffer = VK_NULL_HANDLE;
3153 VmaAllocation allocation = VK_NULL_HANDLE;
3154 void* mapped = nullptr;
3155 const RHI_COMPUTE_JOB::COMMAND* command = nullptr;
3156 };
3157
3158 std::vector<int> staging;
3159 std::vector<READBACK> readbacks;
3160
3161 // Uploads come from the shared staging pool, readbacks need host-readable buffers of their own
3162 auto release = [&]()
3163 {
3164 for( int index : staging )
3166
3167 for( const READBACK& readback : readbacks )
3168 vmaDestroyBuffer( m_allocator, readback.buffer, readback.allocation );
3169 };
3170
3171 auto fail = [&]( VkResult aResult )
3172 {
3173 release();
3174 return aResult == VK_ERROR_DEVICE_LOST ? RHI_COMPUTE_RESULT::DEVICE_LOST : RHI_COMPUTE_RESULT::INVALID;
3175 };
3176
3177 VkCommandBuffer cmd = VK_NULL_HANDLE;
3178
3179 if( !allocateCommandBuffer( cmd ) )
3180 return fail( VK_ERROR_OUT_OF_HOST_MEMORY );
3181
3182 VkResult result = beginOneTimeCommands( cmd );
3183
3184 if( result != VK_SUCCESS )
3185 {
3186 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
3187 return fail( result );
3188 }
3189
3190 auto barrier = [&]( VkPipelineStageFlags aSrcStage, VkAccessFlags aSrcAccess, VkPipelineStageFlags aDstStage,
3191 VkAccessFlags aDstAccess )
3192 {
3193 VkMemoryBarrier memory{};
3194 memory.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
3195 memory.srcAccessMask = aSrcAccess;
3196 memory.dstAccessMask = aDstAccess;
3197 m_vk.CmdPipelineBarrier( cmd, aSrcStage, aDstStage, 0, 1, &memory, 0, nullptr, 0, nullptr );
3198 };
3199
3200 const VkPipelineStageFlags work = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
3201 const VkAccessFlags workAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
3202 | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
3203
3204 // Waiting on the queue makes the previous job's writes available but not visible to this one
3205 barrier( VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_MEMORY_WRITE_BIT, work, workAccess );
3206
3207 const ComputePipelineAllocation* pipeline = nullptr;
3208 std::array<RHI_BindGroupHandle, RHI_MAX_BIND_GROUPS> bound{};
3209
3210 for( const RHI_COMPUTE_JOB::COMMAND& command : aJob.Commands() )
3211 {
3212 switch( command.op )
3213 {
3214 case OP::UPLOAD:
3215 case OP::READBACK:
3216 {
3217 if( !command.bytes )
3218 break;
3219
3220 VkBuffer host = VK_NULL_HANDLE;
3221 VkBufferCopy copy{};
3222 copy.size = command.bytes;
3223
3224 if( command.op == OP::UPLOAD )
3225 {
3226 const int index = stageData( command.data.data(), command.bytes, host );
3227
3228 if( index < 0 )
3229 {
3230 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
3231 return fail( VK_ERROR_OUT_OF_DEVICE_MEMORY );
3232 }
3233
3234 staging.push_back( index );
3235 copy.dstOffset = command.offset;
3236 }
3237 else
3238 {
3239 VkBufferCreateInfo info{};
3240 info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
3241 info.size = command.bytes;
3242 info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
3243 VmaAllocationCreateInfo allocInfo{};
3244 allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
3245 allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
3246 VmaAllocationInfo mapped{};
3247 READBACK readback{ VK_NULL_HANDLE, VK_NULL_HANDLE, nullptr, &command };
3248 result = vmaCreateBuffer( m_allocator, &info, &allocInfo, &readback.buffer, &readback.allocation,
3249 &mapped );
3250
3251 if( result != VK_SUCCESS )
3252 {
3253 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
3254 return fail( result );
3255 }
3256
3257 readback.mapped = mapped.pMappedData;
3258 readbacks.push_back( readback );
3259 host = readback.buffer;
3260 copy.srcOffset = command.offset;
3261 }
3262
3263 VkBuffer device = m_buffers.at( RhiHandleIndex( command.buffer ) ).buffer;
3264
3265 barrier( work, workAccess, VK_PIPELINE_STAGE_TRANSFER_BIT,
3266 VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT );
3267 m_vk.CmdCopyBuffer( cmd, command.op == OP::UPLOAD ? host : device, command.op == OP::UPLOAD ? device : host,
3268 1, &copy );
3269 barrier( VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, work | VK_PIPELINE_STAGE_HOST_BIT,
3270 workAccess | VK_ACCESS_HOST_READ_BIT );
3271 break;
3272 }
3273
3274 case OP::BIND:
3275 pipeline = &m_computePipelines.at( RhiHandleIndex( command.pipeline ) );
3276 m_vk.CmdBindPipeline( cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->native.pipeline );
3277 break;
3278
3279 case OP::BIND_GROUP: bound[command.index] = command.group; break;
3280
3281 case OP::PUSH_CONSTANTS:
3282 m_vk.CmdPushConstants( cmd, pipeline->native.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0,
3283 static_cast<uint32_t>( command.bytes ), command.data.data() );
3284 break;
3285
3286 case OP::DISPATCH:
3287 for( size_t i = 0; i < pipeline->desc.bindGroupLayouts.size(); ++i )
3288 {
3289 if( pipeline->desc.bindGroupLayouts[i].entries.empty() )
3290 continue;
3291
3292 VkDescriptorSet set = m_bindGroups.at( RhiHandleIndex( bound[i] ) ).descriptorSet;
3293 m_vk.CmdBindDescriptorSets( cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->native.layout,
3294 static_cast<uint32_t>( i ), 1, &set, 0, nullptr );
3295 }
3296
3297 m_vk.CmdDispatch( cmd, command.groupsX, command.groupsY, 1 );
3298 break;
3299
3300 case OP::BARRIER:
3301 barrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT,
3302 VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT );
3303 break;
3304 }
3305 }
3306
3307 result = m_vk.EndCommandBuffer( cmd );
3308
3309 if( result == VK_SUCCESS )
3310 result = submitCommands( cmd );
3311
3312 if( result == VK_SUCCESS )
3313 result = m_vk.QueueWaitIdle( m_graphicsQueue );
3314
3315 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
3316
3317 if( result != VK_SUCCESS )
3318 return fail( result );
3319
3320 OnQueueIdle();
3321
3322 for( const READBACK& readback : readbacks )
3323 {
3324 vmaInvalidateAllocation( m_allocator, readback.allocation, 0, readback.command->bytes );
3325 std::memcpy( readback.command->destination, readback.mapped, readback.command->bytes );
3326 }
3327
3328 release();
3330}
3331
3332
3334 const RHI_BIND_GROUP_LAYOUT_DESC& desc )
3335{
3336 if( !desc.Validate() )
3337 return {};
3338
3339 if( !m_vk.CreateDescriptorSetLayout )
3340 return {};
3341
3342 const VkDescriptorSetLayout vkLayout = createSetLayout( desc );
3343
3344 if( !vkLayout )
3345 return {};
3346
3348
3349 if( !handle )
3350 {
3351 m_vk.DestroyDescriptorSetLayout( m_device, vkLayout, nullptr );
3352 return {};
3353 }
3354
3355 uint32_t idx = RhiHandleIndex( handle );
3356
3357 m_bindGroupLayouts[idx].layout = vkLayout;
3358 m_bindGroupLayouts[idx].desc = desc;
3359
3360 for( const RHI_BINDING_ENTRY& entry : desc.entries )
3361 {
3363 m_storageDescriptors = true;
3364 }
3365
3366 return handle;
3367}
3368
3369
3374
3375
3377{
3378 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3379
3380 for( auto& entry : m_descriptorPools )
3381 {
3382 if( entry.allocatedSets < entry.maxSets )
3383 {
3384 entry.allocatedSets++;
3385 return entry.pool;
3386 }
3387 }
3388
3389 // Every pool is full. FREE_DESCRIPTOR_SET_BIT lets a destroyed bind group free its own set.
3390 const uint32_t setsPerPool = 128;
3391
3392 VkDescriptorPoolSize poolSizes[] = {
3393 { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, setsPerPool * 2 },
3394 { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, setsPerPool },
3395 { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, setsPerPool * 16 },
3396 };
3397
3398 VkDescriptorPoolCreateInfo poolInfo{};
3399 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
3400 poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
3401 poolInfo.maxSets = setsPerPool;
3402 // Storage descriptors are last, so graphics-only devices leave them out of the pool
3403 poolInfo.poolSizeCount = static_cast<uint32_t>( std::size( poolSizes ) ) - ( m_storageDescriptors ? 0 : 1 );
3404 poolInfo.pPoolSizes = poolSizes;
3405
3406 VkDescriptorPool pool = VK_NULL_HANDLE;
3407
3408 if( m_vk.CreateDescriptorPool( m_device, &poolInfo, nullptr, &pool ) != VK_SUCCESS )
3409 {
3410 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireDescriptorPool FAILED" ) );
3411 return VK_NULL_HANDLE;
3412 }
3413
3414 m_descriptorPools.push_back( { pool, 1, setsPerPool } );
3415 return pool;
3416}
3417
3418
3419void RHI_VULKAN_DEVICE::ReleaseDescriptorSet( VkDescriptorPool pool, VkDescriptorSet dset )
3420{
3421 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3422
3423 for( auto& entry : m_descriptorPools )
3424 {
3425 if( entry.pool == pool )
3426 {
3427 if( dset != VK_NULL_HANDLE && m_vk.FreeDescriptorSets )
3428 m_vk.FreeDescriptorSets( m_device, pool, 1, &dset );
3429
3430 if( entry.allocatedSets > 0 )
3431 entry.allocatedSets--;
3432
3433 return;
3434 }
3435 }
3436}
3437
3438
3440 VkImageLayout newLayout,
3441 VkPipelineStageFlags srcStage,
3442 VkPipelineStageFlags dstStage )
3443{
3444 if( tex.currentLayout == newLayout )
3445 return;
3446
3447 const VkImageMemoryBarrier barrier = imageBarrier( tex.image, VK_IMAGE_ASPECT_COLOR_BIT, tex.currentLayout,
3448 newLayout, LayoutToAccessMask( tex.currentLayout ),
3449 LayoutToAccessMask( newLayout ) );
3450
3451 m_vk.CmdPipelineBarrier( cmd, srcStage, dstStage, 0, 0, nullptr, 0, nullptr, 1, &barrier );
3452 tex.currentLayout = newLayout;
3453}
3454
3455
3457{
3458 if( !m_bindGroupLayoutPool.IsValid( desc.layout ) )
3459 {
3460 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup invalid layout=%u" ),
3461 static_cast<unsigned>( desc.layout.value ) );
3462 return {};
3463 }
3464
3465 if( !m_vk.CreateDescriptorPool || !m_vk.AllocateDescriptorSets || !m_vk.UpdateDescriptorSets )
3466 {
3467 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup missing descriptor funcs" ) );
3468 return {};
3469 }
3470
3471 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3472
3473 uint32_t layoutIdx = RhiHandleIndex( desc.layout );
3474 auto layoutIt = m_bindGroupLayouts.find( layoutIdx );
3475
3476 if( layoutIt == m_bindGroupLayouts.end() )
3477 {
3478 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup layout not found=%u" ),
3479 static_cast<unsigned>( desc.layout.value ) );
3480 return {};
3481 }
3482
3483 const auto& layoutDesc = layoutIt->second.desc;
3484
3485 VkDescriptorPool pool = AcquireDescriptorPool();
3486
3487 if( pool == VK_NULL_HANDLE )
3488 return {};
3489
3490 VkDescriptorSetAllocateInfo allocInfo{};
3491 allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
3492 allocInfo.descriptorPool = pool;
3493 allocInfo.descriptorSetCount = 1;
3494 allocInfo.pSetLayouts = &layoutIt->second.layout;
3495
3496 VkDescriptorSet dset = VK_NULL_HANDLE;
3497
3498 if( m_vk.AllocateDescriptorSets( m_device, &allocInfo, &dset ) != VK_SUCCESS )
3499 {
3500 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup descriptor set alloc FAILED "
3501 "layout=%u" ),
3502 static_cast<unsigned>( desc.layout.value ) );
3503 ReleaseDescriptorSet( pool );
3504 return {};
3505 }
3506
3507 std::vector<VkWriteDescriptorSet> writes;
3508 std::vector<VkDescriptorBufferInfo> bufferInfos;
3509 std::vector<VkDescriptorImageInfo> imageInfos;
3510 bufferInfos.reserve( desc.entries.size() );
3511 imageInfos.reserve( desc.entries.size() );
3512
3513 for( const auto& res : desc.entries )
3514 {
3515 const RHI_BINDING_ENTRY* layoutEntry = layoutDesc.Find( res.binding );
3516 const RHI_BINDING_TYPE bindType = layoutEntry ? layoutEntry->type : RHI_BINDING_TYPE::UNIFORM_BUFFER;
3517
3518 VkWriteDescriptorSet write{};
3519 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
3520 write.dstSet = dset;
3521 write.dstBinding = res.binding;
3522 write.dstArrayElement = 0;
3523 write.descriptorCount = 1;
3524 write.descriptorType = TranslateBindingType( bindType );
3525
3527 {
3528 if( !m_bufferPool.IsValid( res.bufferHandle ) )
3529 {
3530 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup missing buffer "
3531 "layout=%u binding=%u handle=%u" ),
3532 static_cast<unsigned>( desc.layout.value ), res.binding,
3533 static_cast<unsigned>( res.bufferHandle.value ) );
3534 continue;
3535 }
3536
3537 auto bufIt = m_buffers.find( RhiHandleIndex( res.bufferHandle ) );
3538
3539 if( bufIt == m_buffers.end() )
3540 {
3541 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup buffer lookup FAILED "
3542 "layout=%u binding=%u handle=%u" ),
3543 static_cast<unsigned>( desc.layout.value ), res.binding,
3544 static_cast<unsigned>( res.bufferHandle.value ) );
3545 continue;
3546 }
3547
3548 VkDescriptorBufferInfo bi{};
3549 bi.buffer = bufIt->second.buffer;
3550 bi.offset = res.offset;
3551 bi.range = ( res.size > 0 ) ? res.size : bufIt->second.size;
3552 bufferInfos.push_back( bi );
3553
3554 write.pBufferInfo = &bufferInfos.back();
3555 }
3556 else
3557 {
3558 if( !m_texturePool.IsValid( res.textureHandle ) )
3559 {
3560 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup missing texture "
3561 "layout=%u binding=%u handle=%u" ),
3562 static_cast<unsigned>( desc.layout.value ), res.binding,
3563 static_cast<unsigned>( res.textureHandle.value ) );
3564 continue;
3565 }
3566
3567 auto texIt = m_textures.find( RhiHandleIndex( res.textureHandle ) );
3568
3569 if( texIt == m_textures.end() )
3570 {
3571 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup texture lookup FAILED "
3572 "layout=%u binding=%u handle=%u" ),
3573 static_cast<unsigned>( desc.layout.value ), res.binding,
3574 static_cast<unsigned>( res.textureHandle.value ) );
3575 continue;
3576 }
3577
3578 VkDescriptorImageInfo ii{};
3579 ii.sampler = texIt->second.sampler;
3580 ii.imageView = texIt->second.imageView;
3581 ii.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
3582 imageInfos.push_back( ii );
3583
3584 write.pImageInfo = &imageInfos.back();
3585 }
3586
3587 writes.push_back( write );
3588 }
3589
3590 // An unwritten descriptor set would fail validation on its first draw
3591 if( !desc.entries.empty() && writes.empty() )
3592 {
3593 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup produced no descriptor writes "
3594 "layout=%u entries=%zu" ),
3595 static_cast<unsigned>( desc.layout.value ), desc.entries.size() );
3596 ReleaseDescriptorSet( pool, dset );
3597 return {};
3598 }
3599
3600 if( !writes.empty() )
3601 {
3602 m_vk.UpdateDescriptorSets( m_device, static_cast<uint32_t>( writes.size() ),
3603 writes.data(), 0, nullptr );
3604 }
3605
3606 RHI_BindGroupHandle handle = m_bindGroupPool.Allocate();
3607
3608 if( !handle )
3609 {
3610 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateBindGroup handle alloc FAILED" ) );
3611 ReleaseDescriptorSet( pool, dset );
3612 return {};
3613 }
3614
3615 uint32_t idx = RhiHandleIndex( handle );
3616
3618 bga.descriptorSet = dset;
3619 bga.pool = pool;
3620 bga.layout = desc.layout;
3621 bga.bufferIndices.clear();
3622
3623 for( const auto& entry : desc.entries )
3624 {
3625 if( entry.bufferHandle )
3626 bga.bufferIndices.push_back( RhiHandleIndex( entry.bufferHandle ) );
3627 }
3628
3629 return handle;
3630}
3631
3636
3637
3639{
3640 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3641
3642 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdBindDescriptorSets )
3643 {
3644 wxLogTrace( traceRhi, wxS( "VulkanDevice: BindGroup skipped renderPassActive=%d "
3645 "cmd=%p group=%u handle=%u" ),
3646 m_renderPassActive ? 1 : 0, m_currentCmd, groupIndex,
3647 static_cast<unsigned>( group.value ) );
3648 return;
3649 }
3650
3651 if( groupIndex >= static_cast<uint32_t>( RHI_MAX_BIND_GROUPS ) )
3652 {
3653 wxLogTrace( traceRhi, wxS( "VulkanDevice: BindGroup invalid group index=%u" ),
3654 groupIndex );
3655 return;
3656 }
3657
3658 if( !m_bindGroupPool.IsValid( group ) )
3659 {
3660 wxLogTrace( traceRhi, wxS( "VulkanDevice: BindGroup invalid handle=%u" ),
3661 static_cast<unsigned>( group.value ) );
3662 return;
3663 }
3664
3665 auto bgIt = m_bindGroups.find( RhiHandleIndex( group ) );
3666
3667 if( bgIt == m_bindGroups.end() )
3668 {
3669 wxLogTrace( traceRhi, wxS( "VulkanDevice: BindGroup lookup FAILED handle=%u" ),
3670 static_cast<unsigned>( group.value ) );
3671 return;
3672 }
3673
3674 if( !m_pipelinePool.IsValid( m_activePipeline ) )
3675 {
3676 wxLogTrace( traceRhi, wxS( "VulkanDevice: BindGroup missing active pipeline "
3677 "group=%u handle=%u active=%u" ),
3678 groupIndex, static_cast<unsigned>( group.value ),
3679 static_cast<unsigned>( m_activePipeline.value ) );
3680 return;
3681 }
3682
3683 auto pipeIt = m_pipelines.find( RhiHandleIndex( m_activePipeline ) );
3684
3685 if( pipeIt == m_pipelines.end() )
3686 {
3687 wxLogTrace( traceRhi, wxS( "VulkanDevice: BindGroup pipeline lookup FAILED active=%u" ),
3688 static_cast<unsigned>( m_activePipeline.value ) );
3689 return;
3690 }
3691
3692 for( uint32_t bufferIndex : bgIt->second.bufferIndices )
3693 {
3694 auto bufIt = m_buffers.find( bufferIndex );
3695
3696 if( bufIt != m_buffers.end() )
3697 MarkBufferInUse( bufIt->second );
3698 }
3699
3700 m_vk.CmdBindDescriptorSets( m_currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
3701 pipeIt->second.layout, groupIndex, 1,
3702 &bgIt->second.descriptorSet, 0, nullptr );
3703}
3704
3705
3706static VkAttachmentLoadOp TranslateLoadOp( RHI_LOAD_OP op )
3707{
3708 switch( op )
3709 {
3710 case RHI_LOAD_OP::LOAD: return VK_ATTACHMENT_LOAD_OP_LOAD;
3711 case RHI_LOAD_OP::CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR;
3712 case RHI_LOAD_OP::DONT_CARE: return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3713 }
3714
3715 return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
3716}
3717
3718
3719static VkAttachmentStoreOp TranslateStoreOp( RHI_STORE_OP op )
3720{
3721 switch( op )
3722 {
3723 case RHI_STORE_OP::STORE: return VK_ATTACHMENT_STORE_OP_STORE;
3724 case RHI_STORE_OP::DONT_CARE: return VK_ATTACHMENT_STORE_OP_DONT_CARE;
3725 }
3726
3727 return VK_ATTACHMENT_STORE_OP_DONT_CARE;
3728}
3729
3730
3732{
3733 if( !m_vk.CreateRenderPass )
3734 return {};
3735
3736 if( desc.colorAttachments.empty() && !desc.depthStencilAttachment.enabled )
3737 return {};
3738
3739 // After rendering a color target is left ready to sample. CLEAR and DONT_CARE discard old
3740 // contents, so their initial layout is irrelevant.
3741 std::vector<VK_ATTACHMENT> colors;
3742
3743 for( const auto& ca : desc.colorAttachments )
3744 {
3745 colors.push_back( { TranslateTextureFormat( ca.format ), TranslateLoadOp( ca.loadOp ),
3746 TranslateStoreOp( ca.storeOp ),
3747 ca.loadOp == RHI_LOAD_OP::LOAD ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
3748 : VK_IMAGE_LAYOUT_UNDEFINED,
3749 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL } );
3750 }
3751
3752 std::optional<VK_ATTACHMENT> depth;
3753 const auto& ds = desc.depthStencilAttachment;
3754
3755 if( ds.enabled )
3756 {
3757 depth = VK_ATTACHMENT{ RhiDefaultDepthFormat(), TranslateLoadOp( ds.depthLoadOp ),
3758 TranslateStoreOp( ds.depthStoreOp ),
3759 ds.depthLoadOp == RHI_LOAD_OP::LOAD ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
3760 : VK_IMAGE_LAYOUT_UNDEFINED,
3761 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
3762 }
3763
3764 const VkRenderPass renderPass = makeRenderPass( colors, depth );
3765
3766 if( !renderPass )
3767 return {};
3768
3769 RHI_RenderPassHandle handle = m_renderPassPool.Allocate();
3770
3771 if( !handle )
3772 {
3773 m_vk.DestroyRenderPass( m_device, renderPass, nullptr );
3774 return {};
3775 }
3776
3777 uint32_t idx = RhiHandleIndex( handle );
3778
3780 rpa.renderPass = renderPass;
3781 rpa.colorDescs = desc.colorAttachments;
3783
3784 return handle;
3785}
3786
3787
3792
3793
3795{
3796 if( desc.width == 0 || desc.height == 0 || !m_allocator || !m_vk.CreateRenderPass )
3797 return {};
3798
3799 if( desc.colorFormats.empty() && !desc.hasDepthStencil )
3800 return {};
3801
3803 fb.colorFormats = desc.colorFormats;
3804 fb.width = desc.width;
3805 fb.height = desc.height;
3806
3807 auto fail = [&]()
3808 {
3810 return RHI_FramebufferHandle{};
3811 };
3812
3813 for( RHI_TEXTURE_FORMAT format : desc.colorFormats )
3814 {
3815 const VkFormat vkFormat = TranslateTextureFormat( format );
3816 VkImage image = VK_NULL_HANDLE;
3817 VmaAllocation allocation = VK_NULL_HANDLE;
3818
3819 if( !createImage2D( vkFormat, desc.width, desc.height,
3820 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT
3821 | VK_IMAGE_USAGE_SAMPLED_BIT,
3822 true, image, allocation ) )
3823 {
3824 return fail();
3825 }
3826
3827 fb.colorImages.push_back( image );
3828 fb.colorAllocations.push_back( allocation );
3829 fb.colorViews.push_back( createView( image, vkFormat, VK_IMAGE_ASPECT_COLOR_BIT ) );
3830
3831 if( !fb.colorViews.back() )
3832 return fail();
3833 }
3834
3835 if( desc.hasDepthStencil )
3836 {
3837 if( !createImage2D( RhiDefaultDepthFormat(), desc.width, desc.height,
3838 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT
3839 | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
3840 false, fb.depthImage, fb.depthAllocation ) )
3841 {
3842 return fail();
3843 }
3844
3845 fb.depthView = createView( fb.depthImage, RhiDefaultDepthFormat(), VK_IMAGE_ASPECT_DEPTH_BIT );
3846
3847 if( !fb.depthView )
3848 return fail();
3849 }
3850
3851 // An internal render pass used only for VkFramebuffer compatibility
3852 std::vector<VK_ATTACHMENT> colors;
3853
3854 for( RHI_TEXTURE_FORMAT format : desc.colorFormats )
3855 {
3856 colors.push_back( { TranslateTextureFormat( format ), VK_ATTACHMENT_LOAD_OP_DONT_CARE,
3857 VK_ATTACHMENT_STORE_OP_STORE, VK_IMAGE_LAYOUT_UNDEFINED,
3858 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL } );
3859 }
3860
3861 std::optional<VK_ATTACHMENT> depth;
3862
3863 if( desc.hasDepthStencil )
3864 {
3865 depth = VK_ATTACHMENT{ RhiDefaultDepthFormat(), VK_ATTACHMENT_LOAD_OP_DONT_CARE,
3866 VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_UNDEFINED,
3867 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
3868 }
3869
3870 fb.internalRenderPass = makeRenderPass( colors, depth );
3871
3872 if( !fb.internalRenderPass )
3873 return fail();
3874
3875 std::vector<VkImageView> framebufferAttachments = fb.colorViews;
3876
3877 if( fb.depthView )
3878 framebufferAttachments.push_back( fb.depthView );
3879
3880 VkFramebufferCreateInfo fbInfo{};
3881 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
3882 fbInfo.renderPass = fb.internalRenderPass;
3883 fbInfo.attachmentCount = static_cast<uint32_t>( framebufferAttachments.size() );
3884 fbInfo.pAttachments = framebufferAttachments.data();
3885 fbInfo.width = desc.width;
3886 fbInfo.height = desc.height;
3887 fbInfo.layers = 1;
3888
3889 if( m_vk.CreateFramebuffer( m_device, &fbInfo, nullptr, &fb.framebuffer ) != VK_SUCCESS )
3890 return fail();
3891
3892 RHI_FramebufferHandle handle = m_framebufferPool.Allocate();
3893
3894 if( !handle )
3895 return fail();
3896
3898 fba = std::move( fb );
3899
3900 for( uint32_t i = 0; i < fba.colorViews.size(); i++ )
3901 {
3902 RHI_TextureHandle texHandle = m_texturePool.Allocate();
3903
3904 if( texHandle )
3905 {
3906 // The sampler lets the compositor bind FBO color textures as combined image samplers
3907 const VkSampler sampler = createSampler( VK_FILTER_LINEAR );
3908
3909 if( !sampler )
3910 {
3911 // Skip the texture rather than store a null sampler that a later bind would dereference
3912 wxLogTrace( traceRhi, wxS( "VulkanDevice: FBO color sampler creation failed" ) );
3913 m_texturePool.Release( texHandle );
3914 fba.colorTextureHandles.push_back( RHI_TextureHandle{} );
3915 continue;
3916 }
3917
3918 TextureAllocation& texAlloc = m_textures[RhiHandleIndex( texHandle )];
3919 texAlloc.image = fba.colorImages[i];
3920 texAlloc.allocation = fba.colorAllocations[i];
3921 texAlloc.imageView = fba.colorViews[i];
3922 texAlloc.sampler = sampler;
3923 texAlloc.width = desc.width;
3924 texAlloc.height = desc.height;
3925 texAlloc.format = desc.colorFormats[i];
3926 texAlloc.ownedByFBO = true;
3927 }
3928
3929 fba.colorTextureHandles.push_back( texHandle );
3930 }
3931
3932 // The compositor may sample these images before anything renders into them
3933 if( !fba.colorTextureHandles.empty() && m_commandPool )
3934 {
3935 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
3936
3937 VkCommandBuffer initCmd = VK_NULL_HANDLE;
3938
3939 if( allocateCommandBuffer( initCmd ) )
3940 {
3941 beginOneTimeCommands( initCmd );
3942
3943 for( RHI_TextureHandle texHandle : fba.colorTextureHandles )
3944 {
3945 // A null handle marks a failed registration, and RhiHandleIndex({}) would alias slot 0
3946 if( !texHandle )
3947 continue;
3948
3949 auto texIt = m_textures.find( RhiHandleIndex( texHandle ) );
3950
3951 if( texIt != m_textures.end() )
3952 {
3953 TransitionTexture( initCmd, texIt->second,
3954 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
3955 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
3956 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT );
3957 }
3958 }
3959
3960 m_vk.EndCommandBuffer( initCmd );
3961
3962 // The queue runs the transitions ahead of any pass that uses the images, so waiting
3963 // for them would only stall this thread behind every frame still queued
3964 if( submitCommands( initCmd ) == VK_SUCCESS )
3965 {
3966 m_submittedCmds.push_back( initCmd );
3968 }
3969 else
3970 {
3971 wxLogTrace( traceRhi, wxS( "VulkanDevice: FBO init-transition submit failed" ) );
3972 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &initCmd );
3973 }
3974 }
3975 }
3976
3977 return handle;
3978}
3979
3980
3985
3986
3988 RHI_FramebufferHandle handle, uint32_t attachmentIndex ) const
3989{
3990 if( !handle )
3991 return {};
3992
3993 auto it = m_framebuffers.find( RhiHandleIndex( handle ) );
3994
3995 if( it == m_framebuffers.end() )
3996 return {};
3997
3998 if( attachmentIndex >= it->second.colorTextureHandles.size() )
3999 return {};
4000
4001 return it->second.colorTextureHandles[attachmentIndex];
4002}
4003
4004
4006{
4007 if( m_renderPassActive )
4008 return false;
4009
4010 if( !m_renderPassPool.IsValid( desc.renderPass ) )
4011 return false;
4012
4013 if( !m_framebufferPool.IsValid( desc.framebuffer ) )
4014 return false;
4015
4016 auto rpIt = m_renderPasses.find( RhiHandleIndex( desc.renderPass ) );
4017 auto fbIt = m_framebuffers.find( RhiHandleIndex( desc.framebuffer ) );
4018
4019 if( rpIt == m_renderPasses.end() || fbIt == m_framebuffers.end() )
4020 return false;
4021
4022 RenderPassAllocation& rpa = rpIt->second;
4023 FramebufferAllocation& fba = fbIt->second;
4024
4025 // Command pools are externally synchronized, and this one is shared with main-thread uploads
4026 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4027
4028 // A submitted buffer stays pending until the queue idles, so each pass takes its own
4030 return false;
4031
4032 if( beginOneTimeCommands( m_currentCmd ) != VK_SUCCESS )
4033 return false;
4034
4035 // Every render pass leaves its depth attachment in the attachment layout
4036 if( fba.depthImage && rpa.depthStencilDesc.enabled )
4037 fba.depthRendered = true;
4038
4039 std::vector<VkClearValue> clearValues;
4040 clearValues.reserve( rpa.colorDescs.size() + 1 );
4041
4042 for( const auto& ca : rpa.colorDescs )
4043 {
4044 VkClearValue cv{};
4045
4046 if( desc.hasClearColorOverride )
4047 {
4048 cv.color.float32[0] = desc.clearColorOverride[0];
4049 cv.color.float32[1] = desc.clearColorOverride[1];
4050 cv.color.float32[2] = desc.clearColorOverride[2];
4051 cv.color.float32[3] = desc.clearColorOverride[3];
4052 }
4053 else
4054 {
4055 cv.color.float32[0] = ca.clearColor[0];
4056 cv.color.float32[1] = ca.clearColor[1];
4057 cv.color.float32[2] = ca.clearColor[2];
4058 cv.color.float32[3] = ca.clearColor[3];
4059 }
4060
4061 clearValues.push_back( cv );
4062 }
4063
4067 {
4068 VkClearValue depthStencilClear{};
4069 depthStencilClear.depthStencil.depth = rpa.depthStencilDesc.clearDepth;
4070 depthStencilClear.depthStencil.stencil = rpa.depthStencilDesc.clearStencil;
4071 clearValues.push_back( depthStencilClear );
4072 }
4073
4074 VkRenderPassBeginInfo rpBeginInfo{};
4075 rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
4076 rpBeginInfo.renderPass = rpa.renderPass;
4077 rpBeginInfo.framebuffer = fba.framebuffer;
4078 rpBeginInfo.renderArea = { { 0, 0 }, { fba.width, fba.height } };
4079 rpBeginInfo.clearValueCount = static_cast<uint32_t>( clearValues.size() );
4080 rpBeginInfo.pClearValues = clearValues.data();
4081
4082 m_vk.CmdBeginRenderPass( m_currentCmd, &rpBeginInfo, VK_SUBPASS_CONTENTS_INLINE );
4083
4084 m_renderPassActive = true;
4085
4086 // BindPipeline skips a matching pipeline, so the first bind of each pass must not match
4087 m_activePipeline = {};
4088
4089 return true;
4090}
4091
4092
4094{
4095 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4096
4098 return false;
4099
4100 m_vk.CmdEndRenderPass( m_currentCmd );
4101 m_vk.EndCommandBuffer( m_currentCmd );
4102
4103 // No wait, as the queue runs passes in order and paths that need the GPU caught up call SyncSubmittedPasses()
4104 if( VkResult result = submitPassCmd( m_currentCmd ); result != VK_SUCCESS )
4105 wxLogTrace( traceRhi, wxS( "VulkanDevice: EndRenderPass submit=%d" ), result );
4106
4108
4109 m_renderPassActive = false;
4110 m_activePipeline = {};
4111
4112 return true;
4113}
4114
4115
4117{
4118 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4119
4120 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdSetViewport )
4121 return;
4122
4123 VkViewport vp{};
4124 vp.x = viewport.x;
4125 vp.y = viewport.y;
4126 vp.width = viewport.width;
4127 vp.height = viewport.height;
4128 vp.minDepth = viewport.minDepth;
4129 vp.maxDepth = viewport.maxDepth;
4130
4131 m_vk.CmdSetViewport( m_currentCmd, 0, 1, &vp );
4132}
4133
4134
4136{
4137 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4138
4139 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdSetScissor )
4140 return;
4141
4142 VkRect2D rect{};
4143 rect.offset = { scissor.x, scissor.y };
4144 rect.extent = { scissor.width, scissor.height };
4145
4146 m_vk.CmdSetScissor( m_currentCmd, 0, 1, &rect );
4147}
4148
4149
4151{
4152 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4153
4154 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdBindPipeline )
4155 return;
4156
4157 if( !m_pipelinePool.IsValid( pipeline ) )
4158 return;
4159
4160 if( pipeline == m_activePipeline )
4161 return;
4162
4163 auto it = m_pipelines.find( RhiHandleIndex( pipeline ) );
4164
4165 if( it == m_pipelines.end() )
4166 return;
4167
4168 m_vk.CmdBindPipeline( m_currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, it->second.pipeline );
4169 m_activePipeline = pipeline;
4170}
4171
4172
4173void RHI_VULKAN_DEVICE::SetPushConstants( const void* data, size_t sizeBytes )
4174{
4175 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4176
4177 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdPushConstants )
4178 return;
4179
4180 if( !data || sizeBytes == 0 || sizeBytes > RHI_MAX_PUSH_CONSTANT_BYTES )
4181 return;
4182
4183 if( !m_pipelinePool.IsValid( m_activePipeline ) )
4184 return;
4185
4186 auto it = m_pipelines.find( RhiHandleIndex( m_activePipeline ) );
4187
4188 if( it == m_pipelines.end() )
4189 return;
4190
4191 m_vk.CmdPushConstants( m_currentCmd, it->second.layout,
4192 VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
4193 0, static_cast<uint32_t>( sizeBytes ), data );
4194}
4195
4196
4198{
4199 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4200
4201 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdBindVertexBuffers )
4202 return;
4203
4204 if( !m_bufferPool.IsValid( buffer ) )
4205 return;
4206
4207 auto it = m_buffers.find( RhiHandleIndex( buffer ) );
4208
4209 if( it == m_buffers.end() )
4210 return;
4211
4212 MarkBufferInUse( it->second );
4213
4214 VkBuffer vkBuf = it->second.buffer;
4215 VkDeviceSize vkOffset = 0;
4216
4217 m_vk.CmdBindVertexBuffers( m_currentCmd, slot, 1, &vkBuf, &vkOffset );
4218}
4219
4220
4222{
4223 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4224
4225 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdBindIndexBuffer )
4226 return;
4227
4228 if( !m_bufferPool.IsValid( buffer ) )
4229 return;
4230
4231 auto it = m_buffers.find( RhiHandleIndex( buffer ) );
4232
4233 if( it == m_buffers.end() )
4234 return;
4235
4236 MarkBufferInUse( it->second );
4237
4238 m_vk.CmdBindIndexBuffer( m_currentCmd, it->second.buffer, 0, VK_INDEX_TYPE_UINT16 );
4239}
4240
4241
4242void RHI_VULKAN_DEVICE::Draw( uint32_t vertexCount, uint32_t firstVertex )
4243{
4244 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4245
4246 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdDraw )
4247 return;
4248
4249 m_vk.CmdDraw( m_currentCmd, vertexCount, 1, firstVertex, 0 );
4250}
4251
4252
4253void RHI_VULKAN_DEVICE::DrawInstanced( uint32_t vertexCount, uint32_t instanceCount,
4254 uint32_t firstVertex, int32_t /*vertexOffset*/,
4255 uint32_t firstInstance )
4256{
4257 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4258
4259 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdDraw )
4260 return;
4261
4262 m_vk.CmdDraw( m_currentCmd, vertexCount, instanceCount, firstVertex, firstInstance );
4263}
4264
4265
4266void RHI_VULKAN_DEVICE::DrawIndexedInstanced( uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex,
4267 int32_t vertexOffset, uint32_t firstInstance )
4268{
4269 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
4270
4271 if( !m_renderPassActive || !m_currentCmd || !m_vk.CmdDrawIndexed )
4272 return;
4273
4274 m_vk.CmdDrawIndexed( m_currentCmd, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance );
4275}
4276
4277
4278bool RHI_VULKAN_DEVICE::FindPresentQueueFamily( VkSurfaceKHR vkSurface, uint32_t& outFamily )
4279{
4280 if( !m_vk.GetPhysicalDeviceSurfaceSupportKHR )
4281 return false;
4282
4283 // Present goes through the graphics queue, the only queue the device creates
4284 VkBool32 presentSupport = VK_FALSE;
4285
4286 if( m_vk.GetPhysicalDeviceSurfaceSupportKHR( m_physicalDevice, m_graphicsQueueFamily, vkSurface,
4287 &presentSupport ) != VK_SUCCESS
4288 || !presentSupport )
4289 {
4290 return false;
4291 }
4292
4293 outFamily = m_graphicsQueueFamily;
4294 return true;
4295}
4296
4297
4298VkPipeline RHI_VULKAN_DEVICE::EnsurePresentBlit( VkFormat aSwapchainFormat )
4299{
4300 if( auto it = m_presentBlitPipelines.find( aSwapchainFormat ); it != m_presentBlitPipelines.end() )
4301 return it->second;
4302
4304 {
4305 VkDescriptorSetLayoutBinding dsBinding{};
4306 dsBinding.binding = 0;
4307 dsBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
4308 dsBinding.descriptorCount = 1;
4309 dsBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
4310
4311 VkDescriptorSetLayoutCreateInfo dsLayoutInfo{};
4312 dsLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
4313 dsLayoutInfo.bindingCount = 1;
4314 dsLayoutInfo.pBindings = &dsBinding;
4315
4316 if( m_vk.CreateDescriptorSetLayout( m_device, &dsLayoutInfo, nullptr, &m_presentBlitDSLayout )
4317 != VK_SUCCESS )
4318 {
4320 return VK_NULL_HANDLE;
4321 }
4322 }
4323
4324 if( !m_presentBlitLayout )
4325 {
4326 VkPipelineLayoutCreateInfo layoutInfo{};
4327 layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
4328 layoutInfo.setLayoutCount = 1;
4329 layoutInfo.pSetLayouts = &m_presentBlitDSLayout;
4330
4331 if( m_vk.CreatePipelineLayout( m_device, &layoutInfo, nullptr, &m_presentBlitLayout ) != VK_SUCCESS )
4332 {
4334 return VK_NULL_HANDLE;
4335 }
4336 }
4337
4338 VkShaderModuleCreateInfo vertModuleInfo{};
4339 vertModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
4340 vertModuleInfo.codeSize = k_present_blit_vert.spirvSize;
4341 vertModuleInfo.pCode = reinterpret_cast<const uint32_t*>( k_present_blit_vert.spirv );
4342
4343 VkShaderModule vertModule = VK_NULL_HANDLE;
4344
4345 if( m_vk.CreateShaderModule( m_device, &vertModuleInfo, nullptr, &vertModule ) != VK_SUCCESS )
4346 return VK_NULL_HANDLE;
4347
4348 VkShaderModuleCreateInfo fragModuleInfo{};
4349 fragModuleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
4350 fragModuleInfo.codeSize = k_present_blit_frag.spirvSize;
4351 fragModuleInfo.pCode = reinterpret_cast<const uint32_t*>( k_present_blit_frag.spirv );
4352
4353 VkShaderModule fragModule = VK_NULL_HANDLE;
4354
4355 if( m_vk.CreateShaderModule( m_device, &fragModuleInfo, nullptr, &fragModule ) != VK_SUCCESS )
4356 {
4357 m_vk.DestroyShaderModule( m_device, vertModule, nullptr );
4358 return VK_NULL_HANDLE;
4359 }
4360
4361 // Render pass compatible with the swapchain format
4362 const VkRenderPass pipelineRenderPass = makeRenderPass( { presentAttachment( aSwapchainFormat ) }, std::nullopt );
4363
4364 if( !pipelineRenderPass )
4365 {
4366 m_vk.DestroyShaderModule( m_device, vertModule, nullptr );
4367 m_vk.DestroyShaderModule( m_device, fragModule, nullptr );
4368 return VK_NULL_HANDLE;
4369 }
4370
4371 // The fullscreen triangle comes from gl_VertexIndex, so there is no vertex input, blending or depth
4372 VkPipelineVertexInputStateCreateInfo vertexInput{};
4373 vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
4374
4375 VkPipelineColorBlendAttachmentState blendAtt{};
4376 blendAtt.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
4377 | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
4378
4379 VkPipelineDepthStencilStateCreateInfo depthStencil{};
4380 depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
4381
4382 VkPipeline pipeline = VK_NULL_HANDLE;
4383 const VkResult result = buildPipeline( vertModule, fragModule, vertexInput, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
4384 { blendAtt }, depthStencil,
4385 { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR },
4386 m_presentBlitLayout, pipelineRenderPass, pipeline );
4387
4388 m_vk.DestroyShaderModule( m_device, vertModule, nullptr );
4389 m_vk.DestroyShaderModule( m_device, fragModule, nullptr );
4390 m_vk.DestroyRenderPass( m_device, pipelineRenderPass, nullptr );
4391
4392 if( result != VK_SUCCESS )
4393 return VK_NULL_HANDLE;
4394
4395 m_presentBlitPipelines[aSwapchainFormat] = pipeline;
4396
4397 wxLogTrace( traceRhi, wxS( "VulkanDevice: present blit pipeline created for format=%d" ),
4398 static_cast<int>( aSwapchainFormat ) );
4399
4400 return pipeline;
4401}
4402
4403
4405{
4406 for( const auto& [format, pipeline] : m_presentBlitPipelines )
4407 m_vk.DestroyPipeline( m_device, pipeline, nullptr );
4408
4409 m_vk.DestroyPipelineLayout( m_device, m_presentBlitLayout, nullptr );
4410 m_vk.DestroyDescriptorSetLayout( m_device, m_presentBlitDSLayout, nullptr );
4411
4412 m_presentBlitPipelines.clear();
4415}
4416
4417
4419{
4420 std::vector<RHI_PRESENT_MODE> offered;
4421 bool composited = aSurface.wayland;
4422
4423#ifdef VK_USE_PLATFORM_XLIB_KHR
4424 // Asked on every swapchain build since a compositing manager can start or stop at any time
4425 composited = composited || aSurface.x11Owner.HasCompositor();
4426#endif
4427
4428 if( m_vk.GetPhysicalDeviceSurfacePresentModesKHR )
4429 {
4430 uint32_t modeCount = 0;
4431 m_vk.GetPhysicalDeviceSurfacePresentModesKHR( m_physicalDevice, aSurface.surface, &modeCount, nullptr );
4432 std::vector<VkPresentModeKHR> modes( modeCount );
4433 m_vk.GetPhysicalDeviceSurfacePresentModesKHR( m_physicalDevice, aSurface.surface, &modeCount, modes.data() );
4434
4435 for( VkPresentModeKHR mode : modes )
4436 {
4437 wxLogTrace( traceRhi, wxS( "VulkanDevice: present mode available=%d" ), static_cast<int>( mode ) );
4438
4439 if( mode == VK_PRESENT_MODE_MAILBOX_KHR )
4440 offered.push_back( RHI_PRESENT_MODE::MAILBOX );
4441 else if( mode == VK_PRESENT_MODE_IMMEDIATE_KHR )
4442 offered.push_back( RHI_PRESENT_MODE::IMMEDIATE );
4443 }
4444 }
4445
4446#if defined( __linux__ )
4447 const bool immediateUnderCompositor = true;
4448#else
4449 const bool immediateUnderCompositor = false;
4450#endif
4451
4452 const RHI_PRESENT_MODE chosen = RhiChoosePresentMode( offered, composited, immediateUnderCompositor );
4453
4454 wxLogTrace( traceRhi, wxS( "VulkanDevice: present mode %s (compositor %s)" ),
4455 std::string( magic_enum::enum_name( chosen ) ), composited ? "owns the present" : "absent" );
4456
4457 switch( chosen )
4458 {
4459 case RHI_PRESENT_MODE::MAILBOX: return VK_PRESENT_MODE_MAILBOX_KHR;
4460 case RHI_PRESENT_MODE::IMMEDIATE: return VK_PRESENT_MODE_IMMEDIATE_KHR;
4461 case RHI_PRESENT_MODE::FIFO: break;
4462 }
4463
4464 return VK_PRESENT_MODE_FIFO_KHR;
4465}
4466
4467
4469{
4470 if( !m_vk.GetSwapchainImagesKHR || !m_vk.CreateImageView )
4471 return false;
4472
4473 uint32_t imageCount = 0;
4474 m_vk.GetSwapchainImagesKHR( m_device, swa.swapchain, &imageCount, nullptr );
4475
4476 if( imageCount == 0 )
4477 return false;
4478
4479 swa.images.resize( imageCount );
4480 m_vk.GetSwapchainImagesKHR( m_device, swa.swapchain, &imageCount, swa.images.data() );
4481
4482 swa.imageViews.resize( imageCount );
4483
4484 for( uint32_t i = 0; i < imageCount; i++ )
4485 {
4486 swa.imageViews[i] = createView( swa.images[i], swa.format, VK_IMAGE_ASPECT_COLOR_BIT );
4487
4488 if( !swa.imageViews[i] )
4489 return false;
4490 }
4491
4492 // Kept across resizes and destroyed with the swapchain, so only a larger image count adds semaphores
4493 VkSemaphoreCreateInfo semInfo{};
4494 semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
4495
4496 while( swa.renderFinished.size() < imageCount )
4497 {
4498 VkSemaphore semaphore = VK_NULL_HANDLE;
4499
4500 if( !m_vk.CreateSemaphore || m_vk.CreateSemaphore( m_device, &semInfo, nullptr, &semaphore ) != VK_SUCCESS )
4501 return false;
4502
4503 swa.renderFinished.push_back( semaphore );
4504 }
4505
4506 // The present pass takes each swapchain image from UNDEFINED to PRESENT_SRC
4508 swa.presentRenderPass = makeRenderPass( { presentAttachment( swa.format ) }, std::nullopt );
4509
4510 if( !swa.presentRenderPass )
4511 {
4512 return false;
4513 }
4514
4515 swa.presentFramebuffers.resize( imageCount );
4516
4517 for( uint32_t i = 0; i < imageCount; i++ )
4518 {
4519 VkFramebufferCreateInfo fbInfo{};
4520 fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
4521 fbInfo.renderPass = swa.presentRenderPass;
4522 fbInfo.attachmentCount = 1;
4523 fbInfo.pAttachments = &swa.imageViews[i];
4524 fbInfo.width = swa.extent.width;
4525 fbInfo.height = swa.extent.height;
4526 fbInfo.layers = 1;
4527
4528 if( m_vk.CreateFramebuffer( m_device, &fbInfo, nullptr,
4529 &swa.presentFramebuffers[i] ) != VK_SUCCESS )
4530 {
4531 return false;
4532 }
4533 }
4534
4535 // The present blit has its own descriptor pool because rendering can exhaust the shared pool's samplers
4537 {
4538 VkDescriptorPoolSize presentPoolSize{};
4539 presentPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
4540 presentPoolSize.descriptorCount = 1;
4541
4542 VkDescriptorPoolCreateInfo presentPoolInfo{};
4543 presentPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
4544 presentPoolInfo.maxSets = 1;
4545 presentPoolInfo.poolSizeCount = 1;
4546 presentPoolInfo.pPoolSizes = &presentPoolSize;
4547
4548 if( m_vk.CreateDescriptorPool( m_device, &presentPoolInfo, nullptr,
4549 &swa.presentDescPool ) != VK_SUCCESS )
4550 {
4551 return false;
4552 }
4553
4554 VkDescriptorSetAllocateInfo dsAlloc{};
4555 dsAlloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
4556 dsAlloc.descriptorPool = swa.presentDescPool;
4557 dsAlloc.descriptorSetCount = 1;
4558 dsAlloc.pSetLayouts = &m_presentBlitDSLayout;
4559
4560 if( m_vk.AllocateDescriptorSets( m_device, &dsAlloc,
4561 &swa.presentDescSet ) != VK_SUCCESS )
4562 {
4563 return false;
4564 }
4565 }
4566
4567 return true;
4568}
4569
4570
4572{
4573 if( m_device && m_vk.DeviceWaitIdle )
4574 m_vk.DeviceWaitIdle( m_device );
4575
4576 // Device idle does not cover the presentation engine's wait on renderFinished, which is destroyed
4577 // with the swapchain. Resizes reuse the semaphores, so only a full destroy waits.
4578 if( !aKeepSwapchain && m_presentWait && swa.swapchain && swa.lastPresentId > 0 )
4579 m_vk.WaitForPresentKHR( m_device, swa.swapchain, swa.lastPresentId, 100'000'000 );
4580
4581 // A VkFramebuffer still references its image views, so it goes first
4582 for( VkFramebuffer fb : swa.presentFramebuffers )
4583 m_vk.DestroyFramebuffer( m_device, fb, nullptr );
4584
4585 m_vk.DestroyRenderPass( m_device, swa.presentRenderPass, nullptr );
4586
4587 for( VkImageView view : swa.imageViews )
4588 m_vk.DestroyImageView( m_device, view, nullptr );
4589
4590 m_vk.DestroyDescriptorPool( m_device, swa.presentDescPool, nullptr );
4591
4592 swa.presentFramebuffers.clear();
4593 swa.imageViews.clear();
4597
4598 swa.images.clear();
4599
4600 if( !aKeepSwapchain && swa.swapchain && m_vk.DestroySwapchainKHR )
4601 {
4602 m_vk.DestroySwapchainKHR( m_device, swa.swapchain, nullptr );
4604 }
4605}
4606
4607
4609{
4610 if( !m_initialized || !desc.window.nativeHandle )
4611 return {};
4612
4613 VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
4614 bool waylandSurface = false;
4615#ifdef VK_USE_PLATFORM_XLIB_KHR
4616 X11_CONNECTION_OWNER ownedX11;
4617
4618 if( vkSurface == VK_NULL_HANDLE && desc.window.nativeDisplay
4621 && ( m_vk.CreateXcbSurfaceKHR || m_vk.CreateXlibSurfaceKHR ) )
4622 {
4623 // GTK owns the UI thread's Display, so the render thread opens its own connection to the
4624 // widget's display, which need not be $DISPLAY
4625 Display* uiDisplay = static_cast<Display*>( desc.window.nativeDisplay );
4626 const char* displayName = ( desc.window.displayType == RHI_DISPLAY_TYPE::X11 )
4627 ? XDisplayString( uiDisplay )
4628 : nullptr;
4629 ::Window window = reinterpret_cast<::Window>( desc.window.nativeHandle );
4630
4631#ifdef VK_USE_PLATFORM_XCB_KHR
4632 // Connect through xcb, never XOpenDisplay. Xlib connects under its process-wide lock,
4633 // and GDK takes that lock while holding a server grab, which deadlocks both threads.
4634 if( m_vk.CreateXcbSurfaceKHR )
4635 {
4636 xcb_connection_t* connection = xcb_connect( displayName, &ownedX11.screen );
4637
4638 if( connection && !xcb_connection_has_error( connection ) )
4639 {
4640 VkXcbSurfaceCreateInfoKHR xcbInfo{};
4641 xcbInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
4642 xcbInfo.connection = connection;
4643 xcbInfo.window = static_cast<xcb_window_t>( window );
4644
4645 VkResult xcbResult =
4646 m_vk.CreateXcbSurfaceKHR( m_instance, &xcbInfo, nullptr, &vkSurface );
4647
4648 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateXcbSurface conn=%p "
4649 "window=0x%x result=%d surface=%p" ),
4650 static_cast<void*>( xcbInfo.connection ),
4651 static_cast<unsigned>( xcbInfo.window ),
4652 static_cast<int>( xcbResult ),
4653 static_cast<void*>( vkSurface ) );
4654 }
4655 else
4656 {
4657 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateSurface xcb_connect failed" ) );
4658 }
4659
4660 if( vkSurface != VK_NULL_HANDLE )
4661 ownedX11.xcb = connection;
4662 else if( connection )
4663 xcb_disconnect( connection );
4664 }
4665#endif
4666
4667 // Only drivers without VK_KHR_xcb_surface get here, so the Xlib lock hazard remains
4668 if( vkSurface == VK_NULL_HANDLE && m_vk.CreateXlibSurfaceKHR )
4669 {
4670 Display* ownedDisplay = XOpenDisplay( displayName );
4671 Display* surfaceDisplay = ownedDisplay ? ownedDisplay : uiDisplay;
4672
4673 VkXlibSurfaceCreateInfoKHR xlibInfo{};
4674 xlibInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
4675 xlibInfo.dpy = surfaceDisplay;
4676 xlibInfo.window = window;
4677
4678 VkResult xlibResult =
4679 m_vk.CreateXlibSurfaceKHR( m_instance, &xlibInfo, nullptr, &vkSurface );
4680 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateXlibSurface dpy=%p window=0x%lx "
4681 "result=%d surface=%p" ),
4682 static_cast<void*>( xlibInfo.dpy ),
4683 static_cast<unsigned long>( xlibInfo.window ),
4684 static_cast<int>( xlibResult ),
4685 static_cast<void*>( vkSurface ) );
4686
4687 if( vkSurface != VK_NULL_HANDLE && ownedDisplay )
4688 {
4689 ownedX11.xlib = ownedDisplay;
4690 ownedX11.screen = DefaultScreen( ownedDisplay );
4691 }
4692 else if( ownedDisplay )
4693 XCloseDisplay( ownedDisplay );
4694 }
4695 }
4696#endif
4697
4698#ifdef VK_USE_PLATFORM_WAYLAND_KHR
4699 if( vkSurface == VK_NULL_HANDLE && desc.window.nativeDisplay
4702 && m_vk.CreateWaylandSurfaceKHR )
4703 {
4704 VkWaylandSurfaceCreateInfoKHR waylandInfo{};
4705 waylandInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
4706 waylandInfo.display = static_cast<struct wl_display*>( desc.window.nativeDisplay );
4707 waylandInfo.surface = static_cast<struct wl_surface*>( desc.window.nativeHandle );
4708
4709 VkResult waylandResult =
4710 m_vk.CreateWaylandSurfaceKHR( m_instance, &waylandInfo, nullptr, &vkSurface );
4711 waylandSurface = vkSurface != VK_NULL_HANDLE;
4712 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateWaylandSurface display=%p surface=%p "
4713 "result=%d vkSurface=%p" ),
4715 static_cast<int>( waylandResult ),
4716 static_cast<void*>( vkSurface ) );
4717 }
4718#endif
4719
4720#ifdef VK_USE_PLATFORM_WIN32_KHR
4721 if( vkSurface == VK_NULL_HANDLE
4724 && m_vk.CreateWin32SurfaceKHR )
4725 {
4726 VkWin32SurfaceCreateInfoKHR win32Info{};
4727 win32Info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
4728 win32Info.hinstance = GetModuleHandle( nullptr );
4729 win32Info.hwnd = static_cast<HWND>( desc.window.nativeHandle );
4730
4731 VkResult win32Result =
4732 m_vk.CreateWin32SurfaceKHR( m_instance, &win32Info, nullptr, &vkSurface );
4733 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateWin32Surface hwnd=%p "
4734 "result=%d vkSurface=%p" ),
4735 desc.window.nativeHandle,
4736 static_cast<int>( win32Result ),
4737 static_cast<void*>( vkSurface ) );
4738 }
4739#endif
4740
4741 if( vkSurface == VK_NULL_HANDLE )
4742 return {};
4743
4744 uint32_t presentFamily = UINT32_MAX;
4745
4746 // Only success keeps the render thread's X11 connection for teardown, so every failure closes it
4747 auto closeOwnedDisplay = [&]()
4748 {
4749#ifdef VK_USE_PLATFORM_XLIB_KHR
4750 ownedX11.Close();
4751#endif
4752 };
4753
4754 if( !FindPresentQueueFamily( vkSurface, presentFamily ) )
4755 {
4756 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateSurface FAILED - no present queue family "
4757 "supports this surface" ) );
4758
4759 if( m_vk.DestroySurfaceKHR )
4760 m_vk.DestroySurfaceKHR( m_instance, vkSurface, nullptr );
4761
4762 closeOwnedDisplay();
4763 return {};
4764 }
4765
4766 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateSurface presentFamily=%u graphicsFamily=%u" ),
4767 presentFamily, m_graphicsQueueFamily );
4768
4769 RHI_SurfaceHandle handle = m_surfacePool.Allocate();
4770
4771 if( !handle )
4772 {
4773 if( m_vk.DestroySurfaceKHR )
4774 m_vk.DestroySurfaceKHR( m_instance, vkSurface, nullptr );
4775
4776 closeOwnedDisplay();
4777 return {};
4778 }
4779
4781 sa.surface = vkSurface;
4782 sa.wayland = waylandSurface;
4783#ifdef VK_USE_PLATFORM_XLIB_KHR
4784 sa.x11Owner = ownedX11;
4785#endif
4786
4787 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateSurface OK (handle=%u)" ),
4788 RhiHandleIndex( handle ) );
4789
4790 return handle;
4791}
4792
4793
4795{
4796 if( !m_surfacePool.IsValid( handle ) )
4797 return;
4798
4799 auto it = m_surfaces.find( RhiHandleIndex( handle ) );
4800
4801 if( it == m_surfaces.end() )
4802 return;
4803
4804 if( it->second.surface && m_vk.DestroySurfaceKHR )
4805 m_vk.DestroySurfaceKHR( m_instance, it->second.surface, nullptr );
4806
4807#ifdef VK_USE_PLATFORM_XLIB_KHR
4808 it->second.x11Owner.Close();
4809#endif
4810
4811 m_surfaces.erase( it );
4812 m_surfacePool.Release( handle );
4813}
4814
4815
4816static VkExtent2D swapchainExtent( const VkSurfaceCapabilitiesKHR& aCaps, uint32_t aWidth, uint32_t aHeight )
4817{
4818 if( aCaps.currentExtent.width != UINT32_MAX )
4819 return aCaps.currentExtent;
4820
4821 return { std::max( aCaps.minImageExtent.width, std::min( aCaps.maxImageExtent.width, aWidth ) ),
4822 std::max( aCaps.minImageExtent.height, std::min( aCaps.maxImageExtent.height, aHeight ) ) };
4823}
4824
4825
4827 const VkSurfaceCapabilitiesKHR& aCaps,
4828 VkSurfaceFormatKHR aFormat, VkExtent2D aExtent,
4829 VkSwapchainKHR aOld,
4830 bool aResize )
4831{
4832 VkSwapchainTry made;
4833
4834 uint32_t imageCount = aCaps.minImageCount + 1;
4835
4836 if( aCaps.maxImageCount > 0 && imageCount > aCaps.maxImageCount )
4837 imageCount = aCaps.maxImageCount;
4838
4839 VkPresentModeKHR presentMode = ChoosePresentMode( aSurface );
4840
4841 VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
4842 const VkCompositeAlphaFlagBitsKHR alphaPreference[] = {
4843 VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
4844 VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
4845 VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
4846 VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR
4847 };
4848
4849 for( auto alpha : alphaPreference )
4850 {
4851 if( aCaps.supportedCompositeAlpha & alpha )
4852 {
4853 compositeAlpha = alpha;
4854 break;
4855 }
4856 }
4857
4858 if( !aResize )
4859 {
4860 wxLogTrace( traceRhi, wxS( "VulkanDevice: compositeAlpha=0x%x" ),
4861 static_cast<unsigned>( compositeAlpha ) );
4862 }
4863
4864 VkSurfaceTransformFlagBitsKHR preTransform =
4865 ( aCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR )
4866 ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR
4867 : aCaps.currentTransform;
4868
4869 wxLogTrace( traceRhi, wxS( "VulkanDevice: %spreTransform current=0x%x chosen=0x%x "
4870 "supported=0x%x" ),
4871 aResize ? "ResizeSwapchain " : "",
4872 static_cast<unsigned>( aCaps.currentTransform ),
4873 static_cast<unsigned>( preTransform ),
4874 static_cast<unsigned>( aCaps.supportedTransforms ) );
4875
4876 VkSwapchainCreateInfoKHR swapInfo{};
4877 swapInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
4878 swapInfo.surface = aSurface.surface;
4879 swapInfo.minImageCount = imageCount;
4880 swapInfo.imageFormat = aFormat.format;
4881 swapInfo.imageColorSpace = aFormat.colorSpace;
4882 swapInfo.imageExtent = aExtent;
4883 swapInfo.imageArrayLayers = 1;
4884 swapInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
4885 | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
4886 swapInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
4887 swapInfo.preTransform = preTransform;
4888 swapInfo.compositeAlpha = compositeAlpha;
4889 swapInfo.presentMode = presentMode;
4890 swapInfo.clipped = VK_TRUE;
4891 swapInfo.oldSwapchain = aOld;
4892
4893 if( !aResize )
4894 {
4895 wxLogTrace( traceRhi, wxS( "VulkanDevice: swapInfo surface=%p minImages=%u format=%d "
4896 "colorSpace=%d extent=%ux%u layers=%u usage=0x%x "
4897 "sharingMode=%d preTransform=0x%x compositeAlpha=0x%x "
4898 "presentMode=%d clipped=%d oldSwapchain=%p" ),
4899 static_cast<void*>( swapInfo.surface ),
4900 swapInfo.minImageCount,
4901 static_cast<int>( swapInfo.imageFormat ),
4902 static_cast<int>( swapInfo.imageColorSpace ),
4903 swapInfo.imageExtent.width, swapInfo.imageExtent.height,
4904 swapInfo.imageArrayLayers,
4905 swapInfo.imageUsage,
4906 static_cast<int>( swapInfo.imageSharingMode ),
4907 static_cast<unsigned>( swapInfo.preTransform ),
4908 static_cast<unsigned>( swapInfo.compositeAlpha ),
4909 static_cast<int>( swapInfo.presentMode ),
4910 static_cast<int>( swapInfo.clipped ),
4911 static_cast<void*>( swapInfo.oldSwapchain ) );
4912 }
4913
4914 const char* who = aResize ? "ResizeSwapchain" : "CreateSwapchain";
4915
4916 auto tryCreate = [&]( VkPresentModeKHR aMode, uint32_t aImageCount, const char* aLabel ) -> bool
4917 {
4918 swapInfo.presentMode = aMode;
4919 swapInfo.minImageCount = aImageCount;
4920
4921 wxLogTrace( traceRhi, wxS( "VulkanDevice: %s attempt=%s "
4922 "presentMode=%d minImages=%u" ),
4923 who, aLabel, static_cast<int>( aMode ), aImageCount );
4924
4925 made.result = m_vk.CreateSwapchainKHR( m_device, &swapInfo, nullptr, &made.swapchain );
4926
4927 wxLogTrace( traceRhi, wxS( "VulkanDevice: %s result=%s(%d) "
4928 "attempt=%s" ),
4929 who, VkResultToString( made.result ), static_cast<int>( made.result ),
4930 aLabel );
4931 return made.result == VK_SUCCESS;
4932 };
4933
4934 made.imageCount = imageCount;
4935 made.preferredMode = presentMode;
4936
4937 if( tryCreate( presentMode, imageCount, "preferred" )
4938 || ( presentMode != VK_PRESENT_MODE_FIFO_KHR
4939 && tryCreate( VK_PRESENT_MODE_FIFO_KHR, imageCount, "fifo" ) )
4940 || ( imageCount != aCaps.minImageCount
4941 && tryCreate( presentMode, aCaps.minImageCount, "min-images" ) )
4942 || ( presentMode != VK_PRESENT_MODE_FIFO_KHR
4943 && imageCount != aCaps.minImageCount
4944 && tryCreate( VK_PRESENT_MODE_FIFO_KHR, aCaps.minImageCount, "fifo-min-images" ) ) )
4945 {
4946 made.presentMode = swapInfo.presentMode;
4947 }
4948
4949 return made;
4950}
4951
4952
4954{
4955 if( !m_initialized || !m_surfacePool.IsValid( desc.surface ) )
4956 return {};
4957
4958 if( desc.width == 0 || desc.height == 0 )
4959 return {};
4960
4961 auto surfIt = m_surfaces.find( RhiHandleIndex( desc.surface ) );
4962
4963 if( surfIt == m_surfaces.end() )
4964 return {};
4965
4966 VkSurfaceKHR vkSurface = surfIt->second.surface;
4967
4968 if( !m_vk.GetPhysicalDeviceSurfaceCapabilitiesKHR
4969 || !m_vk.GetPhysicalDeviceSurfaceFormatsKHR
4970 || !m_vk.CreateSwapchainKHR )
4971 {
4972 return {};
4973 }
4974
4975 VkSurfaceCapabilitiesKHR surfCaps{};
4976
4977 if( m_vk.GetPhysicalDeviceSurfaceCapabilitiesKHR( m_physicalDevice, vkSurface,
4978 &surfCaps ) != VK_SUCCESS )
4979 {
4980 wxLogTrace( traceRhi, wxS( "VulkanDevice: GetSurfaceCapabilities failed" ) );
4981 return {};
4982 }
4983
4984 wxLogTrace( traceRhi, wxS( "VulkanDevice: surfCaps extent=%ux%u (min=%ux%u max=%ux%u) "
4985 "requested=%ux%u supportedComposite=0x%x "
4986 "supportedUsage=0x%x minImages=%u maxImages=%u" ),
4987 surfCaps.currentExtent.width, surfCaps.currentExtent.height,
4988 surfCaps.minImageExtent.width, surfCaps.minImageExtent.height,
4989 surfCaps.maxImageExtent.width, surfCaps.maxImageExtent.height,
4990 desc.width, desc.height,
4991 surfCaps.supportedCompositeAlpha,
4992 surfCaps.supportedUsageFlags,
4993 surfCaps.minImageCount, surfCaps.maxImageCount );
4994
4995 uint32_t formatCount = 0;
4996 m_vk.GetPhysicalDeviceSurfaceFormatsKHR( m_physicalDevice, vkSurface,
4997 &formatCount, nullptr );
4998
4999 if( formatCount == 0 )
5000 return {};
5001
5002 std::vector<VkSurfaceFormatKHR> surfFormats( formatCount );
5003 m_vk.GetPhysicalDeviceSurfaceFormatsKHR( m_physicalDevice, vkSurface,
5004 &formatCount, surfFormats.data() );
5005
5006 VkSurfaceFormatKHR chosenFormat = surfFormats[0];
5007
5008 wxLogTrace( traceRhi, wxS( "VulkanDevice: %u surface formats available:" ), formatCount );
5009
5010 for( uint32_t i = 0; i < formatCount; i++ )
5011 {
5012 wxLogTrace( traceRhi, wxS( " format[%u]: format=%d colorSpace=%d" ),
5013 i, static_cast<int>( surfFormats[i].format ),
5014 static_cast<int>( surfFormats[i].colorSpace ) );
5015 }
5016
5017 // R8G8B8A8 matches the compositor FBO, so the present blit needs no conversion
5018 static const VkFormat formatPrefs[] = {
5019 VK_FORMAT_R8G8B8A8_UNORM,
5020 VK_FORMAT_B8G8R8A8_UNORM,
5021 };
5022
5023 for( VkFormat preferred : formatPrefs )
5024 {
5025 bool found = false;
5026
5027 for( const auto& fmt : surfFormats )
5028 {
5029 if( fmt.format == preferred
5030 && fmt.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR )
5031 {
5032 chosenFormat = fmt;
5033 found = true;
5034 break;
5035 }
5036 }
5037
5038 if( found )
5039 break;
5040 }
5041
5042 wxLogTrace( traceRhi, wxS( "VulkanDevice: chosen format=%d colorSpace=%d" ),
5043 static_cast<int>( chosenFormat.format ),
5044 static_cast<int>( chosenFormat.colorSpace ) );
5045
5046 const VkExtent2D extent = swapchainExtent( surfCaps, desc.width, desc.height );
5047
5048 // A minimized window reports a zero extent, which a swapchain may not have. The render thread retries.
5049 if( extent.width == 0 || extent.height == 0 )
5050 {
5051 wxLogTrace( traceRhi, wxS( "VulkanDevice: CreateSwapchain SKIPPED (zero extent)" ) );
5052 return {};
5053 }
5054
5055 const VkSwapchainTry made = createVkSwapchain( surfIt->second, surfCaps, chosenFormat, extent, VK_NULL_HANDLE,
5056 false );
5057
5058 if( made.result != VK_SUCCESS )
5059 {
5060 wxLogTrace( traceRhi, wxS( "VulkanDevice: vkCreateSwapchainKHR failed (%d)" ),
5061 static_cast<int>( made.result ) );
5062 return {};
5063 }
5064
5065 VkSwapchainKHR vkSwapchain = made.swapchain;
5066
5067 wxLogTrace( traceRhi, wxS( "VulkanDevice: swapchain created %ux%u format=%d images=%u "
5068 "present=%d" ),
5069 extent.width, extent.height, static_cast<int>( chosenFormat.format ),
5070 made.imageCount, static_cast<int>( made.preferredMode ) );
5071
5072 RHI_SwapchainHandle handle = m_swapchainPool.Allocate();
5073
5074 if( !handle )
5075 {
5076 m_vk.DestroySwapchainKHR( m_device, vkSwapchain, nullptr );
5077 return {};
5078 }
5079
5081 swa.swapchain = vkSwapchain;
5082 swa.surface = desc.surface;
5083 swa.format = chosenFormat.format;
5084 swa.colorSpace = chosenFormat.colorSpace;
5085 swa.extent = extent;
5086 swa.requestedExtent = { desc.width, desc.height };
5087 swa.waitingForSurfaceExtent = surfCaps.currentExtent.width != UINT32_MAX
5088 && ( extent.width != desc.width || extent.height != desc.height );
5089 swa.dpiScale = desc.dpiScale;
5090
5091 // The attempt that succeeded may have fallen back to FIFO
5092 swa.presentMode = made.presentMode;
5093
5094 if( !CreateSwapchainResources( swa ) )
5095 {
5097 m_swapchains.erase( RhiHandleIndex( handle ) );
5098 m_swapchainPool.Release( handle );
5099 return {};
5100 }
5101
5102 VkSemaphoreCreateInfo semInfo{};
5103 semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
5104
5105 VkFenceCreateInfo fenceInfo{};
5106 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
5107 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
5108
5109 if( !m_vk.CreateSemaphore || !m_vk.CreateFence
5110 || m_vk.CreateSemaphore( m_device, &semInfo, nullptr,
5111 &swa.imageAvailable ) != VK_SUCCESS
5112 || m_vk.CreateFence( m_device, &fenceInfo, nullptr,
5113 &swa.inFlightFence ) != VK_SUCCESS )
5114 {
5116 m_swapchains.erase( RhiHandleIndex( handle ) );
5117 m_swapchainPool.Release( handle );
5118 return {};
5119 }
5120
5121 return handle;
5122}
5123
5124
5126 uint32_t height, float dpiScale )
5127{
5128 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain %ux%u dpi=%.1f" ),
5129 width, height, dpiScale );
5130
5131 if( !m_swapchainPool.IsValid( handle ) || width == 0 || height == 0 )
5132 return false;
5133
5134 auto it = m_swapchains.find( RhiHandleIndex( handle ) );
5135
5136 if( it == m_swapchains.end() )
5137 return false;
5138
5139 SwapchainAllocation& swa = it->second;
5140 swa.requestedExtent = { width, height };
5141
5142 if( !m_surfacePool.IsValid( swa.surface ) )
5143 return false;
5144
5145 auto surfIt = m_surfaces.find( RhiHandleIndex( swa.surface ) );
5146
5147 if( surfIt == m_surfaces.end() )
5148 return false;
5149
5150 VkSurfaceKHR vkSurface = surfIt->second.surface;
5151
5152 VkSurfaceCapabilitiesKHR surfCaps{};
5153 VkResult capsResult = m_vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(
5154 m_physicalDevice, vkSurface, &surfCaps );
5155
5156 if( capsResult != VK_SUCCESS )
5157 {
5158 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain GetSurfaceCaps FAILED (%d)" ),
5159 static_cast<int>( capsResult ) );
5160 return false;
5161 }
5162
5163 const VkExtent2D extent = swapchainExtent( surfCaps, width, height );
5164
5165 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain surfExtent=%ux%u "
5166 "requested=%ux%u minExtent=%ux%u maxExtent=%ux%u" ),
5167 extent.width, extent.height, width, height,
5168 surfCaps.minImageExtent.width, surfCaps.minImageExtent.height,
5169 surfCaps.maxImageExtent.width, surfCaps.maxImageExtent.height );
5170
5171 if( extent.width == 0 || extent.height == 0 )
5172 {
5173 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain SKIPPED (zero extent)" ) );
5174 return false;
5175 }
5176
5177 // Keep the swapchain so it can be passed as oldSwapchain
5178 DestroySwapchainResources( swa, true );
5179
5180 VkSwapchainKHR oldSwapchain = swa.swapchain;
5181
5182 const VkSwapchainTry made = createVkSwapchain( surfIt->second, surfCaps,
5183 { swa.format, swa.colorSpace }, extent,
5184 oldSwapchain, true );
5185
5186 if( made.result != VK_SUCCESS )
5187 {
5188 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain CreateSwapchain FAILED (%d) "
5189 "extent=%ux%u" ),
5190 static_cast<int>( made.result ), extent.width, extent.height );
5191 return false;
5192 }
5193
5194 m_vk.DestroySwapchainKHR( m_device, oldSwapchain, nullptr );
5195
5196 swa.swapchain = made.swapchain;
5197 swa.presentMode = made.presentMode;
5198 swa.lastPresentId = 0;
5199 swa.extent = extent;
5200 swa.dpiScale = dpiScale;
5201
5202 bool resourcesOk = CreateSwapchainResources( swa );
5203
5204 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain %s extent=%ux%u images=%zu" ),
5205 resourcesOk ? "OK" : "RESOURCES_FAILED",
5206 extent.width, extent.height, swa.images.size() );
5207
5208 // Release what a failed CreateSwapchainResources left behind, keeping the swapchain handle
5209 if( !resourcesOk )
5210 DestroySwapchainResources( swa, /* aKeepSwapchain */ true );
5211 else
5212 {
5213 swa.needsResize = false;
5214 swa.waitingForSurfaceExtent = surfCaps.currentExtent.width != UINT32_MAX
5215 && ( extent.width != width || extent.height != height );
5216 }
5217
5218 if( swa.waitingForSurfaceExtent )
5219 swa.nextSurfaceExtentCheck = {};
5220
5221 return resourcesOk;
5222}
5223
5224
5226{
5227 if( !m_swapchainPool.IsValid( aSwapchain ) )
5229
5230 auto it = m_swapchains.find( RhiHandleIndex( aSwapchain ) );
5231
5232 if( it == m_swapchains.end() || !it->second.waitingForSurfaceExtent )
5234
5235 SwapchainAllocation& swa = it->second;
5236 auto now = std::chrono::steady_clock::now();
5237
5238 if( now < swa.nextSurfaceExtentCheck )
5240
5241 swa.nextSurfaceExtentCheck = now + std::chrono::milliseconds( 50 );
5242
5243 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
5244 auto surfIt = m_surfaces.find( RhiHandleIndex( swa.surface ) );
5245
5246 if( surfIt == m_surfaces.end() )
5248
5249 VkSurfaceCapabilitiesKHR caps{};
5250 VkResult capsResult = m_vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(
5251 m_physicalDevice, surfIt->second.surface, &caps );
5252
5253 if( capsResult != VK_SUCCESS )
5255
5256 if( caps.currentExtent.width != UINT32_MAX
5257 && ( caps.currentExtent.width != swa.requestedExtent.width
5258 || caps.currentExtent.height != swa.requestedExtent.height ) )
5259 {
5261 }
5262
5263 wxLogTrace( traceRhi, wxS( "VulkanDevice: surface reached requested extent %ux%u; "
5264 "resizing swapchain" ),
5265 swa.requestedExtent.width, swa.requestedExtent.height );
5266
5267 if( !ResizeSwapchain( aSwapchain, swa.requestedExtent.width,
5268 swa.requestedExtent.height, swa.dpiScale ) )
5269 {
5271 }
5272
5275}
5276
5277
5279{
5280 if( !m_swapchainPool.IsValid( handle ) )
5281 return;
5282
5283 auto it = m_swapchains.find( RhiHandleIndex( handle ) );
5284
5285 if( it == m_swapchains.end() )
5286 return;
5287
5288 destroySwapchainObjects( it->second );
5289
5290 m_swapchains.erase( it );
5291 m_swapchainPool.Release( handle );
5292}
5293
5294
5296{
5298
5299 // The destructor gets here after the pool is gone, which already freed the buffer
5300 if( aSwa.lastPresentCmd && m_commandPool && m_vk.FreeCommandBuffers )
5301 {
5302 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
5303 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &aSwa.lastPresentCmd );
5304 }
5305
5307
5308 m_vk.DestroySemaphore( m_device, aSwa.imageAvailable, nullptr );
5309 m_vk.DestroyFence( m_device, aSwa.inFlightFence, nullptr );
5310
5311 for( VkSemaphore semaphore : aSwa.renderFinished )
5312 m_vk.DestroySemaphore( m_device, semaphore, nullptr );
5313
5316 aSwa.renderFinished.clear();
5317}
5318
5319
5320bool RHI_VULKAN_DEVICE::WaitForPresentSlot( RHI_SwapchainHandle aSwapchain, std::chrono::milliseconds aTimeout,
5321 int aAllowed )
5322{
5323 auto it = m_swapchains.find( RhiHandleIndex( aSwapchain ) );
5324
5325 if( !m_swapchainPool.IsValid( aSwapchain ) || it == m_swapchains.end() )
5326 return false;
5327
5328 SwapchainAllocation& swa = it->second;
5329 const uint64_t timeoutNs =
5330 static_cast<uint64_t>( std::chrono::duration_cast<std::chrono::nanoseconds>( aTimeout ).count() );
5331
5332 // With present ids, wait until all but the allowed presents are on the display. Without them, wait for
5333 // the GPU to finish the last frame so the CPU cannot run ahead.
5334 const uint64_t allowed = static_cast<uint64_t>( std::max( aAllowed, 1 ) );
5335
5336 if( !swa.swapchain || swa.lastPresentId == 0 )
5337 return false;
5338
5339 if( m_presentWait )
5340 {
5341 if( swa.lastPresentId < allowed )
5342 return false;
5343
5344 const uint64_t waitFor = swa.lastPresentId + 1 - allowed;
5345 const VkResult result = m_vk.WaitForPresentKHR( m_device, swa.swapchain, waitFor, timeoutNs );
5346
5347 // An out of date or lost swapchain is reported by the next present
5348 return result == VK_SUCCESS || result == VK_TIMEOUT;
5349 }
5350
5351 if( !swa.inFlightFence || !m_vk.WaitForFences )
5352 return false;
5353
5354 return m_vk.WaitForFences( m_device, 1, &swa.inFlightFence, VK_TRUE, timeoutNs ) == VK_SUCCESS;
5355}
5356
5357
5359{
5360 auto it = m_swapchains.find( RhiHandleIndex( aSwapchain ) );
5361
5362 return it == m_swapchains.end() || it->second.presentMode == VK_PRESENT_MODE_FIFO_KHR
5363 || it->second.presentMode == VK_PRESENT_MODE_FIFO_RELAXED_KHR;
5364}
5365
5366
5368{
5369 auto it = m_swapchains.find( RhiHandleIndex( aSwapchain ) );
5370
5371 return it != m_swapchains.end() && it->second.presentMode == VK_PRESENT_MODE_MAILBOX_KHR;
5372}
5373
5374
5376 const RHI_SCISSOR_RECT* aSourceRect )
5377{
5378 if( !m_swapchainPool.IsValid( handle ) )
5380
5381 auto it = m_swapchains.find( RhiHandleIndex( handle ) );
5382
5383 if( it == m_swapchains.end() )
5385
5386 SwapchainAllocation& swa = it->second;
5387 uint64_t attempt = ++swa.presentAttemptCount;
5388
5389 wxLogTrace( traceRhi, wxS( "VulkanDevice: Present begin attempt=%llu handle=%u "
5390 "extent=%ux%u srcFBO=%s needsResize=%s" ),
5391 static_cast<unsigned long long>( attempt ),
5392 static_cast<unsigned>( RhiHandleIndex( handle ) ),
5393 swa.extent.width, swa.extent.height,
5394 aSrcFBO ? "valid" : "null",
5395 swa.needsResize ? "yes" : "no" );
5396
5397 VkImage srcImage = VK_NULL_HANDLE;
5398
5399 if( aSrcFBO )
5400 {
5401 auto fbIt = m_framebuffers.find( RhiHandleIndex( aSrcFBO ) );
5402
5403 if( fbIt != m_framebuffers.end() && !fbIt->second.colorImages.empty() )
5404 srcImage = fbIt->second.colorImages[0];
5405 }
5406
5407 if( !m_vk.AcquireNextImageKHR || !m_vk.QueuePresentKHR
5408 || !m_vk.WaitForFences || !m_vk.ResetFences )
5409 {
5411 }
5412
5413 // A failed resize can leave a valid swapchain with no images, which the caller must recreate
5414 if( swa.images.empty() )
5416
5417 PROF_TIMER waitTimer;
5418 PROF_TIMER waitLogTimer;
5419 VkResult waitResult = VK_TIMEOUT;
5420
5421 while( waitResult == VK_TIMEOUT )
5422 {
5423 waitResult = m_vk.WaitForFences( m_device, 1, &swa.inFlightFence, VK_TRUE,
5424 250000000ULL );
5425
5426 if( waitResult == VK_TIMEOUT && VkElapsedMs( waitLogTimer ) >= 1000 )
5427 {
5428 wxLogTrace( traceRhi, wxS( "VulkanDevice: WaitForFences still waiting "
5429 "attempt=%llu elapsed=%lldms" ),
5430 static_cast<unsigned long long>( attempt ),
5431 VkElapsedMs( waitTimer ) );
5432 waitLogTimer.Start();
5433 }
5434 }
5435
5436 waitTimer.Stop();
5437
5438 wxLogTrace( traceRhi, wxS( "VulkanDevice: WaitForFences attempt=%llu result=%s(%d) "
5439 "elapsed=%lldms" ),
5440 static_cast<unsigned long long>( attempt ),
5441 VkResultToString( waitResult ), static_cast<int>( waitResult ),
5442 VkElapsedMs( waitTimer ) );
5443
5444 if( waitResult != VK_SUCCESS )
5446
5447 // Held through QueuePresentKHR because the main thread shares the command pool and queue
5448 std::unique_lock<std::recursive_mutex> presentLock( m_deviceMutex );
5449
5450 const VkPipeline blitPipeline = EnsurePresentBlit( swa.format );
5451
5452 // Free the previous frame's command buffer now that the GPU is done with it
5453 if( swa.lastPresentCmd )
5454 {
5455 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &swa.lastPresentCmd );
5457 }
5458
5459 // Allocate before acquiring so a failure cannot strand an acquired image with a signaled semaphore
5460 VkCommandBuffer cmd = VK_NULL_HANDLE;
5461
5462 if( !allocateCommandBuffer( cmd ) )
5463 {
5464 wxLogTrace( traceRhi, wxS( "VulkanDevice: AllocateCommandBuffers FAILED "
5465 "attempt=%llu" ),
5466 static_cast<unsigned long long>( attempt ) );
5468 }
5469
5470 if( swa.needsResize )
5471 {
5472 wxLogTrace( traceRhi, wxS( "VulkanDevice: Present deferred resize %ux%u" ),
5473 swa.extent.width, swa.extent.height );
5474
5475 bool resizeOk = ResizeSwapchain( handle, swa.requestedExtent.width,
5476 swa.requestedExtent.height, swa.dpiScale );
5477 swa.needsResize = false;
5478
5479 // A failed resize leaves no images to acquire, so the caller must recreate the swapchain
5480 if( !resizeOk || swa.images.empty() )
5481 {
5482 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
5484 }
5485 }
5486
5487 uint32_t imageIndex = 0;
5488 PROF_TIMER acquireTimer;
5489 VkResult acquireResult = m_vk.AcquireNextImageKHR( m_device, swa.swapchain, UINT64_MAX,
5491 &imageIndex );
5492 acquireTimer.Stop();
5493
5494 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireNextImage attempt=%llu result=%s(%d) "
5495 "image=%u elapsed=%lldms" ),
5496 static_cast<unsigned long long>( attempt ),
5497 VkResultToString( acquireResult ), static_cast<int>( acquireResult ),
5498 imageIndex, VkElapsedMs( acquireTimer ) );
5499
5500 if( acquireResult == VK_ERROR_OUT_OF_DATE_KHR )
5501 {
5502 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireNextImage out-of-date, "
5503 "resizing swapchain" ) );
5504
5505 if( !ResizeSwapchain( handle, swa.requestedExtent.width,
5506 swa.requestedExtent.height, swa.dpiScale ) )
5507 {
5508 wxLogTrace( traceRhi, wxS( "VulkanDevice: ResizeSwapchain after OUT_OF_DATE FAILED" ) );
5509 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
5511 }
5512
5513 acquireTimer.Start();
5514 acquireResult = m_vk.AcquireNextImageKHR( m_device, swa.swapchain, UINT64_MAX,
5516 &imageIndex );
5517 acquireTimer.Stop();
5518
5519 if( acquireResult != VK_SUCCESS )
5520 {
5521 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireNextImage retry FAILED "
5522 "attempt=%llu result=%s(%d) image=%u elapsed=%lldms" ),
5523 static_cast<unsigned long long>( attempt ),
5524 VkResultToString( acquireResult ), static_cast<int>( acquireResult ),
5525 imageIndex, VkElapsedMs( acquireTimer ) );
5526 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
5528 }
5529
5530 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireNextImage retry OK "
5531 "attempt=%llu image=%u elapsed=%lldms" ),
5532 static_cast<unsigned long long>( attempt ),
5533 imageIndex, VkElapsedMs( acquireTimer ) );
5534 }
5535 else if( acquireResult == VK_SUBOPTIMAL_KHR )
5536 {
5537 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireNextImage suboptimal "
5538 "attempt=%llu (continuing)" ),
5539 static_cast<unsigned long long>( attempt ) );
5540 }
5541 else if( acquireResult != VK_SUCCESS )
5542 {
5543 wxLogTrace( traceRhi, wxS( "VulkanDevice: AcquireNextImage FAILED attempt=%llu "
5544 "result=%s(%d)" ),
5545 static_cast<unsigned long long>( attempt ),
5546 VkResultToString( acquireResult ), static_cast<int>( acquireResult ) );
5547 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
5548
5549 if( acquireResult == VK_ERROR_DEVICE_LOST )
5551
5552 if( acquireResult == VK_ERROR_SURFACE_LOST_KHR )
5554
5556 }
5557
5558 // Reset the fence only after acquire succeeds, or an early exit would leave it never signaled
5559 m_vk.ResetFences( m_device, 1, &swa.inFlightFence );
5560
5561 VkSemaphore renderFinished = swa.renderFinished[imageIndex];
5562
5563 swa.currentImageIndex = imageIndex;
5564
5565 beginOneTimeCommands( cmd );
5566
5567 // Sample the FBO in a render pass because vkCmdBlitImage and vkCmdCopyImage intermittently read
5568 // zeros on some NVIDIA drivers
5569 bool presentBlitOk = false;
5570
5571 if( srcImage && blitPipeline && swa.presentRenderPass
5572 && swa.presentDescSet && imageIndex < swa.presentFramebuffers.size() )
5573 {
5574 auto fbIt = m_framebuffers.find( RhiHandleIndex( aSrcFBO ) );
5575 RHI_TextureHandle srcTexHandle;
5576
5577 if( fbIt != m_framebuffers.end() && !fbIt->second.colorTextureHandles.empty() )
5578 srcTexHandle = fbIt->second.colorTextureHandles[0];
5579
5580 auto texIt = srcTexHandle ? m_textures.find( RhiHandleIndex( srcTexHandle ) )
5581 : m_textures.end();
5582
5583 if( texIt != m_textures.end() )
5584 {
5585 // NVIDIA may not flush attachment writes for texture reads in a later submission, so a
5586 // barrier forces it before sampling
5587 const VkImageMemoryBarrier fboBarrier = imageBarrier(
5588 srcImage, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
5589 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
5590 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
5591 VK_ACCESS_SHADER_READ_BIT );
5592
5593 m_vk.CmdPipelineBarrier( cmd,
5594 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
5595 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
5596 0, 0, nullptr, 0, nullptr, 1, &fboBarrier );
5597
5598 // The fence wait above means the previous frame no longer reads this descriptor set
5599 VkDescriptorImageInfo imgInfo{};
5600 imgInfo.sampler = texIt->second.sampler;
5601 imgInfo.imageView = texIt->second.imageView;
5602 imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5603
5604 VkWriteDescriptorSet write{};
5605 write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
5606 write.dstSet = swa.presentDescSet;
5607 write.dstBinding = 0;
5608 write.descriptorCount = 1;
5609 write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
5610 write.pImageInfo = &imgInfo;
5611
5612 m_vk.UpdateDescriptorSets( m_device, 1, &write, 0, nullptr );
5613
5614 VkRenderPassBeginInfo rpBegin{};
5615 rpBegin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
5616 rpBegin.renderPass = swa.presentRenderPass;
5617 rpBegin.framebuffer = swa.presentFramebuffers[imageIndex];
5618 rpBegin.renderArea.extent = swa.extent;
5619
5620 m_vk.CmdBeginRenderPass( cmd, &rpBegin, VK_SUBPASS_CONTENTS_INLINE );
5621
5622 VkViewport vp{};
5623 vp.width = static_cast<float>( swa.extent.width );
5624 vp.height = static_cast<float>( swa.extent.height );
5625 vp.maxDepth = 1.0f;
5626
5627 // The blit draws the whole source across the viewport, so a part of it is shown by
5628 // growing the viewport until that part covers the image. The scissor clips the rest.
5629 if( aSourceRect && aSourceRect->width > 0 && aSourceRect->height > 0 )
5630 {
5631 const float scaleX = vp.width / static_cast<float>( aSourceRect->width );
5632 const float scaleY = vp.height / static_cast<float>( aSourceRect->height );
5633
5634 vp.x = -static_cast<float>( aSourceRect->x ) * scaleX;
5635 vp.y = -static_cast<float>( aSourceRect->y ) * scaleY;
5636 vp.width = static_cast<float>( fbIt->second.width ) * scaleX;
5637 vp.height = static_cast<float>( fbIt->second.height ) * scaleY;
5638 }
5639
5640 m_vk.CmdSetViewport( cmd, 0, 1, &vp );
5641
5642 VkRect2D scissor{};
5643 scissor.extent = swa.extent;
5644 m_vk.CmdSetScissor( cmd, 0, 1, &scissor );
5645
5646 m_vk.CmdBindPipeline( cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, blitPipeline );
5647
5648 m_vk.CmdBindDescriptorSets( cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
5650 0, 1, &swa.presentDescSet, 0, nullptr );
5651
5652 m_vk.CmdDraw( cmd, 3, 1, 0, 0 );
5653
5654 m_vk.CmdEndRenderPass( cmd );
5655
5656 presentBlitOk = true;
5657 }
5658 }
5659
5660 // With no source texture or the pipeline not ready, clear to magenta
5661 if( !presentBlitOk )
5662 {
5663 const VkImageMemoryBarrier toDst = imageBarrier( swa.images[imageIndex], VK_IMAGE_ASPECT_COLOR_BIT,
5664 VK_IMAGE_LAYOUT_UNDEFINED,
5665 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 0,
5666 VK_ACCESS_TRANSFER_WRITE_BIT );
5667
5668 m_vk.CmdPipelineBarrier( cmd,
5669 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
5670 VK_PIPELINE_STAGE_TRANSFER_BIT,
5671 0, 0, nullptr, 0, nullptr, 1, &toDst );
5672
5673 VkClearColorValue magenta = { { 1.0f, 0.0f, 1.0f, 1.0f } };
5674 m_vk.CmdClearColorImage( cmd, swa.images[imageIndex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &magenta, 1,
5675 &toDst.subresourceRange );
5676
5677 const VkImageMemoryBarrier toPresent = imageBarrier( swa.images[imageIndex], VK_IMAGE_ASPECT_COLOR_BIT,
5678 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
5679 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
5680 VK_ACCESS_TRANSFER_WRITE_BIT, 0 );
5681
5682 m_vk.CmdPipelineBarrier( cmd,
5683 VK_PIPELINE_STAGE_TRANSFER_BIT,
5684 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
5685 0, 0, nullptr, 0, nullptr, 1, &toPresent );
5686 }
5687
5688 m_vk.EndCommandBuffer( cmd );
5689
5690 // Waiting only at COLOR_ATTACHMENT_OUTPUT lets the fragment shader read a stale FBO on NVIDIA
5691 VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
5692
5693 VkSubmitInfo submitInfo{};
5694 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
5695 submitInfo.waitSemaphoreCount = 1;
5696 submitInfo.pWaitSemaphores = &swa.imageAvailable;
5697 submitInfo.pWaitDstStageMask = &waitStage;
5698 submitInfo.commandBufferCount = 1;
5699 submitInfo.pCommandBuffers = &cmd;
5700 submitInfo.signalSemaphoreCount = 1;
5701 submitInfo.pSignalSemaphores = &renderFinished;
5702
5703 PROF_TIMER submitTimer;
5704 VkResult submitResult = m_vk.QueueSubmit( m_graphicsQueue, 1, &submitInfo,
5705 swa.inFlightFence );
5706 submitTimer.Stop();
5707
5708 wxLogTrace( traceRhi, wxS( "VulkanDevice: QueueSubmit attempt=%llu result=%s(%d) "
5709 "image=%u elapsed=%lldms" ),
5710 static_cast<unsigned long long>( attempt ),
5711 VkResultToString( submitResult ), static_cast<int>( submitResult ),
5712 imageIndex, VkElapsedMs( submitTimer ) );
5713
5714 if( submitResult != VK_SUCCESS )
5715 {
5716 m_vk.FreeCommandBuffers( m_device, m_commandPool, 1, &cmd );
5717
5718 // The fence was reset but never submitted, so signal it again or the next wait deadlocks
5719 VkFenceCreateInfo fenceInfo{};
5720 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
5721 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
5722 VkFence newFence = VK_NULL_HANDLE;
5723
5724 if( m_vk.CreateFence( m_device, &fenceInfo, nullptr, &newFence ) == VK_SUCCESS )
5725 {
5726 m_vk.DestroyFence( m_device, swa.inFlightFence, nullptr );
5727 swa.inFlightFence = newFence;
5728 }
5729
5730 if( submitResult == VK_ERROR_DEVICE_LOST )
5732
5733 // No submit consumed the acquired image and its signaled semaphore, so the caller must recreate
5734 // the swapchain rather than acquire against that state
5736 }
5737
5738
5739 VkPresentInfoKHR presentInfo{};
5740 presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
5741 presentInfo.waitSemaphoreCount = 1;
5742 presentInfo.pWaitSemaphores = &renderFinished;
5743 presentInfo.swapchainCount = 1;
5744 presentInfo.pSwapchains = &swa.swapchain;
5745 presentInfo.pImageIndices = &imageIndex;
5746
5747 VkPresentIdKHR presentId{};
5748 const uint64_t thisPresent = swa.lastPresentId + 1;
5749
5750 if( m_presentWait )
5751 {
5752 presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR;
5753 presentId.swapchainCount = 1;
5754 presentId.pPresentIds = &thisPresent;
5755 presentInfo.pNext = &presentId;
5756 }
5757
5758 PROF_TIMER presentTimer;
5759 VkResult presentResult = m_vk.QueuePresentKHR( m_graphicsQueue, &presentInfo );
5760 presentTimer.Stop();
5761
5762 if( presentResult == VK_SUCCESS || presentResult == VK_SUBOPTIMAL_KHR )
5763 {
5764 swa.lastPresentId = thisPresent;
5765 }
5766
5767 swa.lastPresentCmd = cmd;
5768
5769 // Past the last queue call, so the main thread need not wait while stats are logged
5770 presentLock.unlock();
5771
5772 wxLogTrace( traceRhi, wxS( "VulkanDevice: QueuePresent attempt=%llu result=%s(%d) "
5773 "image=%u elapsed=%lldms" ),
5774 static_cast<unsigned long long>( attempt ),
5775 VkResultToString( presentResult ), static_cast<int>( presentResult ),
5776 imageIndex, VkElapsedMs( presentTimer ) );
5777
5778 if( presentResult == VK_ERROR_OUT_OF_DATE_KHR || presentResult == VK_SUBOPTIMAL_KHR )
5779 {
5780 wxLogTrace( traceRhi, wxS( "VulkanDevice: QueuePresent %s (will resize next frame)" ),
5781 presentResult == VK_ERROR_OUT_OF_DATE_KHR ? "out-of-date" : "suboptimal" );
5782 swa.needsResize = true;
5784 }
5785
5786 if( presentResult == VK_ERROR_DEVICE_LOST )
5788
5789 if( presentResult == VK_ERROR_SURFACE_LOST_KHR )
5791
5792 if( presentResult != VK_SUCCESS )
5794
5795 if( swa.extent.width != swa.lastLoggedExtent.width
5796 || swa.extent.height != swa.lastLoggedExtent.height )
5797 {
5798 wxLogTrace( traceRhi, wxS( "VulkanDevice: Present OK extent=%ux%u srcFBO=%s" ),
5799 swa.extent.width, swa.extent.height,
5800 aSrcFBO ? "valid" : "null" );
5801 swa.lastLoggedExtent = swa.extent;
5802 }
5803
5805}
5806
5807
5809 uint32_t attachmentIndex,
5810 void* buffer, size_t bufferSize )
5811{
5812 if( !m_framebufferPool.IsValid( handle ) || !buffer || bufferSize == 0 || !m_allocator )
5813 return false;
5814
5815 auto it = m_framebuffers.find( RhiHandleIndex( handle ) );
5816
5817 if( it == m_framebuffers.end() )
5818 return false;
5819
5820 FramebufferAllocation& fba = it->second;
5821
5822 if( attachmentIndex >= fba.colorImages.size() )
5823 return false;
5824
5825 uint32_t bpp = RhiTextureBytesPerPixel( fba.colorFormats[attachmentIndex] );
5826 size_t required = static_cast<size_t>( fba.width ) * fba.height * bpp;
5827
5828 if( bufferSize < required )
5829 return false;
5830
5831 VkImage srcImage = fba.colorImages[attachmentIndex];
5832
5833 VkBufferCreateInfo stagingBufInfo{};
5834 stagingBufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
5835 stagingBufInfo.size = required;
5836 stagingBufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
5837
5838 VmaAllocationCreateInfo stagingAllocInfo{};
5839 stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO;
5840 stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT
5841 | VMA_ALLOCATION_CREATE_MAPPED_BIT;
5842
5843 VkBuffer stagingBuffer = VK_NULL_HANDLE;
5844 VmaAllocation stagingAllocation = VK_NULL_HANDLE;
5845 VmaAllocationInfo stagingInfo{};
5846
5847 if( vmaCreateBuffer( m_allocator, &stagingBufInfo, &stagingAllocInfo,
5848 &stagingBuffer, &stagingAllocation, &stagingInfo ) != VK_SUCCESS )
5849 {
5850 return false;
5851 }
5852
5853 // Held from allocation through the wait so main-thread uploads cannot race the readback
5854 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
5855
5856 VkCommandBuffer cmd = VK_NULL_HANDLE;
5857
5858 if( !allocateCommandBuffer( cmd ) )
5859 {
5860 vmaDestroyBuffer( m_allocator, stagingBuffer, stagingAllocation );
5861 return false;
5862 }
5863
5864 beginOneTimeCommands( cmd );
5865
5866 // The writes of earlier passes must be made visible to the copy, as in Present()
5867 VkImageMemoryBarrier barrier = imageBarrier(
5868 srcImage, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
5869 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
5870 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
5871 VK_ACCESS_TRANSFER_READ_BIT );
5872
5873 m_vk.CmdPipelineBarrier( cmd,
5874 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
5875 VK_PIPELINE_STAGE_TRANSFER_BIT,
5876 0, 0, nullptr, 0, nullptr, 1, &barrier );
5877
5878 VkBufferImageCopy region{};
5879 region.bufferOffset = 0;
5880 region.bufferRowLength = 0;
5881 region.bufferImageHeight = 0;
5882 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
5883 region.imageSubresource.mipLevel = 0;
5884 region.imageSubresource.baseArrayLayer = 0;
5885 region.imageSubresource.layerCount = 1;
5886 region.imageOffset = { 0, 0, 0 };
5887 region.imageExtent = { fba.width, fba.height, 1 };
5888
5889 m_vk.CmdCopyImageToBuffer( cmd, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
5890 stagingBuffer, 1, &region );
5891
5892 barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
5893 barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
5894 barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
5895 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
5896
5897 m_vk.CmdPipelineBarrier( cmd,
5898 VK_PIPELINE_STAGE_TRANSFER_BIT,
5899 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
5900 0, 0, nullptr, 0, nullptr, 1, &barrier );
5901
5902 if( !SubmitOneShot( cmd ) )
5903 {
5904 vmaDestroyBuffer( m_allocator, stagingBuffer, stagingAllocation );
5905 return false;
5906 }
5907
5908 vmaInvalidateAllocation( m_allocator, stagingAllocation, 0, VK_WHOLE_SIZE );
5909 std::memcpy( buffer, stagingInfo.pMappedData, required );
5910
5911 vmaDestroyBuffer( m_allocator, stagingBuffer, stagingAllocation );
5912 return true;
5913}
5914
5915
5917 uint32_t aSourceX, uint32_t aSourceY )
5918{
5919 std::lock_guard<std::recursive_mutex> lock( m_deviceMutex );
5920
5921 if( m_renderPassActive || !m_framebufferPool.IsValid( aSource ) || !m_framebufferPool.IsValid( aDestination ) )
5922 return false;
5923
5924 auto srcIt = m_framebuffers.find( RhiHandleIndex( aSource ) );
5925 auto dstIt = m_framebuffers.find( RhiHandleIndex( aDestination ) );
5926
5927 if( srcIt == m_framebuffers.end() || dstIt == m_framebuffers.end() )
5928 return false;
5929
5930 FramebufferAllocation& src = srcIt->second;
5931 FramebufferAllocation& dst = dstIt->second;
5932
5933 if( !src.depthImage || !dst.depthImage || !src.depthRendered || aSourceX + dst.width > src.width
5934 || aSourceY + dst.height > src.height )
5935 {
5936 return false;
5937 }
5938
5939 VkCommandBuffer cmd = VK_NULL_HANDLE;
5940
5941 if( !acquirePassCmd( cmd ) )
5942 return false;
5943
5944 if( beginOneTimeCommands( cmd ) != VK_SUCCESS )
5945 {
5946 m_freeCmds.push_back( cmd );
5947 return false;
5948 }
5949
5950
5951 const VkPipelineStageFlags depthStages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
5952 | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
5953 const VkAccessFlags depthAccess = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
5954 | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
5955
5956 auto barrier = [&]( VkImage aImage, VkImageLayout aOld, VkImageLayout aNew, VkAccessFlags aSrcAccess,
5957 VkAccessFlags aDstAccess )
5958 {
5959 return imageBarrier( aImage, VK_IMAGE_ASPECT_DEPTH_BIT, aOld, aNew, aSrcAccess, aDstAccess );
5960 };
5961
5962 // The destination's old content is overwritten, so its layout before does not matter
5963 VkImageMemoryBarrier before[2] = {
5964 barrier( src.depthImage, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
5965 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, depthAccess, VK_ACCESS_TRANSFER_READ_BIT ),
5966 barrier( dst.depthImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, depthAccess,
5967 VK_ACCESS_TRANSFER_WRITE_BIT )
5968 };
5969
5970 m_vk.CmdPipelineBarrier( cmd, depthStages, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 2,
5971 before );
5972
5973 VkImageCopy region{};
5974 region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
5975 region.srcSubresource.layerCount = 1;
5976 region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
5977 region.dstSubresource.layerCount = 1;
5978 region.srcOffset = { static_cast<int32_t>( aSourceX ), static_cast<int32_t>( aSourceY ), 0 };
5979 region.extent = { dst.width, dst.height, 1 };
5980
5981 m_vk.CmdCopyImage( cmd, src.depthImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst.depthImage,
5982 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region );
5983
5984 VkImageMemoryBarrier after[2] = {
5985 barrier( src.depthImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
5986 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, depthAccess ),
5987 barrier( dst.depthImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
5988 VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, depthAccess )
5989 };
5990
5991 m_vk.CmdPipelineBarrier( cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, depthStages, 0, 0, nullptr, 0, nullptr, 2,
5992 after );
5993
5994 m_vk.EndCommandBuffer( cmd );
5995
5996 if( submitPassCmd( cmd ) != VK_SUCCESS )
5997 return false;
5998
5999 dst.depthRendered = true;
6000 return true;
6001}
6002
6003
6004// ---- Factory function ----
6005
6006std::unique_ptr<RHI_DEVICE> RhiCreateVulkanDevice( bool enableValidation )
6007{
6008 auto device = std::make_unique<RHI_VULKAN_DEVICE>( enableValidation );
6009
6010 if( !device->IsInitialized() )
6011 return nullptr;
6012
6013 return device;
6014}
int index
const char * name
static wxString formatCount(int aCount)
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
void Start()
Start or restart the counter.
Definition profile.h:74
double msecs(bool aSinceLast=false)
Definition profile.h:148
Plain owning command data, recordable on any thread without device access.
Definition rhi_compute.h:91
const std::vector< COMMAND > & Commands() const
Creates, uses and destroys GPU resources through one graphics API backend.
Definition rhi_device.h:66
RHI_DEFERRED_RELEASE_QUEUE m_deferredReleaseQueue
Definition rhi_device.h:373
void deferDestroy(RHI_HANDLE_POOL< Tag > &aPool, RHI_DEFERRED_RESOURCE_TYPE aType, RHI_HANDLE< Tag > aHandle)
Only the destroy that wins the pending-destroy claim enqueues, so racing destroys of one handle canno...
Definition rhi_device.h:365
uint64_t m_frameCounter
Definition rhi_device.h:372
Thread-safe pool that allocates and recycles generation-tracked handles for device implementations.
Definition rhi_handle.h:101
RHI_HANDLE_POOL< RHI_TAG_BIND_GROUP > m_bindGroupPool
std::unordered_map< uint32_t, SurfaceAllocation > m_surfaces
static constexpr size_t STAGING_POOL_BYTES
bool acquirePassCmd(VkCommandBuffer &aCmd)
std::unordered_map< uint32_t, ComputePipelineAllocation > m_computePipelines
std::atomic< std::thread::id > m_uploadBatchOwner
std::unordered_map< uint32_t, ShaderAllocation > m_shaders
void ExecuteDeferredRelease(const RHI_DEFERRED_RELEASE_ENTRY &aEntry)
VkResult buildPipeline(VkShaderModule aVertex, VkShaderModule aFragment, const VkPipelineVertexInputStateCreateInfo &aVertexInput, VkPrimitiveTopology aTopology, const std::vector< VkPipelineColorBlendAttachmentState > &aBlend, const VkPipelineDepthStencilStateCreateInfo &aDepthStencil, const std::vector< VkDynamicState > &aDynamicStates, VkPipelineLayout aLayout, VkRenderPass aRenderPass, VkPipeline &aPipeline)
void SetScissor(const RHI_SCISSOR_RECT &scissor) override
Only valid inside a render pass.
static VkFilter TranslateFilter(RHI_FILTER filter)
RHI_FramebufferHandle CreateFramebuffer(const RHI_FRAMEBUFFER_DESC &desc) override
Returns a null handle on failure.
VkDebugUtilsMessengerEXT m_debugMessenger
void UploadTexture(RHI_TextureHandle handle, const void *data, size_t sizeBytes) override
Data is tightly packed rows in the texture's format, exactly width * height * bpp bytes.
void * LoadInstanceFunction(const char *name)
static VkFormat TranslateTextureFormat(RHI_TEXTURE_FORMAT format)
RHI_HANDLE_POOL< RHI_TAG_PIPELINE > m_pipelinePool
void DestroyBindGroupLayout(RHI_BindGroupLayoutHandle handle) override
VkPipelineCache m_pipelineCache
VkDescriptorSetLayout createSetLayout(const RHI_BIND_GROUP_LAYOUT_DESC &aDesc)
RHI_SURFACE_EXTENT_POLL_RESULT PollSurfaceExtent(RHI_SwapchainHandle aSwapchain) override
Retry a fixed surface extent that lagged behind the requested window size.
void TransitionTexture(VkCommandBuffer cmd, TextureAllocation &tex, VkImageLayout newLayout, VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage)
RHI_COMPUTE_RESULT ExecuteCompute(const RHI_COMPUTE_JOB &aJob) override
Submit, wait for completion, and copy all readbacks before returning.
bool IsInitialized() const override
bool PresentWaitsForDisplay(RHI_SwapchainHandle aSwapchain) const override
False when presents show at once instead of at the next vertical blank, so frames drawn faster than t...
void BindPipeline(RHI_PipelineHandle pipeline) override
Only valid inside a render pass.
void DestroyShader(RHI_ShaderHandle handle) override
void DestroyBuffer(RHI_BufferHandle handle) override
The handle's generation is retired, so later use of the stale handle is detected.
void SetPushConstants(const void *data, size_t sizeBytes) override
Size must not exceed RHI_MAX_PUSH_CONSTANT_BYTES.
RHI_HANDLE_POOL< RHI_TAG_SWAPCHAIN > m_swapchainPool
void DestroyRenderPass(RHI_RenderPassHandle handle) override
VkPipelineLayout m_presentBlitLayout
RHI_HANDLE_POOL< RHI_TAG_BUFFER > m_bufferPool
void DrawInstanced(uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex=0, int32_t vertexOffset=0, uint32_t firstInstance=0) override
Non-indexed, so vertexOffset is ignored.
int AcquireStaging(size_t aSizeBytes)
RHI_BindGroupHandle CreateBindGroup(const RHI_BIND_GROUP_DESC &desc) override
Returns a null handle on failure.
RHI_PipelineHandle CreatePipeline(const RHI_PIPELINE_DESC &desc) override
Returns a null handle on failure.
VkResult submitCommands(VkCommandBuffer aCmd)
void SetViewport(const RHI_VIEWPORT &viewport) override
Only valid inside a render pass.
void DrawIndexedInstanced(uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex=0, int32_t vertexOffset=0, uint32_t firstInstance=0) override
Reads 16-bit indices from the bound index buffer.
bool EndRenderPass() override
Returns false if no render pass is active.
RHI_SurfaceHandle CreateSurface(const RHI_SURFACE_DESC &desc) override
Returns a null handle if the native window handle is invalid.
VkCommandBuffer EnsureUploadCmdBuf()
RHI_HANDLE_POOL< RHI_TAG_COMPUTE_PIPELINE > m_computePipelinePool
VkPipeline EnsurePresentBlit(VkFormat aSwapchainFormat)
RHI_PipelineHandle m_activePipeline
bool ReadbackFramebufferPixels(RHI_FramebufferHandle handle, uint32_t attachmentIndex, void *buffer, size_t bufferSize) override
buffer must hold at least width * height * bpp bytes.
void endStagedUpload(VkCommandBuffer aCmd, std::unique_lock< std::recursive_mutex > &aLock, int aStaging)
RHI_VULKAN_DEVICE(bool enableValidation)
std::unordered_map< uint32_t, PipelineAllocation > m_pipelines
RHI_BACKEND GetBackend() const override
bool allocateCommandBuffer(VkCommandBuffer &aCmd)
VkRenderPass makeRenderPass(const std::vector< VK_ATTACHMENT > &aColor, const std::optional< VK_ATTACHMENT > &aDepth)
RHI_HANDLE_POOL< RHI_TAG_SHADER > m_shaderPool
std::vector< size_t > m_batchPooledStaging
void BeginUploadBatch() override
UpdateBuffer() and UploadTexture() calls between these share one submission instead of syncing with t...
void ReleaseDescriptorSet(VkDescriptorPool pool, VkDescriptorSet dset=VK_NULL_HANDLE)
VkCommandBuffer m_currentCmd
RHI_HANDLE_POOL< RHI_TAG_TEXTURE > m_texturePool
void ExecuteDeferredReleases() override
void MarkBufferInUse(BufferAllocation &aBuffer)
void DestroySurface(RHI_SurfaceHandle handle) override
Destroy the surface's swapchain first.
RHI_ShaderHandle CreateShader(const RHI_SHADER_DESC &desc) override
Returns a null handle on failure.
RHI_TextureHandle GetFramebufferColorTexture(RHI_FramebufferHandle handle, uint32_t attachmentIndex=0) const override
The texture lets a later pass sample the framebuffer.
bool SubmitOneShot(VkCommandBuffer cmd)
bool endUploadCommands(VkCommandBuffer aCmd, const std::unique_lock< std::recursive_mutex > &aLock)
RHI_FRAME_OP_RESULT Present(RHI_SwapchainHandle handle, RHI_FramebufferHandle aSrcFBO={}, const RHI_SCISSOR_RECT *aSourceRect=nullptr) override
When aSrcFBO is valid its first color attachment is copied to the swapchain first.
void BindGroup(uint32_t groupIndex, RHI_BindGroupHandle group) override
Only valid inside a render pass with a bound pipeline.
VkCommandPool m_commandPool
bool RealizeBuffer(RHI_BufferHandle handle, const RHI_BUFFER_DESC &desc) override
Create the GPU buffer for a reserved handle.
RHI_TextureHandle ReserveTextureHandle() override
Thread-safe handle reservation, realized later by RealizeTexture().
void destroyTexture(TextureAllocation &aTexture)
VkCommandBuffer beginUploadCommands(std::unique_lock< std::recursive_mutex > &aLock)
int stageData(const void *aData, size_t aSizeBytes, VkBuffer &aBuffer)
std::vector< VkCommandBuffer > m_freeCmds
RHI_RenderPassHandle CreateRenderPass(const RHI_RENDER_PASS_DESC &desc) override
Returns a null handle on failure.
VkSampler createSampler(VkFilter aFilter)
void DestroyComputePipeline(RHI_ComputePipelineHandle aHandle) override
RHI_SwapchainHandle CreateSwapchain(const RHI_SWAPCHAIN_DESC &desc) override
Returns a null handle on failure.
bool CreateSwapchainResources(SwapchainAllocation &swa)
std::vector< DescriptorPoolEntry > m_descriptorPools
uint32_t MaxTextureSize() const override
Largest texture width or height in texels.
bool FindPresentQueueFamily(VkSurfaceKHR vkSurface, uint32_t &outFamily)
bool PresentReplacesQueued(RHI_SwapchainHandle aSwapchain) const override
True when a present replaces one still queued for the next vertical blank, as in Vulkan MAILBOX mode,...
bool CopyFramebufferDepth(RHI_FramebufferHandle aSource, RHI_FramebufferHandle aDestination, uint32_t aSourceX=0, uint32_t aSourceY=0) override
Fill all of aDestination with source depth from (aSourceX, aSourceY) on, ordered after passes already...
void destroySwapchainObjects(SwapchainAllocation &aSwa)
std::unordered_map< uint32_t, BindGroupAllocation > m_bindGroups
std::unordered_map< uint32_t, SwapchainAllocation > m_swapchains
std::unordered_map< uint32_t, TextureAllocation > m_textures
RHI_HANDLE_POOL< RHI_TAG_RENDER_PASS > m_renderPassPool
std::unordered_map< uint32_t, FramebufferAllocation > m_framebuffers
void DestroyPipeline(RHI_PipelineHandle handle) override
bool RealizeTexture(RHI_TextureHandle handle, const RHI_TEXTURE_DESC &desc) override
Create the GPU texture for a reserved handle.
std::unordered_map< uint32_t, BindGroupLayoutAllocation > m_bindGroupLayouts
std::unordered_map< VkFormat, VkPipeline > m_presentBlitPipelines
bool CreateVkInstance(bool enableValidation)
void DestroyFramebuffer(RHI_FramebufferHandle handle) override
bool BeginRenderPass(const RHI_RENDER_PASS_BEGIN_DESC &desc) override
Draws and dynamic state changes must come between this and EndRenderPass().
void DestroyBindGroup(RHI_BindGroupHandle handle) override
RHI_COMPUTE_DEVICE_LIMITS ComputeDeviceLimits() const override
VkPhysicalDeviceLimits m_deviceLimits
VkCommandBuffer m_uploadCmdBuf
void Draw(uint32_t vertexCount, uint32_t firstVertex=0) override
Draws are only valid inside a render pass with a bound pipeline.
RHI_COMPUTE_PIPELINE_LIMITS ComputePipelineLimits(RHI_ComputePipelineHandle aHandle) const override
VkResult beginOneTimeCommands(VkCommandBuffer aCmd)
void DestroyTexture(RHI_TextureHandle handle) override
Render thread only.
void * LoadVulkanFunction(const char *name)
wxDynamicLibrary m_vulkanLib
void EndUploadBatch() override
RHI_HANDLE_POOL< RHI_TAG_FRAMEBUFFER > m_framebufferPool
bool SupportsCompute() const override
Compute defaults keep backends without compute support usable for graphics.
RHI_ComputePipelineHandle CreateComputePipeline(const RHI_COMPUTE_PIPELINE_DESC &aDesc) override
VkResult submitPassCmd(VkCommandBuffer aCmd)
bool WaitForPresentSlot(RHI_SwapchainHandle aSwapchain, std::chrono::milliseconds aTimeout, int aAllowed) override
Wait until fewer than aAllowed presents are queued for the display or the timeout passes,...
std::unordered_map< uint32_t, RenderPassAllocation > m_renderPasses
VkDescriptorSetLayout m_presentBlitDSLayout
RHI_HANDLE_POOL< RHI_TAG_SURFACE > m_surfacePool
void UpdateBuffer(RHI_BufferHandle handle, const void *data, size_t sizeBytes, size_t offset=0) override
The buffer need not be mappable.
RHI_BufferHandle ReserveBufferHandle() override
Reserve a handle without creating the GPU buffer, so the UI thread can hand out handles while Realize...
std::unordered_map< uint32_t, BufferAllocation > m_buffers
void DestroySwapchain(RHI_SwapchainHandle handle) override
VkDescriptorPool AcquireDescriptorPool()
bool createImage2D(VkFormat aFormat, uint32_t aWidth, uint32_t aHeight, VkImageUsageFlags aUsage, bool aDedicated, VkImage &aImage, VmaAllocation &aAllocation)
VkPresentModeKHR ChoosePresentMode(const SurfaceAllocation &aSurface)
void BindVertexBuffer(uint32_t slot, RHI_BufferHandle buffer) override
Only valid inside a render pass.
VkPhysicalDevice m_physicalDevice
RHI_BindGroupLayoutHandle CreateBindGroupLayout(const RHI_BIND_GROUP_LAYOUT_DESC &desc) override
Returns a null handle on failure.
std::recursive_mutex m_deviceMutex
bool ResizeSwapchain(RHI_SwapchainHandle handle, uint32_t width, uint32_t height, float dpiScale) override
Call when the window size or DPI scale changes.
void BindIndexBuffer(RHI_BufferHandle buffer) override
Indices are 16 bits on every backend.
void ReleaseStaging(int aIndex)
VkImageView createView(VkImage aImage, VkFormat aFormat, VkImageAspectFlags aAspect)
void DestroySwapchainResources(SwapchainAllocation &swa, bool aKeepSwapchain=false)
std::vector< VkCommandBuffer > m_submittedCmds
void destroyPipeline(PipelineAllocation &aPipeline)
void destroyFramebufferObjects(FramebufferAllocation &aFb)
std::vector< StagingBuffer > m_stagingPool
VkSwapchainTry createVkSwapchain(const SurfaceAllocation &aSurface, const VkSurfaceCapabilitiesKHR &aCaps, VkSurfaceFormatKHR aFormat, VkExtent2D aExtent, VkSwapchainKHR aOld, bool aResize)
static constexpr size_t STAGING_MIN_BYTES
RHI_HANDLE_POOL< RHI_TAG_BIND_GROUP_LAYOUT > m_bindGroupLayoutPool
const char * GetName() const override
Backend and GPU name for display, such as "Vulkan (GeForce RTX 3080)".
void * LoadDeviceFunction(const char *name)
static VkBufferUsageFlags TranslateBufferUsage(RHI_BUFFER_TYPE type)
bool CurrentThreadOwnsUploadBatch() const
const StructColors * colorRefs()
Global list of legacy color names, still used all over the place for constructing COLOR4D's.
Definition color4d.cpp:36
static wxString displayName(const DATABASE_LIB_TABLE &aTable)
const wxChar *const traceRhi
Flag to enable debug output of the RHI rendering backend.
static const std::vector< std::string > extensions
Definition plugin.cpp:63
RHI_SHADER_STAGE
Bit flags for the shader stages that see a binding.
RHI_BINDING_TYPE
constexpr int RHI_MAX_BIND_GROUPS
Most bind groups one pipeline can use.
RHI_BUFFER_TYPE
Definition rhi_buffer.h:26
constexpr bool RhiRangeWithinBuffer(size_t aOffset, size_t aSize, size_t aTotal)
Return true when [aOffset, aOffset + aSize) lies within aTotal bytes.
Definition rhi_buffer.h:44
constexpr uint32_t RHI_MAX_PUSH_CONSTANT_BYTES
Largest push constant size SetPushConstants() accepts, in bytes.
Definition rhi_command.h:60
bool RhiValidateComputeJob(const RHI_COMPUTE_JOB &aJob, const RHI_COMPUTE_DEVICE_LIMITS &aLimits, const RHI_COMPUTE_JOB_LOOKUP &aLookup)
Check a whole job against live resources before any of it executes, so a bad job has no partial effec...
bool RhiValidateComputePipelineDesc(const RHI_COMPUTE_PIPELINE_DESC &aDesc)
Validate the portable contract without accessing a GPU or inspecting shader bytecode.
RHI_COMPUTE_RESULT
Definition rhi_compute.h:79
constexpr uint32_t RHI_SPEC_ID_WORKGROUP_Y
Definition rhi_compute.h:33
constexpr uint32_t RHI_SPEC_ID_WORKGROUP_X
Specialization IDs reserved for the workgroup dimensions; Z is always one.
Definition rhi_compute.h:32
RHI_SURFACE_EXTENT_POLL_RESULT
Definition rhi_device.h:54
RHI_FRAME_OP_RESULT
Windows headers define ERROR as a macro.
Definition rhi_device.h:44
RHI_HANDLE< RHI_TAG_FRAMEBUFFER > RHI_FramebufferHandle
Definition rhi_handle.h:93
RHI_HANDLE< RHI_TAG_TEXTURE > RHI_TextureHandle
Definition rhi_handle.h:84
RHI_HANDLE< RHI_TAG_BUFFER > RHI_BufferHandle
Definition rhi_handle.h:83
RHI_HANDLE< RHI_TAG_PIPELINE > RHI_PipelineHandle
Definition rhi_handle.h:86
RHI_HANDLE< RHI_TAG_BIND_GROUP > RHI_BindGroupHandle
Definition rhi_handle.h:90
RHI_HANDLE< RHI_TAG_COMPUTE_PIPELINE > RHI_ComputePipelineHandle
Definition rhi_handle.h:87
RHI_HANDLE< RHI_TAG_BIND_GROUP_LAYOUT > RHI_BindGroupLayoutHandle
Definition rhi_handle.h:89
uint32_t RhiHandleIndex(RHI_HANDLE< Tag > h)
Definition rhi_handle.h:50
RHI_HANDLE< RHI_TAG_SURFACE > RHI_SurfaceHandle
Definition rhi_handle.h:91
RHI_HANDLE< RHI_TAG_SHADER > RHI_ShaderHandle
Definition rhi_handle.h:85
RHI_HANDLE< RHI_TAG_RENDER_PASS > RHI_RenderPassHandle
Definition rhi_handle.h:88
RHI_HANDLE< RHI_TAG_SWAPCHAIN > RHI_SwapchainHandle
Definition rhi_handle.h:92
constexpr uint32_t RHI_HANDLE_INDEX_MASK
Definition rhi_handle.h:45
RHI_DYNAMIC_STATE
RHI_PRIMITIVE_TOPOLOGY
bool RhiPipelineUsesDepthStencil(const RHI_PIPELINE_DESC &aDesc)
Return true when the pipeline targets framebuffers with a depth/stencil attachment.
RHI_COMPARE_OP
RHI_VERTEX_FORMAT
RHI_BLEND_OP
RHI_BLEND_FACTOR
RHI_PRESENT_MODE
How a swapchain hands finished images to the display.
@ MAILBOX
Replaces the queued image at each blank, never tears, no wait.
@ FIFO
Waits for a vertical blank, never tears, adds up to a refresh of latency.
@ IMMEDIATE
Shows the image at once, tears unless a compositor owns the final present.
RHI_PRESENT_MODE RhiChoosePresentMode(const std::vector< RHI_PRESENT_MODE > &aOffered, bool aComposited, bool aImmediateUnderCompositor=false)
Choose the present mode for a swapchain from the modes the surface offers.
RHI_LOAD_OP
RHI_STORE_OP
RHI_FILTER
Definition rhi_texture.h:32
uint32_t RhiTextureBytesPerPixel(RHI_TEXTURE_FORMAT format)
Definition rhi_texture.h:45
RHI_TEXTURE_FORMAT
Definition rhi_texture.h:27
RHI_BACKEND
Identifies which graphics backend a device implements.
Definition rhi_types.h:28
static VkBlendOp TranslateBlendOp(RHI_BLEND_OP op)
#define LOAD_INST_FN(fn)
static VkAttachmentLoadOp TranslateLoadOp(RHI_LOAD_OP op)
static VkAttachmentStoreOp TranslateStoreOp(RHI_STORE_OP op)
static VkFormat RhiDefaultDepthFormat()
static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT aSeverity, VkDebugUtilsMessageTypeFlagsEXT aType, const VkDebugUtilsMessengerCallbackDataEXT *aCallbackData, void *aUserData)
static VkDynamicState TranslateDynamicState(RHI_DYNAMIC_STATE state)
static VkFormat TranslateVertexFormat(RHI_VERTEX_FORMAT format)
#define LOAD_DEV_FN(fn)
static VkShaderStageFlags TranslateShaderStage(RHI_SHADER_STAGE stages)
static long long VkElapsedMs(PROF_TIMER &aTimer)
static VkDescriptorType TranslateBindingType(RHI_BINDING_TYPE type)
static VkCompareOp TranslateCompareOp(RHI_COMPARE_OP op)
static VkExtent2D swapchainExtent(const VkSurfaceCapabilitiesKHR &aCaps, uint32_t aWidth, uint32_t aHeight)
static VkPrimitiveTopology TranslateTopology(RHI_PRIMITIVE_TOPOLOGY topology)
std::unique_ptr< RHI_DEVICE > RhiCreateVulkanDevice(bool enableValidation)
Create the Vulkan device, loading libvulkan at run time.
static VkAccessFlags LayoutToAccessMask(VkImageLayout layout)
static VK_ATTACHMENT presentAttachment(VkFormat aFormat)
static const char * VkResultToString(VkResult aResult)
static VkImageMemoryBarrier imageBarrier(VkImage aImage, VkImageAspectFlags aAspect, VkImageLayout aOld, VkImageLayout aNew, VkAccessFlags aSrcAccess, VkAccessFlags aDstAccess)
static VkBlendFactor TranslateBlendFactor(RHI_BLEND_FACTOR factor)
RHI_BINDING_TYPE type
RHI_SHADER_STAGE stages
uint32_t binding
std::vector< RHI_BIND_GROUP_RESOURCE > entries
RHI_BindGroupLayoutHandle layout
std::vector< RHI_BINDING_ENTRY > entries
RHI_BUFFER_TYPE type
Definition rhi_buffer.h:35
uint64_t minStorageBufferOffsetAlignment
Definition rhi_compute.h:69
std::array< uint32_t, 3 > maxWorkgroupSize
Definition rhi_compute.h:64
std::array< uint32_t, 3 > maxDispatchGroups
Definition rhi_compute.h:66
Backend command representation; payload bytes are owned by the job.
Backend lookups for RhiValidateComputeJob.
std::function< const RHI_BIND_GROUP_LAYOUT_DESC *(RHI_BindGroupHandle)> groupLayout
std::function< bool(RHI_BufferHandle, size_t aOffset, size_t aBytes)> storageRange
std::function< const RHI_COMPUTE_PIPELINE_DESC *(RHI_ComputePipelineHandle)> pipeline
std::vector< RHI_SPECIALIZATION_CONSTANT > constants
Definition rhi_compute.h:47
std::vector< RHI_BIND_GROUP_LAYOUT_DESC > bindGroupLayouts
Definition rhi_compute.h:46
RHI_SHADER_BLOB shader
Definition rhi_compute.h:45
RHI_DEFERRED_RESOURCE_TYPE type
uint32_t handleValue
generation and index
RHI_COMPARE_OP depthCompareOp
std::vector< RHI_TEXTURE_FORMAT > colorFormats
uint32_t value
Definition rhi_handle.h:35
RHI_DISPLAY_TYPE displayType
Definition rhi_surface.h:48
Everything here is fixed at creation except the states listed in dynamicStates.
std::vector< RHI_BIND_GROUP_LAYOUT_DESC > bindGroupLayouts
Element n is bind group n, as passed to RHI_DEVICE::BindGroup().
std::vector< RHI_DYNAMIC_STATE > dynamicStates
RHI_TEXTURE_FORMAT colorFormat
RHI_PRIMITIVE_TOPOLOGY topology
std::vector< RHI_VERTEX_BUFFER_LAYOUT_DESC > vertexBuffers
RHI_ShaderHandle vertexShader
RHI_ShaderHandle fragmentShader
std::vector< RHI_BLEND_STATE > blendStates
One per color attachment.
RHI_DEPTH_STENCIL_STATE depthStencil
RHI_FramebufferHandle framebuffer
Definition rhi_command.h:49
bool hasClearColorOverride
Replaces the render pass clear color of every CLEAR color attachment, so the background can change pe...
Definition rhi_command.h:55
RHI_RenderPassHandle renderPass
Definition rhi_command.h:48
std::vector< RHI_COLOR_ATTACHMENT_DESC > colorAttachments
RHI_DEPTH_STENCIL_ATTACHMENT_DESC depthStencilAttachment
Same pixel space as RHI_VIEWPORT.
Definition rhi_command.h:39
const uint8_t * spirv
Definition rhi_shader.h:47
The bytecode is SPIR-V on Vulkan and MSL source text on Metal.
Definition rhi_shader.h:35
const uint8_t * bytecode
Definition rhi_shader.h:37
Descriptor for creating a surface from a native window.
Definition rhi_surface.h:71
RHI_NATIVE_WINDOW_HANDLE window
Definition rhi_surface.h:72
Descriptor for creating a swapchain.
Definition rhi_surface.h:79
RHI_SurfaceHandle surface
Definition rhi_surface.h:80
RHI_TEXTURE_FORMAT format
Definition rhi_texture.h:40
RHI_FILTER filter
Definition rhi_texture.h:41
Top-left origin, in device pixels.
Definition rhi_command.h:28
std::vector< RHI_TEXTURE_FORMAT > colorFormats
std::vector< RHI_TextureHandle > colorTextureHandles
std::vector< VmaAllocation > colorAllocations
std::vector< VkDescriptorSetLayout > dsLayouts
std::vector< RHI_COLOR_ATTACHMENT_DESC > colorDescs
RHI_DEPTH_STENCIL_ATTACHMENT_DESC depthStencilDesc
std::vector< VkFramebuffer > presentFramebuffers
std::chrono::steady_clock::time_point nextSurfaceExtentCheck
VkImageLayout initialLayout
VkAttachmentLoadOp loadOp
VkAttachmentStoreOp storeOp
VkImageLayout finalLayout
PFN_vkCreateFence CreateFence
PFN_vkCmdClearColorImage CmdClearColorImage
PFN_vkDestroyDevice DestroyDevice
PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties
PFN_vkDestroySemaphore DestroySemaphore
PFN_vkCmdCopyImage CmdCopyImage
PFN_vkCreateDescriptorPool CreateDescriptorPool
PFN_vkQueuePresentKHR QueuePresentKHR
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR GetPhysicalDeviceSurfacePresentModesKHR
PFN_vkDestroyDescriptorSetLayout DestroyDescriptorSetLayout
PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties
PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR
PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage
PFN_vkCmdPipelineBarrier CmdPipelineBarrier
PFN_vkQueueWaitIdle QueueWaitIdle
PFN_vkCmdSetScissor CmdSetScissor
PFN_vkCreateCommandPool CreateCommandPool
PFN_vkAcquireNextImageKHR AcquireNextImageKHR
PFN_vkGetDeviceProcAddr GetDeviceProcAddr
PFN_vkEnumerateInstanceLayerProperties EnumerateInstanceLayerProperties
PFN_vkResetFences ResetFences
PFN_vkCmdBindPipeline CmdBindPipeline
PFN_vkDestroySwapchainKHR DestroySwapchainKHR
PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout
PFN_vkCreateSemaphore CreateSemaphore
PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices
PFN_vkGetPhysicalDeviceProperties GetPhysicalDeviceProperties
PFN_vkCreateSwapchainKHR CreateSwapchainKHR
PFN_vkCmdDispatch CmdDispatch
PFN_vkCmdEndRenderPass CmdEndRenderPass
PFN_vkBeginCommandBuffer BeginCommandBuffer
PFN_vkQueueSubmit QueueSubmit
PFN_vkCmdBindVertexBuffers CmdBindVertexBuffers
PFN_vkCmdBindIndexBuffer CmdBindIndexBuffer
PFN_vkEndCommandBuffer EndCommandBuffer
PFN_vkGetPhysicalDeviceFeatures2 GetPhysicalDeviceFeatures2
PFN_vkGetInstanceProcAddr GetInstanceProcAddr
PFN_vkCreatePipelineCache CreatePipelineCache
PFN_vkCmdSetViewport CmdSetViewport
PFN_vkCreateInstance CreateInstance
PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2
PFN_vkDestroyCommandPool DestroyCommandPool
PFN_vkCreateDevice CreateDevice
PFN_vkCreateImageView CreateImageView
PFN_vkCreateShaderModule CreateShaderModule
PFN_vkAllocateCommandBuffers AllocateCommandBuffers
PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT
PFN_vkDestroySurfaceKHR DestroySurfaceKHR
PFN_vkFreeDescriptorSets FreeDescriptorSets
PFN_vkCmdCopyImageToBuffer CmdCopyImageToBuffer
PFN_vkDestroyRenderPass DestroyRenderPass
PFN_vkDestroyFence DestroyFence
PFN_vkCreateFramebuffer CreateFramebuffer
PFN_vkCreateRenderPass CreateRenderPass
PFN_vkGetDeviceQueue GetDeviceQueue
PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties
PFN_vkCmdPushConstants CmdPushConstants
PFN_vkWaitForPresentKHR WaitForPresentKHR
PFN_vkCmdDrawIndexed CmdDrawIndexed
PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines
PFN_vkDestroyImageView DestroyImageView
PFN_vkUpdateDescriptorSets UpdateDescriptorSets
PFN_vkDestroyShaderModule DestroyShaderModule
PFN_vkDestroyPipelineLayout DestroyPipelineLayout
PFN_vkDestroyPipeline DestroyPipeline
PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets
PFN_vkCreateComputePipelines CreateComputePipelines
PFN_vkCreatePipelineLayout CreatePipelineLayout
PFN_vkCreateSampler CreateSampler
PFN_vkAllocateDescriptorSets AllocateDescriptorSets
PFN_vkFreeCommandBuffers FreeCommandBuffers
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR
PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT
PFN_vkWaitForFences WaitForFences
PFN_vkDestroyFramebuffer DestroyFramebuffer
PFN_vkDeviceWaitIdle DeviceWaitIdle
PFN_vkDestroySampler DestroySampler
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR
PFN_vkDestroyInstance DestroyInstance
PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR
PFN_vkDestroyPipelineCache DestroyPipelineCache
PFN_vkCmdCopyBuffer CmdCopyBuffer
PFN_vkCmdBeginRenderPass CmdBeginRenderPass
PFN_vkDestroyDescriptorPool DestroyDescriptorPool
VECTOR3I expected(15, 30, 45)
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.