1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Gui module |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include "qrhivulkan_p_p.h" |
38 | #include "qrhivulkanext_p.h" |
39 | |
40 | #define VMA_IMPLEMENTATION |
41 | #define VMA_STATIC_VULKAN_FUNCTIONS 0 |
42 | #define VMA_RECORDING_ENABLED 0 |
43 | #define VMA_DEDICATED_ALLOCATION 0 |
44 | #ifdef QT_DEBUG |
45 | #define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 |
46 | #endif |
47 | #include "vk_mem_alloc.h" |
48 | |
49 | #include <qmath.h> |
50 | #include <QVulkanFunctions> |
51 | #include <QtGui/qwindow.h> |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | /* |
56 | Vulkan 1.0 backend. Provides a double-buffered swapchain that throttles the |
57 | rendering thread to vsync. Textures and "static" buffers are device local, |
58 | and a separate, host visible staging buffer is used to upload data to them. |
59 | "Dynamic" buffers are in host visible memory and are duplicated (since there |
60 | can be 2 frames in flight). This is handled transparently to the application. |
61 | |
62 | Barriers are generated automatically for each render or compute pass, based |
63 | on the resources that are used in that pass (in QRhiShaderResourceBindings, |
64 | vertex inputs, etc.). This implies deferring the recording of the command |
65 | buffer since the barriers have to be placed at the right place (before the |
66 | pass), and that can only be done once we know all the things the pass does. |
67 | |
68 | This in turn has implications for integrating external commands |
69 | (beginExternal() - direct Vulkan calls - endExternal()) because that is |
70 | incompatible with this approach by nature. Therefore we support another mode |
71 | of operation, where each render or compute pass uses one or more secondary |
72 | command buffers (recorded right away), with each beginExternal() leading to |
73 | closing the current secondary cb, creating a new secondary cb for the |
74 | external content, and then starting yet another one in endExternal() for |
75 | whatever comes afterwards in the pass. This way the primary command buffer |
76 | only has vkCmdExecuteCommand(s) within a renderpass instance |
77 | (Begin-EndRenderPass). (i.e. our only subpass is then |
78 | VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS instead of |
79 | VK_SUBPASS_CONTENTS_INLINE) |
80 | |
81 | The command buffer management mode is decided on a per frame basis, |
82 | controlled by the ExternalContentsInPass flag of beginFrame(). |
83 | */ |
84 | |
85 | /*! |
86 | \class QRhiVulkanInitParams |
87 | \internal |
88 | \inmodule QtGui |
89 | \brief Vulkan specific initialization parameters. |
90 | |
91 | A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to |
92 | the user to ensure this is available and initialized. This is typically |
93 | done in main() similarly to the following: |
94 | |
95 | \badcode |
96 | int main(int argc, char **argv) |
97 | { |
98 | ... |
99 | |
100 | QVulkanInstance inst; |
101 | #ifndef Q_OS_ANDROID |
102 | inst.setLayers(QByteArrayList() << "VK_LAYER_LUNARG_standard_validation"); |
103 | #else |
104 | inst.setLayers(QByteArrayList() |
105 | << "VK_LAYER_GOOGLE_threading" |
106 | << "VK_LAYER_LUNARG_parameter_validation" |
107 | << "VK_LAYER_LUNARG_object_tracker" |
108 | << "VK_LAYER_LUNARG_core_validation" |
109 | << "VK_LAYER_LUNARG_image" |
110 | << "VK_LAYER_LUNARG_swapchain" |
111 | << "VK_LAYER_GOOGLE_unique_objects"); |
112 | #endif |
113 | inst.setExtensions(QByteArrayList() |
114 | << "VK_KHR_get_physical_device_properties2"); |
115 | if (!inst.create()) |
116 | qFatal("Vulkan not available"); |
117 | |
118 | ... |
119 | } |
120 | \endcode |
121 | |
122 | The example here has two optional aspects: it enables the |
123 | \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan |
124 | validation layers}, when they are available, and also enables the |
125 | VK_KHR_get_physical_device_properties2 extension (part of Vulkan 1.1), when |
126 | available. The former is useful during the development phase (remember that |
127 | QVulkanInstance conveniently redirects messages and warnings to qDebug). |
128 | Avoid enabling it in production builds, however. The latter is important in |
129 | order to make QRhi::CustomInstanceStepRate available with Vulkan since |
130 | VK_EXT_vertex_attribute_divisor (part of Vulkan 1.1) depends on it. It can |
131 | be omitted when instanced drawing with a non-one step rate is not used. |
132 | |
133 | Once this is done, a Vulkan-based QRhi can be created by passing the |
134 | instance and a QWindow with its surface type set to |
135 | QSurface::VulkanSurface: |
136 | |
137 | \badcode |
138 | QRhiVulkanInitParams params; |
139 | params.inst = vulkanInstance; |
140 | params.window = window; |
141 | rhi = QRhi::create(QRhi::Vulkan, ¶ms); |
142 | \endcode |
143 | |
144 | The window is optional and can be omitted. This is not recommended however |
145 | because there is then no way to ensure presenting is supported while |
146 | choosing a graphics queue. |
147 | |
148 | \note Even when a window is specified, QRhiSwapChain objects can be created |
149 | for other windows as well, as long as they all have their |
150 | QWindow::surfaceType() set to QSurface::VulkanSurface. |
151 | |
152 | To request additional extensions to be enabled on the Vulkan device, list them |
153 | in deviceExtensions. This can be relevant when integrating with native Vulkan |
154 | rendering code. |
155 | |
156 | \section2 Working with existing Vulkan devices |
157 | |
158 | When interoperating with another graphics engine, it may be necessary to |
159 | get a QRhi instance that uses the same Vulkan device. This can be achieved |
160 | by passing a pointer to a QRhiVulkanNativeHandles to QRhi::create(). |
161 | |
162 | The physical device and device object must then be set to a non-null value. |
163 | In addition, either the graphics queue family index or the graphics queue |
164 | object itself is required. Prefer the former, whenever possible since |
165 | deducing the index is not possible afterwards. Optionally, an existing |
166 | command pool object can be specified as well, and, also optionally, |
167 | vmemAllocator can be used to share the same |
168 | \l{https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator}{Vulkan |
169 | memory allocator} between two QRhi instances. |
170 | |
171 | The QRhi does not take ownership of any of the external objects. |
172 | */ |
173 | |
174 | /*! |
175 | \class QRhiVulkanNativeHandles |
176 | \internal |
177 | \inmodule QtGui |
178 | \brief Collects device, queue, and other Vulkan objects that are used by the QRhi. |
179 | |
180 | \note Ownership of the Vulkan objects is never transferred. |
181 | */ |
182 | |
183 | /*! |
184 | \class QRhiVulkanCommandBufferNativeHandles |
185 | \internal |
186 | \inmodule QtGui |
187 | \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer. |
188 | |
189 | \note The Vulkan command buffer object is only guaranteed to be valid, and |
190 | in recording state, while recording a frame. That is, between a |
191 | \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or |
192 | \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} - |
193 | \l{QRhi::endOffsrceenFrame()}{endOffscreenFrame()} pair. |
194 | */ |
195 | |
196 | /*! |
197 | \class QRhiVulkanRenderPassNativeHandles |
198 | \internal |
199 | \inmodule QtGui |
200 | \brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor. |
201 | */ |
202 | |
203 | template <class Int> |
204 | inline Int aligned(Int v, Int byteAlign) |
205 | { |
206 | return (v + byteAlign - 1) & ~(byteAlign - 1); |
207 | } |
208 | |
209 | static QVulkanInstance *globalVulkanInstance; |
210 | |
211 | static void VKAPI_PTR wrap_vkGetPhysicalDeviceProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties* pProperties) |
212 | { |
213 | globalVulkanInstance->functions()->vkGetPhysicalDeviceProperties(physicalDevice, pProperties); |
214 | } |
215 | |
216 | static void VKAPI_PTR wrap_vkGetPhysicalDeviceMemoryProperties(VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties* pMemoryProperties) |
217 | { |
218 | globalVulkanInstance->functions()->vkGetPhysicalDeviceMemoryProperties(physicalDevice, pMemoryProperties); |
219 | } |
220 | |
221 | static VkResult VKAPI_PTR wrap_vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) |
222 | { |
223 | return globalVulkanInstance->deviceFunctions(device)->vkAllocateMemory(device, pAllocateInfo, pAllocator, pMemory); |
224 | } |
225 | |
226 | void VKAPI_PTR wrap_vkFreeMemory(VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks* pAllocator) |
227 | { |
228 | globalVulkanInstance->deviceFunctions(device)->vkFreeMemory(device, memory, pAllocator); |
229 | } |
230 | |
231 | VkResult VKAPI_PTR wrap_vkMapMemory(VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData) |
232 | { |
233 | return globalVulkanInstance->deviceFunctions(device)->vkMapMemory(device, memory, offset, size, flags, ppData); |
234 | } |
235 | |
236 | void VKAPI_PTR wrap_vkUnmapMemory(VkDevice device, VkDeviceMemory memory) |
237 | { |
238 | globalVulkanInstance->deviceFunctions(device)->vkUnmapMemory(device, memory); |
239 | } |
240 | |
241 | VkResult VKAPI_PTR wrap_vkFlushMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges) |
242 | { |
243 | return globalVulkanInstance->deviceFunctions(device)->vkFlushMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges); |
244 | } |
245 | |
246 | VkResult VKAPI_PTR wrap_vkInvalidateMappedMemoryRanges(VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange* pMemoryRanges) |
247 | { |
248 | return globalVulkanInstance->deviceFunctions(device)->vkInvalidateMappedMemoryRanges(device, memoryRangeCount, pMemoryRanges); |
249 | } |
250 | |
251 | VkResult VKAPI_PTR wrap_vkBindBufferMemory(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) |
252 | { |
253 | return globalVulkanInstance->deviceFunctions(device)->vkBindBufferMemory(device, buffer, memory, memoryOffset); |
254 | } |
255 | |
256 | VkResult VKAPI_PTR wrap_vkBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) |
257 | { |
258 | return globalVulkanInstance->deviceFunctions(device)->vkBindImageMemory(device, image, memory, memoryOffset); |
259 | } |
260 | |
261 | void VKAPI_PTR wrap_vkGetBufferMemoryRequirements(VkDevice device, VkBuffer buffer, VkMemoryRequirements* pMemoryRequirements) |
262 | { |
263 | globalVulkanInstance->deviceFunctions(device)->vkGetBufferMemoryRequirements(device, buffer, pMemoryRequirements); |
264 | } |
265 | |
266 | void VKAPI_PTR wrap_vkGetImageMemoryRequirements(VkDevice device, VkImage image, VkMemoryRequirements* pMemoryRequirements) |
267 | { |
268 | globalVulkanInstance->deviceFunctions(device)->vkGetImageMemoryRequirements(device, image, pMemoryRequirements); |
269 | } |
270 | |
271 | VkResult VKAPI_PTR wrap_vkCreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) |
272 | { |
273 | return globalVulkanInstance->deviceFunctions(device)->vkCreateBuffer(device, pCreateInfo, pAllocator, pBuffer); |
274 | } |
275 | |
276 | void VKAPI_PTR wrap_vkDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator) |
277 | { |
278 | globalVulkanInstance->deviceFunctions(device)->vkDestroyBuffer(device, buffer, pAllocator); |
279 | } |
280 | |
281 | VkResult VKAPI_PTR wrap_vkCreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkImage* pImage) |
282 | { |
283 | return globalVulkanInstance->deviceFunctions(device)->vkCreateImage(device, pCreateInfo, pAllocator, pImage); |
284 | } |
285 | |
286 | void VKAPI_PTR wrap_vkDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator) |
287 | { |
288 | globalVulkanInstance->deviceFunctions(device)->vkDestroyImage(device, image, pAllocator); |
289 | } |
290 | |
291 | static inline VmaAllocation toVmaAllocation(QVkAlloc a) |
292 | { |
293 | return reinterpret_cast<VmaAllocation>(a); |
294 | } |
295 | |
296 | static inline VmaAllocator toVmaAllocator(QVkAllocator a) |
297 | { |
298 | return reinterpret_cast<VmaAllocator>(a); |
299 | } |
300 | |
301 | QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importDevice) |
302 | : ofr(this) |
303 | { |
304 | inst = params->inst; |
305 | maybeWindow = params->window; // may be null |
306 | requestedDeviceExtensions = params->deviceExtensions; |
307 | |
308 | importedDevice = importDevice != nullptr; |
309 | if (importedDevice) { |
310 | physDev = importDevice->physDev; |
311 | dev = importDevice->dev; |
312 | if (physDev && dev) { |
313 | gfxQueueFamilyIdx = importDevice->gfxQueueFamilyIdx; |
314 | gfxQueue = importDevice->gfxQueue; |
315 | if (importDevice->cmdPool) { |
316 | importedCmdPool = true; |
317 | cmdPool = importDevice->cmdPool; |
318 | } |
319 | if (importDevice->vmemAllocator) { |
320 | importedAllocator = true; |
321 | allocator = importDevice->vmemAllocator; |
322 | } |
323 | } else { |
324 | qWarning(msg: "No (physical) Vulkan device is given, cannot import" ); |
325 | importedDevice = false; |
326 | } |
327 | } |
328 | } |
329 | |
330 | static bool qvk_debug_filter(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, |
331 | size_t location, int32_t messageCode, const char *pLayerPrefix, const char *pMessage) |
332 | { |
333 | Q_UNUSED(flags); |
334 | Q_UNUSED(objectType); |
335 | Q_UNUSED(object); |
336 | Q_UNUSED(location); |
337 | Q_UNUSED(messageCode); |
338 | Q_UNUSED(pLayerPrefix); |
339 | |
340 | // Filter out certain misleading validation layer messages, as per |
341 | // VulkanMemoryAllocator documentation. |
342 | if (strstr(haystack: pMessage, needle: "Mapping an image with layout" ) |
343 | && strstr(haystack: pMessage, needle: "can result in undefined behavior if this memory is used by the device" )) |
344 | { |
345 | return true; |
346 | } |
347 | |
348 | // In certain cases allocateDescriptorSet() will attempt to allocate from a |
349 | // pool that does not have enough descriptors of a certain type. This makes |
350 | // the validation layer shout. However, this is not an error since we will |
351 | // then move on to another pool. If there is a real error, a qWarning |
352 | // message is shown by allocateDescriptorSet(), so the validation warning |
353 | // does not have any value and is just noise. |
354 | if (strstr(haystack: pMessage, needle: "VUID-VkDescriptorSetAllocateInfo-descriptorPool-00307" )) |
355 | return true; |
356 | |
357 | return false; |
358 | } |
359 | |
360 | bool QRhiVulkan::create(QRhi::Flags flags) |
361 | { |
362 | Q_UNUSED(flags); |
363 | Q_ASSERT(inst); |
364 | |
365 | if (!inst->isValid()) { |
366 | qWarning(msg: "Vulkan instance is not valid" ); |
367 | return false; |
368 | } |
369 | |
370 | globalVulkanInstance = inst; // assume this will not change during the lifetime of the entire application |
371 | |
372 | f = inst->functions(); |
373 | |
374 | QVector<VkQueueFamilyProperties> queueFamilyProps; |
375 | auto queryQueueFamilyProps = [this, &queueFamilyProps] { |
376 | uint32_t queueCount = 0; |
377 | f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr); |
378 | queueFamilyProps.resize(asize: int(queueCount)); |
379 | f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data()); |
380 | }; |
381 | |
382 | if (!importedDevice) { |
383 | uint32_t physDevCount = 0; |
384 | f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr); |
385 | if (!physDevCount) { |
386 | qWarning(msg: "No physical devices" ); |
387 | return false; |
388 | } |
389 | QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount); |
390 | VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data()); |
391 | if (err != VK_SUCCESS || !physDevCount) { |
392 | qWarning(msg: "Failed to enumerate physical devices: %d" , err); |
393 | return false; |
394 | } |
395 | |
396 | int physDevIndex = -1; |
397 | int requestedPhysDevIndex = -1; |
398 | if (qEnvironmentVariableIsSet(varName: "QT_VK_PHYSICAL_DEVICE_INDEX" )) |
399 | requestedPhysDevIndex = qEnvironmentVariableIntValue(varName: "QT_VK_PHYSICAL_DEVICE_INDEX" ); |
400 | |
401 | if (requestedPhysDevIndex < 0 && flags.testFlag(flag: QRhi::PreferSoftwareRenderer)) { |
402 | for (int i = 0; i < int(physDevCount); ++i) { |
403 | f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties); |
404 | if (physDevProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) { |
405 | requestedPhysDevIndex = i; |
406 | break; |
407 | } |
408 | } |
409 | } |
410 | |
411 | for (int i = 0; i < int(physDevCount); ++i) { |
412 | f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties); |
413 | qCDebug(QRHI_LOG_INFO, "Physical device %d: '%s' %d.%d.%d (api %d.%d.%d vendor 0x%X device 0x%X type %d)" , |
414 | i, |
415 | physDevProperties.deviceName, |
416 | VK_VERSION_MAJOR(physDevProperties.driverVersion), |
417 | VK_VERSION_MINOR(physDevProperties.driverVersion), |
418 | VK_VERSION_PATCH(physDevProperties.driverVersion), |
419 | VK_VERSION_MAJOR(physDevProperties.apiVersion), |
420 | VK_VERSION_MINOR(physDevProperties.apiVersion), |
421 | VK_VERSION_PATCH(physDevProperties.apiVersion), |
422 | physDevProperties.vendorID, |
423 | physDevProperties.deviceID, |
424 | physDevProperties.deviceType); |
425 | if (physDevIndex < 0 && (requestedPhysDevIndex < 0 || requestedPhysDevIndex == int(i))) { |
426 | physDevIndex = i; |
427 | qCDebug(QRHI_LOG_INFO, " using this physical device" ); |
428 | } |
429 | } |
430 | |
431 | if (physDevIndex < 0) { |
432 | qWarning(msg: "No matching physical device" ); |
433 | return false; |
434 | } |
435 | physDev = physDevs[physDevIndex]; |
436 | |
437 | queryQueueFamilyProps(); |
438 | |
439 | gfxQueue = VK_NULL_HANDLE; |
440 | |
441 | // We only support combined graphics+present queues. When it comes to |
442 | // compute, only combined graphics+compute queue is used, compute gets |
443 | // disabled otherwise. |
444 | gfxQueueFamilyIdx = -1; |
445 | int computelessGfxQueueCandidateIdx = -1; |
446 | for (int i = 0; i < queueFamilyProps.count(); ++i) { |
447 | qCDebug(QRHI_LOG_INFO, "queue family %d: flags=0x%x count=%d" , |
448 | i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount); |
449 | if (gfxQueueFamilyIdx == -1 |
450 | && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) |
451 | && (!maybeWindow || inst->supportsPresent(physicalDevice: physDev, queueFamilyIndex: uint32_t(i), window: maybeWindow))) |
452 | { |
453 | if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) |
454 | gfxQueueFamilyIdx = i; |
455 | else if (computelessGfxQueueCandidateIdx == -1) |
456 | computelessGfxQueueCandidateIdx = i; |
457 | } |
458 | } |
459 | if (gfxQueueFamilyIdx == -1) { |
460 | if (computelessGfxQueueCandidateIdx != -1) { |
461 | gfxQueueFamilyIdx = computelessGfxQueueCandidateIdx; |
462 | } else { |
463 | qWarning(msg: "No graphics (or no graphics+present) queue family found" ); |
464 | return false; |
465 | } |
466 | } |
467 | |
468 | VkDeviceQueueCreateInfo queueInfo[2]; |
469 | const float prio[] = { 0 }; |
470 | memset(s: queueInfo, c: 0, n: sizeof(queueInfo)); |
471 | queueInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; |
472 | queueInfo[0].queueFamilyIndex = uint32_t(gfxQueueFamilyIdx); |
473 | queueInfo[0].queueCount = 1; |
474 | queueInfo[0].pQueuePriorities = prio; |
475 | |
476 | QVector<const char *> devLayers; |
477 | if (inst->layers().contains(t: "VK_LAYER_LUNARG_standard_validation" )) |
478 | devLayers.append(t: "VK_LAYER_LUNARG_standard_validation" ); |
479 | |
480 | QVulkanInfoVector<QVulkanExtension> devExts; |
481 | uint32_t devExtCount = 0; |
482 | f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr); |
483 | if (devExtCount) { |
484 | QVector<VkExtensionProperties> extProps(devExtCount); |
485 | f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data()); |
486 | for (const VkExtensionProperties &p : qAsConst(t&: extProps)) |
487 | devExts.append(t: { .name: p.extensionName, .version: p.specVersion }); |
488 | } |
489 | qCDebug(QRHI_LOG_INFO, "%d device extensions available" , devExts.count()); |
490 | |
491 | QVector<const char *> requestedDevExts; |
492 | requestedDevExts.append(t: "VK_KHR_swapchain" ); |
493 | |
494 | debugMarkersAvailable = false; |
495 | if (devExts.contains(VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) { |
496 | requestedDevExts.append(VK_EXT_DEBUG_MARKER_EXTENSION_NAME); |
497 | debugMarkersAvailable = true; |
498 | } |
499 | |
500 | vertexAttribDivisorAvailable = false; |
501 | if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) { |
502 | if (inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2" ))) { |
503 | requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME); |
504 | vertexAttribDivisorAvailable = true; |
505 | } |
506 | } |
507 | |
508 | for (const QByteArray &ext : requestedDeviceExtensions) { |
509 | if (!ext.isEmpty()) { |
510 | if (devExts.contains(name: ext)) |
511 | requestedDevExts.append(t: ext.constData()); |
512 | else |
513 | qWarning(msg: "Device extension %s is not supported" , ext.constData()); |
514 | } |
515 | } |
516 | |
517 | QByteArrayList envExtList = qgetenv(varName: "QT_VULKAN_DEVICE_EXTENSIONS" ).split(sep: ';'); |
518 | for (const QByteArray &ext : envExtList) { |
519 | if (!ext.isEmpty() && !requestedDevExts.contains(t: ext)) { |
520 | if (devExts.contains(name: ext)) |
521 | requestedDevExts.append(t: ext.constData()); |
522 | else |
523 | qWarning(msg: "Device extension %s is not supported" , ext.constData()); |
524 | } |
525 | } |
526 | |
527 | if (QRHI_LOG_INFO().isEnabled(type: QtDebugMsg)) { |
528 | qCDebug(QRHI_LOG_INFO, "Enabling device extensions:" ); |
529 | for (const char *ext : requestedDevExts) |
530 | qCDebug(QRHI_LOG_INFO, " %s" , ext); |
531 | } |
532 | |
533 | VkDeviceCreateInfo devInfo; |
534 | memset(s: &devInfo, c: 0, n: sizeof(devInfo)); |
535 | devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; |
536 | devInfo.queueCreateInfoCount = 1; |
537 | devInfo.pQueueCreateInfos = queueInfo; |
538 | devInfo.enabledLayerCount = uint32_t(devLayers.count()); |
539 | devInfo.ppEnabledLayerNames = devLayers.constData(); |
540 | devInfo.enabledExtensionCount = uint32_t(requestedDevExts.count()); |
541 | devInfo.ppEnabledExtensionNames = requestedDevExts.constData(); |
542 | |
543 | err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev); |
544 | if (err != VK_SUCCESS) { |
545 | qWarning(msg: "Failed to create device: %d" , err); |
546 | return false; |
547 | } |
548 | } |
549 | |
550 | df = inst->deviceFunctions(device: dev); |
551 | |
552 | if (!importedCmdPool) { |
553 | VkCommandPoolCreateInfo poolInfo; |
554 | memset(s: &poolInfo, c: 0, n: sizeof(poolInfo)); |
555 | poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; |
556 | poolInfo.queueFamilyIndex = uint32_t(gfxQueueFamilyIdx); |
557 | VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool); |
558 | if (err != VK_SUCCESS) { |
559 | qWarning(msg: "Failed to create command pool: %d" , err); |
560 | return false; |
561 | } |
562 | } |
563 | |
564 | if (gfxQueueFamilyIdx != -1) { |
565 | if (!gfxQueue) |
566 | df->vkGetDeviceQueue(dev, uint32_t(gfxQueueFamilyIdx), 0, &gfxQueue); |
567 | |
568 | if (queueFamilyProps.isEmpty()) |
569 | queryQueueFamilyProps(); |
570 | |
571 | hasCompute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; |
572 | timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits; |
573 | } |
574 | |
575 | f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties); |
576 | ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment; |
577 | // helps little with an optimal offset of 1 (on some drivers) when the spec |
578 | // elsewhere states that the minimum bufferOffset is 4... |
579 | texbufAlign = qMax<VkDeviceSize>(a: 4, b: physDevProperties.limits.optimalBufferCopyOffsetAlignment); |
580 | |
581 | f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures); |
582 | hasWideLines = physDevFeatures.wideLines; |
583 | |
584 | if (!importedAllocator) { |
585 | VmaVulkanFunctions afuncs; |
586 | afuncs.vkGetPhysicalDeviceProperties = wrap_vkGetPhysicalDeviceProperties; |
587 | afuncs.vkGetPhysicalDeviceMemoryProperties = wrap_vkGetPhysicalDeviceMemoryProperties; |
588 | afuncs.vkAllocateMemory = wrap_vkAllocateMemory; |
589 | afuncs.vkFreeMemory = wrap_vkFreeMemory; |
590 | afuncs.vkMapMemory = wrap_vkMapMemory; |
591 | afuncs.vkUnmapMemory = wrap_vkUnmapMemory; |
592 | afuncs.vkFlushMappedMemoryRanges = wrap_vkFlushMappedMemoryRanges; |
593 | afuncs.vkInvalidateMappedMemoryRanges = wrap_vkInvalidateMappedMemoryRanges; |
594 | afuncs.vkBindBufferMemory = wrap_vkBindBufferMemory; |
595 | afuncs.vkBindImageMemory = wrap_vkBindImageMemory; |
596 | afuncs.vkGetBufferMemoryRequirements = wrap_vkGetBufferMemoryRequirements; |
597 | afuncs.vkGetImageMemoryRequirements = wrap_vkGetImageMemoryRequirements; |
598 | afuncs.vkCreateBuffer = wrap_vkCreateBuffer; |
599 | afuncs.vkDestroyBuffer = wrap_vkDestroyBuffer; |
600 | afuncs.vkCreateImage = wrap_vkCreateImage; |
601 | afuncs.vkDestroyImage = wrap_vkDestroyImage; |
602 | |
603 | VmaAllocatorCreateInfo allocatorInfo; |
604 | memset(s: &allocatorInfo, c: 0, n: sizeof(allocatorInfo)); |
605 | // A QRhi is supposed to be used from one single thread only. Disable |
606 | // the allocator's own mutexes. This gives a performance boost. |
607 | allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; |
608 | allocatorInfo.physicalDevice = physDev; |
609 | allocatorInfo.device = dev; |
610 | allocatorInfo.pVulkanFunctions = &afuncs; |
611 | VmaAllocator vmaallocator; |
612 | VkResult err = vmaCreateAllocator(pCreateInfo: &allocatorInfo, pAllocator: &vmaallocator); |
613 | if (err != VK_SUCCESS) { |
614 | qWarning(msg: "Failed to create allocator: %d" , err); |
615 | return false; |
616 | } |
617 | allocator = vmaallocator; |
618 | } |
619 | |
620 | inst->installDebugOutputFilter(filter: qvk_debug_filter); |
621 | |
622 | VkDescriptorPool pool; |
623 | VkResult err = createDescriptorPool(pool: &pool); |
624 | if (err == VK_SUCCESS) |
625 | descriptorPools.append(t: pool); |
626 | else |
627 | qWarning(msg: "Failed to create initial descriptor pool: %d" , err); |
628 | |
629 | VkQueryPoolCreateInfo timestampQueryPoolInfo; |
630 | memset(s: ×tampQueryPoolInfo, c: 0, n: sizeof(timestampQueryPoolInfo)); |
631 | timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; |
632 | timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP; |
633 | timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2; |
634 | err = df->vkCreateQueryPool(dev, ×tampQueryPoolInfo, nullptr, ×tampQueryPool); |
635 | if (err != VK_SUCCESS) { |
636 | qWarning(msg: "Failed to create timestamp query pool: %d" , err); |
637 | return false; |
638 | } |
639 | timestampQueryPoolMap.resize(size: QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair |
640 | timestampQueryPoolMap.fill(aval: false); |
641 | |
642 | if (debugMarkersAvailable) { |
643 | vkCmdDebugMarkerBegin = reinterpret_cast<PFN_vkCmdDebugMarkerBeginEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerBeginEXT" )); |
644 | vkCmdDebugMarkerEnd = reinterpret_cast<PFN_vkCmdDebugMarkerEndEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerEndEXT" )); |
645 | vkCmdDebugMarkerInsert = reinterpret_cast<PFN_vkCmdDebugMarkerInsertEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdDebugMarkerInsertEXT" )); |
646 | vkDebugMarkerSetObjectName = reinterpret_cast<PFN_vkDebugMarkerSetObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkDebugMarkerSetObjectNameEXT" )); |
647 | } |
648 | |
649 | deviceLost = false; |
650 | |
651 | nativeHandlesStruct.physDev = physDev; |
652 | nativeHandlesStruct.dev = dev; |
653 | nativeHandlesStruct.gfxQueueFamilyIdx = gfxQueueFamilyIdx; |
654 | nativeHandlesStruct.gfxQueue = gfxQueue; |
655 | nativeHandlesStruct.cmdPool = cmdPool; |
656 | nativeHandlesStruct.vmemAllocator = allocator; |
657 | |
658 | return true; |
659 | } |
660 | |
661 | void QRhiVulkan::destroy() |
662 | { |
663 | if (!df) |
664 | return; |
665 | |
666 | if (!deviceLost) |
667 | df->vkDeviceWaitIdle(dev); |
668 | |
669 | executeDeferredReleases(forced: true); |
670 | finishActiveReadbacks(forced: true); |
671 | |
672 | if (ofr.cmdFence) { |
673 | df->vkDestroyFence(dev, ofr.cmdFence, nullptr); |
674 | ofr.cmdFence = VK_NULL_HANDLE; |
675 | } |
676 | |
677 | if (ofr.cbWrapper.cb) { |
678 | df->vkFreeCommandBuffers(dev, cmdPool, 1, &ofr.cbWrapper.cb); |
679 | ofr.cbWrapper.cb = VK_NULL_HANDLE; |
680 | } |
681 | |
682 | if (pipelineCache) { |
683 | df->vkDestroyPipelineCache(dev, pipelineCache, nullptr); |
684 | pipelineCache = VK_NULL_HANDLE; |
685 | } |
686 | |
687 | for (const DescriptorPoolData &pool : descriptorPools) |
688 | df->vkDestroyDescriptorPool(dev, pool.pool, nullptr); |
689 | |
690 | descriptorPools.clear(); |
691 | |
692 | if (timestampQueryPool) { |
693 | df->vkDestroyQueryPool(dev, timestampQueryPool, nullptr); |
694 | timestampQueryPool = VK_NULL_HANDLE; |
695 | } |
696 | |
697 | if (!importedAllocator && allocator) { |
698 | vmaDestroyAllocator(allocator: toVmaAllocator(a: allocator)); |
699 | allocator = nullptr; |
700 | } |
701 | |
702 | if (!importedCmdPool && cmdPool) { |
703 | df->vkDestroyCommandPool(dev, cmdPool, nullptr); |
704 | cmdPool = VK_NULL_HANDLE; |
705 | } |
706 | |
707 | if (!importedDevice && dev) { |
708 | df->vkDestroyDevice(dev, nullptr); |
709 | inst->resetDeviceFunctions(device: dev); |
710 | dev = VK_NULL_HANDLE; |
711 | } |
712 | |
713 | f = nullptr; |
714 | df = nullptr; |
715 | } |
716 | |
717 | VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool) |
718 | { |
719 | VkDescriptorPoolSize descPoolSizes[] = { |
720 | { .type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount: QVK_UNIFORM_BUFFERS_PER_POOL }, |
721 | { .type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .descriptorCount: QVK_UNIFORM_BUFFERS_PER_POOL }, |
722 | { .type: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount: QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL }, |
723 | { .type: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount: QVK_STORAGE_BUFFERS_PER_POOL }, |
724 | { .type: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount: QVK_STORAGE_IMAGES_PER_POOL } |
725 | }; |
726 | VkDescriptorPoolCreateInfo descPoolInfo; |
727 | memset(s: &descPoolInfo, c: 0, n: sizeof(descPoolInfo)); |
728 | descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; |
729 | // Do not enable vkFreeDescriptorSets - sets are never freed on their own |
730 | // (good so no trouble with fragmentation), they just deref their pool |
731 | // which is then reset at some point (or not). |
732 | descPoolInfo.flags = 0; |
733 | descPoolInfo.maxSets = QVK_DESC_SETS_PER_POOL; |
734 | descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]); |
735 | descPoolInfo.pPoolSizes = descPoolSizes; |
736 | return df->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, pool); |
737 | } |
738 | |
739 | bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex) |
740 | { |
741 | auto tryAllocate = [this, allocInfo, result](int poolIndex) { |
742 | allocInfo->descriptorPool = descriptorPools[poolIndex].pool; |
743 | VkResult r = df->vkAllocateDescriptorSets(dev, allocInfo, result); |
744 | if (r == VK_SUCCESS) |
745 | descriptorPools[poolIndex].refCount += 1; |
746 | return r; |
747 | }; |
748 | |
749 | int lastPoolIdx = descriptorPools.count() - 1; |
750 | for (int i = lastPoolIdx; i >= 0; --i) { |
751 | if (descriptorPools[i].refCount == 0) { |
752 | df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0); |
753 | descriptorPools[i].allocedDescSets = 0; |
754 | } |
755 | if (descriptorPools[i].allocedDescSets + int(allocInfo->descriptorSetCount) <= QVK_DESC_SETS_PER_POOL) { |
756 | VkResult err = tryAllocate(i); |
757 | if (err == VK_SUCCESS) { |
758 | descriptorPools[i].allocedDescSets += allocInfo->descriptorSetCount; |
759 | *resultPoolIndex = i; |
760 | return true; |
761 | } |
762 | } |
763 | } |
764 | |
765 | VkDescriptorPool newPool; |
766 | VkResult poolErr = createDescriptorPool(pool: &newPool); |
767 | if (poolErr == VK_SUCCESS) { |
768 | descriptorPools.append(t: newPool); |
769 | lastPoolIdx = descriptorPools.count() - 1; |
770 | VkResult err = tryAllocate(lastPoolIdx); |
771 | if (err != VK_SUCCESS) { |
772 | qWarning(msg: "Failed to allocate descriptor set from new pool too, giving up: %d" , err); |
773 | return false; |
774 | } |
775 | descriptorPools[lastPoolIdx].allocedDescSets += allocInfo->descriptorSetCount; |
776 | *resultPoolIndex = lastPoolIdx; |
777 | return true; |
778 | } else { |
779 | qWarning(msg: "Failed to allocate new descriptor pool: %d" , poolErr); |
780 | return false; |
781 | } |
782 | } |
783 | |
784 | static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags) |
785 | { |
786 | const bool srgb = flags.testFlag(flag: QRhiTexture::sRGB); |
787 | switch (format) { |
788 | case QRhiTexture::RGBA8: |
789 | return srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; |
790 | case QRhiTexture::BGRA8: |
791 | return srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; |
792 | case QRhiTexture::R8: |
793 | return srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; |
794 | case QRhiTexture::R16: |
795 | return VK_FORMAT_R16_UNORM; |
796 | case QRhiTexture::RED_OR_ALPHA8: |
797 | return VK_FORMAT_R8_UNORM; |
798 | |
799 | case QRhiTexture::RGBA16F: |
800 | return VK_FORMAT_R16G16B16A16_SFLOAT; |
801 | case QRhiTexture::RGBA32F: |
802 | return VK_FORMAT_R32G32B32A32_SFLOAT; |
803 | case QRhiTexture::R16F: |
804 | return VK_FORMAT_R16_SFLOAT; |
805 | case QRhiTexture::R32F: |
806 | return VK_FORMAT_R32_SFLOAT; |
807 | |
808 | case QRhiTexture::D16: |
809 | return VK_FORMAT_D16_UNORM; |
810 | case QRhiTexture::D32F: |
811 | return VK_FORMAT_D32_SFLOAT; |
812 | |
813 | case QRhiTexture::BC1: |
814 | return srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK : VK_FORMAT_BC1_RGB_UNORM_BLOCK; |
815 | case QRhiTexture::BC2: |
816 | return srgb ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK; |
817 | case QRhiTexture::BC3: |
818 | return srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK; |
819 | case QRhiTexture::BC4: |
820 | return VK_FORMAT_BC4_UNORM_BLOCK; |
821 | case QRhiTexture::BC5: |
822 | return VK_FORMAT_BC5_UNORM_BLOCK; |
823 | case QRhiTexture::BC6H: |
824 | return VK_FORMAT_BC6H_UFLOAT_BLOCK; |
825 | case QRhiTexture::BC7: |
826 | return srgb ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK; |
827 | |
828 | case QRhiTexture::ETC2_RGB8: |
829 | return srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; |
830 | case QRhiTexture::ETC2_RGB8A1: |
831 | return srgb ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK; |
832 | case QRhiTexture::ETC2_RGBA8: |
833 | return srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK; |
834 | |
835 | case QRhiTexture::ASTC_4x4: |
836 | return srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK; |
837 | case QRhiTexture::ASTC_5x4: |
838 | return srgb ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK; |
839 | case QRhiTexture::ASTC_5x5: |
840 | return srgb ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK; |
841 | case QRhiTexture::ASTC_6x5: |
842 | return srgb ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK; |
843 | case QRhiTexture::ASTC_6x6: |
844 | return srgb ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK; |
845 | case QRhiTexture::ASTC_8x5: |
846 | return srgb ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK; |
847 | case QRhiTexture::ASTC_8x6: |
848 | return srgb ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK; |
849 | case QRhiTexture::ASTC_8x8: |
850 | return srgb ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK; |
851 | case QRhiTexture::ASTC_10x5: |
852 | return srgb ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK; |
853 | case QRhiTexture::ASTC_10x6: |
854 | return srgb ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK; |
855 | case QRhiTexture::ASTC_10x8: |
856 | return srgb ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK; |
857 | case QRhiTexture::ASTC_10x10: |
858 | return srgb ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK; |
859 | case QRhiTexture::ASTC_12x10: |
860 | return srgb ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK; |
861 | case QRhiTexture::ASTC_12x12: |
862 | return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK; |
863 | |
864 | default: |
865 | Q_UNREACHABLE(); |
866 | return VK_FORMAT_R8G8B8A8_UNORM; |
867 | } |
868 | } |
869 | |
870 | static inline QRhiTexture::Format colorTextureFormatFromVkFormat(VkFormat format, QRhiTexture::Flags *flags) |
871 | { |
872 | switch (format) { |
873 | case VK_FORMAT_R8G8B8A8_UNORM: |
874 | return QRhiTexture::RGBA8; |
875 | case VK_FORMAT_R8G8B8A8_SRGB: |
876 | if (flags) |
877 | (*flags) |= QRhiTexture::sRGB; |
878 | return QRhiTexture::RGBA8; |
879 | case VK_FORMAT_B8G8R8A8_UNORM: |
880 | return QRhiTexture::BGRA8; |
881 | case VK_FORMAT_B8G8R8A8_SRGB: |
882 | if (flags) |
883 | (*flags) |= QRhiTexture::sRGB; |
884 | return QRhiTexture::BGRA8; |
885 | case VK_FORMAT_R8_UNORM: |
886 | return QRhiTexture::R8; |
887 | case VK_FORMAT_R8_SRGB: |
888 | if (flags) |
889 | (*flags) |= QRhiTexture::sRGB; |
890 | return QRhiTexture::R8; |
891 | case VK_FORMAT_R16_UNORM: |
892 | return QRhiTexture::R16; |
893 | default: // this cannot assert, must warn and return unknown |
894 | qWarning(msg: "VkFormat %d is not a recognized uncompressed color format" , format); |
895 | break; |
896 | } |
897 | return QRhiTexture::UnknownFormat; |
898 | } |
899 | |
900 | static inline bool isDepthTextureFormat(QRhiTexture::Format format) |
901 | { |
902 | switch (format) { |
903 | case QRhiTexture::Format::D16: |
904 | case QRhiTexture::Format::D32F: |
905 | return true; |
906 | |
907 | default: |
908 | return false; |
909 | } |
910 | } |
911 | |
912 | // Transient images ("render buffers") backed by lazily allocated memory are |
913 | // managed manually without going through vk_mem_alloc since it does not offer |
914 | // any support for such images. This should be ok since in practice there |
915 | // should be very few of such images. |
916 | |
917 | uint32_t QRhiVulkan::chooseTransientImageMemType(VkImage img, uint32_t startIndex) |
918 | { |
919 | VkPhysicalDeviceMemoryProperties physDevMemProps; |
920 | f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps); |
921 | |
922 | VkMemoryRequirements memReq; |
923 | df->vkGetImageMemoryRequirements(dev, img, &memReq); |
924 | uint32_t memTypeIndex = uint32_t(-1); |
925 | |
926 | if (memReq.memoryTypeBits) { |
927 | // Find a device local + lazily allocated, or at least device local memtype. |
928 | const VkMemoryType *memType = physDevMemProps.memoryTypes; |
929 | bool foundDevLocal = false; |
930 | for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) { |
931 | if (memReq.memoryTypeBits & (1 << i)) { |
932 | if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { |
933 | if (!foundDevLocal) { |
934 | foundDevLocal = true; |
935 | memTypeIndex = i; |
936 | } |
937 | if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) { |
938 | memTypeIndex = i; |
939 | break; |
940 | } |
941 | } |
942 | } |
943 | } |
944 | } |
945 | |
946 | return memTypeIndex; |
947 | } |
948 | |
949 | bool QRhiVulkan::createTransientImage(VkFormat format, |
950 | const QSize &pixelSize, |
951 | VkImageUsageFlags usage, |
952 | VkImageAspectFlags aspectMask, |
953 | VkSampleCountFlagBits samples, |
954 | VkDeviceMemory *mem, |
955 | VkImage *images, |
956 | VkImageView *views, |
957 | int count) |
958 | { |
959 | VkMemoryRequirements memReq; |
960 | VkResult err; |
961 | |
962 | for (int i = 0; i < count; ++i) { |
963 | VkImageCreateInfo imgInfo; |
964 | memset(s: &imgInfo, c: 0, n: sizeof(imgInfo)); |
965 | imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; |
966 | imgInfo.imageType = VK_IMAGE_TYPE_2D; |
967 | imgInfo.format = format; |
968 | imgInfo.extent.width = uint32_t(pixelSize.width()); |
969 | imgInfo.extent.height = uint32_t(pixelSize.height()); |
970 | imgInfo.extent.depth = 1; |
971 | imgInfo.mipLevels = imgInfo.arrayLayers = 1; |
972 | imgInfo.samples = samples; |
973 | imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL; |
974 | imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; |
975 | imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
976 | |
977 | err = df->vkCreateImage(dev, &imgInfo, nullptr, images + i); |
978 | if (err != VK_SUCCESS) { |
979 | qWarning(msg: "Failed to create image: %d" , err); |
980 | return false; |
981 | } |
982 | |
983 | // Assume the reqs are the same since the images are same in every way. |
984 | // Still, call GetImageMemReq for every image, in order to prevent the |
985 | // validation layer from complaining. |
986 | df->vkGetImageMemoryRequirements(dev, images[i], &memReq); |
987 | } |
988 | |
989 | VkMemoryAllocateInfo memInfo; |
990 | memset(s: &memInfo, c: 0, n: sizeof(memInfo)); |
991 | memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; |
992 | memInfo.allocationSize = aligned(v: memReq.size, byteAlign: memReq.alignment) * VkDeviceSize(count); |
993 | |
994 | uint32_t startIndex = 0; |
995 | do { |
996 | memInfo.memoryTypeIndex = chooseTransientImageMemType(img: images[0], startIndex); |
997 | if (memInfo.memoryTypeIndex == uint32_t(-1)) { |
998 | qWarning(msg: "No suitable memory type found" ); |
999 | return false; |
1000 | } |
1001 | startIndex = memInfo.memoryTypeIndex + 1; |
1002 | err = df->vkAllocateMemory(dev, &memInfo, nullptr, mem); |
1003 | if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) { |
1004 | qWarning(msg: "Failed to allocate image memory: %d" , err); |
1005 | return false; |
1006 | } |
1007 | } while (err != VK_SUCCESS); |
1008 | |
1009 | VkDeviceSize ofs = 0; |
1010 | for (int i = 0; i < count; ++i) { |
1011 | err = df->vkBindImageMemory(dev, images[i], *mem, ofs); |
1012 | if (err != VK_SUCCESS) { |
1013 | qWarning(msg: "Failed to bind image memory: %d" , err); |
1014 | return false; |
1015 | } |
1016 | ofs += aligned(v: memReq.size, byteAlign: memReq.alignment); |
1017 | |
1018 | VkImageViewCreateInfo imgViewInfo; |
1019 | memset(s: &imgViewInfo, c: 0, n: sizeof(imgViewInfo)); |
1020 | imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
1021 | imgViewInfo.image = images[i]; |
1022 | imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
1023 | imgViewInfo.format = format; |
1024 | imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
1025 | imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
1026 | imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
1027 | imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
1028 | imgViewInfo.subresourceRange.aspectMask = aspectMask; |
1029 | imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; |
1030 | |
1031 | err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i); |
1032 | if (err != VK_SUCCESS) { |
1033 | qWarning(msg: "Failed to create image view: %d" , err); |
1034 | return false; |
1035 | } |
1036 | } |
1037 | |
1038 | return true; |
1039 | } |
1040 | |
1041 | VkFormat QRhiVulkan::optimalDepthStencilFormat() |
1042 | { |
1043 | if (optimalDsFormat != VK_FORMAT_UNDEFINED) |
1044 | return optimalDsFormat; |
1045 | |
1046 | const VkFormat dsFormatCandidates[] = { |
1047 | VK_FORMAT_D24_UNORM_S8_UINT, |
1048 | VK_FORMAT_D32_SFLOAT_S8_UINT, |
1049 | VK_FORMAT_D16_UNORM_S8_UINT |
1050 | }; |
1051 | const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat); |
1052 | int dsFormatIdx = 0; |
1053 | while (dsFormatIdx < dsFormatCandidateCount) { |
1054 | optimalDsFormat = dsFormatCandidates[dsFormatIdx]; |
1055 | VkFormatProperties fmtProp; |
1056 | f->vkGetPhysicalDeviceFormatProperties(physDev, optimalDsFormat, &fmtProp); |
1057 | if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) |
1058 | break; |
1059 | ++dsFormatIdx; |
1060 | } |
1061 | if (dsFormatIdx == dsFormatCandidateCount) |
1062 | qWarning(msg: "Failed to find an optimal depth-stencil format" ); |
1063 | |
1064 | return optimalDsFormat; |
1065 | } |
1066 | |
1067 | bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat) |
1068 | { |
1069 | // attachment list layout is color (1), ds (0-1), resolve (0-1) |
1070 | |
1071 | VkAttachmentDescription attDesc; |
1072 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1073 | attDesc.format = colorFormat; |
1074 | attDesc.samples = samples; |
1075 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1076 | attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; |
1077 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1078 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1079 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1080 | attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
1081 | rpD->attDescs.append(t: attDesc); |
1082 | |
1083 | rpD->colorRefs.append(t: { .attachment: 0, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); |
1084 | |
1085 | if (hasDepthStencil) { |
1086 | // clear on load + no store + lazy alloc + transient image should play |
1087 | // nicely with tiled GPUs (no physical backing necessary for ds buffer) |
1088 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1089 | attDesc.format = optimalDepthStencilFormat(); |
1090 | attDesc.samples = samples; |
1091 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1092 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1093 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1094 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1095 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1096 | attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
1097 | rpD->attDescs.append(t: attDesc); |
1098 | |
1099 | rpD->dsRef = { .attachment: 1, .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; |
1100 | } |
1101 | |
1102 | if (samples > VK_SAMPLE_COUNT_1_BIT) { |
1103 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1104 | attDesc.format = colorFormat; |
1105 | attDesc.samples = VK_SAMPLE_COUNT_1_BIT; |
1106 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1107 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; |
1108 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1109 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1110 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1111 | attDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
1112 | rpD->attDescs.append(t: attDesc); |
1113 | |
1114 | rpD->resolveRefs.append(t: { .attachment: 2, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); |
1115 | } |
1116 | |
1117 | VkSubpassDescription subpassDesc; |
1118 | memset(s: &subpassDesc, c: 0, n: sizeof(subpassDesc)); |
1119 | subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
1120 | subpassDesc.colorAttachmentCount = 1; |
1121 | subpassDesc.pColorAttachments = rpD->colorRefs.constData(); |
1122 | subpassDesc.pDepthStencilAttachment = hasDepthStencil ? &rpD->dsRef : nullptr; |
1123 | |
1124 | // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own. |
1125 | VkSubpassDependency subpassDep; |
1126 | memset(s: &subpassDep, c: 0, n: sizeof(subpassDep)); |
1127 | subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; |
1128 | subpassDep.dstSubpass = 0; |
1129 | subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
1130 | subpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
1131 | subpassDep.srcAccessMask = 0; |
1132 | subpassDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
1133 | |
1134 | VkRenderPassCreateInfo rpInfo; |
1135 | memset(s: &rpInfo, c: 0, n: sizeof(rpInfo)); |
1136 | rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; |
1137 | rpInfo.attachmentCount = 1; |
1138 | rpInfo.pAttachments = rpD->attDescs.constData(); |
1139 | rpInfo.subpassCount = 1; |
1140 | rpInfo.pSubpasses = &subpassDesc; |
1141 | rpInfo.dependencyCount = 1; |
1142 | rpInfo.pDependencies = &subpassDep; |
1143 | |
1144 | if (hasDepthStencil) |
1145 | rpInfo.attachmentCount += 1; |
1146 | |
1147 | if (samples > VK_SAMPLE_COUNT_1_BIT) { |
1148 | rpInfo.attachmentCount += 1; |
1149 | subpassDesc.pResolveAttachments = rpD->resolveRefs.constData(); |
1150 | } |
1151 | |
1152 | VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); |
1153 | if (err != VK_SUCCESS) { |
1154 | qWarning(msg: "Failed to create renderpass: %d" , err); |
1155 | return false; |
1156 | } |
1157 | |
1158 | rpD->hasDepthStencil = hasDepthStencil; |
1159 | |
1160 | return true; |
1161 | } |
1162 | |
1163 | bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, |
1164 | const QRhiColorAttachment *firstColorAttachment, |
1165 | const QRhiColorAttachment *lastColorAttachment, |
1166 | bool preserveColor, |
1167 | bool preserveDs, |
1168 | QRhiRenderBuffer *depthStencilBuffer, |
1169 | QRhiTexture *depthTexture) |
1170 | { |
1171 | // attachment list layout is color (0-8), ds (0-1), resolve (0-8) |
1172 | |
1173 | for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) { |
1174 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
1175 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
1176 | Q_ASSERT(texD || rbD); |
1177 | const VkFormat vkformat = texD ? texD->vkformat : rbD->vkformat; |
1178 | const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples; |
1179 | |
1180 | VkAttachmentDescription attDesc; |
1181 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1182 | attDesc.format = vkformat; |
1183 | attDesc.samples = samples; |
1184 | attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR; |
1185 | attDesc.storeOp = it->resolveTexture() ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; |
1186 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1187 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1188 | // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT |
1189 | attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; |
1190 | attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
1191 | rpD->attDescs.append(t: attDesc); |
1192 | |
1193 | const VkAttachmentReference ref = { .attachment: uint32_t(rpD->attDescs.count() - 1), .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
1194 | rpD->colorRefs.append(t: ref); |
1195 | } |
1196 | |
1197 | rpD->hasDepthStencil = depthStencilBuffer || depthTexture; |
1198 | if (rpD->hasDepthStencil) { |
1199 | const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->vkformat |
1200 | : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat; |
1201 | const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples |
1202 | : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples; |
1203 | const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR; |
1204 | const VkAttachmentStoreOp storeOp = depthTexture ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1205 | VkAttachmentDescription attDesc; |
1206 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1207 | attDesc.format = dsFormat; |
1208 | attDesc.samples = samples; |
1209 | attDesc.loadOp = loadOp; |
1210 | attDesc.storeOp = storeOp; |
1211 | attDesc.stencilLoadOp = loadOp; |
1212 | attDesc.stencilStoreOp = storeOp; |
1213 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1214 | attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
1215 | rpD->attDescs.append(t: attDesc); |
1216 | } |
1217 | rpD->dsRef = { .attachment: uint32_t(rpD->attDescs.count() - 1), .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; |
1218 | |
1219 | for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) { |
1220 | if (it->resolveTexture()) { |
1221 | QVkTexture *rtexD = QRHI_RES(QVkTexture, it->resolveTexture()); |
1222 | if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT) |
1223 | qWarning(msg: "Resolving into a multisample texture is not supported" ); |
1224 | |
1225 | VkAttachmentDescription attDesc; |
1226 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1227 | attDesc.format = rtexD->vkformat; |
1228 | attDesc.samples = VK_SAMPLE_COUNT_1_BIT; |
1229 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored |
1230 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; |
1231 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1232 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1233 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1234 | attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
1235 | rpD->attDescs.append(t: attDesc); |
1236 | |
1237 | const VkAttachmentReference ref = { .attachment: uint32_t(rpD->attDescs.count() - 1), .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
1238 | rpD->resolveRefs.append(t: ref); |
1239 | } else { |
1240 | const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
1241 | rpD->resolveRefs.append(t: ref); |
1242 | } |
1243 | } |
1244 | |
1245 | VkSubpassDescription subpassDesc; |
1246 | memset(s: &subpassDesc, c: 0, n: sizeof(subpassDesc)); |
1247 | subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
1248 | subpassDesc.colorAttachmentCount = uint32_t(rpD->colorRefs.count()); |
1249 | Q_ASSERT(rpD->colorRefs.count() == rpD->resolveRefs.count()); |
1250 | subpassDesc.pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr; |
1251 | subpassDesc.pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr; |
1252 | subpassDesc.pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr; |
1253 | |
1254 | VkRenderPassCreateInfo rpInfo; |
1255 | memset(s: &rpInfo, c: 0, n: sizeof(rpInfo)); |
1256 | rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; |
1257 | rpInfo.attachmentCount = uint32_t(rpD->attDescs.count()); |
1258 | rpInfo.pAttachments = rpD->attDescs.constData(); |
1259 | rpInfo.subpassCount = 1; |
1260 | rpInfo.pSubpasses = &subpassDesc; |
1261 | // don't yet know the correct initial/final access and stage stuff for the |
1262 | // implicit deps at this point, so leave it to the resource tracking to |
1263 | // generate barriers |
1264 | |
1265 | VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); |
1266 | if (err != VK_SUCCESS) { |
1267 | qWarning(msg: "Failed to create renderpass: %d" , err); |
1268 | return false; |
1269 | } |
1270 | |
1271 | return true; |
1272 | } |
1273 | |
1274 | bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) |
1275 | { |
1276 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
1277 | if (swapChainD->pixelSize.isEmpty()) { |
1278 | qWarning(msg: "Surface size is 0, cannot create swapchain" ); |
1279 | return false; |
1280 | } |
1281 | |
1282 | df->vkDeviceWaitIdle(dev); |
1283 | |
1284 | if (!vkCreateSwapchainKHR) { |
1285 | vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR" )); |
1286 | vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR" )); |
1287 | vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR" )); |
1288 | vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR" )); |
1289 | vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR" )); |
1290 | if (!vkCreateSwapchainKHR || !vkDestroySwapchainKHR || !vkGetSwapchainImagesKHR || !vkAcquireNextImageKHR || !vkQueuePresentKHR) { |
1291 | qWarning(msg: "Swapchain functions not available" ); |
1292 | return false; |
1293 | } |
1294 | } |
1295 | |
1296 | VkSurfaceCapabilitiesKHR surfaceCaps; |
1297 | vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, swapChainD->surface, &surfaceCaps); |
1298 | quint32 reqBufferCount; |
1299 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::MinimalBufferCount)) { |
1300 | reqBufferCount = qMax<quint32>(a: 2, b: surfaceCaps.minImageCount); |
1301 | } else { |
1302 | const quint32 maxBuffers = QVkSwapChain::MAX_BUFFER_COUNT; |
1303 | if (surfaceCaps.maxImageCount) |
1304 | reqBufferCount = qMax(a: qMin(a: surfaceCaps.maxImageCount, b: maxBuffers), b: surfaceCaps.minImageCount); |
1305 | else |
1306 | reqBufferCount = qMax<quint32>(a: 2, b: surfaceCaps.minImageCount); |
1307 | } |
1308 | |
1309 | VkSurfaceTransformFlagBitsKHR preTransform = |
1310 | (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) |
1311 | ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR |
1312 | : surfaceCaps.currentTransform; |
1313 | |
1314 | VkCompositeAlphaFlagBitsKHR compositeAlpha = |
1315 | (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) |
1316 | ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR |
1317 | : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; |
1318 | |
1319 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::SurfaceHasPreMulAlpha) |
1320 | && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)) |
1321 | { |
1322 | compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; |
1323 | } |
1324 | |
1325 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::SurfaceHasNonPreMulAlpha) |
1326 | && (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)) |
1327 | { |
1328 | compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; |
1329 | } |
1330 | |
1331 | VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
1332 | swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT); |
1333 | if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(flag: QRhiSwapChain::UsedAsTransferSource)) |
1334 | usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
1335 | |
1336 | VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; |
1337 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::NoVSync)) { |
1338 | if (swapChainD->supportedPresentationModes.contains(t: VK_PRESENT_MODE_MAILBOX_KHR)) |
1339 | presentMode = VK_PRESENT_MODE_MAILBOX_KHR; |
1340 | else if (swapChainD->supportedPresentationModes.contains(t: VK_PRESENT_MODE_IMMEDIATE_KHR)) |
1341 | presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; |
1342 | } |
1343 | |
1344 | // If the surface is different than before, then passing in the old |
1345 | // swapchain associated with the old surface can fail the swapchain |
1346 | // creation. (for example, Android loses the surface when backgrounding and |
1347 | // restoring applications, and it also enforces failing swapchain creation |
1348 | // with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR if the old swapchain is provided) |
1349 | const bool reuseExisting = swapChainD->sc && swapChainD->lastConnectedSurface == swapChainD->surface; |
1350 | |
1351 | qCDebug(QRHI_LOG_INFO, "Creating %s swapchain of %u buffers, size %dx%d, presentation mode %d" , |
1352 | reuseExisting ? "recycled" : "new" , |
1353 | reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode); |
1354 | |
1355 | VkSwapchainCreateInfoKHR swapChainInfo; |
1356 | memset(s: &swapChainInfo, c: 0, n: sizeof(swapChainInfo)); |
1357 | swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
1358 | swapChainInfo.surface = swapChainD->surface; |
1359 | swapChainInfo.minImageCount = reqBufferCount; |
1360 | swapChainInfo.imageFormat = swapChainD->colorFormat; |
1361 | swapChainInfo.imageColorSpace = swapChainD->colorSpace; |
1362 | swapChainInfo.imageExtent = VkExtent2D { .width: uint32_t(swapChainD->pixelSize.width()), .height: uint32_t(swapChainD->pixelSize.height()) }; |
1363 | swapChainInfo.imageArrayLayers = 1; |
1364 | swapChainInfo.imageUsage = usage; |
1365 | swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; |
1366 | swapChainInfo.preTransform = preTransform; |
1367 | swapChainInfo.compositeAlpha = compositeAlpha; |
1368 | swapChainInfo.presentMode = presentMode; |
1369 | swapChainInfo.clipped = true; |
1370 | swapChainInfo.oldSwapchain = reuseExisting ? swapChainD->sc : VK_NULL_HANDLE; |
1371 | |
1372 | VkSwapchainKHR newSwapChain; |
1373 | VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain); |
1374 | if (err != VK_SUCCESS) { |
1375 | qWarning(msg: "Failed to create swapchain: %d" , err); |
1376 | return false; |
1377 | } |
1378 | |
1379 | if (swapChainD->sc) |
1380 | releaseSwapChainResources(swapChain); |
1381 | |
1382 | swapChainD->sc = newSwapChain; |
1383 | swapChainD->lastConnectedSurface = swapChainD->surface; |
1384 | |
1385 | quint32 actualSwapChainBufferCount = 0; |
1386 | err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, nullptr); |
1387 | if (err != VK_SUCCESS || actualSwapChainBufferCount == 0) { |
1388 | qWarning(msg: "Failed to get swapchain images: %d" , err); |
1389 | return false; |
1390 | } |
1391 | |
1392 | if (actualSwapChainBufferCount > QVkSwapChain::MAX_BUFFER_COUNT) { |
1393 | qWarning(msg: "Too many swapchain buffers (%u)" , actualSwapChainBufferCount); |
1394 | return false; |
1395 | } |
1396 | if (actualSwapChainBufferCount != reqBufferCount) |
1397 | qCDebug(QRHI_LOG_INFO, "Actual swapchain buffer count is %u" , actualSwapChainBufferCount); |
1398 | swapChainD->bufferCount = int(actualSwapChainBufferCount); |
1399 | |
1400 | VkImage swapChainImages[QVkSwapChain::MAX_BUFFER_COUNT]; |
1401 | err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, swapChainImages); |
1402 | if (err != VK_SUCCESS) { |
1403 | qWarning(msg: "Failed to get swapchain images: %d" , err); |
1404 | return false; |
1405 | } |
1406 | |
1407 | VkImage msaaImages[QVkSwapChain::MAX_BUFFER_COUNT]; |
1408 | VkImageView msaaViews[QVkSwapChain::MAX_BUFFER_COUNT]; |
1409 | if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) { |
1410 | if (!createTransientImage(format: swapChainD->colorFormat, |
1411 | pixelSize: swapChainD->pixelSize, |
1412 | usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, |
1413 | aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, |
1414 | samples: swapChainD->samples, |
1415 | mem: &swapChainD->msaaImageMem, |
1416 | images: msaaImages, |
1417 | views: msaaViews, |
1418 | count: swapChainD->bufferCount)) |
1419 | { |
1420 | qWarning(msg: "Failed to create transient image for MSAA color buffer" ); |
1421 | return false; |
1422 | } |
1423 | } |
1424 | |
1425 | VkFenceCreateInfo fenceInfo; |
1426 | memset(s: &fenceInfo, c: 0, n: sizeof(fenceInfo)); |
1427 | fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; |
1428 | fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; |
1429 | |
1430 | for (int i = 0; i < swapChainD->bufferCount; ++i) { |
1431 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]); |
1432 | image.image = swapChainImages[i]; |
1433 | if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) { |
1434 | image.msaaImage = msaaImages[i]; |
1435 | image.msaaImageView = msaaViews[i]; |
1436 | } |
1437 | |
1438 | VkImageViewCreateInfo imgViewInfo; |
1439 | memset(s: &imgViewInfo, c: 0, n: sizeof(imgViewInfo)); |
1440 | imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
1441 | imgViewInfo.image = swapChainImages[i]; |
1442 | imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
1443 | imgViewInfo.format = swapChainD->colorFormat; |
1444 | imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
1445 | imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
1446 | imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
1447 | imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
1448 | imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
1449 | imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; |
1450 | err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView); |
1451 | if (err != VK_SUCCESS) { |
1452 | qWarning(msg: "Failed to create swapchain image view %d: %d" , i, err); |
1453 | return false; |
1454 | } |
1455 | |
1456 | image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone; |
1457 | } |
1458 | |
1459 | swapChainD->currentImageIndex = 0; |
1460 | |
1461 | VkSemaphoreCreateInfo semInfo; |
1462 | memset(s: &semInfo, c: 0, n: sizeof(semInfo)); |
1463 | semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; |
1464 | |
1465 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
1466 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]); |
1467 | |
1468 | frame.imageAcquired = false; |
1469 | frame.imageSemWaitable = false; |
1470 | |
1471 | df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.imageFence); |
1472 | frame.imageFenceWaitable = true; // fence was created in signaled state |
1473 | |
1474 | df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem); |
1475 | df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem); |
1476 | |
1477 | err = df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence); |
1478 | if (err != VK_SUCCESS) { |
1479 | qWarning(msg: "Failed to create command buffer fence: %d" , err); |
1480 | return false; |
1481 | } |
1482 | frame.cmdFenceWaitable = true; // fence was created in signaled state |
1483 | } |
1484 | |
1485 | swapChainD->currentFrameSlot = 0; |
1486 | |
1487 | return true; |
1488 | } |
1489 | |
1490 | void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain) |
1491 | { |
1492 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
1493 | |
1494 | if (swapChainD->sc == VK_NULL_HANDLE) |
1495 | return; |
1496 | |
1497 | if (!deviceLost) |
1498 | df->vkDeviceWaitIdle(dev); |
1499 | |
1500 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
1501 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]); |
1502 | if (frame.cmdFence) { |
1503 | if (frame.cmdFenceWaitable) |
1504 | df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX); |
1505 | df->vkDestroyFence(dev, frame.cmdFence, nullptr); |
1506 | frame.cmdFence = VK_NULL_HANDLE; |
1507 | frame.cmdFenceWaitable = false; |
1508 | } |
1509 | if (frame.imageFence) { |
1510 | if (frame.imageFenceWaitable) |
1511 | df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX); |
1512 | df->vkDestroyFence(dev, frame.imageFence, nullptr); |
1513 | frame.imageFence = VK_NULL_HANDLE; |
1514 | frame.imageFenceWaitable = false; |
1515 | } |
1516 | if (frame.imageSem) { |
1517 | df->vkDestroySemaphore(dev, frame.imageSem, nullptr); |
1518 | frame.imageSem = VK_NULL_HANDLE; |
1519 | } |
1520 | if (frame.drawSem) { |
1521 | df->vkDestroySemaphore(dev, frame.drawSem, nullptr); |
1522 | frame.drawSem = VK_NULL_HANDLE; |
1523 | } |
1524 | if (frame.cmdBuf) { |
1525 | df->vkFreeCommandBuffers(dev, cmdPool, 1, &frame.cmdBuf); |
1526 | frame.cmdBuf = VK_NULL_HANDLE; |
1527 | } |
1528 | } |
1529 | |
1530 | for (int i = 0; i < swapChainD->bufferCount; ++i) { |
1531 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]); |
1532 | if (image.fb) { |
1533 | df->vkDestroyFramebuffer(dev, image.fb, nullptr); |
1534 | image.fb = VK_NULL_HANDLE; |
1535 | } |
1536 | if (image.imageView) { |
1537 | df->vkDestroyImageView(dev, image.imageView, nullptr); |
1538 | image.imageView = VK_NULL_HANDLE; |
1539 | } |
1540 | if (image.msaaImageView) { |
1541 | df->vkDestroyImageView(dev, image.msaaImageView, nullptr); |
1542 | image.msaaImageView = VK_NULL_HANDLE; |
1543 | } |
1544 | if (image.msaaImage) { |
1545 | df->vkDestroyImage(dev, image.msaaImage, nullptr); |
1546 | image.msaaImage = VK_NULL_HANDLE; |
1547 | } |
1548 | } |
1549 | |
1550 | if (swapChainD->msaaImageMem) { |
1551 | df->vkFreeMemory(dev, swapChainD->msaaImageMem, nullptr); |
1552 | swapChainD->msaaImageMem = VK_NULL_HANDLE; |
1553 | } |
1554 | |
1555 | vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr); |
1556 | swapChainD->sc = VK_NULL_HANDLE; |
1557 | |
1558 | // NB! surface and similar must remain intact |
1559 | } |
1560 | |
1561 | QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) |
1562 | { |
1563 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
1564 | const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; |
1565 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); |
1566 | QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); |
1567 | |
1568 | if (!frame.imageAcquired) { |
1569 | // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate |
1570 | // (note that we are using FIFO mode -> vsync) |
1571 | if (frame.imageFenceWaitable) { |
1572 | df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX); |
1573 | df->vkResetFences(dev, 1, &frame.imageFence); |
1574 | frame.imageFenceWaitable = false; |
1575 | } |
1576 | |
1577 | // move on to next swapchain image |
1578 | VkResult err = vkAcquireNextImageKHR(dev, swapChainD->sc, UINT64_MAX, |
1579 | frame.imageSem, frame.imageFence, &frame.imageIndex); |
1580 | if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) { |
1581 | swapChainD->currentImageIndex = frame.imageIndex; |
1582 | frame.imageSemWaitable = true; |
1583 | frame.imageAcquired = true; |
1584 | frame.imageFenceWaitable = true; |
1585 | } else if (err == VK_ERROR_OUT_OF_DATE_KHR) { |
1586 | return QRhi::FrameOpSwapChainOutOfDate; |
1587 | } else { |
1588 | if (err == VK_ERROR_DEVICE_LOST) { |
1589 | qWarning(msg: "Device loss detected in vkAcquireNextImageKHR()" ); |
1590 | deviceLost = true; |
1591 | return QRhi::FrameOpDeviceLost; |
1592 | } |
1593 | qWarning(msg: "Failed to acquire next swapchain image: %d" , err); |
1594 | return QRhi::FrameOpError; |
1595 | } |
1596 | } |
1597 | |
1598 | // Make sure the previous commands for the same image have finished. (note |
1599 | // that this is based on the fence from the command buffer submit, nothing |
1600 | // to do with the Present) |
1601 | // |
1602 | // Do this also for any other swapchain's commands with the same frame slot |
1603 | // While this reduces concurrency, it keeps resource usage safe: swapchain |
1604 | // A starting its frame 0, followed by swapchain B starting its own frame 0 |
1605 | // will make B wait for A's frame 0 commands, so if a resource is written |
1606 | // in B's frame or when B checks for pending resource releases, that won't |
1607 | // mess up A's in-flight commands (as they are not in flight anymore). |
1608 | waitCommandCompletion(frameSlot: frameResIndex); |
1609 | |
1610 | // Now is the time to read the timestamps for the previous frame for this slot. |
1611 | if (frame.timestampQueryIndex >= 0) { |
1612 | quint64 timestamp[2] = { 0, 0 }; |
1613 | VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2, |
1614 | 2 * sizeof(quint64), timestamp, sizeof(quint64), |
1615 | VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); |
1616 | timestampQueryPoolMap.clearBit(i: frame.timestampQueryIndex / 2); |
1617 | frame.timestampQueryIndex = -1; |
1618 | if (err == VK_SUCCESS) { |
1619 | quint64 mask = 0; |
1620 | for (quint64 i = 0; i < timestampValidBits; i += 8) |
1621 | mask |= 0xFFULL << i; |
1622 | const quint64 ts0 = timestamp[0] & mask; |
1623 | const quint64 ts1 = timestamp[1] & mask; |
1624 | const float nsecsPerTick = physDevProperties.limits.timestampPeriod; |
1625 | if (!qFuzzyIsNull(f: nsecsPerTick)) { |
1626 | const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f; |
1627 | // now we have the gpu time for the previous frame for this slot, report it |
1628 | // (does not matter that it is not for this frame) |
1629 | QRHI_PROF_F(swapChainFrameGpuTime(swapChain, elapsedMs)); |
1630 | } |
1631 | } else { |
1632 | qWarning(msg: "Failed to query timestamp: %d" , err); |
1633 | } |
1634 | } |
1635 | |
1636 | // build new draw command buffer |
1637 | QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(cb: &frame.cmdBuf); |
1638 | if (cbres != QRhi::FrameOpSuccess) |
1639 | return cbres; |
1640 | |
1641 | // when profiling is enabled, pick a free query (pair) from the pool |
1642 | int timestampQueryIdx = -1; |
1643 | if (profilerPrivateOrNull() && swapChainD->bufferCount > 1) { // no timestamps if not having at least 2 frames in flight |
1644 | for (int i = 0; i < timestampQueryPoolMap.count(); ++i) { |
1645 | if (!timestampQueryPoolMap.testBit(i)) { |
1646 | timestampQueryPoolMap.setBit(i); |
1647 | timestampQueryIdx = i * 2; |
1648 | break; |
1649 | } |
1650 | } |
1651 | } |
1652 | if (timestampQueryIdx >= 0) { |
1653 | df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2); |
1654 | // record timestamp at the start of the command buffer |
1655 | df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, |
1656 | timestampQueryPool, uint32_t(timestampQueryIdx)); |
1657 | frame.timestampQueryIndex = timestampQueryIdx; |
1658 | } |
1659 | |
1660 | swapChainD->cbWrapper.cb = frame.cmdBuf; |
1661 | swapChainD->cbWrapper.useSecondaryCb = flags.testFlag(flag: QRhi::ExternalContentsInPass); |
1662 | |
1663 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); |
1664 | swapChainD->rtWrapper.d.fb = image.fb; |
1665 | |
1666 | currentFrameSlot = int(swapChainD->currentFrameSlot); |
1667 | currentSwapChain = swapChainD; |
1668 | if (swapChainD->ds) |
1669 | swapChainD->ds->lastActiveFrameSlot = currentFrameSlot; |
1670 | |
1671 | QRHI_PROF_F(beginSwapChainFrame(swapChain)); |
1672 | |
1673 | prepareNewFrame(cb: &swapChainD->cbWrapper); |
1674 | |
1675 | return QRhi::FrameOpSuccess; |
1676 | } |
1677 | |
1678 | QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) |
1679 | { |
1680 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
1681 | Q_ASSERT(currentSwapChain == swapChainD); |
1682 | |
1683 | recordPrimaryCommandBuffer(cbD: &swapChainD->cbWrapper); |
1684 | |
1685 | int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; |
1686 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); |
1687 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); |
1688 | |
1689 | if (image.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { |
1690 | VkImageMemoryBarrier presTrans; |
1691 | memset(s: &presTrans, c: 0, n: sizeof(presTrans)); |
1692 | presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
1693 | presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
1694 | presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
1695 | presTrans.image = image.image; |
1696 | presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
1697 | presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1; |
1698 | |
1699 | if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseNone) { |
1700 | // was not used at all (no render pass), just transition from undefined to presentable |
1701 | presTrans.srcAccessMask = 0; |
1702 | presTrans.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1703 | df->vkCmdPipelineBarrier(frame.cmdBuf, |
1704 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
1705 | 0, 0, nullptr, 0, nullptr, |
1706 | 1, &presTrans); |
1707 | } else if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseTransferSource) { |
1708 | // was used in a readback as transfer source, go back to presentable layout |
1709 | presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; |
1710 | presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
1711 | df->vkCmdPipelineBarrier(frame.cmdBuf, |
1712 | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
1713 | 0, 0, nullptr, 0, nullptr, |
1714 | 1, &presTrans); |
1715 | } |
1716 | image.lastUse = QVkSwapChain::ImageResources::ScImageUseRender; |
1717 | } |
1718 | |
1719 | // record another timestamp, when enabled |
1720 | if (frame.timestampQueryIndex >= 0) { |
1721 | df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, |
1722 | timestampQueryPool, uint32_t(frame.timestampQueryIndex + 1)); |
1723 | } |
1724 | |
1725 | // stop recording and submit to the queue |
1726 | Q_ASSERT(!frame.cmdFenceWaitable); |
1727 | const bool needsPresent = !flags.testFlag(flag: QRhi::SkipPresent); |
1728 | QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb: frame.cmdBuf, |
1729 | cmdFence: frame.cmdFence, |
1730 | waitSem: frame.imageSemWaitable ? &frame.imageSem : nullptr, |
1731 | signalSem: needsPresent ? &frame.drawSem : nullptr); |
1732 | if (submitres != QRhi::FrameOpSuccess) |
1733 | return submitres; |
1734 | |
1735 | frame.imageSemWaitable = false; |
1736 | frame.cmdFenceWaitable = true; |
1737 | |
1738 | QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); |
1739 | // this must be done before the Present |
1740 | QRHI_PROF_F(endSwapChainFrame(swapChain, swapChainD->frameCount + 1)); |
1741 | |
1742 | if (needsPresent) { |
1743 | // add the Present to the queue |
1744 | VkPresentInfoKHR presInfo; |
1745 | memset(s: &presInfo, c: 0, n: sizeof(presInfo)); |
1746 | presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
1747 | presInfo.swapchainCount = 1; |
1748 | presInfo.pSwapchains = &swapChainD->sc; |
1749 | presInfo.pImageIndices = &swapChainD->currentImageIndex; |
1750 | presInfo.waitSemaphoreCount = 1; |
1751 | presInfo.pWaitSemaphores = &frame.drawSem; // gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem; |
1752 | |
1753 | // Do platform-specific WM notification. F.ex. essential on Wayland in |
1754 | // order to circumvent driver frame callbacks |
1755 | inst->presentAboutToBeQueued(window: swapChainD->window); |
1756 | |
1757 | VkResult err = vkQueuePresentKHR(gfxQueue, &presInfo); |
1758 | if (err != VK_SUCCESS) { |
1759 | if (err == VK_ERROR_OUT_OF_DATE_KHR) { |
1760 | return QRhi::FrameOpSwapChainOutOfDate; |
1761 | } else if (err != VK_SUBOPTIMAL_KHR) { |
1762 | if (err == VK_ERROR_DEVICE_LOST) { |
1763 | qWarning(msg: "Device loss detected in vkQueuePresentKHR()" ); |
1764 | deviceLost = true; |
1765 | return QRhi::FrameOpDeviceLost; |
1766 | } |
1767 | qWarning(msg: "Failed to present: %d" , err); |
1768 | return QRhi::FrameOpError; |
1769 | } |
1770 | } |
1771 | |
1772 | // Do platform-specific WM notification. F.ex. essential on X11 in |
1773 | // order to prevent glitches on resizing the window. |
1774 | inst->presentQueued(window: swapChainD->window); |
1775 | |
1776 | // mark the current swapchain buffer as unused from our side |
1777 | frame.imageAcquired = false; |
1778 | // and move on to the next buffer |
1779 | swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT; |
1780 | } |
1781 | |
1782 | swapChainD->frameCount += 1; |
1783 | currentSwapChain = nullptr; |
1784 | return QRhi::FrameOpSuccess; |
1785 | } |
1786 | |
1787 | void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb) |
1788 | { |
1789 | // Now is the time to do things for frame N-F, where N is the current one, |
1790 | // F is QVK_FRAMES_IN_FLIGHT, because only here it is guaranteed that that |
1791 | // frame has completed on the GPU (due to the fence wait in beginFrame). To |
1792 | // decide if something is safe to handle now a simple "lastActiveFrameSlot |
1793 | // == currentFrameSlot" is sufficient (remember that e.g. with F==2 |
1794 | // currentFrameSlot goes 0, 1, 0, 1, 0, ...) |
1795 | // |
1796 | // With multiple swapchains on the same QRhi things get more convoluted |
1797 | // (and currentFrameSlot strictly alternating is not true anymore) but |
1798 | // beginNonWrapperFrame() solves that by blocking as necessary so the rest |
1799 | // here is safe regardless. |
1800 | |
1801 | executeDeferredReleases(); |
1802 | |
1803 | QRHI_RES(QVkCommandBuffer, cb)->resetState(); |
1804 | |
1805 | finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls |
1806 | } |
1807 | |
1808 | QRhi::FrameOpResult QRhiVulkan::startPrimaryCommandBuffer(VkCommandBuffer *cb) |
1809 | { |
1810 | if (*cb) { |
1811 | df->vkFreeCommandBuffers(dev, cmdPool, 1, cb); |
1812 | *cb = VK_NULL_HANDLE; |
1813 | } |
1814 | |
1815 | VkCommandBufferAllocateInfo cmdBufInfo; |
1816 | memset(s: &cmdBufInfo, c: 0, n: sizeof(cmdBufInfo)); |
1817 | cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; |
1818 | cmdBufInfo.commandPool = cmdPool; |
1819 | cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; |
1820 | cmdBufInfo.commandBufferCount = 1; |
1821 | |
1822 | VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, cb); |
1823 | if (err != VK_SUCCESS) { |
1824 | if (err == VK_ERROR_DEVICE_LOST) { |
1825 | qWarning(msg: "Device loss detected in vkAllocateCommandBuffers()" ); |
1826 | deviceLost = true; |
1827 | return QRhi::FrameOpDeviceLost; |
1828 | } |
1829 | qWarning(msg: "Failed to allocate frame command buffer: %d" , err); |
1830 | return QRhi::FrameOpError; |
1831 | } |
1832 | |
1833 | VkCommandBufferBeginInfo cmdBufBeginInfo; |
1834 | memset(s: &cmdBufBeginInfo, c: 0, n: sizeof(cmdBufBeginInfo)); |
1835 | cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; |
1836 | |
1837 | err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo); |
1838 | if (err != VK_SUCCESS) { |
1839 | if (err == VK_ERROR_DEVICE_LOST) { |
1840 | qWarning(msg: "Device loss detected in vkBeginCommandBuffer()" ); |
1841 | deviceLost = true; |
1842 | return QRhi::FrameOpDeviceLost; |
1843 | } |
1844 | qWarning(msg: "Failed to begin frame command buffer: %d" , err); |
1845 | return QRhi::FrameOpError; |
1846 | } |
1847 | |
1848 | return QRhi::FrameOpSuccess; |
1849 | } |
1850 | |
1851 | QRhi::FrameOpResult QRhiVulkan::endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence, |
1852 | VkSemaphore *waitSem, VkSemaphore *signalSem) |
1853 | { |
1854 | VkResult err = df->vkEndCommandBuffer(cb); |
1855 | if (err != VK_SUCCESS) { |
1856 | if (err == VK_ERROR_DEVICE_LOST) { |
1857 | qWarning(msg: "Device loss detected in vkEndCommandBuffer()" ); |
1858 | deviceLost = true; |
1859 | return QRhi::FrameOpDeviceLost; |
1860 | } |
1861 | qWarning(msg: "Failed to end frame command buffer: %d" , err); |
1862 | return QRhi::FrameOpError; |
1863 | } |
1864 | |
1865 | VkSubmitInfo submitInfo; |
1866 | memset(s: &submitInfo, c: 0, n: sizeof(submitInfo)); |
1867 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; |
1868 | submitInfo.commandBufferCount = 1; |
1869 | submitInfo.pCommandBuffers = &cb; |
1870 | if (waitSem) { |
1871 | submitInfo.waitSemaphoreCount = 1; |
1872 | submitInfo.pWaitSemaphores = waitSem; |
1873 | } |
1874 | if (signalSem) { |
1875 | submitInfo.signalSemaphoreCount = 1; |
1876 | submitInfo.pSignalSemaphores = signalSem; |
1877 | } |
1878 | VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
1879 | submitInfo.pWaitDstStageMask = &psf; |
1880 | |
1881 | err = df->vkQueueSubmit(gfxQueue, 1, &submitInfo, cmdFence); |
1882 | if (err != VK_SUCCESS) { |
1883 | if (err == VK_ERROR_DEVICE_LOST) { |
1884 | qWarning(msg: "Device loss detected in vkQueueSubmit()" ); |
1885 | deviceLost = true; |
1886 | return QRhi::FrameOpDeviceLost; |
1887 | } |
1888 | qWarning(msg: "Failed to submit to graphics queue: %d" , err); |
1889 | return QRhi::FrameOpError; |
1890 | } |
1891 | |
1892 | return QRhi::FrameOpSuccess; |
1893 | } |
1894 | |
1895 | void QRhiVulkan::waitCommandCompletion(int frameSlot) |
1896 | { |
1897 | for (QVkSwapChain *sc : qAsConst(t&: swapchains)) { |
1898 | const int frameResIndex = sc->bufferCount > 1 ? frameSlot : 0; |
1899 | QVkSwapChain::FrameResources &frame(sc->frameRes[frameResIndex]); |
1900 | if (frame.cmdFenceWaitable) { |
1901 | df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX); |
1902 | df->vkResetFences(dev, 1, &frame.cmdFence); |
1903 | frame.cmdFenceWaitable = false; |
1904 | } |
1905 | } |
1906 | } |
1907 | |
1908 | QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) |
1909 | { |
1910 | QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(cb: &ofr.cbWrapper.cb); |
1911 | if (cbres != QRhi::FrameOpSuccess) |
1912 | return cbres; |
1913 | |
1914 | // Switch to the next slot manually. Swapchains do not know about this |
1915 | // which is good. So for example a - unusual but possible - onscreen, |
1916 | // onscreen, offscreen, onscreen, onscreen, onscreen sequence of |
1917 | // begin/endFrame leads to 0, 1, 0, 0, 1, 0. This works because the |
1918 | // offscreen frame is synchronous in the sense that we wait for execution |
1919 | // to complete in endFrame, and so no resources used in that frame are busy |
1920 | // anymore in the next frame. |
1921 | currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT; |
1922 | // except that this gets complicated with multiple swapchains so make sure |
1923 | // any pending commands have finished for the frame slot we are going to use |
1924 | if (swapchains.count() > 1) |
1925 | waitCommandCompletion(frameSlot: currentFrameSlot); |
1926 | |
1927 | ofr.cbWrapper.useSecondaryCb = flags.testFlag(flag: QRhi::ExternalContentsInPass); |
1928 | |
1929 | prepareNewFrame(cb: &ofr.cbWrapper); |
1930 | ofr.active = true; |
1931 | |
1932 | *cb = &ofr.cbWrapper; |
1933 | return QRhi::FrameOpSuccess; |
1934 | } |
1935 | |
1936 | QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags) |
1937 | { |
1938 | Q_UNUSED(flags); |
1939 | Q_ASSERT(ofr.active); |
1940 | ofr.active = false; |
1941 | |
1942 | recordPrimaryCommandBuffer(cbD: &ofr.cbWrapper); |
1943 | |
1944 | if (!ofr.cmdFence) { |
1945 | VkFenceCreateInfo fenceInfo; |
1946 | memset(s: &fenceInfo, c: 0, n: sizeof(fenceInfo)); |
1947 | fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; |
1948 | VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence); |
1949 | if (err != VK_SUCCESS) { |
1950 | qWarning(msg: "Failed to create command buffer fence: %d" , err); |
1951 | return QRhi::FrameOpError; |
1952 | } |
1953 | } |
1954 | |
1955 | QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb: ofr.cbWrapper.cb, cmdFence: ofr.cmdFence, waitSem: nullptr, signalSem: nullptr); |
1956 | if (submitres != QRhi::FrameOpSuccess) |
1957 | return submitres; |
1958 | |
1959 | // wait for completion |
1960 | df->vkWaitForFences(dev, 1, &ofr.cmdFence, VK_TRUE, UINT64_MAX); |
1961 | df->vkResetFences(dev, 1, &ofr.cmdFence); |
1962 | |
1963 | // Here we know that executing the host-side reads for this (or any |
1964 | // previous) frame is safe since we waited for completion above. |
1965 | finishActiveReadbacks(forced: true); |
1966 | |
1967 | return QRhi::FrameOpSuccess; |
1968 | } |
1969 | |
1970 | QRhi::FrameOpResult QRhiVulkan::finish() |
1971 | { |
1972 | QVkSwapChain *swapChainD = nullptr; |
1973 | if (inFrame) { |
1974 | // There is either a swapchain or an offscreen frame on-going. |
1975 | // End command recording and submit what we have. |
1976 | VkCommandBuffer cb; |
1977 | if (ofr.active) { |
1978 | Q_ASSERT(!currentSwapChain); |
1979 | Q_ASSERT(ofr.cbWrapper.recordingPass == QVkCommandBuffer::NoPass); |
1980 | recordPrimaryCommandBuffer(cbD: &ofr.cbWrapper); |
1981 | ofr.cbWrapper.resetCommands(); |
1982 | cb = ofr.cbWrapper.cb; |
1983 | } else { |
1984 | Q_ASSERT(currentSwapChain); |
1985 | Q_ASSERT(currentSwapChain->cbWrapper.recordingPass == QVkCommandBuffer::NoPass); |
1986 | swapChainD = currentSwapChain; |
1987 | recordPrimaryCommandBuffer(cbD: &swapChainD->cbWrapper); |
1988 | swapChainD->cbWrapper.resetCommands(); |
1989 | cb = swapChainD->cbWrapper.cb; |
1990 | } |
1991 | QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb, VK_NULL_HANDLE, waitSem: nullptr, signalSem: nullptr); |
1992 | if (submitres != QRhi::FrameOpSuccess) |
1993 | return submitres; |
1994 | } |
1995 | |
1996 | df->vkQueueWaitIdle(gfxQueue); |
1997 | |
1998 | if (inFrame) { |
1999 | // Allocate and begin recording on a new command buffer. |
2000 | if (ofr.active) |
2001 | startPrimaryCommandBuffer(cb: &ofr.cbWrapper.cb); |
2002 | else |
2003 | startPrimaryCommandBuffer(cb: &swapChainD->frameRes[swapChainD->currentFrameSlot].cmdBuf); |
2004 | } |
2005 | |
2006 | executeDeferredReleases(forced: true); |
2007 | finishActiveReadbacks(forced: true); |
2008 | |
2009 | return QRhi::FrameOpSuccess; |
2010 | } |
2011 | |
2012 | static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkBuffer::UsageState &bufUsage) |
2013 | { |
2014 | QRhiPassResourceTracker::UsageState u; |
2015 | u.layout = 0; // unused with buffers |
2016 | u.access = int(bufUsage.access); |
2017 | u.stage = int(bufUsage.stage); |
2018 | return u; |
2019 | } |
2020 | |
2021 | static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkTexture::UsageState &texUsage) |
2022 | { |
2023 | QRhiPassResourceTracker::UsageState u; |
2024 | u.layout = texUsage.layout; |
2025 | u.access = int(texUsage.access); |
2026 | u.stage = int(texUsage.stage); |
2027 | return u; |
2028 | } |
2029 | |
2030 | void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD) |
2031 | { |
2032 | rtD->lastActiveFrameSlot = currentFrameSlot; |
2033 | rtD->d.rp->lastActiveFrameSlot = currentFrameSlot; |
2034 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
2035 | for (auto it = rtD->m_desc.cbeginColorAttachments(), itEnd = rtD->m_desc.cendColorAttachments(); it != itEnd; ++it) { |
2036 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
2037 | QVkTexture *resolveTexD = QRHI_RES(QVkTexture, it->resolveTexture()); |
2038 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
2039 | if (texD) { |
2040 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
2041 | access: QRhiPassResourceTracker::TexColorOutput, |
2042 | stage: QRhiPassResourceTracker::TexColorOutputStage); |
2043 | texD->lastActiveFrameSlot = currentFrameSlot; |
2044 | } else if (rbD) { |
2045 | // Won't register rbD->backingTexture because it cannot be used for |
2046 | // anything in a renderpass, its use makes only sense in |
2047 | // combination with a resolveTexture. |
2048 | rbD->lastActiveFrameSlot = currentFrameSlot; |
2049 | } |
2050 | if (resolveTexD) { |
2051 | trackedRegisterTexture(passResTracker: &passResTracker, texD: resolveTexD, |
2052 | access: QRhiPassResourceTracker::TexColorOutput, |
2053 | stage: QRhiPassResourceTracker::TexColorOutputStage); |
2054 | resolveTexD->lastActiveFrameSlot = currentFrameSlot; |
2055 | } |
2056 | } |
2057 | if (rtD->m_desc.depthStencilBuffer()) |
2058 | QRHI_RES(QVkRenderBuffer, rtD->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot; |
2059 | if (rtD->m_desc.depthTexture()) { |
2060 | QVkTexture *depthTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthTexture()); |
2061 | trackedRegisterTexture(passResTracker: &passResTracker, texD: depthTexD, |
2062 | access: QRhiPassResourceTracker::TexDepthOutput, |
2063 | stage: QRhiPassResourceTracker::TexDepthOutputStage); |
2064 | depthTexD->lastActiveFrameSlot = currentFrameSlot; |
2065 | } |
2066 | } |
2067 | |
2068 | void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2069 | { |
2070 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2071 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2072 | |
2073 | enqueueResourceUpdates(cbD, resourceUpdates); |
2074 | } |
2075 | |
2076 | VkCommandBuffer QRhiVulkan::startSecondaryCommandBuffer(QVkRenderTargetData *rtD) |
2077 | { |
2078 | VkCommandBuffer secondaryCb; |
2079 | |
2080 | VkCommandBufferAllocateInfo cmdBufInfo; |
2081 | memset(s: &cmdBufInfo, c: 0, n: sizeof(cmdBufInfo)); |
2082 | cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; |
2083 | cmdBufInfo.commandPool = cmdPool; |
2084 | cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; |
2085 | cmdBufInfo.commandBufferCount = 1; |
2086 | VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, &secondaryCb); |
2087 | if (err != VK_SUCCESS) { |
2088 | qWarning(msg: "Failed to create secondary command buffer: %d" , err); |
2089 | return VK_NULL_HANDLE; |
2090 | } |
2091 | |
2092 | VkCommandBufferBeginInfo cmdBufBeginInfo; |
2093 | memset(s: &cmdBufBeginInfo, c: 0, n: sizeof(cmdBufBeginInfo)); |
2094 | cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; |
2095 | cmdBufBeginInfo.flags = rtD ? VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT : 0; |
2096 | VkCommandBufferInheritanceInfo cmdBufInheritInfo; |
2097 | memset(s: &cmdBufInheritInfo, c: 0, n: sizeof(cmdBufInheritInfo)); |
2098 | cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; |
2099 | cmdBufInheritInfo.subpass = 0; |
2100 | if (rtD) { |
2101 | cmdBufInheritInfo.renderPass = rtD->rp->rp; |
2102 | cmdBufInheritInfo.framebuffer = rtD->fb; |
2103 | } |
2104 | cmdBufBeginInfo.pInheritanceInfo = &cmdBufInheritInfo; |
2105 | |
2106 | err = df->vkBeginCommandBuffer(secondaryCb, &cmdBufBeginInfo); |
2107 | if (err != VK_SUCCESS) { |
2108 | qWarning(msg: "Failed to begin secondary command buffer: %d" , err); |
2109 | df->vkFreeCommandBuffers(dev, cmdPool, 1, &secondaryCb); |
2110 | return VK_NULL_HANDLE; |
2111 | } |
2112 | |
2113 | return secondaryCb; |
2114 | } |
2115 | |
2116 | void QRhiVulkan::endAndEnqueueSecondaryCommandBuffer(VkCommandBuffer cb, QVkCommandBuffer *cbD) |
2117 | { |
2118 | VkResult err = df->vkEndCommandBuffer(cb); |
2119 | if (err != VK_SUCCESS) |
2120 | qWarning(msg: "Failed to end secondary command buffer: %d" , err); |
2121 | |
2122 | QVkCommandBuffer::Command cmd; |
2123 | cmd.cmd = QVkCommandBuffer::Command::ExecuteSecondary; |
2124 | cmd.args.executeSecondary.cb = cb; |
2125 | cbD->commands.append(t: cmd); |
2126 | |
2127 | deferredReleaseSecondaryCommandBuffer(cb); |
2128 | } |
2129 | |
2130 | void QRhiVulkan::deferredReleaseSecondaryCommandBuffer(VkCommandBuffer cb) |
2131 | { |
2132 | QRhiVulkan::DeferredReleaseEntry e; |
2133 | e.type = QRhiVulkan::DeferredReleaseEntry::CommandBuffer; |
2134 | e.lastActiveFrameSlot = currentFrameSlot; |
2135 | e.commandBuffer.cb = cb; |
2136 | releaseQueue.append(t: e); |
2137 | } |
2138 | |
2139 | void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, |
2140 | QRhiRenderTarget *rt, |
2141 | const QColor &colorClearValue, |
2142 | const QRhiDepthStencilClearValue &depthStencilClearValue, |
2143 | QRhiResourceUpdateBatch *resourceUpdates) |
2144 | { |
2145 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2146 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2147 | |
2148 | if (resourceUpdates) |
2149 | enqueueResourceUpdates(cbD, resourceUpdates); |
2150 | |
2151 | // Insert a TransitionPassResources into the command stream, pointing to |
2152 | // the tracker this pass is going to use. That's how we generate the |
2153 | // barriers later during recording the real VkCommandBuffer, right before |
2154 | // the vkCmdBeginRenderPass. |
2155 | enqueueTransitionPassResources(cbD); |
2156 | |
2157 | QVkRenderTargetData *rtD = nullptr; |
2158 | switch (rt->resourceType()) { |
2159 | case QRhiResource::RenderTarget: |
2160 | rtD = &QRHI_RES(QVkReferenceRenderTarget, rt)->d; |
2161 | rtD->rp->lastActiveFrameSlot = currentFrameSlot; |
2162 | Q_ASSERT(currentSwapChain); |
2163 | currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse = |
2164 | QVkSwapChain::ImageResources::ScImageUseRender; |
2165 | break; |
2166 | case QRhiResource::TextureRenderTarget: |
2167 | { |
2168 | QVkTextureRenderTarget *rtTex = QRHI_RES(QVkTextureRenderTarget, rt); |
2169 | rtD = &rtTex->d; |
2170 | activateTextureRenderTarget(cbD, rtD: rtTex); |
2171 | } |
2172 | break; |
2173 | default: |
2174 | Q_UNREACHABLE(); |
2175 | break; |
2176 | } |
2177 | |
2178 | cbD->recordingPass = QVkCommandBuffer::RenderPass; |
2179 | cbD->currentTarget = rt; |
2180 | |
2181 | // No copy operations or image layout transitions allowed after this point |
2182 | // (up until endPass) as we are going to begin the renderpass. |
2183 | |
2184 | VkRenderPassBeginInfo rpBeginInfo; |
2185 | memset(s: &rpBeginInfo, c: 0, n: sizeof(rpBeginInfo)); |
2186 | rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; |
2187 | rpBeginInfo.renderPass = rtD->rp->rp; |
2188 | rpBeginInfo.framebuffer = rtD->fb; |
2189 | rpBeginInfo.renderArea.extent.width = uint32_t(rtD->pixelSize.width()); |
2190 | rpBeginInfo.renderArea.extent.height = uint32_t(rtD->pixelSize.height()); |
2191 | |
2192 | QVarLengthArray<VkClearValue, 4> cvs; |
2193 | for (int i = 0; i < rtD->colorAttCount; ++i) { |
2194 | VkClearValue cv; |
2195 | cv.color = { .float32: { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()), |
2196 | float(colorClearValue.alphaF()) } }; |
2197 | cvs.append(t: cv); |
2198 | } |
2199 | for (int i = 0; i < rtD->dsAttCount; ++i) { |
2200 | VkClearValue cv; |
2201 | cv.depthStencil = { .depth: depthStencilClearValue.depthClearValue(), .stencil: depthStencilClearValue.stencilClearValue() }; |
2202 | cvs.append(t: cv); |
2203 | } |
2204 | for (int i = 0; i < rtD->resolveAttCount; ++i) { |
2205 | VkClearValue cv; |
2206 | cv.color = { .float32: { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()), |
2207 | float(colorClearValue.alphaF()) } }; |
2208 | cvs.append(t: cv); |
2209 | } |
2210 | rpBeginInfo.clearValueCount = uint32_t(cvs.count()); |
2211 | |
2212 | QVkCommandBuffer::Command cmd; |
2213 | cmd.cmd = QVkCommandBuffer::Command::BeginRenderPass; |
2214 | cmd.args.beginRenderPass.desc = rpBeginInfo; |
2215 | cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.count(); |
2216 | cbD->pools.clearValue.append(abuf: cvs.constData(), increment: cvs.count()); |
2217 | cbD->commands.append(t: cmd); |
2218 | |
2219 | if (cbD->useSecondaryCb) |
2220 | cbD->secondaryCbs.append(t: startSecondaryCommandBuffer(rtD)); |
2221 | } |
2222 | |
2223 | void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2224 | { |
2225 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2226 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
2227 | |
2228 | if (cbD->useSecondaryCb) { |
2229 | VkCommandBuffer secondaryCb = cbD->secondaryCbs.last(); |
2230 | cbD->secondaryCbs.removeLast(); |
2231 | endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD); |
2232 | cbD->resetCachedState(); |
2233 | } |
2234 | |
2235 | QVkCommandBuffer::Command cmd; |
2236 | cmd.cmd = QVkCommandBuffer::Command::EndRenderPass; |
2237 | cbD->commands.append(t: cmd); |
2238 | |
2239 | cbD->recordingPass = QVkCommandBuffer::NoPass; |
2240 | cbD->currentTarget = nullptr; |
2241 | |
2242 | if (resourceUpdates) |
2243 | enqueueResourceUpdates(cbD, resourceUpdates); |
2244 | } |
2245 | |
2246 | void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2247 | { |
2248 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2249 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2250 | |
2251 | if (resourceUpdates) |
2252 | enqueueResourceUpdates(cbD, resourceUpdates); |
2253 | |
2254 | enqueueTransitionPassResources(cbD); |
2255 | |
2256 | cbD->recordingPass = QVkCommandBuffer::ComputePass; |
2257 | |
2258 | cbD->computePassState.reset(); |
2259 | |
2260 | if (cbD->useSecondaryCb) |
2261 | cbD->secondaryCbs.append(t: startSecondaryCommandBuffer()); |
2262 | } |
2263 | |
2264 | void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2265 | { |
2266 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2267 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); |
2268 | |
2269 | if (cbD->useSecondaryCb) { |
2270 | VkCommandBuffer secondaryCb = cbD->secondaryCbs.last(); |
2271 | cbD->secondaryCbs.removeLast(); |
2272 | endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD); |
2273 | cbD->resetCachedState(); |
2274 | } |
2275 | |
2276 | cbD->recordingPass = QVkCommandBuffer::NoPass; |
2277 | |
2278 | if (resourceUpdates) |
2279 | enqueueResourceUpdates(cbD, resourceUpdates); |
2280 | } |
2281 | |
2282 | void QRhiVulkan::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) |
2283 | { |
2284 | QVkComputePipeline *psD = QRHI_RES(QVkComputePipeline, ps); |
2285 | Q_ASSERT(psD->pipeline); |
2286 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2287 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); |
2288 | |
2289 | if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) { |
2290 | if (cbD->useSecondaryCb) { |
2291 | df->vkCmdBindPipeline(cbD->secondaryCbs.last(), VK_PIPELINE_BIND_POINT_COMPUTE, psD->pipeline); |
2292 | } else { |
2293 | QVkCommandBuffer::Command cmd; |
2294 | cmd.cmd = QVkCommandBuffer::Command::BindPipeline; |
2295 | cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE; |
2296 | cmd.args.bindPipeline.pipeline = psD->pipeline; |
2297 | cbD->commands.append(t: cmd); |
2298 | } |
2299 | |
2300 | cbD->currentGraphicsPipeline = nullptr; |
2301 | cbD->currentComputePipeline = ps; |
2302 | cbD->currentPipelineGeneration = psD->generation; |
2303 | } |
2304 | |
2305 | psD->lastActiveFrameSlot = currentFrameSlot; |
2306 | } |
2307 | |
2308 | template<typename T> |
2309 | inline void qrhivk_accumulateComputeResource(T *writtenResources, QRhiResource *resource, |
2310 | QRhiShaderResourceBinding::Type bindingType, |
2311 | int loadTypeVal, int storeTypeVal, int loadStoreTypeVal) |
2312 | { |
2313 | VkAccessFlags access = 0; |
2314 | if (bindingType == loadTypeVal) { |
2315 | access = VK_ACCESS_SHADER_READ_BIT; |
2316 | } else { |
2317 | access = VK_ACCESS_SHADER_WRITE_BIT; |
2318 | if (bindingType == loadStoreTypeVal) |
2319 | access |= VK_ACCESS_SHADER_READ_BIT; |
2320 | } |
2321 | auto it = writtenResources->find(resource); |
2322 | if (it != writtenResources->end()) |
2323 | it->first |= access; |
2324 | else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal) |
2325 | writtenResources->insert(resource, { access, true }); |
2326 | } |
2327 | |
2328 | void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) |
2329 | { |
2330 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2331 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); |
2332 | |
2333 | // When there are multiple dispatches, read-after-write and |
2334 | // write-after-write need a barrier. |
2335 | QVarLengthArray<VkImageMemoryBarrier, 8> imageBarriers; |
2336 | QVarLengthArray<VkBufferMemoryBarrier, 8> bufferBarriers; |
2337 | if (cbD->currentComputeSrb) { |
2338 | // The key in the writtenResources map indicates that the resource was |
2339 | // written in a previous dispatch, whereas the value accumulates the |
2340 | // access mask in the current one. |
2341 | for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources) |
2342 | accessAndIsNewFlag = { 0, false }; |
2343 | |
2344 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, cbD->currentComputeSrb); |
2345 | const int bindingCount = srbD->m_bindings.count(); |
2346 | for (int i = 0; i < bindingCount; ++i) { |
2347 | const QRhiShaderResourceBinding::Data *b = srbD->m_bindings.at(idx: i).data(); |
2348 | switch (b->type) { |
2349 | case QRhiShaderResourceBinding::ImageLoad: |
2350 | case QRhiShaderResourceBinding::ImageStore: |
2351 | case QRhiShaderResourceBinding::ImageLoadStore: |
2352 | qrhivk_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources, |
2353 | resource: b->u.simage.tex, |
2354 | bindingType: b->type, |
2355 | loadTypeVal: QRhiShaderResourceBinding::ImageLoad, |
2356 | storeTypeVal: QRhiShaderResourceBinding::ImageStore, |
2357 | loadStoreTypeVal: QRhiShaderResourceBinding::ImageLoadStore); |
2358 | break; |
2359 | case QRhiShaderResourceBinding::BufferLoad: |
2360 | case QRhiShaderResourceBinding::BufferStore: |
2361 | case QRhiShaderResourceBinding::BufferLoadStore: |
2362 | qrhivk_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources, |
2363 | resource: b->u.sbuf.buf, |
2364 | bindingType: b->type, |
2365 | loadTypeVal: QRhiShaderResourceBinding::BufferLoad, |
2366 | storeTypeVal: QRhiShaderResourceBinding::BufferStore, |
2367 | loadStoreTypeVal: QRhiShaderResourceBinding::BufferLoadStore); |
2368 | break; |
2369 | default: |
2370 | break; |
2371 | } |
2372 | } |
2373 | |
2374 | for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) { |
2375 | const int accessInThisDispatch = it->first; |
2376 | const bool isNewInThisDispatch = it->second; |
2377 | if (accessInThisDispatch && !isNewInThisDispatch) { |
2378 | if (it.key()->resourceType() == QRhiResource::Texture) { |
2379 | QVkTexture *texD = QRHI_RES(QVkTexture, it.key()); |
2380 | VkImageMemoryBarrier barrier; |
2381 | memset(s: &barrier, c: 0, n: sizeof(barrier)); |
2382 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
2383 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
2384 | // won't care about subresources, pretend the whole resource was written |
2385 | barrier.subresourceRange.baseMipLevel = 0; |
2386 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
2387 | barrier.subresourceRange.baseArrayLayer = 0; |
2388 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
2389 | barrier.oldLayout = texD->usageState.layout; |
2390 | barrier.newLayout = texD->usageState.layout; |
2391 | barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; |
2392 | barrier.dstAccessMask = accessInThisDispatch; |
2393 | barrier.image = texD->image; |
2394 | imageBarriers.append(t: barrier); |
2395 | } else { |
2396 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key()); |
2397 | VkBufferMemoryBarrier barrier; |
2398 | memset(s: &barrier, c: 0, n: sizeof(barrier)); |
2399 | barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; |
2400 | barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
2401 | barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
2402 | barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; |
2403 | barrier.dstAccessMask = accessInThisDispatch; |
2404 | barrier.buffer = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0]; |
2405 | barrier.size = VK_WHOLE_SIZE; |
2406 | bufferBarriers.append(t: barrier); |
2407 | } |
2408 | } |
2409 | // Anything that was previously written, but is only read now, can be |
2410 | // removed from the written list (because that previous write got a |
2411 | // corresponding barrier now). |
2412 | if (accessInThisDispatch == VK_ACCESS_SHADER_READ_BIT) |
2413 | it = cbD->computePassState.writtenResources.erase(it); |
2414 | else |
2415 | ++it; |
2416 | } |
2417 | } |
2418 | |
2419 | if (cbD->useSecondaryCb) { |
2420 | VkCommandBuffer secondaryCb = cbD->secondaryCbs.last(); |
2421 | if (!imageBarriers.isEmpty()) { |
2422 | df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, |
2423 | 0, 0, nullptr, |
2424 | 0, nullptr, |
2425 | imageBarriers.count(), imageBarriers.constData()); |
2426 | } |
2427 | if (!bufferBarriers.isEmpty()) { |
2428 | df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, |
2429 | 0, 0, nullptr, |
2430 | bufferBarriers.count(), bufferBarriers.constData(), |
2431 | 0, nullptr); |
2432 | } |
2433 | df->vkCmdDispatch(secondaryCb, uint32_t(x), uint32_t(y), uint32_t(z)); |
2434 | } else { |
2435 | QVkCommandBuffer::Command cmd; |
2436 | if (!imageBarriers.isEmpty()) { |
2437 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
2438 | cmd.args.imageBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
2439 | cmd.args.imageBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
2440 | cmd.args.imageBarrier.count = imageBarriers.count(); |
2441 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); |
2442 | cbD->pools.imageBarrier.append(abuf: imageBarriers.constData(), increment: imageBarriers.count()); |
2443 | cbD->commands.append(t: cmd); |
2444 | } |
2445 | if (!bufferBarriers.isEmpty()) { |
2446 | cmd.cmd = QVkCommandBuffer::Command::BufferBarrier; |
2447 | cmd.args.bufferBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
2448 | cmd.args.bufferBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
2449 | cmd.args.bufferBarrier.count = bufferBarriers.count(); |
2450 | cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.count(); |
2451 | cbD->pools.bufferBarrier.append(abuf: bufferBarriers.constData(), increment: bufferBarriers.count()); |
2452 | cbD->commands.append(t: cmd); |
2453 | } |
2454 | cmd.cmd = QVkCommandBuffer::Command::Dispatch; |
2455 | cmd.args.dispatch.x = x; |
2456 | cmd.args.dispatch.y = y; |
2457 | cmd.args.dispatch.z = z; |
2458 | cbD->commands.append(t: cmd); |
2459 | } |
2460 | } |
2461 | |
2462 | VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv) |
2463 | { |
2464 | VkShaderModuleCreateInfo shaderInfo; |
2465 | memset(s: &shaderInfo, c: 0, n: sizeof(shaderInfo)); |
2466 | shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; |
2467 | shaderInfo.codeSize = size_t(spirv.size()); |
2468 | shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData()); |
2469 | VkShaderModule shaderModule; |
2470 | VkResult err = df->vkCreateShaderModule(dev, &shaderInfo, nullptr, &shaderModule); |
2471 | if (err != VK_SUCCESS) { |
2472 | qWarning(msg: "Failed to create shader module: %d" , err); |
2473 | return VK_NULL_HANDLE; |
2474 | } |
2475 | return shaderModule; |
2476 | } |
2477 | |
2478 | bool QRhiVulkan::ensurePipelineCache() |
2479 | { |
2480 | if (pipelineCache) |
2481 | return true; |
2482 | |
2483 | VkPipelineCacheCreateInfo pipelineCacheInfo; |
2484 | memset(s: &pipelineCacheInfo, c: 0, n: sizeof(pipelineCacheInfo)); |
2485 | pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; |
2486 | VkResult err = df->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &pipelineCache); |
2487 | if (err != VK_SUCCESS) { |
2488 | qWarning(msg: "Failed to create pipeline cache: %d" , err); |
2489 | return false; |
2490 | } |
2491 | return true; |
2492 | } |
2493 | |
2494 | void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx) |
2495 | { |
2496 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb); |
2497 | |
2498 | QVarLengthArray<VkDescriptorBufferInfo, 8> bufferInfos; |
2499 | using ArrayOfImageDesc = QVarLengthArray<VkDescriptorImageInfo, 8>; |
2500 | QVarLengthArray<ArrayOfImageDesc, 8> imageInfos; |
2501 | QVarLengthArray<VkWriteDescriptorSet, 12> writeInfos; |
2502 | QVarLengthArray<QPair<int, int>, 12> infoIndices; |
2503 | |
2504 | const bool updateAll = descSetIdx < 0; |
2505 | int frameSlot = updateAll ? 0 : descSetIdx; |
2506 | while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) { |
2507 | srbD->boundResourceData[frameSlot].resize(asize: srbD->sortedBindings.count()); |
2508 | for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { |
2509 | const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(idx: i).data(); |
2510 | QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]); |
2511 | |
2512 | VkWriteDescriptorSet writeInfo; |
2513 | memset(s: &writeInfo, c: 0, n: sizeof(writeInfo)); |
2514 | writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; |
2515 | writeInfo.dstSet = srbD->descSets[frameSlot]; |
2516 | writeInfo.dstBinding = uint32_t(b->binding); |
2517 | writeInfo.descriptorCount = 1; |
2518 | |
2519 | int bufferInfoIndex = -1; |
2520 | int imageInfoIndex = -1; |
2521 | |
2522 | switch (b->type) { |
2523 | case QRhiShaderResourceBinding::UniformBuffer: |
2524 | { |
2525 | writeInfo.descriptorType = b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC |
2526 | : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
2527 | QRhiBuffer *buf = b->u.ubuf.buf; |
2528 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, buf); |
2529 | bd.ubuf.id = bufD->m_id; |
2530 | bd.ubuf.generation = bufD->generation; |
2531 | VkDescriptorBufferInfo bufInfo; |
2532 | bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; |
2533 | bufInfo.offset = VkDeviceSize(b->u.ubuf.offset); |
2534 | bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size); |
2535 | // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads |
2536 | Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset); |
2537 | bufferInfoIndex = bufferInfos.count(); |
2538 | bufferInfos.append(t: bufInfo); |
2539 | } |
2540 | break; |
2541 | case QRhiShaderResourceBinding::SampledTexture: |
2542 | { |
2543 | const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; |
2544 | writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported |
2545 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; |
2546 | ArrayOfImageDesc imageInfo(data->count); |
2547 | for (int elem = 0; elem < data->count; ++elem) { |
2548 | QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); |
2549 | QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler); |
2550 | bd.stex.d[elem].texId = texD->m_id; |
2551 | bd.stex.d[elem].texGeneration = texD->generation; |
2552 | bd.stex.d[elem].samplerId = samplerD->m_id; |
2553 | bd.stex.d[elem].samplerGeneration = samplerD->generation; |
2554 | imageInfo[elem].sampler = samplerD->sampler; |
2555 | imageInfo[elem].imageView = texD->imageView; |
2556 | imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; |
2557 | } |
2558 | bd.stex.count = data->count; |
2559 | imageInfoIndex = imageInfos.count(); |
2560 | imageInfos.append(t: imageInfo); |
2561 | } |
2562 | break; |
2563 | case QRhiShaderResourceBinding::ImageLoad: |
2564 | case QRhiShaderResourceBinding::ImageStore: |
2565 | case QRhiShaderResourceBinding::ImageLoadStore: |
2566 | { |
2567 | QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); |
2568 | VkImageView view = texD->imageViewForLevel(level: b->u.simage.level); |
2569 | if (view) { |
2570 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; |
2571 | bd.simage.id = texD->m_id; |
2572 | bd.simage.generation = texD->generation; |
2573 | ArrayOfImageDesc imageInfo(1); |
2574 | imageInfo[0].sampler = VK_NULL_HANDLE; |
2575 | imageInfo[0].imageView = view; |
2576 | imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; |
2577 | imageInfoIndex = imageInfos.count(); |
2578 | imageInfos.append(t: imageInfo); |
2579 | } |
2580 | } |
2581 | break; |
2582 | case QRhiShaderResourceBinding::BufferLoad: |
2583 | case QRhiShaderResourceBinding::BufferStore: |
2584 | case QRhiShaderResourceBinding::BufferLoadStore: |
2585 | { |
2586 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf); |
2587 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
2588 | bd.sbuf.id = bufD->m_id; |
2589 | bd.sbuf.generation = bufD->generation; |
2590 | VkDescriptorBufferInfo bufInfo; |
2591 | bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; |
2592 | bufInfo.offset = VkDeviceSize(b->u.ubuf.offset); |
2593 | bufInfo.range = VkDeviceSize(b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size); |
2594 | bufferInfoIndex = bufferInfos.count(); |
2595 | bufferInfos.append(t: bufInfo); |
2596 | } |
2597 | break; |
2598 | default: |
2599 | continue; |
2600 | } |
2601 | |
2602 | writeInfos.append(t: writeInfo); |
2603 | infoIndices.append(t: { bufferInfoIndex, imageInfoIndex }); |
2604 | } |
2605 | ++frameSlot; |
2606 | } |
2607 | |
2608 | for (int i = 0, writeInfoCount = writeInfos.count(); i < writeInfoCount; ++i) { |
2609 | const int bufferInfoIndex = infoIndices[i].first; |
2610 | const int imageInfoIndex = infoIndices[i].second; |
2611 | if (bufferInfoIndex >= 0) |
2612 | writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex]; |
2613 | else if (imageInfoIndex >= 0) |
2614 | writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData(); |
2615 | } |
2616 | |
2617 | df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.count()), writeInfos.constData(), 0, nullptr); |
2618 | } |
2619 | |
2620 | static inline bool accessIsWrite(VkAccessFlags access) |
2621 | { |
2622 | return (access & VK_ACCESS_SHADER_WRITE_BIT) != 0 |
2623 | || (access & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0 |
2624 | || (access & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) != 0 |
2625 | || (access & VK_ACCESS_TRANSFER_WRITE_BIT) != 0 |
2626 | || (access & VK_ACCESS_HOST_WRITE_BIT) != 0 |
2627 | || (access & VK_ACCESS_MEMORY_WRITE_BIT) != 0; |
2628 | } |
2629 | |
2630 | void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot, |
2631 | VkAccessFlags access, VkPipelineStageFlags stage) |
2632 | { |
2633 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2634 | Q_ASSERT(access && stage); |
2635 | QVkBuffer::UsageState &s(bufD->usageState[slot]); |
2636 | if (!s.stage) { |
2637 | s.access = access; |
2638 | s.stage = stage; |
2639 | return; |
2640 | } |
2641 | |
2642 | if (s.access == access && s.stage == stage) { |
2643 | // No need to flood with unnecessary read-after-read barriers. |
2644 | // Write-after-write is a different matter, however. |
2645 | if (!accessIsWrite(access)) |
2646 | return; |
2647 | } |
2648 | |
2649 | VkBufferMemoryBarrier bufMemBarrier; |
2650 | memset(s: &bufMemBarrier, c: 0, n: sizeof(bufMemBarrier)); |
2651 | bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; |
2652 | bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
2653 | bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
2654 | bufMemBarrier.srcAccessMask = s.access; |
2655 | bufMemBarrier.dstAccessMask = access; |
2656 | bufMemBarrier.buffer = bufD->buffers[slot]; |
2657 | bufMemBarrier.size = VK_WHOLE_SIZE; |
2658 | |
2659 | QVkCommandBuffer::Command cmd; |
2660 | cmd.cmd = QVkCommandBuffer::Command::BufferBarrier; |
2661 | cmd.args.bufferBarrier.srcStageMask = s.stage; |
2662 | cmd.args.bufferBarrier.dstStageMask = stage; |
2663 | cmd.args.bufferBarrier.count = 1; |
2664 | cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.count(); |
2665 | cbD->pools.bufferBarrier.append(t: bufMemBarrier); |
2666 | cbD->commands.append(t: cmd); |
2667 | |
2668 | s.access = access; |
2669 | s.stage = stage; |
2670 | } |
2671 | |
2672 | void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, |
2673 | VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage) |
2674 | { |
2675 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2676 | Q_ASSERT(layout && access && stage); |
2677 | QVkTexture::UsageState &s(texD->usageState); |
2678 | if (s.access == access && s.stage == stage && s.layout == layout) { |
2679 | if (!accessIsWrite(access)) |
2680 | return; |
2681 | } |
2682 | |
2683 | VkImageMemoryBarrier barrier; |
2684 | memset(s: &barrier, c: 0, n: sizeof(barrier)); |
2685 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
2686 | barrier.subresourceRange.aspectMask = !isDepthTextureFormat(format: texD->m_format) |
2687 | ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; |
2688 | barrier.subresourceRange.baseMipLevel = 0; |
2689 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
2690 | barrier.subresourceRange.baseArrayLayer = 0; |
2691 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
2692 | barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED |
2693 | barrier.newLayout = layout; |
2694 | barrier.srcAccessMask = s.access; // may be 0 but that's fine |
2695 | barrier.dstAccessMask = access; |
2696 | barrier.image = texD->image; |
2697 | |
2698 | VkPipelineStageFlags srcStage = s.stage; |
2699 | // stage mask cannot be 0 |
2700 | if (!srcStage) |
2701 | srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; |
2702 | |
2703 | QVkCommandBuffer::Command cmd; |
2704 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
2705 | cmd.args.imageBarrier.srcStageMask = srcStage; |
2706 | cmd.args.imageBarrier.dstStageMask = stage; |
2707 | cmd.args.imageBarrier.count = 1; |
2708 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); |
2709 | cbD->pools.imageBarrier.append(t: barrier); |
2710 | cbD->commands.append(t: cmd); |
2711 | |
2712 | s.layout = layout; |
2713 | s.access = access; |
2714 | s.stage = stage; |
2715 | } |
2716 | |
2717 | void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, |
2718 | VkImageLayout oldLayout, VkImageLayout newLayout, |
2719 | VkAccessFlags srcAccess, VkAccessFlags dstAccess, |
2720 | VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, |
2721 | int startLayer, int layerCount, |
2722 | int startLevel, int levelCount) |
2723 | { |
2724 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2725 | VkImageMemoryBarrier barrier; |
2726 | memset(s: &barrier, c: 0, n: sizeof(barrier)); |
2727 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
2728 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
2729 | barrier.subresourceRange.baseMipLevel = uint32_t(startLevel); |
2730 | barrier.subresourceRange.levelCount = uint32_t(levelCount); |
2731 | barrier.subresourceRange.baseArrayLayer = uint32_t(startLayer); |
2732 | barrier.subresourceRange.layerCount = uint32_t(layerCount); |
2733 | barrier.oldLayout = oldLayout; |
2734 | barrier.newLayout = newLayout; |
2735 | barrier.srcAccessMask = srcAccess; |
2736 | barrier.dstAccessMask = dstAccess; |
2737 | barrier.image = image; |
2738 | |
2739 | QVkCommandBuffer::Command cmd; |
2740 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
2741 | cmd.args.imageBarrier.srcStageMask = srcStage; |
2742 | cmd.args.imageBarrier.dstStageMask = dstStage; |
2743 | cmd.args.imageBarrier.count = 1; |
2744 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.count(); |
2745 | cbD->pools.imageBarrier.append(t: barrier); |
2746 | cbD->commands.append(t: cmd); |
2747 | } |
2748 | |
2749 | VkDeviceSize QRhiVulkan::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const |
2750 | { |
2751 | VkDeviceSize size = 0; |
2752 | const qsizetype imageSizeBytes = subresDesc.image().isNull() ? |
2753 | subresDesc.data().size() : subresDesc.image().sizeInBytes(); |
2754 | if (imageSizeBytes > 0) |
2755 | size += aligned(v: VkDeviceSize(imageSizeBytes), byteAlign: texbufAlign); |
2756 | return size; |
2757 | } |
2758 | |
2759 | void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level, |
2760 | const QRhiTextureSubresourceUploadDescription &subresDesc, |
2761 | size_t *curOfs, void *mp, |
2762 | BufferImageCopyList *copyInfos) |
2763 | { |
2764 | qsizetype copySizeBytes = 0; |
2765 | qsizetype imageSizeBytes = 0; |
2766 | const void *src = nullptr; |
2767 | |
2768 | VkBufferImageCopy copyInfo; |
2769 | memset(s: ©Info, c: 0, n: sizeof(copyInfo)); |
2770 | copyInfo.bufferOffset = *curOfs; |
2771 | copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
2772 | copyInfo.imageSubresource.mipLevel = uint32_t(level); |
2773 | copyInfo.imageSubresource.baseArrayLayer = uint32_t(layer); |
2774 | copyInfo.imageSubresource.layerCount = 1; |
2775 | copyInfo.imageExtent.depth = 1; |
2776 | |
2777 | const QByteArray rawData = subresDesc.data(); |
2778 | const QPoint dp = subresDesc.destinationTopLeft(); |
2779 | QImage image = subresDesc.image(); |
2780 | if (!image.isNull()) { |
2781 | copySizeBytes = imageSizeBytes = image.sizeInBytes(); |
2782 | QSize size = image.size(); |
2783 | src = image.constBits(); |
2784 | // Scanlines in QImage are 4 byte aligned so bpl must |
2785 | // be taken into account for bufferRowLength. |
2786 | int bpc = qMax(a: 1, b: image.depth() / 8); |
2787 | // this is in pixels, not bytes, to make it more complicated... |
2788 | copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc); |
2789 | if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) { |
2790 | const int sx = subresDesc.sourceTopLeft().x(); |
2791 | const int sy = subresDesc.sourceTopLeft().y(); |
2792 | if (!subresDesc.sourceSize().isEmpty()) |
2793 | size = subresDesc.sourceSize(); |
2794 | if (image.depth() == 32) { |
2795 | // The staging buffer will get the full image |
2796 | // regardless, just adjust the vk |
2797 | // buffer-to-image copy start offset. |
2798 | copyInfo.bufferOffset += VkDeviceSize(sy * image.bytesPerLine() + sx * 4); |
2799 | // bufferRowLength remains set to the original image's width |
2800 | } else { |
2801 | image = image.copy(x: sx, y: sy, w: size.width(), h: size.height()); |
2802 | src = image.constBits(); |
2803 | // The staging buffer gets the slice only. The rest of the |
2804 | // space reserved for this mip will be unused. |
2805 | copySizeBytes = image.sizeInBytes(); |
2806 | bpc = qMax(a: 1, b: image.depth() / 8); |
2807 | copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc); |
2808 | } |
2809 | } |
2810 | copyInfo.imageOffset.x = dp.x(); |
2811 | copyInfo.imageOffset.y = dp.y(); |
2812 | copyInfo.imageExtent.width = uint32_t(size.width()); |
2813 | copyInfo.imageExtent.height = uint32_t(size.height()); |
2814 | copyInfos->append(t: copyInfo); |
2815 | } else if (!rawData.isEmpty() && isCompressedFormat(format: texD->m_format)) { |
2816 | copySizeBytes = imageSizeBytes = rawData.size(); |
2817 | src = rawData.constData(); |
2818 | QSize size = q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize); |
2819 | const int subresw = size.width(); |
2820 | const int subresh = size.height(); |
2821 | if (!subresDesc.sourceSize().isEmpty()) |
2822 | size = subresDesc.sourceSize(); |
2823 | const int w = size.width(); |
2824 | const int h = size.height(); |
2825 | QSize blockDim; |
2826 | compressedFormatInfo(format: texD->m_format, size: QSize(w, h), bpl: nullptr, byteSize: nullptr, blockDim: &blockDim); |
2827 | // x and y must be multiples of the block width and height |
2828 | copyInfo.imageOffset.x = aligned(v: dp.x(), byteAlign: blockDim.width()); |
2829 | copyInfo.imageOffset.y = aligned(v: dp.y(), byteAlign: blockDim.height()); |
2830 | // width and height must be multiples of the block width and height |
2831 | // or x + width and y + height must equal the subresource width and height |
2832 | copyInfo.imageExtent.width = uint32_t(dp.x() + w == subresw ? w : aligned(v: w, byteAlign: blockDim.width())); |
2833 | copyInfo.imageExtent.height = uint32_t(dp.y() + h == subresh ? h : aligned(v: h, byteAlign: blockDim.height())); |
2834 | copyInfos->append(t: copyInfo); |
2835 | } else if (!rawData.isEmpty()) { |
2836 | copySizeBytes = imageSizeBytes = rawData.size(); |
2837 | src = rawData.constData(); |
2838 | QSize size = q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize); |
2839 | if (!subresDesc.sourceSize().isEmpty()) |
2840 | size = subresDesc.sourceSize(); |
2841 | copyInfo.imageOffset.x = dp.x(); |
2842 | copyInfo.imageOffset.y = dp.y(); |
2843 | copyInfo.imageExtent.width = uint32_t(size.width()); |
2844 | copyInfo.imageExtent.height = uint32_t(size.height()); |
2845 | copyInfos->append(t: copyInfo); |
2846 | } else { |
2847 | qWarning(msg: "Invalid texture upload for %p layer=%d mip=%d" , texD, layer, level); |
2848 | } |
2849 | |
2850 | memcpy(dest: reinterpret_cast<char *>(mp) + *curOfs, src: src, n: size_t(copySizeBytes)); |
2851 | *curOfs += aligned(v: VkDeviceSize(imageSizeBytes), byteAlign: texbufAlign); |
2852 | } |
2853 | |
2854 | void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates) |
2855 | { |
2856 | QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(b: resourceUpdates); |
2857 | QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); |
2858 | |
2859 | for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : ud->bufferOps) { |
2860 | if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { |
2861 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); |
2862 | Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); |
2863 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
2864 | bufD->pendingDynamicUpdates[i].append(t: u); |
2865 | } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { |
2866 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); |
2867 | Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); |
2868 | Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); |
2869 | |
2870 | if (!bufD->stagingBuffers[currentFrameSlot]) { |
2871 | VkBufferCreateInfo bufferInfo; |
2872 | memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo)); |
2873 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
2874 | // must cover the entire buffer - this way multiple, partial updates per frame |
2875 | // are supported even when the staging buffer is reused (Static) |
2876 | bufferInfo.size = VkDeviceSize(bufD->m_size); |
2877 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; |
2878 | |
2879 | VmaAllocationCreateInfo allocInfo; |
2880 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
2881 | allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; |
2882 | |
2883 | VmaAllocation allocation; |
2884 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, |
2885 | pBuffer: &bufD->stagingBuffers[currentFrameSlot], pAllocation: &allocation, pAllocationInfo: nullptr); |
2886 | if (err == VK_SUCCESS) { |
2887 | bufD->stagingAllocations[currentFrameSlot] = allocation; |
2888 | QRHI_PROF_F(newBufferStagingArea(bufD, currentFrameSlot, quint32(bufD->m_size))); |
2889 | } else { |
2890 | qWarning(msg: "Failed to create staging buffer of size %d: %d" , bufD->m_size, err); |
2891 | continue; |
2892 | } |
2893 | } |
2894 | |
2895 | void *p = nullptr; |
2896 | VmaAllocation a = toVmaAllocation(a: bufD->stagingAllocations[currentFrameSlot]); |
2897 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
2898 | if (err != VK_SUCCESS) { |
2899 | qWarning(msg: "Failed to map buffer: %d" , err); |
2900 | continue; |
2901 | } |
2902 | memcpy(dest: static_cast<uchar *>(p) + u.offset, src: u.data.constData(), n: size_t(u.data.size())); |
2903 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
2904 | vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: VkDeviceSize(u.offset), size: VkDeviceSize(u.data.size())); |
2905 | |
2906 | trackedBufferBarrier(cbD, bufD, slot: 0, |
2907 | access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
2908 | |
2909 | VkBufferCopy copyInfo; |
2910 | memset(s: ©Info, c: 0, n: sizeof(copyInfo)); |
2911 | copyInfo.srcOffset = VkDeviceSize(u.offset); |
2912 | copyInfo.dstOffset = VkDeviceSize(u.offset); |
2913 | copyInfo.size = VkDeviceSize(u.data.size()); |
2914 | |
2915 | QVkCommandBuffer::Command cmd; |
2916 | cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; |
2917 | cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot]; |
2918 | cmd.args.copyBuffer.dst = bufD->buffers[0]; |
2919 | cmd.args.copyBuffer.desc = copyInfo; |
2920 | cbD->commands.append(t: cmd); |
2921 | |
2922 | // Where's the barrier for read-after-write? (assuming the common case |
2923 | // of binding this buffer as vertex/index, or, less likely, as uniform |
2924 | // buffer, in a renderpass later on) That is handled by the pass |
2925 | // resource tracking: the appropriate pipeline barrier will be |
2926 | // generated and recorded right before the renderpass, that binds this |
2927 | // buffer in one of its commands, gets its BeginRenderPass recorded. |
2928 | |
2929 | bufD->lastActiveFrameSlot = currentFrameSlot; |
2930 | |
2931 | if (bufD->m_type == QRhiBuffer::Immutable) { |
2932 | QRhiVulkan::DeferredReleaseEntry e; |
2933 | e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; |
2934 | e.lastActiveFrameSlot = currentFrameSlot; |
2935 | e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot]; |
2936 | e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot]; |
2937 | bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; |
2938 | bufD->stagingAllocations[currentFrameSlot] = nullptr; |
2939 | releaseQueue.append(t: e); |
2940 | QRHI_PROF_F(releaseBufferStagingArea(bufD, currentFrameSlot)); |
2941 | } |
2942 | } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { |
2943 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); |
2944 | if (bufD->m_type == QRhiBuffer::Dynamic) { |
2945 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
2946 | void *p = nullptr; |
2947 | VmaAllocation a = toVmaAllocation(a: bufD->allocations[currentFrameSlot]); |
2948 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
2949 | if (err == VK_SUCCESS) { |
2950 | u.result->data.resize(size: u.readSize); |
2951 | memcpy(dest: u.result->data.data(), src: reinterpret_cast<char *>(p) + u.offset, n: size_t(u.readSize)); |
2952 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
2953 | } |
2954 | if (u.result->completed) |
2955 | u.result->completed(); |
2956 | } else { |
2957 | // Non-Dynamic buffers may not be host visible, so have to |
2958 | // create a readback buffer, enqueue a copy from |
2959 | // bufD->buffers[0] to this buffer, and then once the command |
2960 | // buffer completes, copy the data out of the host visible |
2961 | // readback buffer. Quite similar to what we do for texture |
2962 | // readbacks. |
2963 | BufferReadback readback; |
2964 | readback.activeFrameSlot = currentFrameSlot; |
2965 | readback.result = u.result; |
2966 | readback.byteSize = u.readSize; |
2967 | |
2968 | VkBufferCreateInfo bufferInfo; |
2969 | memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo)); |
2970 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
2971 | bufferInfo.size = VkDeviceSize(readback.byteSize); |
2972 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
2973 | |
2974 | VmaAllocationCreateInfo allocInfo; |
2975 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
2976 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; |
2977 | |
2978 | VmaAllocation allocation; |
2979 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &readback.stagingBuf, pAllocation: &allocation, pAllocationInfo: nullptr); |
2980 | if (err == VK_SUCCESS) { |
2981 | readback.stagingAlloc = allocation; |
2982 | QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), bufD, uint(readback.byteSize))); |
2983 | } else { |
2984 | qWarning(msg: "Failed to create readback buffer of size %u: %d" , readback.byteSize, err); |
2985 | continue; |
2986 | } |
2987 | |
2988 | trackedBufferBarrier(cbD, bufD, slot: 0, access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
2989 | |
2990 | VkBufferCopy copyInfo; |
2991 | memset(s: ©Info, c: 0, n: sizeof(copyInfo)); |
2992 | copyInfo.srcOffset = VkDeviceSize(u.offset); |
2993 | copyInfo.size = VkDeviceSize(u.readSize); |
2994 | |
2995 | QVkCommandBuffer::Command cmd; |
2996 | cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; |
2997 | cmd.args.copyBuffer.src = bufD->buffers[0]; |
2998 | cmd.args.copyBuffer.dst = readback.stagingBuf; |
2999 | cmd.args.copyBuffer.desc = copyInfo; |
3000 | cbD->commands.append(t: cmd); |
3001 | |
3002 | bufD->lastActiveFrameSlot = currentFrameSlot; |
3003 | |
3004 | activeBufferReadbacks.append(t: readback); |
3005 | } |
3006 | } |
3007 | } |
3008 | |
3009 | for (const QRhiResourceUpdateBatchPrivate::TextureOp &u : ud->textureOps) { |
3010 | if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { |
3011 | QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); |
3012 | // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos |
3013 | VkDeviceSize stagingSize = 0; |
3014 | for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { |
3015 | for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { |
3016 | for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(t: u.subresDesc[layer][level])) |
3017 | stagingSize += subresUploadByteSize(subresDesc); |
3018 | } |
3019 | } |
3020 | |
3021 | Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]); |
3022 | VkBufferCreateInfo bufferInfo; |
3023 | memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo)); |
3024 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
3025 | bufferInfo.size = stagingSize; |
3026 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; |
3027 | |
3028 | VmaAllocationCreateInfo allocInfo; |
3029 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
3030 | allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; |
3031 | |
3032 | VmaAllocation allocation; |
3033 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, |
3034 | pBuffer: &utexD->stagingBuffers[currentFrameSlot], pAllocation: &allocation, pAllocationInfo: nullptr); |
3035 | if (err != VK_SUCCESS) { |
3036 | qWarning(msg: "Failed to create image staging buffer of size %d: %d" , int(stagingSize), err); |
3037 | continue; |
3038 | } |
3039 | utexD->stagingAllocations[currentFrameSlot] = allocation; |
3040 | QRHI_PROF_F(newTextureStagingArea(utexD, currentFrameSlot, quint32(stagingSize))); |
3041 | |
3042 | BufferImageCopyList copyInfos; |
3043 | size_t curOfs = 0; |
3044 | void *mp = nullptr; |
3045 | VmaAllocation a = toVmaAllocation(a: utexD->stagingAllocations[currentFrameSlot]); |
3046 | err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &mp); |
3047 | if (err != VK_SUCCESS) { |
3048 | qWarning(msg: "Failed to map image data: %d" , err); |
3049 | continue; |
3050 | } |
3051 | |
3052 | for (int layer = 0; layer < QRhi::MAX_LAYERS; ++layer) { |
3053 | for (int level = 0; level < QRhi::MAX_LEVELS; ++level) { |
3054 | const QVector<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]); |
3055 | if (srd.isEmpty()) |
3056 | continue; |
3057 | for (const QRhiTextureSubresourceUploadDescription &subresDesc : qAsConst(t: srd)) { |
3058 | prepareUploadSubres(texD: utexD, layer, level, |
3059 | subresDesc, curOfs: &curOfs, mp, copyInfos: ©Infos); |
3060 | } |
3061 | } |
3062 | } |
3063 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3064 | vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: 0, size: stagingSize); |
3065 | |
3066 | trackedImageBarrier(cbD, texD: utexD, layout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
3067 | access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3068 | |
3069 | QVkCommandBuffer::Command cmd; |
3070 | cmd.cmd = QVkCommandBuffer::Command::CopyBufferToImage; |
3071 | cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot]; |
3072 | cmd.args.copyBufferToImage.dst = utexD->image; |
3073 | cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
3074 | cmd.args.copyBufferToImage.count = copyInfos.count(); |
3075 | cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.count(); |
3076 | cbD->pools.bufferImageCopy.append(abuf: copyInfos.constData(), increment: copyInfos.count()); |
3077 | cbD->commands.append(t: cmd); |
3078 | |
3079 | // no reuse of staging, this is intentional |
3080 | QRhiVulkan::DeferredReleaseEntry e; |
3081 | e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; |
3082 | e.lastActiveFrameSlot = currentFrameSlot; |
3083 | e.stagingBuffer.stagingBuffer = utexD->stagingBuffers[currentFrameSlot]; |
3084 | e.stagingBuffer.stagingAllocation = utexD->stagingAllocations[currentFrameSlot]; |
3085 | utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; |
3086 | utexD->stagingAllocations[currentFrameSlot] = nullptr; |
3087 | releaseQueue.append(t: e); |
3088 | QRHI_PROF_F(releaseTextureStagingArea(utexD, currentFrameSlot)); |
3089 | |
3090 | // Similarly to buffers, transitioning away from DST is done later, |
3091 | // when a renderpass using the texture is encountered. |
3092 | |
3093 | utexD->lastActiveFrameSlot = currentFrameSlot; |
3094 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { |
3095 | Q_ASSERT(u.src && u.dst); |
3096 | if (u.src == u.dst) { |
3097 | qWarning(msg: "Texture copy with matching source and destination is not supported" ); |
3098 | continue; |
3099 | } |
3100 | QVkTexture *srcD = QRHI_RES(QVkTexture, u.src); |
3101 | QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst); |
3102 | |
3103 | VkImageCopy region; |
3104 | memset(s: ®ion, c: 0, n: sizeof(region)); |
3105 | |
3106 | region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3107 | region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel()); |
3108 | region.srcSubresource.baseArrayLayer = uint32_t(u.desc.sourceLayer()); |
3109 | region.srcSubresource.layerCount = 1; |
3110 | |
3111 | region.srcOffset.x = u.desc.sourceTopLeft().x(); |
3112 | region.srcOffset.y = u.desc.sourceTopLeft().y(); |
3113 | |
3114 | region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3115 | region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel()); |
3116 | region.dstSubresource.baseArrayLayer = uint32_t(u.desc.destinationLayer()); |
3117 | region.dstSubresource.layerCount = 1; |
3118 | |
3119 | region.dstOffset.x = u.desc.destinationTopLeft().x(); |
3120 | region.dstOffset.y = u.desc.destinationTopLeft().y(); |
3121 | |
3122 | const QSize mipSize = q->sizeForMipLevel(mipLevel: u.desc.sourceLevel(), baseLevelSize: srcD->m_pixelSize); |
3123 | const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); |
3124 | region.extent.width = uint32_t(copySize.width()); |
3125 | region.extent.height = uint32_t(copySize.height()); |
3126 | region.extent.depth = 1; |
3127 | |
3128 | trackedImageBarrier(cbD, texD: srcD, layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3129 | access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3130 | trackedImageBarrier(cbD, texD: dstD, layout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
3131 | access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3132 | |
3133 | QVkCommandBuffer::Command cmd; |
3134 | cmd.cmd = QVkCommandBuffer::Command::CopyImage; |
3135 | cmd.args.copyImage.src = srcD->image; |
3136 | cmd.args.copyImage.srcLayout = srcD->usageState.layout; |
3137 | cmd.args.copyImage.dst = dstD->image; |
3138 | cmd.args.copyImage.dstLayout = dstD->usageState.layout; |
3139 | cmd.args.copyImage.desc = region; |
3140 | cbD->commands.append(t: cmd); |
3141 | |
3142 | srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot; |
3143 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { |
3144 | TextureReadback readback; |
3145 | readback.activeFrameSlot = currentFrameSlot; |
3146 | readback.desc = u.rb; |
3147 | readback.result = u.result; |
3148 | |
3149 | QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture()); |
3150 | QVkSwapChain *swapChainD = nullptr; |
3151 | if (texD) { |
3152 | if (texD->samples > VK_SAMPLE_COUNT_1_BIT) { |
3153 | qWarning(msg: "Multisample texture cannot be read back" ); |
3154 | continue; |
3155 | } |
3156 | readback.pixelSize = q->sizeForMipLevel(mipLevel: u.rb.level(), baseLevelSize: texD->m_pixelSize); |
3157 | readback.format = texD->m_format; |
3158 | texD->lastActiveFrameSlot = currentFrameSlot; |
3159 | } else { |
3160 | Q_ASSERT(currentSwapChain); |
3161 | swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain); |
3162 | if (!swapChainD->supportsReadback) { |
3163 | qWarning(msg: "Swapchain does not support readback" ); |
3164 | continue; |
3165 | } |
3166 | readback.pixelSize = swapChainD->pixelSize; |
3167 | readback.format = colorTextureFormatFromVkFormat(format: swapChainD->colorFormat, flags: nullptr); |
3168 | if (readback.format == QRhiTexture::UnknownFormat) |
3169 | continue; |
3170 | |
3171 | // Multisample swapchains need nothing special since resolving |
3172 | // happens when ending a renderpass. |
3173 | } |
3174 | textureFormatInfo(format: readback.format, size: readback.pixelSize, bpl: nullptr, byteSize: &readback.byteSize); |
3175 | |
3176 | // Create a host visible readback buffer. |
3177 | VkBufferCreateInfo bufferInfo; |
3178 | memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo)); |
3179 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
3180 | bufferInfo.size = readback.byteSize; |
3181 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
3182 | |
3183 | VmaAllocationCreateInfo allocInfo; |
3184 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
3185 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; |
3186 | |
3187 | VmaAllocation allocation; |
3188 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &readback.stagingBuf, pAllocation: &allocation, pAllocationInfo: nullptr); |
3189 | if (err == VK_SUCCESS) { |
3190 | readback.stagingAlloc = allocation; |
3191 | QRHI_PROF_F(newReadbackBuffer(qint64(readback.stagingBuf), |
3192 | texD ? static_cast<QRhiResource *>(texD) : static_cast<QRhiResource *>(swapChainD), |
3193 | readback.byteSize)); |
3194 | } else { |
3195 | qWarning(msg: "Failed to create readback buffer of size %u: %d" , readback.byteSize, err); |
3196 | continue; |
3197 | } |
3198 | |
3199 | // Copy from the (optimal and not host visible) image into the buffer. |
3200 | VkBufferImageCopy copyDesc; |
3201 | memset(s: ©Desc, c: 0, n: sizeof(copyDesc)); |
3202 | copyDesc.bufferOffset = 0; |
3203 | copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3204 | copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level()); |
3205 | copyDesc.imageSubresource.baseArrayLayer = uint32_t(u.rb.layer()); |
3206 | copyDesc.imageSubresource.layerCount = 1; |
3207 | copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width()); |
3208 | copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height()); |
3209 | copyDesc.imageExtent.depth = 1; |
3210 | |
3211 | if (texD) { |
3212 | trackedImageBarrier(cbD, texD, layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3213 | access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3214 | QVkCommandBuffer::Command cmd; |
3215 | cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; |
3216 | cmd.args.copyImageToBuffer.src = texD->image; |
3217 | cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout; |
3218 | cmd.args.copyImageToBuffer.dst = readback.stagingBuf; |
3219 | cmd.args.copyImageToBuffer.desc = copyDesc; |
3220 | cbD->commands.append(t: cmd); |
3221 | } else { |
3222 | // use the swapchain image |
3223 | QVkSwapChain::ImageResources &imageRes(swapChainD->imageRes[swapChainD->currentImageIndex]); |
3224 | VkImage image = imageRes.image; |
3225 | if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseTransferSource) { |
3226 | if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { |
3227 | qWarning(msg: "Attempted to read back undefined swapchain image content, " |
3228 | "results are undefined. (do a render pass first)" ); |
3229 | } |
3230 | subresourceBarrier(cbD, image, |
3231 | oldLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3232 | srcAccess: VK_ACCESS_MEMORY_READ_BIT, dstAccess: VK_ACCESS_TRANSFER_READ_BIT, |
3233 | srcStage: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3234 | startLayer: 0, layerCount: 1, |
3235 | startLevel: 0, levelCount: 1); |
3236 | imageRes.lastUse = QVkSwapChain::ImageResources::ScImageUseTransferSource; |
3237 | } |
3238 | |
3239 | QVkCommandBuffer::Command cmd; |
3240 | cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; |
3241 | cmd.args.copyImageToBuffer.src = image; |
3242 | cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
3243 | cmd.args.copyImageToBuffer.dst = readback.stagingBuf; |
3244 | cmd.args.copyImageToBuffer.desc = copyDesc; |
3245 | cbD->commands.append(t: cmd); |
3246 | } |
3247 | |
3248 | activeTextureReadbacks.append(t: readback); |
3249 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { |
3250 | QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); |
3251 | Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips)); |
3252 | int w = utexD->m_pixelSize.width(); |
3253 | int h = utexD->m_pixelSize.height(); |
3254 | |
3255 | VkImageLayout origLayout = utexD->usageState.layout; |
3256 | VkAccessFlags origAccess = utexD->usageState.access; |
3257 | VkPipelineStageFlags origStage = utexD->usageState.stage; |
3258 | if (!origStage) |
3259 | origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; |
3260 | |
3261 | for (int level = 1; level < int(utexD->mipLevelCount); ++level) { |
3262 | if (level == 1) { |
3263 | subresourceBarrier(cbD, image: utexD->image, |
3264 | oldLayout: origLayout, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3265 | srcAccess: origAccess, dstAccess: VK_ACCESS_TRANSFER_READ_BIT, |
3266 | srcStage: origStage, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3267 | startLayer: u.layer, layerCount: 1, |
3268 | startLevel: level - 1, levelCount: 1); |
3269 | } else { |
3270 | subresourceBarrier(cbD, image: utexD->image, |
3271 | oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3272 | srcAccess: VK_ACCESS_TRANSFER_WRITE_BIT, dstAccess: VK_ACCESS_TRANSFER_READ_BIT, |
3273 | srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3274 | startLayer: u.layer, layerCount: 1, |
3275 | startLevel: level - 1, levelCount: 1); |
3276 | } |
3277 | |
3278 | subresourceBarrier(cbD, image: utexD->image, |
3279 | oldLayout: origLayout, newLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
3280 | srcAccess: origAccess, dstAccess: VK_ACCESS_TRANSFER_WRITE_BIT, |
3281 | srcStage: origStage, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3282 | startLayer: u.layer, layerCount: 1, |
3283 | startLevel: level, levelCount: 1); |
3284 | |
3285 | VkImageBlit region; |
3286 | memset(s: ®ion, c: 0, n: sizeof(region)); |
3287 | |
3288 | region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3289 | region.srcSubresource.mipLevel = uint32_t(level) - 1; |
3290 | region.srcSubresource.baseArrayLayer = uint32_t(u.layer); |
3291 | region.srcSubresource.layerCount = 1; |
3292 | |
3293 | region.srcOffsets[1].x = qMax(a: 1, b: w); |
3294 | region.srcOffsets[1].y = qMax(a: 1, b: h); |
3295 | region.srcOffsets[1].z = 1; |
3296 | |
3297 | region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3298 | region.dstSubresource.mipLevel = uint32_t(level); |
3299 | region.dstSubresource.baseArrayLayer = uint32_t(u.layer); |
3300 | region.dstSubresource.layerCount = 1; |
3301 | |
3302 | region.dstOffsets[1].x = qMax(a: 1, b: w >> 1); |
3303 | region.dstOffsets[1].y = qMax(a: 1, b: h >> 1); |
3304 | region.dstOffsets[1].z = 1; |
3305 | |
3306 | QVkCommandBuffer::Command cmd; |
3307 | cmd.cmd = QVkCommandBuffer::Command::BlitImage; |
3308 | cmd.args.blitImage.src = utexD->image; |
3309 | cmd.args.blitImage.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
3310 | cmd.args.blitImage.dst = utexD->image; |
3311 | cmd.args.blitImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
3312 | cmd.args.blitImage.filter = VK_FILTER_LINEAR; |
3313 | cmd.args.blitImage.desc = region; |
3314 | cbD->commands.append(t: cmd); |
3315 | |
3316 | w >>= 1; |
3317 | h >>= 1; |
3318 | } |
3319 | |
3320 | if (utexD->mipLevelCount > 1) { |
3321 | subresourceBarrier(cbD, image: utexD->image, |
3322 | oldLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, newLayout: origLayout, |
3323 | srcAccess: VK_ACCESS_TRANSFER_READ_BIT, dstAccess: origAccess, |
3324 | srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: origStage, |
3325 | startLayer: u.layer, layerCount: 1, |
3326 | startLevel: 0, levelCount: int(utexD->mipLevelCount) - 1); |
3327 | subresourceBarrier(cbD, image: utexD->image, |
3328 | oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newLayout: origLayout, |
3329 | srcAccess: VK_ACCESS_TRANSFER_WRITE_BIT, dstAccess: origAccess, |
3330 | srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: origStage, |
3331 | startLayer: u.layer, layerCount: 1, |
3332 | startLevel: int(utexD->mipLevelCount) - 1, levelCount: 1); |
3333 | } |
3334 | |
3335 | utexD->lastActiveFrameSlot = currentFrameSlot; |
3336 | } |
3337 | } |
3338 | |
3339 | ud->free(); |
3340 | } |
3341 | |
3342 | void QRhiVulkan::executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot) |
3343 | { |
3344 | if (bufD->pendingDynamicUpdates[slot].isEmpty()) |
3345 | return; |
3346 | |
3347 | Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); |
3348 | void *p = nullptr; |
3349 | VmaAllocation a = toVmaAllocation(a: bufD->allocations[slot]); |
3350 | // The vmaMap/Unmap are basically a no-op when persistently mapped since it |
3351 | // refcounts; this is great because we don't need to care if the allocation |
3352 | // was created as persistently mapped or not. |
3353 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
3354 | if (err != VK_SUCCESS) { |
3355 | qWarning(msg: "Failed to map buffer: %d" , err); |
3356 | return; |
3357 | } |
3358 | int changeBegin = -1; |
3359 | int changeEnd = -1; |
3360 | for (const QRhiResourceUpdateBatchPrivate::BufferOp &u : qAsConst(t&: bufD->pendingDynamicUpdates[slot])) { |
3361 | Q_ASSERT(bufD == QRHI_RES(QVkBuffer, u.buf)); |
3362 | memcpy(dest: static_cast<char *>(p) + u.offset, src: u.data.constData(), n: size_t(u.data.size())); |
3363 | if (changeBegin == -1 || u.offset < changeBegin) |
3364 | changeBegin = u.offset; |
3365 | if (changeEnd == -1 || u.offset + u.data.size() > changeEnd) |
3366 | changeEnd = u.offset + u.data.size(); |
3367 | } |
3368 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3369 | if (changeBegin >= 0) |
3370 | vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: VkDeviceSize(changeBegin), size: VkDeviceSize(changeEnd - changeBegin)); |
3371 | |
3372 | bufD->pendingDynamicUpdates[slot].clear(); |
3373 | } |
3374 | |
3375 | static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator) |
3376 | { |
3377 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
3378 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.buffer.buffers[i], allocation: toVmaAllocation(a: e.buffer.allocations[i])); |
3379 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.buffer.stagingBuffers[i], allocation: toVmaAllocation(a: e.buffer.stagingAllocations[i])); |
3380 | } |
3381 | } |
3382 | |
3383 | static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df) |
3384 | { |
3385 | df->vkDestroyImageView(dev, e.renderBuffer.imageView, nullptr); |
3386 | df->vkDestroyImage(dev, e.renderBuffer.image, nullptr); |
3387 | df->vkFreeMemory(dev, e.renderBuffer.memory, nullptr); |
3388 | } |
3389 | |
3390 | static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator) |
3391 | { |
3392 | df->vkDestroyImageView(dev, e.texture.imageView, nullptr); |
3393 | vmaDestroyImage(allocator: toVmaAllocator(a: allocator), image: e.texture.image, allocation: toVmaAllocation(a: e.texture.allocation)); |
3394 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
3395 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.texture.stagingBuffers[i], allocation: toVmaAllocation(a: e.texture.stagingAllocations[i])); |
3396 | for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { |
3397 | if (e.texture.extraImageViews[i]) |
3398 | df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr); |
3399 | } |
3400 | } |
3401 | |
3402 | static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df) |
3403 | { |
3404 | df->vkDestroySampler(dev, e.sampler.sampler, nullptr); |
3405 | } |
3406 | |
3407 | void QRhiVulkan::executeDeferredReleases(bool forced) |
3408 | { |
3409 | for (int i = releaseQueue.count() - 1; i >= 0; --i) { |
3410 | const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]); |
3411 | if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) { |
3412 | switch (e.type) { |
3413 | case QRhiVulkan::DeferredReleaseEntry::Pipeline: |
3414 | df->vkDestroyPipeline(dev, e.pipelineState.pipeline, nullptr); |
3415 | df->vkDestroyPipelineLayout(dev, e.pipelineState.layout, nullptr); |
3416 | break; |
3417 | case QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings: |
3418 | df->vkDestroyDescriptorSetLayout(dev, e.shaderResourceBindings.layout, nullptr); |
3419 | if (e.shaderResourceBindings.poolIndex >= 0) { |
3420 | descriptorPools[e.shaderResourceBindings.poolIndex].refCount -= 1; |
3421 | Q_ASSERT(descriptorPools[e.shaderResourceBindings.poolIndex].refCount >= 0); |
3422 | } |
3423 | break; |
3424 | case QRhiVulkan::DeferredReleaseEntry::Buffer: |
3425 | qrhivk_releaseBuffer(e, allocator); |
3426 | break; |
3427 | case QRhiVulkan::DeferredReleaseEntry::RenderBuffer: |
3428 | qrhivk_releaseRenderBuffer(e, dev, df); |
3429 | break; |
3430 | case QRhiVulkan::DeferredReleaseEntry::Texture: |
3431 | qrhivk_releaseTexture(e, dev, df, allocator); |
3432 | break; |
3433 | case QRhiVulkan::DeferredReleaseEntry::Sampler: |
3434 | qrhivk_releaseSampler(e, dev, df); |
3435 | break; |
3436 | case QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget: |
3437 | df->vkDestroyFramebuffer(dev, e.textureRenderTarget.fb, nullptr); |
3438 | for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) { |
3439 | df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr); |
3440 | df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr); |
3441 | } |
3442 | break; |
3443 | case QRhiVulkan::DeferredReleaseEntry::RenderPass: |
3444 | df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr); |
3445 | break; |
3446 | case QRhiVulkan::DeferredReleaseEntry::StagingBuffer: |
3447 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.stagingBuffer.stagingBuffer, allocation: toVmaAllocation(a: e.stagingBuffer.stagingAllocation)); |
3448 | break; |
3449 | case QRhiVulkan::DeferredReleaseEntry::CommandBuffer: |
3450 | df->vkFreeCommandBuffers(dev, cmdPool, 1, &e.commandBuffer.cb); |
3451 | break; |
3452 | default: |
3453 | Q_UNREACHABLE(); |
3454 | break; |
3455 | } |
3456 | releaseQueue.removeAt(i); |
3457 | } |
3458 | } |
3459 | } |
3460 | |
3461 | void QRhiVulkan::finishActiveReadbacks(bool forced) |
3462 | { |
3463 | QVarLengthArray<std::function<void()>, 4> completedCallbacks; |
3464 | QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); |
3465 | |
3466 | for (int i = activeTextureReadbacks.count() - 1; i >= 0; --i) { |
3467 | const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]); |
3468 | if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { |
3469 | readback.result->format = readback.format; |
3470 | readback.result->pixelSize = readback.pixelSize; |
3471 | VmaAllocation a = toVmaAllocation(a: readback.stagingAlloc); |
3472 | void *p = nullptr; |
3473 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
3474 | if (err == VK_SUCCESS && p) { |
3475 | readback.result->data.resize(size: int(readback.byteSize)); |
3476 | memcpy(dest: readback.result->data.data(), src: p, n: readback.byteSize); |
3477 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3478 | } else { |
3479 | qWarning(msg: "Failed to map texture readback buffer of size %u: %d" , readback.byteSize, err); |
3480 | } |
3481 | |
3482 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: readback.stagingBuf, allocation: a); |
3483 | QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); |
3484 | |
3485 | if (readback.result->completed) |
3486 | completedCallbacks.append(t: readback.result->completed); |
3487 | |
3488 | activeTextureReadbacks.removeAt(i); |
3489 | } |
3490 | } |
3491 | |
3492 | for (int i = activeBufferReadbacks.count() - 1; i >= 0; --i) { |
3493 | const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]); |
3494 | if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { |
3495 | VmaAllocation a = toVmaAllocation(a: readback.stagingAlloc); |
3496 | void *p = nullptr; |
3497 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
3498 | if (err == VK_SUCCESS && p) { |
3499 | readback.result->data.resize(size: readback.byteSize); |
3500 | memcpy(dest: readback.result->data.data(), src: p, n: size_t(readback.byteSize)); |
3501 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3502 | } else { |
3503 | qWarning(msg: "Failed to map buffer readback buffer of size %d: %d" , readback.byteSize, err); |
3504 | } |
3505 | |
3506 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: readback.stagingBuf, allocation: a); |
3507 | QRHI_PROF_F(releaseReadbackBuffer(qint64(readback.stagingBuf))); |
3508 | |
3509 | if (readback.result->completed) |
3510 | completedCallbacks.append(t: readback.result->completed); |
3511 | |
3512 | activeBufferReadbacks.removeAt(i); |
3513 | } |
3514 | } |
3515 | |
3516 | for (auto f : completedCallbacks) |
3517 | f(); |
3518 | } |
3519 | |
3520 | static struct { |
3521 | VkSampleCountFlagBits mask; |
3522 | int count; |
3523 | } qvk_sampleCounts[] = { |
3524 | // keep this sorted by 'count' |
3525 | { .mask: VK_SAMPLE_COUNT_1_BIT, .count: 1 }, |
3526 | { .mask: VK_SAMPLE_COUNT_2_BIT, .count: 2 }, |
3527 | { .mask: VK_SAMPLE_COUNT_4_BIT, .count: 4 }, |
3528 | { .mask: VK_SAMPLE_COUNT_8_BIT, .count: 8 }, |
3529 | { .mask: VK_SAMPLE_COUNT_16_BIT, .count: 16 }, |
3530 | { .mask: VK_SAMPLE_COUNT_32_BIT, .count: 32 }, |
3531 | { .mask: VK_SAMPLE_COUNT_64_BIT, .count: 64 } |
3532 | }; |
3533 | |
3534 | QVector<int> QRhiVulkan::supportedSampleCounts() const |
3535 | { |
3536 | const VkPhysicalDeviceLimits *limits = &physDevProperties.limits; |
3537 | VkSampleCountFlags color = limits->framebufferColorSampleCounts; |
3538 | VkSampleCountFlags depth = limits->framebufferDepthSampleCounts; |
3539 | VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts; |
3540 | QVector<int> result; |
3541 | |
3542 | for (const auto &qvk_sampleCount : qvk_sampleCounts) { |
3543 | if ((color & qvk_sampleCount.mask) |
3544 | && (depth & qvk_sampleCount.mask) |
3545 | && (stencil & qvk_sampleCount.mask)) |
3546 | { |
3547 | result.append(t: qvk_sampleCount.count); |
3548 | } |
3549 | } |
3550 | |
3551 | return result; |
3552 | } |
3553 | |
3554 | VkSampleCountFlagBits QRhiVulkan::effectiveSampleCount(int sampleCount) |
3555 | { |
3556 | // Stay compatible with QSurfaceFormat and friends where samples == 0 means the same as 1. |
3557 | sampleCount = qBound(min: 1, val: sampleCount, max: 64); |
3558 | |
3559 | if (!supportedSampleCounts().contains(t: sampleCount)) { |
3560 | qWarning(msg: "Attempted to set unsupported sample count %d" , sampleCount); |
3561 | return VK_SAMPLE_COUNT_1_BIT; |
3562 | } |
3563 | |
3564 | for (const auto &qvk_sampleCount : qvk_sampleCounts) { |
3565 | if (qvk_sampleCount.count == sampleCount) |
3566 | return qvk_sampleCount.mask; |
3567 | } |
3568 | |
3569 | Q_UNREACHABLE(); |
3570 | return VK_SAMPLE_COUNT_1_BIT; |
3571 | } |
3572 | |
3573 | void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD) |
3574 | { |
3575 | cbD->passResTrackers.append(t: QRhiPassResourceTracker()); |
3576 | cbD->currentPassResTrackerIndex = cbD->passResTrackers.count() - 1; |
3577 | |
3578 | QVkCommandBuffer::Command cmd; |
3579 | cmd.cmd = QVkCommandBuffer::Command::TransitionPassResources; |
3580 | cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.count() - 1; |
3581 | cbD->commands.append(t: cmd); |
3582 | } |
3583 | |
3584 | void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD) |
3585 | { |
3586 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
3587 | |
3588 | for (QVkCommandBuffer::Command &cmd : cbD->commands) { |
3589 | switch (cmd.cmd) { |
3590 | case QVkCommandBuffer::Command::CopyBuffer: |
3591 | df->vkCmdCopyBuffer(cbD->cb, cmd.args.copyBuffer.src, cmd.args.copyBuffer.dst, |
3592 | 1, &cmd.args.copyBuffer.desc); |
3593 | break; |
3594 | case QVkCommandBuffer::Command::CopyBufferToImage: |
3595 | df->vkCmdCopyBufferToImage(cbD->cb, cmd.args.copyBufferToImage.src, cmd.args.copyBufferToImage.dst, |
3596 | cmd.args.copyBufferToImage.dstLayout, |
3597 | uint32_t(cmd.args.copyBufferToImage.count), |
3598 | cbD->pools.bufferImageCopy.constData() + cmd.args.copyBufferToImage.bufferImageCopyIndex); |
3599 | break; |
3600 | case QVkCommandBuffer::Command::CopyImage: |
3601 | df->vkCmdCopyImage(cbD->cb, cmd.args.copyImage.src, cmd.args.copyImage.srcLayout, |
3602 | cmd.args.copyImage.dst, cmd.args.copyImage.dstLayout, |
3603 | 1, &cmd.args.copyImage.desc); |
3604 | break; |
3605 | case QVkCommandBuffer::Command::CopyImageToBuffer: |
3606 | df->vkCmdCopyImageToBuffer(cbD->cb, cmd.args.copyImageToBuffer.src, cmd.args.copyImageToBuffer.srcLayout, |
3607 | cmd.args.copyImageToBuffer.dst, |
3608 | 1, &cmd.args.copyImageToBuffer.desc); |
3609 | break; |
3610 | case QVkCommandBuffer::Command::ImageBarrier: |
3611 | df->vkCmdPipelineBarrier(cbD->cb, cmd.args.imageBarrier.srcStageMask, cmd.args.imageBarrier.dstStageMask, |
3612 | 0, 0, nullptr, 0, nullptr, |
3613 | cmd.args.imageBarrier.count, cbD->pools.imageBarrier.constData() + cmd.args.imageBarrier.index); |
3614 | break; |
3615 | case QVkCommandBuffer::Command::BufferBarrier: |
3616 | df->vkCmdPipelineBarrier(cbD->cb, cmd.args.bufferBarrier.srcStageMask, cmd.args.bufferBarrier.dstStageMask, |
3617 | 0, 0, nullptr, |
3618 | cmd.args.bufferBarrier.count, cbD->pools.bufferBarrier.constData() + cmd.args.bufferBarrier.index, |
3619 | 0, nullptr); |
3620 | break; |
3621 | case QVkCommandBuffer::Command::BlitImage: |
3622 | df->vkCmdBlitImage(cbD->cb, cmd.args.blitImage.src, cmd.args.blitImage.srcLayout, |
3623 | cmd.args.blitImage.dst, cmd.args.blitImage.dstLayout, |
3624 | 1, &cmd.args.blitImage.desc, |
3625 | cmd.args.blitImage.filter); |
3626 | break; |
3627 | case QVkCommandBuffer::Command::BeginRenderPass: |
3628 | cmd.args.beginRenderPass.desc.pClearValues = cbD->pools.clearValue.constData() + cmd.args.beginRenderPass.clearValueIndex; |
3629 | df->vkCmdBeginRenderPass(cbD->cb, &cmd.args.beginRenderPass.desc, |
3630 | cbD->useSecondaryCb ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS : VK_SUBPASS_CONTENTS_INLINE); |
3631 | break; |
3632 | case QVkCommandBuffer::Command::EndRenderPass: |
3633 | df->vkCmdEndRenderPass(cbD->cb); |
3634 | break; |
3635 | case QVkCommandBuffer::Command::BindPipeline: |
3636 | df->vkCmdBindPipeline(cbD->cb, cmd.args.bindPipeline.bindPoint, cmd.args.bindPipeline.pipeline); |
3637 | break; |
3638 | case QVkCommandBuffer::Command::BindDescriptorSet: |
3639 | { |
3640 | const uint32_t *offsets = nullptr; |
3641 | if (cmd.args.bindDescriptorSet.dynamicOffsetCount > 0) |
3642 | offsets = cbD->pools.dynamicOffset.constData() + cmd.args.bindDescriptorSet.dynamicOffsetIndex; |
3643 | df->vkCmdBindDescriptorSets(cbD->cb, cmd.args.bindDescriptorSet.bindPoint, |
3644 | cmd.args.bindDescriptorSet.pipelineLayout, |
3645 | 0, 1, &cmd.args.bindDescriptorSet.descSet, |
3646 | uint32_t(cmd.args.bindDescriptorSet.dynamicOffsetCount), |
3647 | offsets); |
3648 | } |
3649 | break; |
3650 | case QVkCommandBuffer::Command::BindVertexBuffer: |
3651 | df->vkCmdBindVertexBuffers(cbD->cb, uint32_t(cmd.args.bindVertexBuffer.startBinding), |
3652 | uint32_t(cmd.args.bindVertexBuffer.count), |
3653 | cbD->pools.vertexBuffer.constData() + cmd.args.bindVertexBuffer.vertexBufferIndex, |
3654 | cbD->pools.vertexBufferOffset.constData() + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex); |
3655 | break; |
3656 | case QVkCommandBuffer::Command::BindIndexBuffer: |
3657 | df->vkCmdBindIndexBuffer(cbD->cb, cmd.args.bindIndexBuffer.buf, |
3658 | cmd.args.bindIndexBuffer.ofs, cmd.args.bindIndexBuffer.type); |
3659 | break; |
3660 | case QVkCommandBuffer::Command::SetViewport: |
3661 | df->vkCmdSetViewport(cbD->cb, 0, 1, &cmd.args.setViewport.viewport); |
3662 | break; |
3663 | case QVkCommandBuffer::Command::SetScissor: |
3664 | df->vkCmdSetScissor(cbD->cb, 0, 1, &cmd.args.setScissor.scissor); |
3665 | break; |
3666 | case QVkCommandBuffer::Command::SetBlendConstants: |
3667 | df->vkCmdSetBlendConstants(cbD->cb, cmd.args.setBlendConstants.c); |
3668 | break; |
3669 | case QVkCommandBuffer::Command::SetStencilRef: |
3670 | df->vkCmdSetStencilReference(cbD->cb, VK_STENCIL_FRONT_AND_BACK, cmd.args.setStencilRef.ref); |
3671 | break; |
3672 | case QVkCommandBuffer::Command::Draw: |
3673 | df->vkCmdDraw(cbD->cb, cmd.args.draw.vertexCount, cmd.args.draw.instanceCount, |
3674 | cmd.args.draw.firstVertex, cmd.args.draw.firstInstance); |
3675 | break; |
3676 | case QVkCommandBuffer::Command::DrawIndexed: |
3677 | df->vkCmdDrawIndexed(cbD->cb, cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount, |
3678 | cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset, |
3679 | cmd.args.drawIndexed.firstInstance); |
3680 | break; |
3681 | case QVkCommandBuffer::Command::DebugMarkerBegin: |
3682 | cmd.args.debugMarkerBegin.marker.pMarkerName = |
3683 | cbD->pools.debugMarkerData[cmd.args.debugMarkerBegin.markerNameIndex].constData(); |
3684 | vkCmdDebugMarkerBegin(cbD->cb, &cmd.args.debugMarkerBegin.marker); |
3685 | break; |
3686 | case QVkCommandBuffer::Command::DebugMarkerEnd: |
3687 | vkCmdDebugMarkerEnd(cbD->cb); |
3688 | break; |
3689 | case QVkCommandBuffer::Command::DebugMarkerInsert: |
3690 | cmd.args.debugMarkerInsert.marker.pMarkerName = |
3691 | cbD->pools.debugMarkerData[cmd.args.debugMarkerInsert.markerNameIndex].constData(); |
3692 | vkCmdDebugMarkerInsert(cbD->cb, &cmd.args.debugMarkerInsert.marker); |
3693 | break; |
3694 | case QVkCommandBuffer::Command::TransitionPassResources: |
3695 | recordTransitionPassResources(cbD, tracker: cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]); |
3696 | break; |
3697 | case QVkCommandBuffer::Command::Dispatch: |
3698 | df->vkCmdDispatch(cbD->cb, uint32_t(cmd.args.dispatch.x), uint32_t(cmd.args.dispatch.y), uint32_t(cmd.args.dispatch.z)); |
3699 | break; |
3700 | case QVkCommandBuffer::Command::ExecuteSecondary: |
3701 | df->vkCmdExecuteCommands(cbD->cb, 1, &cmd.args.executeSecondary.cb); |
3702 | break; |
3703 | default: |
3704 | break; |
3705 | } |
3706 | } |
3707 | } |
3708 | |
3709 | static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::BufferAccess access) |
3710 | { |
3711 | switch (access) { |
3712 | case QRhiPassResourceTracker::BufVertexInput: |
3713 | return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; |
3714 | case QRhiPassResourceTracker::BufIndexRead: |
3715 | return VK_ACCESS_INDEX_READ_BIT; |
3716 | case QRhiPassResourceTracker::BufUniformRead: |
3717 | return VK_ACCESS_UNIFORM_READ_BIT; |
3718 | case QRhiPassResourceTracker::BufStorageLoad: |
3719 | return VK_ACCESS_SHADER_READ_BIT; |
3720 | case QRhiPassResourceTracker::BufStorageStore: |
3721 | return VK_ACCESS_SHADER_WRITE_BIT; |
3722 | case QRhiPassResourceTracker::BufStorageLoadStore: |
3723 | return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; |
3724 | default: |
3725 | Q_UNREACHABLE(); |
3726 | break; |
3727 | } |
3728 | return 0; |
3729 | } |
3730 | |
3731 | static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::BufferStage stage) |
3732 | { |
3733 | switch (stage) { |
3734 | case QRhiPassResourceTracker::BufVertexInputStage: |
3735 | return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; |
3736 | case QRhiPassResourceTracker::BufVertexStage: |
3737 | return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; |
3738 | case QRhiPassResourceTracker::BufFragmentStage: |
3739 | return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; |
3740 | case QRhiPassResourceTracker::BufComputeStage: |
3741 | return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
3742 | default: |
3743 | Q_UNREACHABLE(); |
3744 | break; |
3745 | } |
3746 | return 0; |
3747 | } |
3748 | |
3749 | static inline QVkBuffer::UsageState toVkBufferUsageState(QRhiPassResourceTracker::UsageState usage) |
3750 | { |
3751 | QVkBuffer::UsageState u; |
3752 | u.access = VkAccessFlags(usage.access); |
3753 | u.stage = VkPipelineStageFlags(usage.stage); |
3754 | return u; |
3755 | } |
3756 | |
3757 | static inline VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess access) |
3758 | { |
3759 | switch (access) { |
3760 | case QRhiPassResourceTracker::TexSample: |
3761 | return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; |
3762 | case QRhiPassResourceTracker::TexColorOutput: |
3763 | return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
3764 | case QRhiPassResourceTracker::TexDepthOutput: |
3765 | return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
3766 | case QRhiPassResourceTracker::TexStorageLoad: |
3767 | case QRhiPassResourceTracker::TexStorageStore: |
3768 | case QRhiPassResourceTracker::TexStorageLoadStore: |
3769 | return VK_IMAGE_LAYOUT_GENERAL; |
3770 | default: |
3771 | Q_UNREACHABLE(); |
3772 | break; |
3773 | } |
3774 | return VK_IMAGE_LAYOUT_GENERAL; |
3775 | } |
3776 | |
3777 | static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess access) |
3778 | { |
3779 | switch (access) { |
3780 | case QRhiPassResourceTracker::TexSample: |
3781 | return VK_ACCESS_SHADER_READ_BIT; |
3782 | case QRhiPassResourceTracker::TexColorOutput: |
3783 | return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
3784 | case QRhiPassResourceTracker::TexDepthOutput: |
3785 | return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; |
3786 | case QRhiPassResourceTracker::TexStorageLoad: |
3787 | return VK_ACCESS_SHADER_READ_BIT; |
3788 | case QRhiPassResourceTracker::TexStorageStore: |
3789 | return VK_ACCESS_SHADER_WRITE_BIT; |
3790 | case QRhiPassResourceTracker::TexStorageLoadStore: |
3791 | return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; |
3792 | default: |
3793 | Q_UNREACHABLE(); |
3794 | break; |
3795 | } |
3796 | return 0; |
3797 | } |
3798 | |
3799 | static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::TextureStage stage) |
3800 | { |
3801 | switch (stage) { |
3802 | case QRhiPassResourceTracker::TexVertexStage: |
3803 | return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; |
3804 | case QRhiPassResourceTracker::TexFragmentStage: |
3805 | return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; |
3806 | case QRhiPassResourceTracker::TexColorOutputStage: |
3807 | return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
3808 | case QRhiPassResourceTracker::TexDepthOutputStage: |
3809 | return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; |
3810 | case QRhiPassResourceTracker::TexComputeStage: |
3811 | return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
3812 | default: |
3813 | Q_UNREACHABLE(); |
3814 | break; |
3815 | } |
3816 | return 0; |
3817 | } |
3818 | |
3819 | static inline QVkTexture::UsageState toVkTextureUsageState(QRhiPassResourceTracker::UsageState usage) |
3820 | { |
3821 | QVkTexture::UsageState u; |
3822 | u.layout = VkImageLayout(usage.layout); |
3823 | u.access = VkAccessFlags(usage.access); |
3824 | u.stage = VkPipelineStageFlags(usage.stage); |
3825 | return u; |
3826 | } |
3827 | |
3828 | void QRhiVulkan::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, |
3829 | QVkBuffer *bufD, |
3830 | int slot, |
3831 | QRhiPassResourceTracker::BufferAccess access, |
3832 | QRhiPassResourceTracker::BufferStage stage) |
3833 | { |
3834 | QVkBuffer::UsageState &u(bufD->usageState[slot]); |
3835 | passResTracker->registerBuffer(buf: bufD, slot, access: &access, stage: &stage, state: toPassTrackerUsageState(bufUsage: u)); |
3836 | u.access = toVkAccess(access); |
3837 | u.stage = toVkPipelineStage(stage); |
3838 | } |
3839 | |
3840 | void QRhiVulkan::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, |
3841 | QVkTexture *texD, |
3842 | QRhiPassResourceTracker::TextureAccess access, |
3843 | QRhiPassResourceTracker::TextureStage stage) |
3844 | { |
3845 | QVkTexture::UsageState &u(texD->usageState); |
3846 | passResTracker->registerTexture(tex: texD, access: &access, stage: &stage, state: toPassTrackerUsageState(texUsage: u)); |
3847 | u.layout = toVkLayout(access); |
3848 | u.access = toVkAccess(access); |
3849 | u.stage = toVkPipelineStage(stage); |
3850 | } |
3851 | |
3852 | void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker) |
3853 | { |
3854 | if (tracker.isEmpty()) |
3855 | return; |
3856 | |
3857 | for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) { |
3858 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key()); |
3859 | VkAccessFlags access = toVkAccess(access: it->access); |
3860 | VkPipelineStageFlags stage = toVkPipelineStage(stage: it->stage); |
3861 | QVkBuffer::UsageState s = toVkBufferUsageState(usage: it->stateAtPassBegin); |
3862 | if (!s.stage) |
3863 | continue; |
3864 | if (s.access == access && s.stage == stage) { |
3865 | if (!accessIsWrite(access)) |
3866 | continue; |
3867 | } |
3868 | VkBufferMemoryBarrier bufMemBarrier; |
3869 | memset(s: &bufMemBarrier, c: 0, n: sizeof(bufMemBarrier)); |
3870 | bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; |
3871 | bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
3872 | bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
3873 | bufMemBarrier.srcAccessMask = s.access; |
3874 | bufMemBarrier.dstAccessMask = access; |
3875 | bufMemBarrier.buffer = bufD->buffers[it->slot]; |
3876 | bufMemBarrier.size = VK_WHOLE_SIZE; |
3877 | df->vkCmdPipelineBarrier(cbD->cb, s.stage, stage, 0, |
3878 | 0, nullptr, |
3879 | 1, &bufMemBarrier, |
3880 | 0, nullptr); |
3881 | } |
3882 | |
3883 | for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) { |
3884 | QVkTexture *texD = QRHI_RES(QVkTexture, it.key()); |
3885 | VkImageLayout layout = toVkLayout(access: it->access); |
3886 | VkAccessFlags access = toVkAccess(access: it->access); |
3887 | VkPipelineStageFlags stage = toVkPipelineStage(stage: it->stage); |
3888 | QVkTexture::UsageState s = toVkTextureUsageState(usage: it->stateAtPassBegin); |
3889 | if (s.access == access && s.stage == stage && s.layout == layout) { |
3890 | if (!accessIsWrite(access)) |
3891 | continue; |
3892 | } |
3893 | VkImageMemoryBarrier barrier; |
3894 | memset(s: &barrier, c: 0, n: sizeof(barrier)); |
3895 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
3896 | barrier.subresourceRange.aspectMask = !isDepthTextureFormat(format: texD->m_format) |
3897 | ? VK_IMAGE_ASPECT_COLOR_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; |
3898 | barrier.subresourceRange.baseMipLevel = 0; |
3899 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
3900 | barrier.subresourceRange.baseArrayLayer = 0; |
3901 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
3902 | barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED |
3903 | barrier.newLayout = layout; |
3904 | barrier.srcAccessMask = s.access; // may be 0 but that's fine |
3905 | barrier.dstAccessMask = access; |
3906 | barrier.image = texD->image; |
3907 | VkPipelineStageFlags srcStage = s.stage; |
3908 | // stage mask cannot be 0 |
3909 | if (!srcStage) |
3910 | srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; |
3911 | df->vkCmdPipelineBarrier(cbD->cb, srcStage, stage, 0, |
3912 | 0, nullptr, |
3913 | 0, nullptr, |
3914 | 1, &barrier); |
3915 | } |
3916 | } |
3917 | |
3918 | QRhiSwapChain *QRhiVulkan::createSwapChain() |
3919 | { |
3920 | return new QVkSwapChain(this); |
3921 | } |
3922 | |
3923 | QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, int size) |
3924 | { |
3925 | return new QVkBuffer(this, type, usage, size); |
3926 | } |
3927 | |
3928 | int QRhiVulkan::ubufAlignment() const |
3929 | { |
3930 | return int(ubufAlign); // typically 256 (bytes) |
3931 | } |
3932 | |
3933 | bool QRhiVulkan::isYUpInFramebuffer() const |
3934 | { |
3935 | return false; |
3936 | } |
3937 | |
3938 | bool QRhiVulkan::isYUpInNDC() const |
3939 | { |
3940 | return false; |
3941 | } |
3942 | |
3943 | bool QRhiVulkan::isClipDepthZeroToOne() const |
3944 | { |
3945 | return true; |
3946 | } |
3947 | |
3948 | QMatrix4x4 QRhiVulkan::clipSpaceCorrMatrix() const |
3949 | { |
3950 | // See https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ |
3951 | |
3952 | static QMatrix4x4 m; |
3953 | if (m.isIdentity()) { |
3954 | // NB the ctor takes row-major |
3955 | m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f, |
3956 | 0.0f, -1.0f, 0.0f, 0.0f, |
3957 | 0.0f, 0.0f, 0.5f, 0.5f, |
3958 | 0.0f, 0.0f, 0.0f, 1.0f); |
3959 | } |
3960 | return m; |
3961 | } |
3962 | |
3963 | bool QRhiVulkan::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const |
3964 | { |
3965 | // Note that with some SDKs the validation layer gives an odd warning about |
3966 | // BC not being supported, even when our check here succeeds. Not much we |
3967 | // can do about that. |
3968 | if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7) { |
3969 | if (!physDevFeatures.textureCompressionBC) |
3970 | return false; |
3971 | } |
3972 | |
3973 | if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8) { |
3974 | if (!physDevFeatures.textureCompressionETC2) |
3975 | return false; |
3976 | } |
3977 | |
3978 | if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12) { |
3979 | if (!physDevFeatures.textureCompressionASTC_LDR) |
3980 | return false; |
3981 | } |
3982 | |
3983 | VkFormat vkformat = toVkTextureFormat(format, flags); |
3984 | VkFormatProperties props; |
3985 | f->vkGetPhysicalDeviceFormatProperties(physDev, vkformat, &props); |
3986 | return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0; |
3987 | } |
3988 | |
3989 | bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const |
3990 | { |
3991 | switch (feature) { |
3992 | case QRhi::MultisampleTexture: |
3993 | return true; |
3994 | case QRhi::MultisampleRenderBuffer: |
3995 | return true; |
3996 | case QRhi::DebugMarkers: |
3997 | return debugMarkersAvailable; |
3998 | case QRhi::Timestamps: |
3999 | return timestampValidBits != 0; |
4000 | case QRhi::Instancing: |
4001 | return true; |
4002 | case QRhi::CustomInstanceStepRate: |
4003 | return vertexAttribDivisorAvailable; |
4004 | case QRhi::PrimitiveRestart: |
4005 | return true; |
4006 | case QRhi::NonDynamicUniformBuffers: |
4007 | return true; |
4008 | case QRhi::NonFourAlignedEffectiveIndexBufferOffset: |
4009 | return true; |
4010 | case QRhi::NPOTTextureRepeat: |
4011 | return true; |
4012 | case QRhi::RedOrAlpha8IsRed: |
4013 | return true; |
4014 | case QRhi::ElementIndexUint: |
4015 | return true; |
4016 | case QRhi::Compute: |
4017 | return hasCompute; |
4018 | case QRhi::WideLines: |
4019 | return hasWideLines; |
4020 | case QRhi::VertexShaderPointSize: |
4021 | return true; |
4022 | case QRhi::BaseVertex: |
4023 | return true; |
4024 | case QRhi::BaseInstance: |
4025 | return true; |
4026 | case QRhi::TriangleFanTopology: |
4027 | return true; |
4028 | case QRhi::ReadBackNonUniformBuffer: |
4029 | return true; |
4030 | case QRhi::ReadBackNonBaseMipLevel: |
4031 | return true; |
4032 | case QRhi::TexelFetch: |
4033 | return true; |
4034 | default: |
4035 | Q_UNREACHABLE(); |
4036 | return false; |
4037 | } |
4038 | } |
4039 | |
4040 | int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const |
4041 | { |
4042 | switch (limit) { |
4043 | case QRhi::TextureSizeMin: |
4044 | return 1; |
4045 | case QRhi::TextureSizeMax: |
4046 | return int(physDevProperties.limits.maxImageDimension2D); |
4047 | case QRhi::MaxColorAttachments: |
4048 | return int(physDevProperties.limits.maxColorAttachments); |
4049 | case QRhi::FramesInFlight: |
4050 | return QVK_FRAMES_IN_FLIGHT; |
4051 | case QRhi::MaxAsyncReadbackFrames: |
4052 | return QVK_FRAMES_IN_FLIGHT; |
4053 | default: |
4054 | Q_UNREACHABLE(); |
4055 | return 0; |
4056 | } |
4057 | } |
4058 | |
4059 | const QRhiNativeHandles *QRhiVulkan::nativeHandles() |
4060 | { |
4061 | return &nativeHandlesStruct; |
4062 | } |
4063 | |
4064 | void QRhiVulkan::sendVMemStatsToProfiler() |
4065 | { |
4066 | QRhiProfilerPrivate *rhiP = profilerPrivateOrNull(); |
4067 | if (!rhiP) |
4068 | return; |
4069 | |
4070 | VmaStats stats; |
4071 | vmaCalculateStats(allocator: toVmaAllocator(a: allocator), pStats: &stats); |
4072 | QRHI_PROF_F(vmemStat(stats.total.blockCount, stats.total.allocationCount, |
4073 | quint32(stats.total.usedBytes), quint32(stats.total.unusedBytes))); |
4074 | } |
4075 | |
4076 | bool QRhiVulkan::makeThreadLocalNativeContextCurrent() |
4077 | { |
4078 | // not applicable |
4079 | return false; |
4080 | } |
4081 | |
4082 | void QRhiVulkan::releaseCachedResources() |
4083 | { |
4084 | // nothing to do here |
4085 | } |
4086 | |
4087 | bool QRhiVulkan::isDeviceLost() const |
4088 | { |
4089 | return deviceLost; |
4090 | } |
4091 | |
4092 | QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, |
4093 | int sampleCount, QRhiRenderBuffer::Flags flags) |
4094 | { |
4095 | return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags); |
4096 | } |
4097 | |
4098 | QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, const QSize &pixelSize, |
4099 | int sampleCount, QRhiTexture::Flags flags) |
4100 | { |
4101 | return new QVkTexture(this, format, pixelSize, sampleCount, flags); |
4102 | } |
4103 | |
4104 | QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, |
4105 | QRhiSampler::Filter mipmapMode, |
4106 | QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) |
4107 | { |
4108 | return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v, w); |
4109 | } |
4110 | |
4111 | QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, |
4112 | QRhiTextureRenderTarget::Flags flags) |
4113 | { |
4114 | return new QVkTextureRenderTarget(this, desc, flags); |
4115 | } |
4116 | |
4117 | QRhiGraphicsPipeline *QRhiVulkan::createGraphicsPipeline() |
4118 | { |
4119 | return new QVkGraphicsPipeline(this); |
4120 | } |
4121 | |
4122 | QRhiComputePipeline *QRhiVulkan::createComputePipeline() |
4123 | { |
4124 | return new QVkComputePipeline(this); |
4125 | } |
4126 | |
4127 | QRhiShaderResourceBindings *QRhiVulkan::createShaderResourceBindings() |
4128 | { |
4129 | return new QVkShaderResourceBindings(this); |
4130 | } |
4131 | |
4132 | void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) |
4133 | { |
4134 | QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, ps); |
4135 | Q_ASSERT(psD->pipeline); |
4136 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4137 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4138 | |
4139 | if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) { |
4140 | if (cbD->useSecondaryCb) { |
4141 | df->vkCmdBindPipeline(cbD->secondaryCbs.last(), VK_PIPELINE_BIND_POINT_GRAPHICS, psD->pipeline); |
4142 | } else { |
4143 | QVkCommandBuffer::Command cmd; |
4144 | cmd.cmd = QVkCommandBuffer::Command::BindPipeline; |
4145 | cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
4146 | cmd.args.bindPipeline.pipeline = psD->pipeline; |
4147 | cbD->commands.append(t: cmd); |
4148 | } |
4149 | |
4150 | cbD->currentGraphicsPipeline = ps; |
4151 | cbD->currentComputePipeline = nullptr; |
4152 | cbD->currentPipelineGeneration = psD->generation; |
4153 | } |
4154 | |
4155 | psD->lastActiveFrameSlot = currentFrameSlot; |
4156 | } |
4157 | |
4158 | void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, |
4159 | int dynamicOffsetCount, |
4160 | const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) |
4161 | { |
4162 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4163 | Q_ASSERT(cbD->recordingPass != QVkCommandBuffer::NoPass); |
4164 | QVkGraphicsPipeline *gfxPsD = QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline); |
4165 | QVkComputePipeline *compPsD = QRHI_RES(QVkComputePipeline, cbD->currentComputePipeline); |
4166 | |
4167 | if (!srb) { |
4168 | if (gfxPsD) |
4169 | srb = gfxPsD->m_shaderResourceBindings; |
4170 | else |
4171 | srb = compPsD->m_shaderResourceBindings; |
4172 | } |
4173 | |
4174 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb); |
4175 | bool hasSlottedResourceInSrb = false; |
4176 | bool hasDynamicOffsetInSrb = false; |
4177 | |
4178 | for (const QRhiShaderResourceBinding &binding : qAsConst(t&: srbD->sortedBindings)) { |
4179 | const QRhiShaderResourceBinding::Data *b = binding.data(); |
4180 | switch (b->type) { |
4181 | case QRhiShaderResourceBinding::UniformBuffer: |
4182 | if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->m_type == QRhiBuffer::Dynamic) |
4183 | hasSlottedResourceInSrb = true; |
4184 | if (b->u.ubuf.hasDynamicOffset) |
4185 | hasDynamicOffsetInSrb = true; |
4186 | break; |
4187 | default: |
4188 | break; |
4189 | } |
4190 | } |
4191 | |
4192 | const int descSetIdx = hasSlottedResourceInSrb ? currentFrameSlot : 0; |
4193 | bool rewriteDescSet = false; |
4194 | |
4195 | // Do host writes and mark referenced shader resources as in-use. |
4196 | // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects. |
4197 | for (int i = 0, ie = srbD->sortedBindings.count(); i != ie; ++i) { |
4198 | const QRhiShaderResourceBinding::Data *b = srbD->sortedBindings.at(idx: i).data(); |
4199 | QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[descSetIdx][i]); |
4200 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
4201 | switch (b->type) { |
4202 | case QRhiShaderResourceBinding::UniformBuffer: |
4203 | { |
4204 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.ubuf.buf); |
4205 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)); |
4206 | |
4207 | if (bufD->m_type == QRhiBuffer::Dynamic) |
4208 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
4209 | |
4210 | bufD->lastActiveFrameSlot = currentFrameSlot; |
4211 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot: bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0, |
4212 | access: QRhiPassResourceTracker::BufUniformRead, |
4213 | stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage)); |
4214 | |
4215 | // Check both the "local" id (the generation counter) and the |
4216 | // global id. The latter is relevant when a newly allocated |
4217 | // QRhiResource ends up with the same pointer as a previous one. |
4218 | // (and that previous one could have been in an srb...) |
4219 | if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) { |
4220 | rewriteDescSet = true; |
4221 | bd.ubuf.id = bufD->m_id; |
4222 | bd.ubuf.generation = bufD->generation; |
4223 | } |
4224 | } |
4225 | break; |
4226 | case QRhiShaderResourceBinding::SampledTexture: |
4227 | { |
4228 | const QRhiShaderResourceBinding::Data::SampledTextureData *data = &b->u.stex; |
4229 | if (bd.stex.count != data->count) { |
4230 | bd.stex.count = data->count; |
4231 | rewriteDescSet = true; |
4232 | } |
4233 | for (int elem = 0; elem < data->count; ++elem) { |
4234 | QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); |
4235 | QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler); |
4236 | texD->lastActiveFrameSlot = currentFrameSlot; |
4237 | samplerD->lastActiveFrameSlot = currentFrameSlot; |
4238 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
4239 | access: QRhiPassResourceTracker::TexSample, |
4240 | stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage)); |
4241 | if (texD->generation != bd.stex.d[elem].texGeneration |
4242 | || texD->m_id != bd.stex.d[elem].texId |
4243 | || samplerD->generation != bd.stex.d[elem].samplerGeneration |
4244 | || samplerD->m_id != bd.stex.d[elem].samplerId) |
4245 | { |
4246 | rewriteDescSet = true; |
4247 | bd.stex.d[elem].texId = texD->m_id; |
4248 | bd.stex.d[elem].texGeneration = texD->generation; |
4249 | bd.stex.d[elem].samplerId = samplerD->m_id; |
4250 | bd.stex.d[elem].samplerGeneration = samplerD->generation; |
4251 | } |
4252 | } |
4253 | } |
4254 | break; |
4255 | case QRhiShaderResourceBinding::ImageLoad: |
4256 | case QRhiShaderResourceBinding::ImageStore: |
4257 | case QRhiShaderResourceBinding::ImageLoadStore: |
4258 | { |
4259 | QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); |
4260 | Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore)); |
4261 | texD->lastActiveFrameSlot = currentFrameSlot; |
4262 | QRhiPassResourceTracker::TextureAccess access; |
4263 | if (b->type == QRhiShaderResourceBinding::ImageLoad) |
4264 | access = QRhiPassResourceTracker::TexStorageLoad; |
4265 | else if (b->type == QRhiShaderResourceBinding::ImageStore) |
4266 | access = QRhiPassResourceTracker::TexStorageStore; |
4267 | else |
4268 | access = QRhiPassResourceTracker::TexStorageLoadStore; |
4269 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
4270 | access, |
4271 | stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage)); |
4272 | |
4273 | if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) { |
4274 | rewriteDescSet = true; |
4275 | bd.simage.id = texD->m_id; |
4276 | bd.simage.generation = texD->generation; |
4277 | } |
4278 | } |
4279 | break; |
4280 | case QRhiShaderResourceBinding::BufferLoad: |
4281 | case QRhiShaderResourceBinding::BufferStore: |
4282 | case QRhiShaderResourceBinding::BufferLoadStore: |
4283 | { |
4284 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf); |
4285 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); |
4286 | |
4287 | if (bufD->m_type == QRhiBuffer::Dynamic) |
4288 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
4289 | |
4290 | bufD->lastActiveFrameSlot = currentFrameSlot; |
4291 | QRhiPassResourceTracker::BufferAccess access; |
4292 | if (b->type == QRhiShaderResourceBinding::BufferLoad) |
4293 | access = QRhiPassResourceTracker::BufStorageLoad; |
4294 | else if (b->type == QRhiShaderResourceBinding::BufferStore) |
4295 | access = QRhiPassResourceTracker::BufStorageStore; |
4296 | else |
4297 | access = QRhiPassResourceTracker::BufStorageLoadStore; |
4298 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot: bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0, |
4299 | access, |
4300 | stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage)); |
4301 | |
4302 | if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) { |
4303 | rewriteDescSet = true; |
4304 | bd.sbuf.id = bufD->m_id; |
4305 | bd.sbuf.generation = bufD->generation; |
4306 | } |
4307 | } |
4308 | break; |
4309 | default: |
4310 | Q_UNREACHABLE(); |
4311 | break; |
4312 | } |
4313 | } |
4314 | |
4315 | // write descriptor sets, if needed |
4316 | if (rewriteDescSet) |
4317 | updateShaderResourceBindings(srb, descSetIdx); |
4318 | |
4319 | // make sure the descriptors for the correct slot will get bound. |
4320 | // also, dynamic offsets always need a bind. |
4321 | const bool forceRebind = (hasSlottedResourceInSrb && cbD->currentDescSetSlot != descSetIdx) || hasDynamicOffsetInSrb; |
4322 | |
4323 | const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); |
4324 | |
4325 | if (forceRebind || rewriteDescSet || srbChanged || cbD->currentSrbGeneration != srbD->generation) { |
4326 | QVarLengthArray<uint32_t, 4> dynOfs; |
4327 | if (hasDynamicOffsetInSrb) { |
4328 | // Filling out dynOfs based on the sorted bindings is important |
4329 | // because dynOfs has to be ordered based on the binding numbers, |
4330 | // and neither srb nor dynamicOffsets has any such ordering |
4331 | // requirement. |
4332 | for (const QRhiShaderResourceBinding &binding : qAsConst(t&: srbD->sortedBindings)) { |
4333 | const QRhiShaderResourceBinding::Data *b = binding.data(); |
4334 | if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) { |
4335 | uint32_t offset = 0; |
4336 | for (int i = 0; i < dynamicOffsetCount; ++i) { |
4337 | const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]); |
4338 | if (dynOfs.first == b->binding) { |
4339 | offset = dynOfs.second; |
4340 | break; |
4341 | } |
4342 | } |
4343 | dynOfs.append(t: offset); // use 0 if dynamicOffsets did not contain this binding |
4344 | } |
4345 | } |
4346 | } |
4347 | |
4348 | if (cbD->useSecondaryCb) { |
4349 | df->vkCmdBindDescriptorSets(cbD->secondaryCbs.last(), |
4350 | gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE, |
4351 | gfxPsD ? gfxPsD->layout : compPsD->layout, |
4352 | 0, 1, &srbD->descSets[descSetIdx], |
4353 | uint32_t(dynOfs.count()), |
4354 | dynOfs.count() ? dynOfs.constData() : nullptr); |
4355 | } else { |
4356 | QVkCommandBuffer::Command cmd; |
4357 | cmd.cmd = QVkCommandBuffer::Command::BindDescriptorSet; |
4358 | cmd.args.bindDescriptorSet.bindPoint = gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS |
4359 | : VK_PIPELINE_BIND_POINT_COMPUTE; |
4360 | cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->layout; |
4361 | cmd.args.bindDescriptorSet.descSet = srbD->descSets[descSetIdx]; |
4362 | cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.count(); |
4363 | cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.count(); |
4364 | cbD->pools.dynamicOffset.append(abuf: dynOfs.constData(), increment: dynOfs.count()); |
4365 | cbD->commands.append(t: cmd); |
4366 | } |
4367 | |
4368 | if (gfxPsD) { |
4369 | cbD->currentGraphicsSrb = srb; |
4370 | cbD->currentComputeSrb = nullptr; |
4371 | } else { |
4372 | cbD->currentGraphicsSrb = nullptr; |
4373 | cbD->currentComputeSrb = srb; |
4374 | } |
4375 | cbD->currentSrbGeneration = srbD->generation; |
4376 | cbD->currentDescSetSlot = descSetIdx; |
4377 | } |
4378 | |
4379 | srbD->lastActiveFrameSlot = currentFrameSlot; |
4380 | } |
4381 | |
4382 | void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb, |
4383 | int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, |
4384 | QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) |
4385 | { |
4386 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4387 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4388 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
4389 | |
4390 | bool needsBindVBuf = false; |
4391 | for (int i = 0; i < bindingCount; ++i) { |
4392 | const int inputSlot = startBinding + i; |
4393 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first); |
4394 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer)); |
4395 | bufD->lastActiveFrameSlot = currentFrameSlot; |
4396 | if (bufD->m_type == QRhiBuffer::Dynamic) |
4397 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
4398 | |
4399 | const VkBuffer vkvertexbuf = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0]; |
4400 | if (cbD->currentVertexBuffers[inputSlot] != vkvertexbuf |
4401 | || cbD->currentVertexOffsets[inputSlot] != bindings[i].second) |
4402 | { |
4403 | needsBindVBuf = true; |
4404 | cbD->currentVertexBuffers[inputSlot] = vkvertexbuf; |
4405 | cbD->currentVertexOffsets[inputSlot] = bindings[i].second; |
4406 | } |
4407 | } |
4408 | |
4409 | if (needsBindVBuf) { |
4410 | QVarLengthArray<VkBuffer, 4> bufs; |
4411 | QVarLengthArray<VkDeviceSize, 4> ofs; |
4412 | for (int i = 0; i < bindingCount; ++i) { |
4413 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first); |
4414 | const int slot = bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0; |
4415 | bufs.append(t: bufD->buffers[slot]); |
4416 | ofs.append(t: bindings[i].second); |
4417 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot, |
4418 | access: QRhiPassResourceTracker::BufVertexInput, |
4419 | stage: QRhiPassResourceTracker::BufVertexInputStage); |
4420 | } |
4421 | |
4422 | if (cbD->useSecondaryCb) { |
4423 | df->vkCmdBindVertexBuffers(cbD->secondaryCbs.last(), uint32_t(startBinding), |
4424 | uint32_t(bufs.count()), bufs.constData(), ofs.constData()); |
4425 | } else { |
4426 | QVkCommandBuffer::Command cmd; |
4427 | cmd.cmd = QVkCommandBuffer::Command::BindVertexBuffer; |
4428 | cmd.args.bindVertexBuffer.startBinding = startBinding; |
4429 | cmd.args.bindVertexBuffer.count = bufs.count(); |
4430 | cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.count(); |
4431 | cbD->pools.vertexBuffer.append(abuf: bufs.constData(), increment: bufs.count()); |
4432 | cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.count(); |
4433 | cbD->pools.vertexBufferOffset.append(abuf: ofs.constData(), increment: ofs.count()); |
4434 | cbD->commands.append(t: cmd); |
4435 | } |
4436 | } |
4437 | |
4438 | if (indexBuf) { |
4439 | QVkBuffer *ibufD = QRHI_RES(QVkBuffer, indexBuf); |
4440 | Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer)); |
4441 | ibufD->lastActiveFrameSlot = currentFrameSlot; |
4442 | if (ibufD->m_type == QRhiBuffer::Dynamic) |
4443 | executeBufferHostWritesForSlot(bufD: ibufD, slot: currentFrameSlot); |
4444 | |
4445 | const int slot = ibufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0; |
4446 | const VkBuffer vkindexbuf = ibufD->buffers[slot]; |
4447 | const VkIndexType type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? VK_INDEX_TYPE_UINT16 |
4448 | : VK_INDEX_TYPE_UINT32; |
4449 | |
4450 | if (cbD->currentIndexBuffer != vkindexbuf |
4451 | || cbD->currentIndexOffset != indexOffset |
4452 | || cbD->currentIndexFormat != type) |
4453 | { |
4454 | cbD->currentIndexBuffer = vkindexbuf; |
4455 | cbD->currentIndexOffset = indexOffset; |
4456 | cbD->currentIndexFormat = type; |
4457 | |
4458 | if (cbD->useSecondaryCb) { |
4459 | df->vkCmdBindIndexBuffer(cbD->secondaryCbs.last(), vkindexbuf, indexOffset, type); |
4460 | } else { |
4461 | QVkCommandBuffer::Command cmd; |
4462 | cmd.cmd = QVkCommandBuffer::Command::BindIndexBuffer; |
4463 | cmd.args.bindIndexBuffer.buf = vkindexbuf; |
4464 | cmd.args.bindIndexBuffer.ofs = indexOffset; |
4465 | cmd.args.bindIndexBuffer.type = type; |
4466 | cbD->commands.append(t: cmd); |
4467 | } |
4468 | |
4469 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD: ibufD, slot, |
4470 | access: QRhiPassResourceTracker::BufIndexRead, |
4471 | stage: QRhiPassResourceTracker::BufVertexInputStage); |
4472 | } |
4473 | } |
4474 | } |
4475 | |
4476 | void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) |
4477 | { |
4478 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4479 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4480 | const QSize outputSize = cbD->currentTarget->pixelSize(); |
4481 | |
4482 | // x,y is top-left in VkViewport but bottom-left in QRhiViewport |
4483 | float x, y, w, h; |
4484 | if (!qrhi_toTopLeftRenderTargetRect(outputSize, r: viewport.viewport(), x: &x, y: &y, w: &w, h: &h)) |
4485 | return; |
4486 | |
4487 | QVkCommandBuffer::Command cmd; |
4488 | VkViewport *vp = &cmd.args.setViewport.viewport; |
4489 | vp->x = x; |
4490 | vp->y = y; |
4491 | vp->width = w; |
4492 | vp->height = h; |
4493 | vp->minDepth = viewport.minDepth(); |
4494 | vp->maxDepth = viewport.maxDepth(); |
4495 | |
4496 | if (cbD->useSecondaryCb) { |
4497 | df->vkCmdSetViewport(cbD->secondaryCbs.last(), 0, 1, vp); |
4498 | } else { |
4499 | cmd.cmd = QVkCommandBuffer::Command::SetViewport; |
4500 | cbD->commands.append(t: cmd); |
4501 | } |
4502 | |
4503 | if (!QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesScissor)) { |
4504 | VkRect2D *s = &cmd.args.setScissor.scissor; |
4505 | s->offset.x = int32_t(x); |
4506 | s->offset.y = int32_t(y); |
4507 | s->extent.width = uint32_t(w); |
4508 | s->extent.height = uint32_t(h); |
4509 | if (cbD->useSecondaryCb) { |
4510 | df->vkCmdSetScissor(cbD->secondaryCbs.last(), 0, 1, s); |
4511 | } else { |
4512 | cmd.cmd = QVkCommandBuffer::Command::SetScissor; |
4513 | cbD->commands.append(t: cmd); |
4514 | } |
4515 | } |
4516 | } |
4517 | |
4518 | void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) |
4519 | { |
4520 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4521 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4522 | Q_ASSERT(QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)); |
4523 | const QSize outputSize = cbD->currentTarget->pixelSize(); |
4524 | |
4525 | // x,y is top-left in VkRect2D but bottom-left in QRhiScissor |
4526 | int x, y, w, h; |
4527 | if (!qrhi_toTopLeftRenderTargetRect(outputSize, r: scissor.scissor(), x: &x, y: &y, w: &w, h: &h)) |
4528 | return; |
4529 | |
4530 | QVkCommandBuffer::Command cmd; |
4531 | VkRect2D *s = &cmd.args.setScissor.scissor; |
4532 | s->offset.x = x; |
4533 | s->offset.y = y; |
4534 | s->extent.width = uint32_t(w); |
4535 | s->extent.height = uint32_t(h); |
4536 | |
4537 | if (cbD->useSecondaryCb) { |
4538 | df->vkCmdSetScissor(cbD->secondaryCbs.last(), 0, 1, s); |
4539 | } else { |
4540 | cmd.cmd = QVkCommandBuffer::Command::SetScissor; |
4541 | cbD->commands.append(t: cmd); |
4542 | } |
4543 | } |
4544 | |
4545 | void QRhiVulkan::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) |
4546 | { |
4547 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4548 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4549 | |
4550 | if (cbD->useSecondaryCb) { |
4551 | float constants[] = { float(c.redF()), float(c.greenF()), float(c.blueF()), float(c.alphaF()) }; |
4552 | df->vkCmdSetBlendConstants(cbD->secondaryCbs.last(), constants); |
4553 | } else { |
4554 | QVkCommandBuffer::Command cmd; |
4555 | cmd.cmd = QVkCommandBuffer::Command::SetBlendConstants; |
4556 | cmd.args.setBlendConstants.c[0] = float(c.redF()); |
4557 | cmd.args.setBlendConstants.c[1] = float(c.greenF()); |
4558 | cmd.args.setBlendConstants.c[2] = float(c.blueF()); |
4559 | cmd.args.setBlendConstants.c[3] = float(c.alphaF()); |
4560 | cbD->commands.append(t: cmd); |
4561 | } |
4562 | } |
4563 | |
4564 | void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) |
4565 | { |
4566 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4567 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4568 | |
4569 | if (cbD->useSecondaryCb) { |
4570 | df->vkCmdSetStencilReference(cbD->secondaryCbs.last(), VK_STENCIL_FRONT_AND_BACK, refValue); |
4571 | } else { |
4572 | QVkCommandBuffer::Command cmd; |
4573 | cmd.cmd = QVkCommandBuffer::Command::SetStencilRef; |
4574 | cmd.args.setStencilRef.ref = refValue; |
4575 | cbD->commands.append(t: cmd); |
4576 | } |
4577 | } |
4578 | |
4579 | void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount, |
4580 | quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) |
4581 | { |
4582 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4583 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4584 | |
4585 | if (cbD->useSecondaryCb) { |
4586 | df->vkCmdDraw(cbD->secondaryCbs.last(), vertexCount, instanceCount, firstVertex, firstInstance); |
4587 | } else { |
4588 | QVkCommandBuffer::Command cmd; |
4589 | cmd.cmd = QVkCommandBuffer::Command::Draw; |
4590 | cmd.args.draw.vertexCount = vertexCount; |
4591 | cmd.args.draw.instanceCount = instanceCount; |
4592 | cmd.args.draw.firstVertex = firstVertex; |
4593 | cmd.args.draw.firstInstance = firstInstance; |
4594 | cbD->commands.append(t: cmd); |
4595 | } |
4596 | } |
4597 | |
4598 | void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, |
4599 | quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) |
4600 | { |
4601 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4602 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
4603 | |
4604 | if (cbD->useSecondaryCb) { |
4605 | df->vkCmdDrawIndexed(cbD->secondaryCbs.last(), indexCount, instanceCount, |
4606 | firstIndex, vertexOffset, firstInstance); |
4607 | } else { |
4608 | QVkCommandBuffer::Command cmd; |
4609 | cmd.cmd = QVkCommandBuffer::Command::DrawIndexed; |
4610 | cmd.args.drawIndexed.indexCount = indexCount; |
4611 | cmd.args.drawIndexed.instanceCount = instanceCount; |
4612 | cmd.args.drawIndexed.firstIndex = firstIndex; |
4613 | cmd.args.drawIndexed.vertexOffset = vertexOffset; |
4614 | cmd.args.drawIndexed.firstInstance = firstInstance; |
4615 | cbD->commands.append(t: cmd); |
4616 | } |
4617 | } |
4618 | |
4619 | void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) |
4620 | { |
4621 | if (!debugMarkers || !debugMarkersAvailable) |
4622 | return; |
4623 | |
4624 | VkDebugMarkerMarkerInfoEXT marker; |
4625 | memset(s: &marker, c: 0, n: sizeof(marker)); |
4626 | marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT; |
4627 | |
4628 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4629 | if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->useSecondaryCb) { |
4630 | marker.pMarkerName = name.constData(); |
4631 | vkCmdDebugMarkerBegin(cbD->secondaryCbs.last(), &marker); |
4632 | } else { |
4633 | QVkCommandBuffer::Command cmd; |
4634 | cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin; |
4635 | cmd.args.debugMarkerBegin.marker = marker; |
4636 | cmd.args.debugMarkerBegin.markerNameIndex = cbD->pools.debugMarkerData.count(); |
4637 | cbD->pools.debugMarkerData.append(t: name); |
4638 | cbD->commands.append(t: cmd); |
4639 | } |
4640 | } |
4641 | |
4642 | void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb) |
4643 | { |
4644 | if (!debugMarkers || !debugMarkersAvailable) |
4645 | return; |
4646 | |
4647 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4648 | if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->useSecondaryCb) { |
4649 | vkCmdDebugMarkerEnd(cbD->secondaryCbs.last()); |
4650 | } else { |
4651 | QVkCommandBuffer::Command cmd; |
4652 | cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd; |
4653 | cbD->commands.append(t: cmd); |
4654 | } |
4655 | } |
4656 | |
4657 | void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) |
4658 | { |
4659 | if (!debugMarkers || !debugMarkersAvailable) |
4660 | return; |
4661 | |
4662 | VkDebugMarkerMarkerInfoEXT marker; |
4663 | memset(s: &marker, c: 0, n: sizeof(marker)); |
4664 | marker.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT; |
4665 | |
4666 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4667 | if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->useSecondaryCb) { |
4668 | marker.pMarkerName = msg.constData(); |
4669 | vkCmdDebugMarkerInsert(cbD->secondaryCbs.last(), &marker); |
4670 | } else { |
4671 | QVkCommandBuffer::Command cmd; |
4672 | cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert; |
4673 | cmd.args.debugMarkerInsert.marker = marker; |
4674 | cmd.args.debugMarkerInsert.markerNameIndex = cbD->pools.debugMarkerData.count(); |
4675 | cbD->pools.debugMarkerData.append(t: msg); |
4676 | cbD->commands.append(t: cmd); |
4677 | } |
4678 | } |
4679 | |
4680 | const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb) |
4681 | { |
4682 | return QRHI_RES(QVkCommandBuffer, cb)->nativeHandles(); |
4683 | } |
4684 | |
4685 | static inline QVkRenderTargetData *maybeRenderTargetData(QVkCommandBuffer *cbD) |
4686 | { |
4687 | Q_ASSERT(cbD->currentTarget); |
4688 | QVkRenderTargetData *rtD = nullptr; |
4689 | if (cbD->recordingPass == QVkCommandBuffer::RenderPass) { |
4690 | switch (cbD->currentTarget->resourceType()) { |
4691 | case QRhiResource::RenderTarget: |
4692 | rtD = &QRHI_RES(QVkReferenceRenderTarget, cbD->currentTarget)->d; |
4693 | break; |
4694 | case QRhiResource::TextureRenderTarget: |
4695 | rtD = &QRHI_RES(QVkTextureRenderTarget, cbD->currentTarget)->d; |
4696 | break; |
4697 | default: |
4698 | Q_UNREACHABLE(); |
4699 | break; |
4700 | } |
4701 | } |
4702 | return rtD; |
4703 | } |
4704 | |
4705 | void QRhiVulkan::beginExternal(QRhiCommandBuffer *cb) |
4706 | { |
4707 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4708 | |
4709 | // When not in a pass, it is simple: record what we have (but do not |
4710 | // submit), the cb can then be used to record more external commands. |
4711 | if (cbD->recordingPass == QVkCommandBuffer::NoPass) { |
4712 | recordPrimaryCommandBuffer(cbD); |
4713 | cbD->resetCommands(); |
4714 | return; |
4715 | } |
4716 | |
4717 | // Otherwise, inside a pass, have a secondary command buffer (with |
4718 | // RENDER_PASS_CONTINUE). Using the main one is not acceptable since we |
4719 | // cannot just record at this stage, that would mess up the resource |
4720 | // tracking and commands like TransitionPassResources. |
4721 | |
4722 | if (cbD->inExternal) |
4723 | return; |
4724 | |
4725 | if (!cbD->useSecondaryCb) { |
4726 | qWarning(msg: "beginExternal() within a pass is only supported with secondary command buffers. " |
4727 | "This can be enabled by passing QRhi::ExternalContentsInPass to beginFrame()." ); |
4728 | return; |
4729 | } |
4730 | |
4731 | VkCommandBuffer secondaryCb = cbD->secondaryCbs.last(); |
4732 | cbD->secondaryCbs.removeLast(); |
4733 | endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD); |
4734 | |
4735 | VkCommandBuffer extCb = startSecondaryCommandBuffer(rtD: maybeRenderTargetData(cbD)); |
4736 | if (extCb) { |
4737 | cbD->secondaryCbs.append(t: extCb); |
4738 | cbD->inExternal = true; |
4739 | } |
4740 | } |
4741 | |
4742 | void QRhiVulkan::endExternal(QRhiCommandBuffer *cb) |
4743 | { |
4744 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
4745 | |
4746 | if (cbD->recordingPass == QVkCommandBuffer::NoPass) { |
4747 | Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1); |
4748 | } else if (cbD->inExternal) { |
4749 | VkCommandBuffer extCb = cbD->secondaryCbs.last(); |
4750 | cbD->secondaryCbs.removeLast(); |
4751 | endAndEnqueueSecondaryCommandBuffer(cb: extCb, cbD); |
4752 | cbD->secondaryCbs.append(t: startSecondaryCommandBuffer(rtD: maybeRenderTargetData(cbD))); |
4753 | } |
4754 | |
4755 | cbD->resetCachedState(); |
4756 | } |
4757 | |
4758 | void QRhiVulkan::setObjectName(uint64_t object, VkDebugReportObjectTypeEXT type, const QByteArray &name, int slot) |
4759 | { |
4760 | if (!debugMarkers || !debugMarkersAvailable || name.isEmpty()) |
4761 | return; |
4762 | |
4763 | VkDebugMarkerObjectNameInfoEXT nameInfo; |
4764 | memset(s: &nameInfo, c: 0, n: sizeof(nameInfo)); |
4765 | nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT; |
4766 | nameInfo.objectType = type; |
4767 | nameInfo.object = object; |
4768 | QByteArray decoratedName = name; |
4769 | if (slot >= 0) { |
4770 | decoratedName += '/'; |
4771 | decoratedName += QByteArray::number(slot); |
4772 | } |
4773 | nameInfo.pObjectName = decoratedName.constData(); |
4774 | vkDebugMarkerSetObjectName(dev, &nameInfo); |
4775 | } |
4776 | |
4777 | static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage) |
4778 | { |
4779 | int u = 0; |
4780 | if (usage.testFlag(flag: QRhiBuffer::VertexBuffer)) |
4781 | u |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; |
4782 | if (usage.testFlag(flag: QRhiBuffer::IndexBuffer)) |
4783 | u |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; |
4784 | if (usage.testFlag(flag: QRhiBuffer::UniformBuffer)) |
4785 | u |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; |
4786 | if (usage.testFlag(flag: QRhiBuffer::StorageBuffer)) |
4787 | u |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; |
4788 | return VkBufferUsageFlagBits(u); |
4789 | } |
4790 | |
4791 | static inline VkFilter toVkFilter(QRhiSampler::Filter f) |
4792 | { |
4793 | switch (f) { |
4794 | case QRhiSampler::Nearest: |
4795 | return VK_FILTER_NEAREST; |
4796 | case QRhiSampler::Linear: |
4797 | return VK_FILTER_LINEAR; |
4798 | default: |
4799 | Q_UNREACHABLE(); |
4800 | return VK_FILTER_NEAREST; |
4801 | } |
4802 | } |
4803 | |
4804 | static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f) |
4805 | { |
4806 | switch (f) { |
4807 | case QRhiSampler::None: |
4808 | return VK_SAMPLER_MIPMAP_MODE_NEAREST; |
4809 | case QRhiSampler::Nearest: |
4810 | return VK_SAMPLER_MIPMAP_MODE_NEAREST; |
4811 | case QRhiSampler::Linear: |
4812 | return VK_SAMPLER_MIPMAP_MODE_LINEAR; |
4813 | default: |
4814 | Q_UNREACHABLE(); |
4815 | return VK_SAMPLER_MIPMAP_MODE_NEAREST; |
4816 | } |
4817 | } |
4818 | |
4819 | static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m) |
4820 | { |
4821 | switch (m) { |
4822 | case QRhiSampler::Repeat: |
4823 | return VK_SAMPLER_ADDRESS_MODE_REPEAT; |
4824 | case QRhiSampler::ClampToEdge: |
4825 | return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; |
4826 | case QRhiSampler::Mirror: |
4827 | return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; |
4828 | default: |
4829 | Q_UNREACHABLE(); |
4830 | return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; |
4831 | } |
4832 | } |
4833 | |
4834 | static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type) |
4835 | { |
4836 | switch (type) { |
4837 | case QRhiShaderStage::Vertex: |
4838 | return VK_SHADER_STAGE_VERTEX_BIT; |
4839 | case QRhiShaderStage::Fragment: |
4840 | return VK_SHADER_STAGE_FRAGMENT_BIT; |
4841 | case QRhiShaderStage::Compute: |
4842 | return VK_SHADER_STAGE_COMPUTE_BIT; |
4843 | default: |
4844 | Q_UNREACHABLE(); |
4845 | return VK_SHADER_STAGE_VERTEX_BIT; |
4846 | } |
4847 | } |
4848 | |
4849 | static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format) |
4850 | { |
4851 | switch (format) { |
4852 | case QRhiVertexInputAttribute::Float4: |
4853 | return VK_FORMAT_R32G32B32A32_SFLOAT; |
4854 | case QRhiVertexInputAttribute::Float3: |
4855 | return VK_FORMAT_R32G32B32_SFLOAT; |
4856 | case QRhiVertexInputAttribute::Float2: |
4857 | return VK_FORMAT_R32G32_SFLOAT; |
4858 | case QRhiVertexInputAttribute::Float: |
4859 | return VK_FORMAT_R32_SFLOAT; |
4860 | case QRhiVertexInputAttribute::UNormByte4: |
4861 | return VK_FORMAT_R8G8B8A8_UNORM; |
4862 | case QRhiVertexInputAttribute::UNormByte2: |
4863 | return VK_FORMAT_R8G8_UNORM; |
4864 | case QRhiVertexInputAttribute::UNormByte: |
4865 | return VK_FORMAT_R8_UNORM; |
4866 | default: |
4867 | Q_UNREACHABLE(); |
4868 | return VK_FORMAT_R32G32B32A32_SFLOAT; |
4869 | } |
4870 | } |
4871 | |
4872 | static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t) |
4873 | { |
4874 | switch (t) { |
4875 | case QRhiGraphicsPipeline::Triangles: |
4876 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; |
4877 | case QRhiGraphicsPipeline::TriangleStrip: |
4878 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; |
4879 | case QRhiGraphicsPipeline::TriangleFan: |
4880 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; |
4881 | case QRhiGraphicsPipeline::Lines: |
4882 | return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; |
4883 | case QRhiGraphicsPipeline::LineStrip: |
4884 | return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; |
4885 | case QRhiGraphicsPipeline::Points: |
4886 | return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; |
4887 | default: |
4888 | Q_UNREACHABLE(); |
4889 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; |
4890 | } |
4891 | } |
4892 | |
4893 | static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c) |
4894 | { |
4895 | switch (c) { |
4896 | case QRhiGraphicsPipeline::None: |
4897 | return VK_CULL_MODE_NONE; |
4898 | case QRhiGraphicsPipeline::Front: |
4899 | return VK_CULL_MODE_FRONT_BIT; |
4900 | case QRhiGraphicsPipeline::Back: |
4901 | return VK_CULL_MODE_BACK_BIT; |
4902 | default: |
4903 | Q_UNREACHABLE(); |
4904 | return VK_CULL_MODE_NONE; |
4905 | } |
4906 | } |
4907 | |
4908 | static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f) |
4909 | { |
4910 | switch (f) { |
4911 | case QRhiGraphicsPipeline::CCW: |
4912 | return VK_FRONT_FACE_COUNTER_CLOCKWISE; |
4913 | case QRhiGraphicsPipeline::CW: |
4914 | return VK_FRONT_FACE_CLOCKWISE; |
4915 | default: |
4916 | Q_UNREACHABLE(); |
4917 | return VK_FRONT_FACE_COUNTER_CLOCKWISE; |
4918 | } |
4919 | } |
4920 | |
4921 | static inline VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c) |
4922 | { |
4923 | int f = 0; |
4924 | if (c.testFlag(flag: QRhiGraphicsPipeline::R)) |
4925 | f |= VK_COLOR_COMPONENT_R_BIT; |
4926 | if (c.testFlag(flag: QRhiGraphicsPipeline::G)) |
4927 | f |= VK_COLOR_COMPONENT_G_BIT; |
4928 | if (c.testFlag(flag: QRhiGraphicsPipeline::B)) |
4929 | f |= VK_COLOR_COMPONENT_B_BIT; |
4930 | if (c.testFlag(flag: QRhiGraphicsPipeline::A)) |
4931 | f |= VK_COLOR_COMPONENT_A_BIT; |
4932 | return VkColorComponentFlags(f); |
4933 | } |
4934 | |
4935 | static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f) |
4936 | { |
4937 | switch (f) { |
4938 | case QRhiGraphicsPipeline::Zero: |
4939 | return VK_BLEND_FACTOR_ZERO; |
4940 | case QRhiGraphicsPipeline::One: |
4941 | return VK_BLEND_FACTOR_ONE; |
4942 | case QRhiGraphicsPipeline::SrcColor: |
4943 | return VK_BLEND_FACTOR_SRC_COLOR; |
4944 | case QRhiGraphicsPipeline::OneMinusSrcColor: |
4945 | return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; |
4946 | case QRhiGraphicsPipeline::DstColor: |
4947 | return VK_BLEND_FACTOR_DST_COLOR; |
4948 | case QRhiGraphicsPipeline::OneMinusDstColor: |
4949 | return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; |
4950 | case QRhiGraphicsPipeline::SrcAlpha: |
4951 | return VK_BLEND_FACTOR_SRC_ALPHA; |
4952 | case QRhiGraphicsPipeline::OneMinusSrcAlpha: |
4953 | return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; |
4954 | case QRhiGraphicsPipeline::DstAlpha: |
4955 | return VK_BLEND_FACTOR_DST_ALPHA; |
4956 | case QRhiGraphicsPipeline::OneMinusDstAlpha: |
4957 | return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; |
4958 | case QRhiGraphicsPipeline::ConstantColor: |
4959 | return VK_BLEND_FACTOR_CONSTANT_COLOR; |
4960 | case QRhiGraphicsPipeline::OneMinusConstantColor: |
4961 | return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; |
4962 | case QRhiGraphicsPipeline::ConstantAlpha: |
4963 | return VK_BLEND_FACTOR_CONSTANT_ALPHA; |
4964 | case QRhiGraphicsPipeline::OneMinusConstantAlpha: |
4965 | return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; |
4966 | case QRhiGraphicsPipeline::SrcAlphaSaturate: |
4967 | return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; |
4968 | case QRhiGraphicsPipeline::Src1Color: |
4969 | return VK_BLEND_FACTOR_SRC1_COLOR; |
4970 | case QRhiGraphicsPipeline::OneMinusSrc1Color: |
4971 | return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; |
4972 | case QRhiGraphicsPipeline::Src1Alpha: |
4973 | return VK_BLEND_FACTOR_SRC1_ALPHA; |
4974 | case QRhiGraphicsPipeline::OneMinusSrc1Alpha: |
4975 | return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; |
4976 | default: |
4977 | Q_UNREACHABLE(); |
4978 | return VK_BLEND_FACTOR_ZERO; |
4979 | } |
4980 | } |
4981 | |
4982 | static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op) |
4983 | { |
4984 | switch (op) { |
4985 | case QRhiGraphicsPipeline::Add: |
4986 | return VK_BLEND_OP_ADD; |
4987 | case QRhiGraphicsPipeline::Subtract: |
4988 | return VK_BLEND_OP_SUBTRACT; |
4989 | case QRhiGraphicsPipeline::ReverseSubtract: |
4990 | return VK_BLEND_OP_REVERSE_SUBTRACT; |
4991 | case QRhiGraphicsPipeline::Min: |
4992 | return VK_BLEND_OP_MIN; |
4993 | case QRhiGraphicsPipeline::Max: |
4994 | return VK_BLEND_OP_MAX; |
4995 | default: |
4996 | Q_UNREACHABLE(); |
4997 | return VK_BLEND_OP_ADD; |
4998 | } |
4999 | } |
5000 | |
5001 | static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op) |
5002 | { |
5003 | switch (op) { |
5004 | case QRhiGraphicsPipeline::Never: |
5005 | return VK_COMPARE_OP_NEVER; |
5006 | case QRhiGraphicsPipeline::Less: |
5007 | return VK_COMPARE_OP_LESS; |
5008 | case QRhiGraphicsPipeline::Equal: |
5009 | return VK_COMPARE_OP_EQUAL; |
5010 | case QRhiGraphicsPipeline::LessOrEqual: |
5011 | return VK_COMPARE_OP_LESS_OR_EQUAL; |
5012 | case QRhiGraphicsPipeline::Greater: |
5013 | return VK_COMPARE_OP_GREATER; |
5014 | case QRhiGraphicsPipeline::NotEqual: |
5015 | return VK_COMPARE_OP_NOT_EQUAL; |
5016 | case QRhiGraphicsPipeline::GreaterOrEqual: |
5017 | return VK_COMPARE_OP_GREATER_OR_EQUAL; |
5018 | case QRhiGraphicsPipeline::Always: |
5019 | return VK_COMPARE_OP_ALWAYS; |
5020 | default: |
5021 | Q_UNREACHABLE(); |
5022 | return VK_COMPARE_OP_ALWAYS; |
5023 | } |
5024 | } |
5025 | |
5026 | static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op) |
5027 | { |
5028 | switch (op) { |
5029 | case QRhiGraphicsPipeline::StencilZero: |
5030 | return VK_STENCIL_OP_ZERO; |
5031 | case QRhiGraphicsPipeline::Keep: |
5032 | return VK_STENCIL_OP_KEEP; |
5033 | case QRhiGraphicsPipeline::Replace: |
5034 | return VK_STENCIL_OP_REPLACE; |
5035 | case QRhiGraphicsPipeline::IncrementAndClamp: |
5036 | return VK_STENCIL_OP_INCREMENT_AND_CLAMP; |
5037 | case QRhiGraphicsPipeline::DecrementAndClamp: |
5038 | return VK_STENCIL_OP_DECREMENT_AND_CLAMP; |
5039 | case QRhiGraphicsPipeline::Invert: |
5040 | return VK_STENCIL_OP_INVERT; |
5041 | case QRhiGraphicsPipeline::IncrementAndWrap: |
5042 | return VK_STENCIL_OP_INCREMENT_AND_WRAP; |
5043 | case QRhiGraphicsPipeline::DecrementAndWrap: |
5044 | return VK_STENCIL_OP_DECREMENT_AND_WRAP; |
5045 | default: |
5046 | Q_UNREACHABLE(); |
5047 | return VK_STENCIL_OP_KEEP; |
5048 | } |
5049 | } |
5050 | |
5051 | static inline void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src) |
5052 | { |
5053 | dst->failOp = toVkStencilOp(op: src.failOp); |
5054 | dst->passOp = toVkStencilOp(op: src.passOp); |
5055 | dst->depthFailOp = toVkStencilOp(op: src.depthFailOp); |
5056 | dst->compareOp = toVkCompareOp(op: src.compareOp); |
5057 | } |
5058 | |
5059 | static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBinding::Data *b) |
5060 | { |
5061 | switch (b->type) { |
5062 | case QRhiShaderResourceBinding::UniformBuffer: |
5063 | return b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC |
5064 | : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
5065 | |
5066 | case QRhiShaderResourceBinding::SampledTexture: |
5067 | return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; |
5068 | |
5069 | case QRhiShaderResourceBinding::ImageLoad: |
5070 | case QRhiShaderResourceBinding::ImageStore: |
5071 | case QRhiShaderResourceBinding::ImageLoadStore: |
5072 | return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; |
5073 | |
5074 | case QRhiShaderResourceBinding::BufferLoad: |
5075 | case QRhiShaderResourceBinding::BufferStore: |
5076 | case QRhiShaderResourceBinding::BufferLoadStore: |
5077 | return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
5078 | |
5079 | default: |
5080 | Q_UNREACHABLE(); |
5081 | return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
5082 | } |
5083 | } |
5084 | |
5085 | static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage) |
5086 | { |
5087 | int s = 0; |
5088 | if (stage.testFlag(flag: QRhiShaderResourceBinding::VertexStage)) |
5089 | s |= VK_SHADER_STAGE_VERTEX_BIT; |
5090 | if (stage.testFlag(flag: QRhiShaderResourceBinding::FragmentStage)) |
5091 | s |= VK_SHADER_STAGE_FRAGMENT_BIT; |
5092 | if (stage.testFlag(flag: QRhiShaderResourceBinding::ComputeStage)) |
5093 | s |= VK_SHADER_STAGE_COMPUTE_BIT; |
5094 | return VkShaderStageFlags(s); |
5095 | } |
5096 | |
5097 | static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op) |
5098 | { |
5099 | switch (op) { |
5100 | case QRhiSampler::Never: |
5101 | return VK_COMPARE_OP_NEVER; |
5102 | case QRhiSampler::Less: |
5103 | return VK_COMPARE_OP_LESS; |
5104 | case QRhiSampler::Equal: |
5105 | return VK_COMPARE_OP_EQUAL; |
5106 | case QRhiSampler::LessOrEqual: |
5107 | return VK_COMPARE_OP_LESS_OR_EQUAL; |
5108 | case QRhiSampler::Greater: |
5109 | return VK_COMPARE_OP_GREATER; |
5110 | case QRhiSampler::NotEqual: |
5111 | return VK_COMPARE_OP_NOT_EQUAL; |
5112 | case QRhiSampler::GreaterOrEqual: |
5113 | return VK_COMPARE_OP_GREATER_OR_EQUAL; |
5114 | case QRhiSampler::Always: |
5115 | return VK_COMPARE_OP_ALWAYS; |
5116 | default: |
5117 | Q_UNREACHABLE(); |
5118 | return VK_COMPARE_OP_NEVER; |
5119 | } |
5120 | } |
5121 | |
5122 | QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, int size) |
5123 | : QRhiBuffer(rhi, type, usage, size) |
5124 | { |
5125 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
5126 | buffers[i] = stagingBuffers[i] = VK_NULL_HANDLE; |
5127 | allocations[i] = stagingAllocations[i] = nullptr; |
5128 | } |
5129 | } |
5130 | |
5131 | QVkBuffer::~QVkBuffer() |
5132 | { |
5133 | release(); |
5134 | } |
5135 | |
5136 | void QVkBuffer::release() |
5137 | { |
5138 | if (!buffers[0]) |
5139 | return; |
5140 | |
5141 | QRhiVulkan::DeferredReleaseEntry e; |
5142 | e.type = QRhiVulkan::DeferredReleaseEntry::Buffer; |
5143 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
5144 | |
5145 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
5146 | e.buffer.buffers[i] = buffers[i]; |
5147 | e.buffer.allocations[i] = allocations[i]; |
5148 | e.buffer.stagingBuffers[i] = stagingBuffers[i]; |
5149 | e.buffer.stagingAllocations[i] = stagingAllocations[i]; |
5150 | |
5151 | buffers[i] = VK_NULL_HANDLE; |
5152 | allocations[i] = nullptr; |
5153 | stagingBuffers[i] = VK_NULL_HANDLE; |
5154 | stagingAllocations[i] = nullptr; |
5155 | pendingDynamicUpdates[i].clear(); |
5156 | } |
5157 | |
5158 | QRHI_RES_RHI(QRhiVulkan); |
5159 | rhiD->releaseQueue.append(t: e); |
5160 | |
5161 | QRHI_PROF; |
5162 | QRHI_PROF_F(releaseBuffer(this)); |
5163 | |
5164 | rhiD->unregisterResource(res: this); |
5165 | } |
5166 | |
5167 | bool QVkBuffer::build() |
5168 | { |
5169 | if (buffers[0]) |
5170 | release(); |
5171 | |
5172 | if (m_usage.testFlag(flag: QRhiBuffer::StorageBuffer) && m_type == Dynamic) { |
5173 | qWarning(msg: "StorageBuffer cannot be combined with Dynamic" ); |
5174 | return false; |
5175 | } |
5176 | |
5177 | const int nonZeroSize = m_size <= 0 ? 256 : m_size; |
5178 | |
5179 | VkBufferCreateInfo bufferInfo; |
5180 | memset(s: &bufferInfo, c: 0, n: sizeof(bufferInfo)); |
5181 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
5182 | bufferInfo.size = uint32_t(nonZeroSize); |
5183 | bufferInfo.usage = toVkBufferUsage(usage: m_usage); |
5184 | |
5185 | VmaAllocationCreateInfo allocInfo; |
5186 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
5187 | |
5188 | if (m_type == Dynamic) { |
5189 | #ifndef Q_OS_DARWIN // not for MoltenVK |
5190 | // Keep mapped all the time. Essential f.ex. with some mobile GPUs, |
5191 | // where mapping and unmapping an entire allocation every time updating |
5192 | // a suballocated buffer presents a significant perf. hit. |
5193 | allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; |
5194 | #endif |
5195 | // host visible, frequent changes |
5196 | allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; |
5197 | } else { |
5198 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; |
5199 | bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
5200 | } |
5201 | |
5202 | QRHI_RES_RHI(QRhiVulkan); |
5203 | VkResult err = VK_SUCCESS; |
5204 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
5205 | buffers[i] = VK_NULL_HANDLE; |
5206 | allocations[i] = nullptr; |
5207 | usageState[i].access = usageState[i].stage = 0; |
5208 | if (i == 0 || m_type == Dynamic) { |
5209 | VmaAllocation allocation; |
5210 | err = vmaCreateBuffer(allocator: toVmaAllocator(a: rhiD->allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &buffers[i], pAllocation: &allocation, pAllocationInfo: nullptr); |
5211 | if (err != VK_SUCCESS) |
5212 | break; |
5213 | allocations[i] = allocation; |
5214 | rhiD->setObjectName(object: uint64_t(buffers[i]), type: VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, name: m_objectName, |
5215 | slot: m_type == Dynamic ? i : -1); |
5216 | } |
5217 | } |
5218 | |
5219 | if (err != VK_SUCCESS) { |
5220 | qWarning(msg: "Failed to create buffer: %d" , err); |
5221 | return false; |
5222 | } |
5223 | |
5224 | QRHI_PROF; |
5225 | QRHI_PROF_F(newBuffer(this, uint(nonZeroSize), m_type != Dynamic ? 1 : QVK_FRAMES_IN_FLIGHT, 0)); |
5226 | |
5227 | lastActiveFrameSlot = -1; |
5228 | generation += 1; |
5229 | rhiD->registerResource(res: this); |
5230 | return true; |
5231 | } |
5232 | |
5233 | QRhiBuffer::NativeBuffer QVkBuffer::nativeBuffer() |
5234 | { |
5235 | if (m_type == Dynamic) { |
5236 | QRHI_RES_RHI(QRhiVulkan); |
5237 | NativeBuffer b; |
5238 | Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QVK_FRAMES_IN_FLIGHT)); |
5239 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
5240 | rhiD->executeBufferHostWritesForSlot(bufD: this, slot: i); |
5241 | b.objects[i] = &buffers[i]; |
5242 | } |
5243 | b.slotCount = QVK_FRAMES_IN_FLIGHT; |
5244 | return b; |
5245 | } |
5246 | return { .objects: { &buffers[0] }, .slotCount: 1 }; |
5247 | } |
5248 | |
5249 | QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, |
5250 | int sampleCount, Flags flags) |
5251 | : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags) |
5252 | { |
5253 | } |
5254 | |
5255 | QVkRenderBuffer::~QVkRenderBuffer() |
5256 | { |
5257 | release(); |
5258 | delete backingTexture; |
5259 | } |
5260 | |
5261 | void QVkRenderBuffer::release() |
5262 | { |
5263 | if (!memory && !backingTexture) |
5264 | return; |
5265 | |
5266 | QRhiVulkan::DeferredReleaseEntry e; |
5267 | e.type = QRhiVulkan::DeferredReleaseEntry::RenderBuffer; |
5268 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
5269 | |
5270 | e.renderBuffer.memory = memory; |
5271 | e.renderBuffer.image = image; |
5272 | e.renderBuffer.imageView = imageView; |
5273 | |
5274 | memory = VK_NULL_HANDLE; |
5275 | image = VK_NULL_HANDLE; |
5276 | imageView = VK_NULL_HANDLE; |
5277 | |
5278 | if (backingTexture) { |
5279 | Q_ASSERT(backingTexture->lastActiveFrameSlot == -1); |
5280 | backingTexture->lastActiveFrameSlot = e.lastActiveFrameSlot; |
5281 | backingTexture->release(); |
5282 | } |
5283 | |
5284 | QRHI_RES_RHI(QRhiVulkan); |
5285 | rhiD->releaseQueue.append(t: e); |
5286 | |
5287 | QRHI_PROF; |
5288 | QRHI_PROF_F(releaseRenderBuffer(this)); |
5289 | |
5290 | rhiD->unregisterResource(res: this); |
5291 | } |
5292 | |
5293 | bool QVkRenderBuffer::build() |
5294 | { |
5295 | if (memory || backingTexture) |
5296 | release(); |
5297 | |
5298 | if (m_pixelSize.isEmpty()) |
5299 | return false; |
5300 | |
5301 | QRHI_RES_RHI(QRhiVulkan); |
5302 | QRHI_PROF; |
5303 | samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount); |
5304 | |
5305 | switch (m_type) { |
5306 | case QRhiRenderBuffer::Color: |
5307 | { |
5308 | if (!backingTexture) { |
5309 | backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(QRhiTexture::RGBA8, |
5310 | m_pixelSize, |
5311 | m_sampleCount, |
5312 | QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); |
5313 | } else { |
5314 | backingTexture->setPixelSize(m_pixelSize); |
5315 | backingTexture->setSampleCount(m_sampleCount); |
5316 | } |
5317 | backingTexture->setName(m_objectName); |
5318 | if (!backingTexture->build()) |
5319 | return false; |
5320 | vkformat = backingTexture->vkformat; |
5321 | QRHI_PROF_F(newRenderBuffer(this, false, false, samples)); |
5322 | } |
5323 | break; |
5324 | case QRhiRenderBuffer::DepthStencil: |
5325 | vkformat = rhiD->optimalDepthStencilFormat(); |
5326 | if (!rhiD->createTransientImage(format: vkformat, |
5327 | pixelSize: m_pixelSize, |
5328 | usage: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, |
5329 | aspectMask: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, |
5330 | samples, |
5331 | mem: &memory, |
5332 | images: &image, |
5333 | views: &imageView, |
5334 | count: 1)) |
5335 | { |
5336 | return false; |
5337 | } |
5338 | rhiD->setObjectName(object: uint64_t(image), type: VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name: m_objectName); |
5339 | QRHI_PROF_F(newRenderBuffer(this, true, false, samples)); |
5340 | break; |
5341 | default: |
5342 | Q_UNREACHABLE(); |
5343 | break; |
5344 | } |
5345 | |
5346 | lastActiveFrameSlot = -1; |
5347 | rhiD->registerResource(res: this); |
5348 | return true; |
5349 | } |
5350 | |
5351 | QRhiTexture::Format QVkRenderBuffer::backingFormat() const |
5352 | { |
5353 | return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; |
5354 | } |
5355 | |
5356 | QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, |
5357 | int sampleCount, Flags flags) |
5358 | : QRhiTexture(rhi, format, pixelSize, sampleCount, flags) |
5359 | { |
5360 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
5361 | stagingBuffers[i] = VK_NULL_HANDLE; |
5362 | stagingAllocations[i] = nullptr; |
5363 | } |
5364 | for (int i = 0; i < QRhi::MAX_LEVELS; ++i) |
5365 | perLevelImageViews[i] = VK_NULL_HANDLE; |
5366 | } |
5367 | |
5368 | QVkTexture::~QVkTexture() |
5369 | { |
5370 | release(); |
5371 | } |
5372 | |
5373 | void QVkTexture::release() |
5374 | { |
5375 | if (!image) |
5376 | return; |
5377 | |
5378 | QRhiVulkan::DeferredReleaseEntry e; |
5379 | e.type = QRhiVulkan::DeferredReleaseEntry::Texture; |
5380 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
5381 | |
5382 | e.texture.image = owns ? image : VK_NULL_HANDLE; |
5383 | e.texture.imageView = imageView; |
5384 | e.texture.allocation = owns ? imageAlloc : nullptr; |
5385 | |
5386 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
5387 | e.texture.stagingBuffers[i] = stagingBuffers[i]; |
5388 | e.texture.stagingAllocations[i] = stagingAllocations[i]; |
5389 | |
5390 | stagingBuffers[i] = VK_NULL_HANDLE; |
5391 | stagingAllocations[i] = nullptr; |
5392 | } |
5393 | |
5394 | for (int i = 0; i < QRhi::MAX_LEVELS; ++i) { |
5395 | e.texture.extraImageViews[i] = perLevelImageViews[i]; |
5396 | perLevelImageViews[i] = VK_NULL_HANDLE; |
5397 | } |
5398 | |
5399 | image = VK_NULL_HANDLE; |
5400 | imageView = VK_NULL_HANDLE; |
5401 | imageAlloc = nullptr; |
5402 | |
5403 | QRHI_RES_RHI(QRhiVulkan); |
5404 | rhiD->releaseQueue.append(t: e); |
5405 | |
5406 | QRHI_PROF; |
5407 | QRHI_PROF_F(releaseTexture(this)); |
5408 | |
5409 | rhiD->unregisterResource(res: this); |
5410 | } |
5411 | |
5412 | bool QVkTexture::prepareBuild(QSize *adjustedSize) |
5413 | { |
5414 | if (image) |
5415 | release(); |
5416 | |
5417 | QRHI_RES_RHI(QRhiVulkan); |
5418 | vkformat = toVkTextureFormat(format: m_format, flags: m_flags); |
5419 | VkFormatProperties props; |
5420 | rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props); |
5421 | const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); |
5422 | if (!canSampleOptimal) { |
5423 | qWarning(msg: "Texture sampling with optimal tiling for format %d not supported" , vkformat); |
5424 | return false; |
5425 | } |
5426 | |
5427 | const QSize size = m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize; |
5428 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
5429 | const bool hasMipMaps = m_flags.testFlag(flag: MipMapped); |
5430 | |
5431 | mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1); |
5432 | const int maxLevels = QRhi::MAX_LEVELS; |
5433 | if (mipLevelCount > maxLevels) { |
5434 | qWarning(msg: "Too many mip levels (%d, max is %d), truncating mip chain" , mipLevelCount, maxLevels); |
5435 | mipLevelCount = maxLevels; |
5436 | } |
5437 | samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount); |
5438 | if (samples > VK_SAMPLE_COUNT_1_BIT) { |
5439 | if (isCube) { |
5440 | qWarning(msg: "Cubemap texture cannot be multisample" ); |
5441 | return false; |
5442 | } |
5443 | if (hasMipMaps) { |
5444 | qWarning(msg: "Multisample texture cannot have mipmaps" ); |
5445 | return false; |
5446 | } |
5447 | } |
5448 | |
5449 | usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED; |
5450 | usageState.access = 0; |
5451 | usageState.stage = 0; |
5452 | |
5453 | if (adjustedSize) |
5454 | *adjustedSize = size; |
5455 | |
5456 | return true; |
5457 | } |
5458 | |
5459 | bool QVkTexture::finishBuild() |
5460 | { |
5461 | QRHI_RES_RHI(QRhiVulkan); |
5462 | |
5463 | const bool isDepth = isDepthTextureFormat(format: m_format); |
5464 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
5465 | |
5466 | VkImageViewCreateInfo viewInfo; |
5467 | memset(s: &viewInfo, c: 0, n: sizeof(viewInfo)); |
5468 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
5469 | viewInfo.image = image; |
5470 | viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D; |
5471 | viewInfo.format = vkformat; |
5472 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
5473 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
5474 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
5475 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
5476 | viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; |
5477 | viewInfo.subresourceRange.levelCount = mipLevelCount; |
5478 | viewInfo.subresourceRange.layerCount = isCube ? 6 : 1; |
5479 | |
5480 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView); |
5481 | if (err != VK_SUCCESS) { |
5482 | qWarning(msg: "Failed to create image view: %d" , err); |
5483 | return false; |
5484 | } |
5485 | |
5486 | lastActiveFrameSlot = -1; |
5487 | generation += 1; |
5488 | |
5489 | return true; |
5490 | } |
5491 | |
5492 | bool QVkTexture::build() |
5493 | { |
5494 | QSize size; |
5495 | if (!prepareBuild(adjustedSize: &size)) |
5496 | return false; |
5497 | |
5498 | const bool isRenderTarget = m_flags.testFlag(flag: QRhiTexture::RenderTarget); |
5499 | const bool isDepth = isDepthTextureFormat(format: m_format); |
5500 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
5501 | |
5502 | VkImageCreateInfo imageInfo; |
5503 | memset(s: &imageInfo, c: 0, n: sizeof(imageInfo)); |
5504 | imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; |
5505 | imageInfo.flags = isCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0; |
5506 | imageInfo.imageType = VK_IMAGE_TYPE_2D; |
5507 | imageInfo.format = vkformat; |
5508 | imageInfo.extent.width = uint32_t(size.width()); |
5509 | imageInfo.extent.height = uint32_t(size.height()); |
5510 | imageInfo.extent.depth = 1; |
5511 | imageInfo.mipLevels = mipLevelCount; |
5512 | imageInfo.arrayLayers = isCube ? 6 : 1; |
5513 | imageInfo.samples = samples; |
5514 | imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; |
5515 | imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; |
5516 | |
5517 | imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
5518 | if (isRenderTarget) { |
5519 | if (isDepth) |
5520 | imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; |
5521 | else |
5522 | imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
5523 | } |
5524 | if (m_flags.testFlag(flag: QRhiTexture::UsedAsTransferSource)) |
5525 | imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
5526 | if (m_flags.testFlag(flag: QRhiTexture::UsedWithGenerateMips)) |
5527 | imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
5528 | if (m_flags.testFlag(flag: QRhiTexture::UsedWithLoadStore)) |
5529 | imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; |
5530 | |
5531 | VmaAllocationCreateInfo allocInfo; |
5532 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
5533 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; |
5534 | |
5535 | QRHI_RES_RHI(QRhiVulkan); |
5536 | VmaAllocation allocation; |
5537 | VkResult err = vmaCreateImage(allocator: toVmaAllocator(a: rhiD->allocator), pImageCreateInfo: &imageInfo, pAllocationCreateInfo: &allocInfo, pImage: &image, pAllocation: &allocation, pAllocationInfo: nullptr); |
5538 | if (err != VK_SUCCESS) { |
5539 | qWarning(msg: "Failed to create image: %d" , err); |
5540 | return false; |
5541 | } |
5542 | imageAlloc = allocation; |
5543 | |
5544 | if (!finishBuild()) |
5545 | return false; |
5546 | |
5547 | rhiD->setObjectName(object: uint64_t(image), type: VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, name: m_objectName); |
5548 | |
5549 | QRHI_PROF; |
5550 | QRHI_PROF_F(newTexture(this, true, int(mipLevelCount), isCube ? 6 : 1, samples)); |
5551 | |
5552 | owns = true; |
5553 | rhiD->registerResource(res: this); |
5554 | return true; |
5555 | } |
5556 | |
5557 | bool QVkTexture::buildFrom(QRhiTexture::NativeTexture src) |
5558 | { |
5559 | auto *img = static_cast<const VkImage*>(src.object); |
5560 | if (!img || !*img) |
5561 | return false; |
5562 | |
5563 | if (!prepareBuild()) |
5564 | return false; |
5565 | |
5566 | image = *img; |
5567 | |
5568 | if (!finishBuild()) |
5569 | return false; |
5570 | |
5571 | QRHI_PROF; |
5572 | QRHI_PROF_F(newTexture(this, false, int(mipLevelCount), m_flags.testFlag(CubeMap) ? 6 : 1, samples)); |
5573 | |
5574 | usageState.layout = VkImageLayout(src.layout); |
5575 | |
5576 | owns = false; |
5577 | QRHI_RES_RHI(QRhiVulkan); |
5578 | rhiD->registerResource(res: this); |
5579 | return true; |
5580 | } |
5581 | |
5582 | QRhiTexture::NativeTexture QVkTexture::nativeTexture() |
5583 | { |
5584 | return {.object: &image, .layout: usageState.layout}; |
5585 | } |
5586 | |
5587 | void QVkTexture::setNativeLayout(int layout) |
5588 | { |
5589 | usageState.layout = VkImageLayout(layout); |
5590 | } |
5591 | |
5592 | VkImageView QVkTexture::imageViewForLevel(int level) |
5593 | { |
5594 | Q_ASSERT(level >= 0 && level < int(mipLevelCount)); |
5595 | if (perLevelImageViews[level] != VK_NULL_HANDLE) |
5596 | return perLevelImageViews[level]; |
5597 | |
5598 | const bool isDepth = isDepthTextureFormat(format: m_format); |
5599 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
5600 | |
5601 | VkImageViewCreateInfo viewInfo; |
5602 | memset(s: &viewInfo, c: 0, n: sizeof(viewInfo)); |
5603 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
5604 | viewInfo.image = image; |
5605 | viewInfo.viewType = isCube ? VK_IMAGE_VIEW_TYPE_CUBE : VK_IMAGE_VIEW_TYPE_2D; |
5606 | viewInfo.format = vkformat; |
5607 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
5608 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
5609 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
5610 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
5611 | viewInfo.subresourceRange.aspectMask = isDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; |
5612 | viewInfo.subresourceRange.baseMipLevel = uint32_t(level); |
5613 | viewInfo.subresourceRange.levelCount = 1; |
5614 | viewInfo.subresourceRange.baseArrayLayer = 0; |
5615 | viewInfo.subresourceRange.layerCount = isCube ? 6 : 1; |
5616 | |
5617 | VkImageView v = VK_NULL_HANDLE; |
5618 | QRHI_RES_RHI(QRhiVulkan); |
5619 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &v); |
5620 | if (err != VK_SUCCESS) { |
5621 | qWarning(msg: "Failed to create image view: %d" , err); |
5622 | return VK_NULL_HANDLE; |
5623 | } |
5624 | |
5625 | perLevelImageViews[level] = v; |
5626 | return v; |
5627 | } |
5628 | |
5629 | QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, |
5630 | AddressMode u, AddressMode v, AddressMode w) |
5631 | : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) |
5632 | { |
5633 | } |
5634 | |
5635 | QVkSampler::~QVkSampler() |
5636 | { |
5637 | release(); |
5638 | } |
5639 | |
5640 | void QVkSampler::release() |
5641 | { |
5642 | if (!sampler) |
5643 | return; |
5644 | |
5645 | QRhiVulkan::DeferredReleaseEntry e; |
5646 | e.type = QRhiVulkan::DeferredReleaseEntry::Sampler; |
5647 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
5648 | |
5649 | e.sampler.sampler = sampler; |
5650 | sampler = VK_NULL_HANDLE; |
5651 | |
5652 | QRHI_RES_RHI(QRhiVulkan); |
5653 | rhiD->releaseQueue.append(t: e); |
5654 | rhiD->unregisterResource(res: this); |
5655 | } |
5656 | |
5657 | bool QVkSampler::build() |
5658 | { |
5659 | if (sampler) |
5660 | release(); |
5661 | |
5662 | VkSamplerCreateInfo samplerInfo; |
5663 | memset(s: &samplerInfo, c: 0, n: sizeof(samplerInfo)); |
5664 | samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; |
5665 | samplerInfo.magFilter = toVkFilter(f: m_magFilter); |
5666 | samplerInfo.minFilter = toVkFilter(f: m_minFilter); |
5667 | samplerInfo.mipmapMode = toVkMipmapMode(f: m_mipmapMode); |
5668 | samplerInfo.addressModeU = toVkAddressMode(m: m_addressU); |
5669 | samplerInfo.addressModeV = toVkAddressMode(m: m_addressV); |
5670 | samplerInfo.addressModeW = toVkAddressMode(m: m_addressW); |
5671 | samplerInfo.maxAnisotropy = 1.0f; |
5672 | samplerInfo.compareEnable = m_compareOp != Never; |
5673 | samplerInfo.compareOp = toVkTextureCompareOp(op: m_compareOp); |
5674 | samplerInfo.maxLod = m_mipmapMode == None ? 0.25f : 1000.0f; |
5675 | |
5676 | QRHI_RES_RHI(QRhiVulkan); |
5677 | VkResult err = rhiD->df->vkCreateSampler(rhiD->dev, &samplerInfo, nullptr, &sampler); |
5678 | if (err != VK_SUCCESS) { |
5679 | qWarning(msg: "Failed to create sampler: %d" , err); |
5680 | return false; |
5681 | } |
5682 | |
5683 | lastActiveFrameSlot = -1; |
5684 | generation += 1; |
5685 | rhiD->registerResource(res: this); |
5686 | return true; |
5687 | } |
5688 | |
5689 | QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi) |
5690 | : QRhiRenderPassDescriptor(rhi) |
5691 | { |
5692 | } |
5693 | |
5694 | QVkRenderPassDescriptor::~QVkRenderPassDescriptor() |
5695 | { |
5696 | release(); |
5697 | } |
5698 | |
5699 | void QVkRenderPassDescriptor::release() |
5700 | { |
5701 | if (!rp) |
5702 | return; |
5703 | |
5704 | if (!ownsRp) { |
5705 | rp = VK_NULL_HANDLE; |
5706 | return; |
5707 | } |
5708 | |
5709 | QRhiVulkan::DeferredReleaseEntry e; |
5710 | e.type = QRhiVulkan::DeferredReleaseEntry::RenderPass; |
5711 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
5712 | |
5713 | e.renderPass.rp = rp; |
5714 | |
5715 | rp = VK_NULL_HANDLE; |
5716 | |
5717 | QRHI_RES_RHI(QRhiVulkan); |
5718 | rhiD->releaseQueue.append(t: e); |
5719 | |
5720 | rhiD->unregisterResource(res: this); |
5721 | } |
5722 | |
5723 | static inline bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b) |
5724 | { |
5725 | return a.format == b.format |
5726 | && a.samples == b.samples |
5727 | && a.loadOp == b.loadOp |
5728 | && a.storeOp == b.storeOp |
5729 | && a.stencilLoadOp == b.stencilLoadOp |
5730 | && a.stencilStoreOp == b.stencilStoreOp |
5731 | && a.initialLayout == b.initialLayout |
5732 | && a.finalLayout == b.finalLayout; |
5733 | } |
5734 | |
5735 | bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const |
5736 | { |
5737 | if (!other) |
5738 | return false; |
5739 | |
5740 | const QVkRenderPassDescriptor *o = QRHI_RES(const QVkRenderPassDescriptor, other); |
5741 | |
5742 | if (attDescs.count() != o->attDescs.count()) |
5743 | return false; |
5744 | if (colorRefs.count() != o->colorRefs.count()) |
5745 | return false; |
5746 | if (resolveRefs.count() != o->resolveRefs.count()) |
5747 | return false; |
5748 | if (hasDepthStencil != o->hasDepthStencil) |
5749 | return false; |
5750 | |
5751 | for (int i = 0, ie = colorRefs.count(); i != ie; ++i) { |
5752 | const uint32_t attIdx = colorRefs[i].attachment; |
5753 | if (attIdx != o->colorRefs[i].attachment) |
5754 | return false; |
5755 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
5756 | return false; |
5757 | } |
5758 | |
5759 | if (hasDepthStencil) { |
5760 | const uint32_t attIdx = dsRef.attachment; |
5761 | if (attIdx != o->dsRef.attachment) |
5762 | return false; |
5763 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
5764 | return false; |
5765 | } |
5766 | |
5767 | for (int i = 0, ie = resolveRefs.count(); i != ie; ++i) { |
5768 | const uint32_t attIdx = resolveRefs[i].attachment; |
5769 | if (attIdx != o->resolveRefs[i].attachment) |
5770 | return false; |
5771 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
5772 | return false; |
5773 | } |
5774 | |
5775 | return true; |
5776 | } |
5777 | |
5778 | const QRhiNativeHandles *QVkRenderPassDescriptor::nativeHandles() |
5779 | { |
5780 | nativeHandlesStruct.renderPass = rp; |
5781 | return &nativeHandlesStruct; |
5782 | } |
5783 | |
5784 | QVkReferenceRenderTarget::QVkReferenceRenderTarget(QRhiImplementation *rhi) |
5785 | : QRhiRenderTarget(rhi) |
5786 | { |
5787 | } |
5788 | |
5789 | QVkReferenceRenderTarget::~QVkReferenceRenderTarget() |
5790 | { |
5791 | release(); |
5792 | } |
5793 | |
5794 | void QVkReferenceRenderTarget::release() |
5795 | { |
5796 | // nothing to do here |
5797 | } |
5798 | |
5799 | QSize QVkReferenceRenderTarget::pixelSize() const |
5800 | { |
5801 | return d.pixelSize; |
5802 | } |
5803 | |
5804 | float QVkReferenceRenderTarget::devicePixelRatio() const |
5805 | { |
5806 | return d.dpr; |
5807 | } |
5808 | |
5809 | int QVkReferenceRenderTarget::sampleCount() const |
5810 | { |
5811 | return d.sampleCount; |
5812 | } |
5813 | |
5814 | QVkTextureRenderTarget::QVkTextureRenderTarget(QRhiImplementation *rhi, |
5815 | const QRhiTextureRenderTargetDescription &desc, |
5816 | Flags flags) |
5817 | : QRhiTextureRenderTarget(rhi, desc, flags) |
5818 | { |
5819 | for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) { |
5820 | rtv[att] = VK_NULL_HANDLE; |
5821 | resrtv[att] = VK_NULL_HANDLE; |
5822 | } |
5823 | } |
5824 | |
5825 | QVkTextureRenderTarget::~QVkTextureRenderTarget() |
5826 | { |
5827 | release(); |
5828 | } |
5829 | |
5830 | void QVkTextureRenderTarget::release() |
5831 | { |
5832 | if (!d.fb) |
5833 | return; |
5834 | |
5835 | QRhiVulkan::DeferredReleaseEntry e; |
5836 | e.type = QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget; |
5837 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
5838 | |
5839 | e.textureRenderTarget.fb = d.fb; |
5840 | d.fb = VK_NULL_HANDLE; |
5841 | |
5842 | for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) { |
5843 | e.textureRenderTarget.rtv[att] = rtv[att]; |
5844 | e.textureRenderTarget.resrtv[att] = resrtv[att]; |
5845 | rtv[att] = VK_NULL_HANDLE; |
5846 | resrtv[att] = VK_NULL_HANDLE; |
5847 | } |
5848 | |
5849 | QRHI_RES_RHI(QRhiVulkan); |
5850 | rhiD->releaseQueue.append(t: e); |
5851 | |
5852 | rhiD->unregisterResource(res: this); |
5853 | } |
5854 | |
5855 | QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescriptor() |
5856 | { |
5857 | // not yet built so cannot rely on data computed in build() |
5858 | |
5859 | QRHI_RES_RHI(QRhiVulkan); |
5860 | QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi); |
5861 | if (!rhiD->createOffscreenRenderPass(rpD: rp, |
5862 | firstColorAttachment: m_desc.cbeginColorAttachments(), |
5863 | lastColorAttachment: m_desc.cendColorAttachments(), |
5864 | preserveColor: m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveColorContents), |
5865 | preserveDs: m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveDepthStencilContents), |
5866 | depthStencilBuffer: m_desc.depthStencilBuffer(), |
5867 | depthTexture: m_desc.depthTexture())) |
5868 | { |
5869 | delete rp; |
5870 | return nullptr; |
5871 | } |
5872 | |
5873 | rp->ownsRp = true; |
5874 | rhiD->registerResource(res: rp); |
5875 | return rp; |
5876 | } |
5877 | |
5878 | bool QVkTextureRenderTarget::build() |
5879 | { |
5880 | if (d.fb) |
5881 | release(); |
5882 | |
5883 | const bool hasColorAttachments = m_desc.cbeginColorAttachments() != m_desc.cendColorAttachments(); |
5884 | Q_ASSERT(hasColorAttachments || m_desc.depthTexture()); |
5885 | Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); |
5886 | const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); |
5887 | |
5888 | QRHI_RES_RHI(QRhiVulkan); |
5889 | QVarLengthArray<VkImageView, 8> views; |
5890 | |
5891 | d.colorAttCount = 0; |
5892 | int attIndex = 0; |
5893 | for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { |
5894 | d.colorAttCount += 1; |
5895 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
5896 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
5897 | Q_ASSERT(texD || rbD); |
5898 | if (texD) { |
5899 | Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget)); |
5900 | VkImageViewCreateInfo viewInfo; |
5901 | memset(s: &viewInfo, c: 0, n: sizeof(viewInfo)); |
5902 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
5903 | viewInfo.image = texD->image; |
5904 | viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
5905 | viewInfo.format = texD->vkformat; |
5906 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
5907 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
5908 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
5909 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
5910 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
5911 | viewInfo.subresourceRange.baseMipLevel = uint32_t(it->level()); |
5912 | viewInfo.subresourceRange.levelCount = 1; |
5913 | viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->layer()); |
5914 | viewInfo.subresourceRange.layerCount = 1; |
5915 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[attIndex]); |
5916 | if (err != VK_SUCCESS) { |
5917 | qWarning(msg: "Failed to create render target image view: %d" , err); |
5918 | return false; |
5919 | } |
5920 | views.append(t: rtv[attIndex]); |
5921 | if (attIndex == 0) { |
5922 | d.pixelSize = texD->pixelSize(); |
5923 | d.sampleCount = texD->samples; |
5924 | } |
5925 | } else if (rbD) { |
5926 | Q_ASSERT(rbD->backingTexture); |
5927 | views.append(t: rbD->backingTexture->imageView); |
5928 | if (attIndex == 0) { |
5929 | d.pixelSize = rbD->pixelSize(); |
5930 | d.sampleCount = rbD->samples; |
5931 | } |
5932 | } |
5933 | } |
5934 | d.dpr = 1; |
5935 | |
5936 | if (hasDepthStencil) { |
5937 | if (m_desc.depthTexture()) { |
5938 | QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture()); |
5939 | views.append(t: depthTexD->imageView); |
5940 | if (d.colorAttCount == 0) { |
5941 | d.pixelSize = depthTexD->pixelSize(); |
5942 | d.sampleCount = depthTexD->samples; |
5943 | } |
5944 | } else { |
5945 | QVkRenderBuffer *depthRbD = QRHI_RES(QVkRenderBuffer, m_desc.depthStencilBuffer()); |
5946 | views.append(t: depthRbD->imageView); |
5947 | if (d.colorAttCount == 0) { |
5948 | d.pixelSize = depthRbD->pixelSize(); |
5949 | d.sampleCount = depthRbD->samples; |
5950 | } |
5951 | } |
5952 | d.dsAttCount = 1; |
5953 | } else { |
5954 | d.dsAttCount = 0; |
5955 | } |
5956 | |
5957 | d.resolveAttCount = 0; |
5958 | attIndex = 0; |
5959 | for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { |
5960 | if (it->resolveTexture()) { |
5961 | QVkTexture *resTexD = QRHI_RES(QVkTexture, it->resolveTexture()); |
5962 | Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget)); |
5963 | d.resolveAttCount += 1; |
5964 | |
5965 | VkImageViewCreateInfo viewInfo; |
5966 | memset(s: &viewInfo, c: 0, n: sizeof(viewInfo)); |
5967 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
5968 | viewInfo.image = resTexD->image; |
5969 | viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
5970 | viewInfo.format = resTexD->vkformat; |
5971 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
5972 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
5973 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
5974 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
5975 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
5976 | viewInfo.subresourceRange.baseMipLevel = uint32_t(it->resolveLevel()); |
5977 | viewInfo.subresourceRange.levelCount = 1; |
5978 | viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->resolveLayer()); |
5979 | viewInfo.subresourceRange.layerCount = 1; |
5980 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[attIndex]); |
5981 | if (err != VK_SUCCESS) { |
5982 | qWarning(msg: "Failed to create render target resolve image view: %d" , err); |
5983 | return false; |
5984 | } |
5985 | views.append(t: resrtv[attIndex]); |
5986 | } |
5987 | } |
5988 | |
5989 | if (!m_renderPassDesc) |
5990 | qWarning(msg: "QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()." ); |
5991 | |
5992 | d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); |
5993 | Q_ASSERT(d.rp && d.rp->rp); |
5994 | |
5995 | VkFramebufferCreateInfo fbInfo; |
5996 | memset(s: &fbInfo, c: 0, n: sizeof(fbInfo)); |
5997 | fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; |
5998 | fbInfo.renderPass = d.rp->rp; |
5999 | fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount); |
6000 | fbInfo.pAttachments = views.constData(); |
6001 | fbInfo.width = uint32_t(d.pixelSize.width()); |
6002 | fbInfo.height = uint32_t(d.pixelSize.height()); |
6003 | fbInfo.layers = 1; |
6004 | |
6005 | VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &d.fb); |
6006 | if (err != VK_SUCCESS) { |
6007 | qWarning(msg: "Failed to create framebuffer: %d" , err); |
6008 | return false; |
6009 | } |
6010 | |
6011 | lastActiveFrameSlot = -1; |
6012 | rhiD->registerResource(res: this); |
6013 | return true; |
6014 | } |
6015 | |
6016 | QSize QVkTextureRenderTarget::pixelSize() const |
6017 | { |
6018 | return d.pixelSize; |
6019 | } |
6020 | |
6021 | float QVkTextureRenderTarget::devicePixelRatio() const |
6022 | { |
6023 | return d.dpr; |
6024 | } |
6025 | |
6026 | int QVkTextureRenderTarget::sampleCount() const |
6027 | { |
6028 | return d.sampleCount; |
6029 | } |
6030 | |
6031 | QVkShaderResourceBindings::QVkShaderResourceBindings(QRhiImplementation *rhi) |
6032 | : QRhiShaderResourceBindings(rhi) |
6033 | { |
6034 | } |
6035 | |
6036 | QVkShaderResourceBindings::~QVkShaderResourceBindings() |
6037 | { |
6038 | release(); |
6039 | } |
6040 | |
6041 | void QVkShaderResourceBindings::release() |
6042 | { |
6043 | if (!layout) |
6044 | return; |
6045 | |
6046 | sortedBindings.clear(); |
6047 | |
6048 | QRhiVulkan::DeferredReleaseEntry e; |
6049 | e.type = QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings; |
6050 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6051 | |
6052 | e.shaderResourceBindings.poolIndex = poolIndex; |
6053 | e.shaderResourceBindings.layout = layout; |
6054 | |
6055 | poolIndex = -1; |
6056 | layout = VK_NULL_HANDLE; |
6057 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
6058 | descSets[i] = VK_NULL_HANDLE; |
6059 | |
6060 | QRHI_RES_RHI(QRhiVulkan); |
6061 | rhiD->releaseQueue.append(t: e); |
6062 | |
6063 | rhiD->unregisterResource(res: this); |
6064 | } |
6065 | |
6066 | bool QVkShaderResourceBindings::build() |
6067 | { |
6068 | if (layout) |
6069 | release(); |
6070 | |
6071 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
6072 | descSets[i] = VK_NULL_HANDLE; |
6073 | |
6074 | sortedBindings.clear(); |
6075 | std::copy(first: m_bindings.cbegin(), last: m_bindings.cend(), result: std::back_inserter(x&: sortedBindings)); |
6076 | std::sort(first: sortedBindings.begin(), last: sortedBindings.end(), |
6077 | comp: [](const QRhiShaderResourceBinding &a, const QRhiShaderResourceBinding &b) |
6078 | { |
6079 | return a.data()->binding < b.data()->binding; |
6080 | }); |
6081 | |
6082 | QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings; |
6083 | for (const QRhiShaderResourceBinding &binding : qAsConst(t&: sortedBindings)) { |
6084 | const QRhiShaderResourceBinding::Data *b = binding.data(); |
6085 | VkDescriptorSetLayoutBinding vkbinding; |
6086 | memset(s: &vkbinding, c: 0, n: sizeof(vkbinding)); |
6087 | vkbinding.binding = uint32_t(b->binding); |
6088 | vkbinding.descriptorType = toVkDescriptorType(b); |
6089 | if (b->type == QRhiShaderResourceBinding::SampledTexture) |
6090 | vkbinding.descriptorCount = b->u.stex.count; |
6091 | else |
6092 | vkbinding.descriptorCount = 1; |
6093 | vkbinding.stageFlags = toVkShaderStageFlags(stage: b->stage); |
6094 | vkbindings.append(t: vkbinding); |
6095 | } |
6096 | |
6097 | VkDescriptorSetLayoutCreateInfo layoutInfo; |
6098 | memset(s: &layoutInfo, c: 0, n: sizeof(layoutInfo)); |
6099 | layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; |
6100 | layoutInfo.bindingCount = uint32_t(vkbindings.count()); |
6101 | layoutInfo.pBindings = vkbindings.constData(); |
6102 | |
6103 | QRHI_RES_RHI(QRhiVulkan); |
6104 | VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout); |
6105 | if (err != VK_SUCCESS) { |
6106 | qWarning(msg: "Failed to create descriptor set layout: %d" , err); |
6107 | return false; |
6108 | } |
6109 | |
6110 | VkDescriptorSetAllocateInfo allocInfo; |
6111 | memset(s: &allocInfo, c: 0, n: sizeof(allocInfo)); |
6112 | allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; |
6113 | allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT; |
6114 | VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT]; |
6115 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
6116 | layouts[i] = layout; |
6117 | allocInfo.pSetLayouts = layouts; |
6118 | if (!rhiD->allocateDescriptorSet(allocInfo: &allocInfo, result: descSets, resultPoolIndex: &poolIndex)) |
6119 | return false; |
6120 | |
6121 | rhiD->updateShaderResourceBindings(srb: this); |
6122 | |
6123 | lastActiveFrameSlot = -1; |
6124 | generation += 1; |
6125 | rhiD->registerResource(res: this); |
6126 | return true; |
6127 | } |
6128 | |
6129 | QVkGraphicsPipeline::QVkGraphicsPipeline(QRhiImplementation *rhi) |
6130 | : QRhiGraphicsPipeline(rhi) |
6131 | { |
6132 | } |
6133 | |
6134 | QVkGraphicsPipeline::~QVkGraphicsPipeline() |
6135 | { |
6136 | release(); |
6137 | } |
6138 | |
6139 | void QVkGraphicsPipeline::release() |
6140 | { |
6141 | if (!pipeline && !layout) |
6142 | return; |
6143 | |
6144 | QRhiVulkan::DeferredReleaseEntry e; |
6145 | e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline; |
6146 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6147 | |
6148 | e.pipelineState.pipeline = pipeline; |
6149 | e.pipelineState.layout = layout; |
6150 | |
6151 | pipeline = VK_NULL_HANDLE; |
6152 | layout = VK_NULL_HANDLE; |
6153 | |
6154 | QRHI_RES_RHI(QRhiVulkan); |
6155 | rhiD->releaseQueue.append(t: e); |
6156 | |
6157 | rhiD->unregisterResource(res: this); |
6158 | } |
6159 | |
6160 | bool QVkGraphicsPipeline::build() |
6161 | { |
6162 | if (pipeline) |
6163 | release(); |
6164 | |
6165 | QRHI_RES_RHI(QRhiVulkan); |
6166 | if (!rhiD->sanityCheckGraphicsPipeline(ps: this)) |
6167 | return false; |
6168 | |
6169 | if (!rhiD->ensurePipelineCache()) |
6170 | return false; |
6171 | |
6172 | VkPipelineLayoutCreateInfo pipelineLayoutInfo; |
6173 | memset(s: &pipelineLayoutInfo, c: 0, n: sizeof(pipelineLayoutInfo)); |
6174 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; |
6175 | pipelineLayoutInfo.setLayoutCount = 1; |
6176 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings); |
6177 | Q_ASSERT(m_shaderResourceBindings && srbD->layout); |
6178 | pipelineLayoutInfo.pSetLayouts = &srbD->layout; |
6179 | VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout); |
6180 | if (err != VK_SUCCESS) { |
6181 | qWarning(msg: "Failed to create pipeline layout: %d" , err); |
6182 | return false; |
6183 | } |
6184 | |
6185 | VkGraphicsPipelineCreateInfo pipelineInfo; |
6186 | memset(s: &pipelineInfo, c: 0, n: sizeof(pipelineInfo)); |
6187 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; |
6188 | |
6189 | QVarLengthArray<VkShaderModule, 4> shaders; |
6190 | QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos; |
6191 | for (const QRhiShaderStage &shaderStage : m_shaderStages) { |
6192 | const QShader bakedShader = shaderStage.shader(); |
6193 | const QShaderCode spirv = bakedShader.shader(key: { QShader::SpirvShader, 100, shaderStage.shaderVariant() }); |
6194 | if (spirv.shader().isEmpty()) { |
6195 | qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader; |
6196 | return false; |
6197 | } |
6198 | VkShaderModule shader = rhiD->createShader(spirv: spirv.shader()); |
6199 | if (shader) { |
6200 | shaders.append(t: shader); |
6201 | VkPipelineShaderStageCreateInfo shaderInfo; |
6202 | memset(s: &shaderInfo, c: 0, n: sizeof(shaderInfo)); |
6203 | shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; |
6204 | shaderInfo.stage = toVkShaderStage(type: shaderStage.type()); |
6205 | shaderInfo.module = shader; |
6206 | shaderInfo.pName = spirv.entryPoint().constData(); |
6207 | shaderStageCreateInfos.append(t: shaderInfo); |
6208 | } |
6209 | } |
6210 | pipelineInfo.stageCount = uint32_t(shaderStageCreateInfos.count()); |
6211 | pipelineInfo.pStages = shaderStageCreateInfos.constData(); |
6212 | |
6213 | QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings; |
6214 | QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates; |
6215 | int bindingIndex = 0; |
6216 | for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings(); |
6217 | it != itEnd; ++it, ++bindingIndex) |
6218 | { |
6219 | VkVertexInputBindingDescription bindingInfo = { |
6220 | .binding: uint32_t(bindingIndex), |
6221 | .stride: it->stride(), |
6222 | .inputRate: it->classification() == QRhiVertexInputBinding::PerVertex |
6223 | ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE |
6224 | }; |
6225 | if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) { |
6226 | if (rhiD->vertexAttribDivisorAvailable) { |
6227 | nonOneStepRates.append(t: { .binding: uint32_t(bindingIndex), .divisor: uint32_t(it->instanceStepRate()) }); |
6228 | } else { |
6229 | qWarning(msg: "QRhiVulkan: Instance step rates other than 1 not supported without " |
6230 | "VK_EXT_vertex_attribute_divisor on the device and " |
6231 | "VK_KHR_get_physical_device_properties2 on the instance" ); |
6232 | } |
6233 | } |
6234 | vertexBindings.append(t: bindingInfo); |
6235 | } |
6236 | QVarLengthArray<VkVertexInputAttributeDescription, 4> vertexAttributes; |
6237 | for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes(); |
6238 | it != itEnd; ++it) |
6239 | { |
6240 | VkVertexInputAttributeDescription attributeInfo = { |
6241 | .location: uint32_t(it->location()), |
6242 | .binding: uint32_t(it->binding()), |
6243 | .format: toVkAttributeFormat(format: it->format()), |
6244 | .offset: it->offset() |
6245 | }; |
6246 | vertexAttributes.append(t: attributeInfo); |
6247 | } |
6248 | VkPipelineVertexInputStateCreateInfo vertexInputInfo; |
6249 | memset(s: &vertexInputInfo, c: 0, n: sizeof(vertexInputInfo)); |
6250 | vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; |
6251 | vertexInputInfo.vertexBindingDescriptionCount = uint32_t(vertexBindings.count()); |
6252 | vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData(); |
6253 | vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.count()); |
6254 | vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData(); |
6255 | VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo; |
6256 | if (!nonOneStepRates.isEmpty()) { |
6257 | memset(s: &divisorInfo, c: 0, n: sizeof(divisorInfo)); |
6258 | divisorInfo.sType = VkStructureType(1000190001); // VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT |
6259 | divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.count()); |
6260 | divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData(); |
6261 | vertexInputInfo.pNext = &divisorInfo; |
6262 | } |
6263 | pipelineInfo.pVertexInputState = &vertexInputInfo; |
6264 | |
6265 | QVarLengthArray<VkDynamicState, 8> dynEnable; |
6266 | dynEnable << VK_DYNAMIC_STATE_VIEWPORT; |
6267 | dynEnable << VK_DYNAMIC_STATE_SCISSOR; // ignore UsesScissor - Vulkan requires a scissor for the viewport always |
6268 | if (m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesBlendConstants)) |
6269 | dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS; |
6270 | if (m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesStencilRef)) |
6271 | dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE; |
6272 | |
6273 | VkPipelineDynamicStateCreateInfo dynamicInfo; |
6274 | memset(s: &dynamicInfo, c: 0, n: sizeof(dynamicInfo)); |
6275 | dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; |
6276 | dynamicInfo.dynamicStateCount = uint32_t(dynEnable.count()); |
6277 | dynamicInfo.pDynamicStates = dynEnable.constData(); |
6278 | pipelineInfo.pDynamicState = &dynamicInfo; |
6279 | |
6280 | VkPipelineViewportStateCreateInfo viewportInfo; |
6281 | memset(s: &viewportInfo, c: 0, n: sizeof(viewportInfo)); |
6282 | viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; |
6283 | viewportInfo.viewportCount = viewportInfo.scissorCount = 1; |
6284 | pipelineInfo.pViewportState = &viewportInfo; |
6285 | |
6286 | VkPipelineInputAssemblyStateCreateInfo inputAsmInfo; |
6287 | memset(s: &inputAsmInfo, c: 0, n: sizeof(inputAsmInfo)); |
6288 | inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; |
6289 | inputAsmInfo.topology = toVkTopology(t: m_topology); |
6290 | inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip); |
6291 | pipelineInfo.pInputAssemblyState = &inputAsmInfo; |
6292 | |
6293 | VkPipelineRasterizationStateCreateInfo rastInfo; |
6294 | memset(s: &rastInfo, c: 0, n: sizeof(rastInfo)); |
6295 | rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; |
6296 | rastInfo.cullMode = toVkCullMode(c: m_cullMode); |
6297 | rastInfo.frontFace = toVkFrontFace(f: m_frontFace); |
6298 | if (m_depthBias != 0 || !qFuzzyIsNull(f: m_slopeScaledDepthBias)) { |
6299 | rastInfo.depthBiasEnable = true; |
6300 | rastInfo.depthBiasConstantFactor = float(m_depthBias); |
6301 | rastInfo.depthBiasSlopeFactor = m_slopeScaledDepthBias; |
6302 | } |
6303 | rastInfo.lineWidth = rhiD->hasWideLines ? m_lineWidth : 1.0f; |
6304 | pipelineInfo.pRasterizationState = &rastInfo; |
6305 | |
6306 | VkPipelineMultisampleStateCreateInfo msInfo; |
6307 | memset(s: &msInfo, c: 0, n: sizeof(msInfo)); |
6308 | msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; |
6309 | msInfo.rasterizationSamples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount); |
6310 | pipelineInfo.pMultisampleState = &msInfo; |
6311 | |
6312 | VkPipelineDepthStencilStateCreateInfo dsInfo; |
6313 | memset(s: &dsInfo, c: 0, n: sizeof(dsInfo)); |
6314 | dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; |
6315 | dsInfo.depthTestEnable = m_depthTest; |
6316 | dsInfo.depthWriteEnable = m_depthWrite; |
6317 | dsInfo.depthCompareOp = toVkCompareOp(op: m_depthOp); |
6318 | dsInfo.stencilTestEnable = m_stencilTest; |
6319 | if (m_stencilTest) { |
6320 | fillVkStencilOpState(dst: &dsInfo.front, src: m_stencilFront); |
6321 | dsInfo.front.compareMask = m_stencilReadMask; |
6322 | dsInfo.front.writeMask = m_stencilWriteMask; |
6323 | fillVkStencilOpState(dst: &dsInfo.back, src: m_stencilBack); |
6324 | dsInfo.back.compareMask = m_stencilReadMask; |
6325 | dsInfo.back.writeMask = m_stencilWriteMask; |
6326 | } |
6327 | pipelineInfo.pDepthStencilState = &dsInfo; |
6328 | |
6329 | VkPipelineColorBlendStateCreateInfo blendInfo; |
6330 | memset(s: &blendInfo, c: 0, n: sizeof(blendInfo)); |
6331 | blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; |
6332 | QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends; |
6333 | for (const QRhiGraphicsPipeline::TargetBlend &b : qAsConst(t&: m_targetBlends)) { |
6334 | VkPipelineColorBlendAttachmentState blend; |
6335 | memset(s: &blend, c: 0, n: sizeof(blend)); |
6336 | blend.blendEnable = b.enable; |
6337 | blend.srcColorBlendFactor = toVkBlendFactor(f: b.srcColor); |
6338 | blend.dstColorBlendFactor = toVkBlendFactor(f: b.dstColor); |
6339 | blend.colorBlendOp = toVkBlendOp(op: b.opColor); |
6340 | blend.srcAlphaBlendFactor = toVkBlendFactor(f: b.srcAlpha); |
6341 | blend.dstAlphaBlendFactor = toVkBlendFactor(f: b.dstAlpha); |
6342 | blend.alphaBlendOp = toVkBlendOp(op: b.opAlpha); |
6343 | blend.colorWriteMask = toVkColorComponents(c: b.colorWrite); |
6344 | vktargetBlends.append(t: blend); |
6345 | } |
6346 | if (vktargetBlends.isEmpty()) { |
6347 | VkPipelineColorBlendAttachmentState blend; |
6348 | memset(s: &blend, c: 0, n: sizeof(blend)); |
6349 | blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
6350 | | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; |
6351 | vktargetBlends.append(t: blend); |
6352 | } |
6353 | blendInfo.attachmentCount = uint32_t(vktargetBlends.count()); |
6354 | blendInfo.pAttachments = vktargetBlends.constData(); |
6355 | pipelineInfo.pColorBlendState = &blendInfo; |
6356 | |
6357 | pipelineInfo.layout = layout; |
6358 | |
6359 | Q_ASSERT(m_renderPassDesc && QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp); |
6360 | pipelineInfo.renderPass = QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp; |
6361 | |
6362 | err = rhiD->df->vkCreateGraphicsPipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); |
6363 | |
6364 | for (VkShaderModule shader : shaders) |
6365 | rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr); |
6366 | |
6367 | if (err != VK_SUCCESS) { |
6368 | qWarning(msg: "Failed to create graphics pipeline: %d" , err); |
6369 | return false; |
6370 | } |
6371 | |
6372 | lastActiveFrameSlot = -1; |
6373 | generation += 1; |
6374 | rhiD->registerResource(res: this); |
6375 | return true; |
6376 | } |
6377 | |
6378 | QVkComputePipeline::QVkComputePipeline(QRhiImplementation *rhi) |
6379 | : QRhiComputePipeline(rhi) |
6380 | { |
6381 | } |
6382 | |
6383 | QVkComputePipeline::~QVkComputePipeline() |
6384 | { |
6385 | release(); |
6386 | } |
6387 | |
6388 | void QVkComputePipeline::release() |
6389 | { |
6390 | if (!pipeline && !layout) |
6391 | return; |
6392 | |
6393 | QRhiVulkan::DeferredReleaseEntry e; |
6394 | e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline; |
6395 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6396 | |
6397 | e.pipelineState.pipeline = pipeline; |
6398 | e.pipelineState.layout = layout; |
6399 | |
6400 | pipeline = VK_NULL_HANDLE; |
6401 | layout = VK_NULL_HANDLE; |
6402 | |
6403 | QRHI_RES_RHI(QRhiVulkan); |
6404 | rhiD->releaseQueue.append(t: e); |
6405 | |
6406 | rhiD->unregisterResource(res: this); |
6407 | } |
6408 | |
6409 | bool QVkComputePipeline::build() |
6410 | { |
6411 | if (pipeline) |
6412 | release(); |
6413 | |
6414 | QRHI_RES_RHI(QRhiVulkan); |
6415 | if (!rhiD->ensurePipelineCache()) |
6416 | return false; |
6417 | |
6418 | VkPipelineLayoutCreateInfo pipelineLayoutInfo; |
6419 | memset(s: &pipelineLayoutInfo, c: 0, n: sizeof(pipelineLayoutInfo)); |
6420 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; |
6421 | pipelineLayoutInfo.setLayoutCount = 1; |
6422 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings); |
6423 | Q_ASSERT(m_shaderResourceBindings && srbD->layout); |
6424 | pipelineLayoutInfo.pSetLayouts = &srbD->layout; |
6425 | VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout); |
6426 | if (err != VK_SUCCESS) { |
6427 | qWarning(msg: "Failed to create pipeline layout: %d" , err); |
6428 | return false; |
6429 | } |
6430 | |
6431 | VkComputePipelineCreateInfo pipelineInfo; |
6432 | memset(s: &pipelineInfo, c: 0, n: sizeof(pipelineInfo)); |
6433 | pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; |
6434 | pipelineInfo.layout = layout; |
6435 | |
6436 | if (m_shaderStage.type() != QRhiShaderStage::Compute) { |
6437 | qWarning(msg: "Compute pipeline requires a compute shader stage" ); |
6438 | return false; |
6439 | } |
6440 | const QShader bakedShader = m_shaderStage.shader(); |
6441 | const QShaderCode spirv = bakedShader.shader(key: { QShader::SpirvShader, 100, m_shaderStage.shaderVariant() }); |
6442 | if (spirv.shader().isEmpty()) { |
6443 | qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader; |
6444 | return false; |
6445 | } |
6446 | if (bakedShader.stage() != QShader::ComputeStage) { |
6447 | qWarning() << bakedShader << "is not a compute shader" ; |
6448 | return false; |
6449 | } |
6450 | VkShaderModule shader = rhiD->createShader(spirv: spirv.shader()); |
6451 | VkPipelineShaderStageCreateInfo shaderInfo; |
6452 | memset(s: &shaderInfo, c: 0, n: sizeof(shaderInfo)); |
6453 | shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; |
6454 | shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; |
6455 | shaderInfo.module = shader; |
6456 | shaderInfo.pName = spirv.entryPoint().constData(); |
6457 | pipelineInfo.stage = shaderInfo; |
6458 | |
6459 | err = rhiD->df->vkCreateComputePipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); |
6460 | rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr); |
6461 | if (err != VK_SUCCESS) { |
6462 | qWarning(msg: "Failed to create graphics pipeline: %d" , err); |
6463 | return false; |
6464 | } |
6465 | |
6466 | lastActiveFrameSlot = -1; |
6467 | generation += 1; |
6468 | rhiD->registerResource(res: this); |
6469 | return true; |
6470 | } |
6471 | |
6472 | QVkCommandBuffer::QVkCommandBuffer(QRhiImplementation *rhi) |
6473 | : QRhiCommandBuffer(rhi) |
6474 | { |
6475 | resetState(); |
6476 | } |
6477 | |
6478 | QVkCommandBuffer::~QVkCommandBuffer() |
6479 | { |
6480 | release(); |
6481 | } |
6482 | |
6483 | void QVkCommandBuffer::release() |
6484 | { |
6485 | // nothing to do here, cb is not owned by us |
6486 | } |
6487 | |
6488 | const QRhiNativeHandles *QVkCommandBuffer::nativeHandles() |
6489 | { |
6490 | // Ok this is messy but no other way has been devised yet. Outside |
6491 | // begin(Compute)Pass - end(Compute)Pass it is simple - just return the |
6492 | // primary VkCommandBuffer. Inside, however, we need to provide the current |
6493 | // secondary command buffer (typically the one started by beginExternal(), |
6494 | // in case we are between beginExternal - endExternal inside a pass). |
6495 | |
6496 | if (useSecondaryCb && !secondaryCbs.isEmpty()) |
6497 | nativeHandlesStruct.commandBuffer = secondaryCbs.last(); |
6498 | else |
6499 | nativeHandlesStruct.commandBuffer = cb; |
6500 | |
6501 | return &nativeHandlesStruct; |
6502 | } |
6503 | |
6504 | QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi) |
6505 | : QRhiSwapChain(rhi), |
6506 | rtWrapper(rhi), |
6507 | cbWrapper(rhi) |
6508 | { |
6509 | } |
6510 | |
6511 | QVkSwapChain::~QVkSwapChain() |
6512 | { |
6513 | release(); |
6514 | } |
6515 | |
6516 | void QVkSwapChain::release() |
6517 | { |
6518 | if (sc == VK_NULL_HANDLE) |
6519 | return; |
6520 | |
6521 | QRHI_RES_RHI(QRhiVulkan); |
6522 | rhiD->swapchains.remove(value: this); |
6523 | rhiD->releaseSwapChainResources(swapChain: this); |
6524 | surface = lastConnectedSurface = VK_NULL_HANDLE; |
6525 | |
6526 | QRHI_PROF; |
6527 | QRHI_PROF_F(releaseSwapChain(this)); |
6528 | |
6529 | rhiD->unregisterResource(res: this); |
6530 | } |
6531 | |
6532 | QRhiCommandBuffer *QVkSwapChain::currentFrameCommandBuffer() |
6533 | { |
6534 | return &cbWrapper; |
6535 | } |
6536 | |
6537 | QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget() |
6538 | { |
6539 | return &rtWrapper; |
6540 | } |
6541 | |
6542 | QSize QVkSwapChain::surfacePixelSize() |
6543 | { |
6544 | if (!ensureSurface()) |
6545 | return QSize(); |
6546 | |
6547 | // The size from the QWindow may not exactly match the surface... so if a |
6548 | // size is reported from the surface, use that. |
6549 | VkSurfaceCapabilitiesKHR surfaceCaps; |
6550 | memset(s: &surfaceCaps, c: 0, n: sizeof(surfaceCaps)); |
6551 | QRHI_RES_RHI(QRhiVulkan); |
6552 | rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps); |
6553 | VkExtent2D bufferSize = surfaceCaps.currentExtent; |
6554 | if (bufferSize.width == uint32_t(-1)) { |
6555 | Q_ASSERT(bufferSize.height == uint32_t(-1)); |
6556 | return m_window->size() * m_window->devicePixelRatio(); |
6557 | } |
6558 | return QSize(int(bufferSize.width), int(bufferSize.height)); |
6559 | } |
6560 | |
6561 | QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor() |
6562 | { |
6563 | // not yet built so cannot rely on data computed in buildOrResize() |
6564 | |
6565 | if (!ensureSurface()) // make sure sampleCount and colorFormat reflect what was requested |
6566 | return nullptr; |
6567 | |
6568 | QRHI_RES_RHI(QRhiVulkan); |
6569 | QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi); |
6570 | if (!rhiD->createDefaultRenderPass(rpD: rp, |
6571 | hasDepthStencil: m_depthStencil != nullptr, |
6572 | samples, |
6573 | colorFormat)) |
6574 | { |
6575 | delete rp; |
6576 | return nullptr; |
6577 | } |
6578 | |
6579 | rp->ownsRp = true; |
6580 | rhiD->registerResource(res: rp); |
6581 | return rp; |
6582 | } |
6583 | |
6584 | static inline bool isSrgbFormat(VkFormat format) |
6585 | { |
6586 | switch (format) { |
6587 | case VK_FORMAT_R8_SRGB: |
6588 | case VK_FORMAT_R8G8_SRGB: |
6589 | case VK_FORMAT_R8G8B8_SRGB: |
6590 | case VK_FORMAT_B8G8R8_SRGB: |
6591 | case VK_FORMAT_R8G8B8A8_SRGB: |
6592 | case VK_FORMAT_B8G8R8A8_SRGB: |
6593 | case VK_FORMAT_A8B8G8R8_SRGB_PACK32: |
6594 | return true; |
6595 | default: |
6596 | return false; |
6597 | } |
6598 | } |
6599 | |
6600 | bool QVkSwapChain::ensureSurface() |
6601 | { |
6602 | // Do nothing when already done, however window may change so check the |
6603 | // surface is still the same. Some of the queries below are very expensive |
6604 | // with some implementations so it is important to do the rest only once |
6605 | // per surface. |
6606 | |
6607 | Q_ASSERT(m_window); |
6608 | VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(window: m_window); |
6609 | if (!surf) { |
6610 | qWarning(msg: "Failed to get surface for window" ); |
6611 | return false; |
6612 | } |
6613 | if (surface == surf) |
6614 | return true; |
6615 | |
6616 | surface = surf; |
6617 | |
6618 | QRHI_RES_RHI(QRhiVulkan); |
6619 | if (rhiD->gfxQueueFamilyIdx != -1) { |
6620 | if (!rhiD->inst->supportsPresent(physicalDevice: rhiD->physDev, queueFamilyIndex: uint32_t(rhiD->gfxQueueFamilyIdx), window: m_window)) { |
6621 | qWarning(msg: "Presenting not supported on this window" ); |
6622 | return false; |
6623 | } |
6624 | } |
6625 | |
6626 | if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR) { |
6627 | rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>( |
6628 | rhiD->inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfaceCapabilitiesKHR" )); |
6629 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>( |
6630 | rhiD->inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfaceFormatsKHR" )); |
6631 | rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>( |
6632 | rhiD->inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfacePresentModesKHR" )); |
6633 | if (!rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR |
6634 | || !rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR |
6635 | || !rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR) |
6636 | { |
6637 | qWarning(msg: "Physical device surface queries not available" ); |
6638 | return false; |
6639 | } |
6640 | } |
6641 | |
6642 | quint32 formatCount = 0; |
6643 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, nullptr); |
6644 | QVector<VkSurfaceFormatKHR> formats(formatCount); |
6645 | if (formatCount) |
6646 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, formats.data()); |
6647 | |
6648 | const bool srgbRequested = m_flags.testFlag(flag: sRGB); |
6649 | for (int i = 0; i < int(formatCount); ++i) { |
6650 | if (formats[i].format != VK_FORMAT_UNDEFINED && srgbRequested == isSrgbFormat(format: formats[i].format)) { |
6651 | colorFormat = formats[i].format; |
6652 | colorSpace = formats[i].colorSpace; |
6653 | break; |
6654 | } |
6655 | } |
6656 | |
6657 | samples = rhiD->effectiveSampleCount(sampleCount: m_sampleCount); |
6658 | |
6659 | quint32 presModeCount = 0; |
6660 | rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr); |
6661 | QVector<VkPresentModeKHR> presModes(presModeCount); |
6662 | rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, presModes.data()); |
6663 | supportedPresentationModes = presModes; |
6664 | |
6665 | return true; |
6666 | } |
6667 | |
6668 | bool QVkSwapChain::buildOrResize() |
6669 | { |
6670 | QRHI_RES_RHI(QRhiVulkan); |
6671 | const bool needsRegistration = !window || window != m_window; |
6672 | |
6673 | // Can be called multiple times due to window resizes - that is not the |
6674 | // same as a simple release+build (as with other resources). Thus no |
6675 | // release() here. See recreateSwapChain(). |
6676 | |
6677 | // except if the window actually changes |
6678 | if (window && window != m_window) |
6679 | release(); |
6680 | |
6681 | window = m_window; |
6682 | m_currentPixelSize = surfacePixelSize(); |
6683 | pixelSize = m_currentPixelSize; |
6684 | |
6685 | if (!rhiD->recreateSwapChain(swapChain: this)) { |
6686 | qWarning(msg: "Failed to create new swapchain" ); |
6687 | return false; |
6688 | } |
6689 | |
6690 | if (needsRegistration) |
6691 | rhiD->swapchains.insert(value: this); |
6692 | |
6693 | if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) { |
6694 | qWarning(msg: "Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems." , |
6695 | m_depthStencil->sampleCount(), m_sampleCount); |
6696 | } |
6697 | if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) { |
6698 | if (m_depthStencil->flags().testFlag(flag: QRhiRenderBuffer::UsedWithSwapChainOnly)) { |
6699 | m_depthStencil->setPixelSize(pixelSize); |
6700 | if (!m_depthStencil->build()) |
6701 | qWarning(msg: "Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d" , |
6702 | pixelSize.width(), pixelSize.height()); |
6703 | } else { |
6704 | qWarning(msg: "Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems." , |
6705 | m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(), |
6706 | pixelSize.width(), pixelSize.height()); |
6707 | } |
6708 | } |
6709 | |
6710 | if (!m_renderPassDesc) |
6711 | qWarning(msg: "QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()." ); |
6712 | |
6713 | rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); |
6714 | Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp); |
6715 | |
6716 | rtWrapper.d.pixelSize = pixelSize; |
6717 | rtWrapper.d.dpr = float(window->devicePixelRatio()); |
6718 | rtWrapper.d.sampleCount = samples; |
6719 | rtWrapper.d.colorAttCount = 1; |
6720 | if (m_depthStencil) { |
6721 | rtWrapper.d.dsAttCount = 1; |
6722 | ds = QRHI_RES(QVkRenderBuffer, m_depthStencil); |
6723 | } else { |
6724 | rtWrapper.d.dsAttCount = 0; |
6725 | ds = nullptr; |
6726 | } |
6727 | if (samples > VK_SAMPLE_COUNT_1_BIT) |
6728 | rtWrapper.d.resolveAttCount = 1; |
6729 | else |
6730 | rtWrapper.d.resolveAttCount = 0; |
6731 | |
6732 | for (int i = 0; i < bufferCount; ++i) { |
6733 | QVkSwapChain::ImageResources &image(imageRes[i]); |
6734 | VkImageView views[3] = { // color, ds, resolve |
6735 | samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView, |
6736 | ds ? ds->imageView : VK_NULL_HANDLE, |
6737 | samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE |
6738 | }; |
6739 | |
6740 | VkFramebufferCreateInfo fbInfo; |
6741 | memset(s: &fbInfo, c: 0, n: sizeof(fbInfo)); |
6742 | fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; |
6743 | fbInfo.renderPass = rtWrapper.d.rp->rp; |
6744 | fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount); |
6745 | fbInfo.pAttachments = views; |
6746 | fbInfo.width = uint32_t(pixelSize.width()); |
6747 | fbInfo.height = uint32_t(pixelSize.height()); |
6748 | fbInfo.layers = 1; |
6749 | |
6750 | VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb); |
6751 | if (err != VK_SUCCESS) { |
6752 | qWarning(msg: "Failed to create framebuffer: %d" , err); |
6753 | return false; |
6754 | } |
6755 | } |
6756 | |
6757 | frameCount = 0; |
6758 | |
6759 | QRHI_PROF; |
6760 | QRHI_PROF_F(resizeSwapChain(this, QVK_FRAMES_IN_FLIGHT, samples > VK_SAMPLE_COUNT_1_BIT ? QVK_FRAMES_IN_FLIGHT : 0, samples)); |
6761 | |
6762 | if (needsRegistration) |
6763 | rhiD->registerResource(res: this); |
6764 | |
6765 | return true; |
6766 | } |
6767 | |
6768 | QT_END_NAMESPACE |
6769 | |