1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qrhivulkan_p.h" |
5 | #include <qpa/qplatformvulkaninstance.h> |
6 | |
7 | #define VMA_IMPLEMENTATION |
8 | #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 |
9 | #define VMA_STATIC_VULKAN_FUNCTIONS 0 |
10 | #define VMA_RECORDING_ENABLED 0 |
11 | #define VMA_DEDICATED_ALLOCATION 0 |
12 | #ifdef QT_DEBUG |
13 | #define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 |
14 | #endif |
15 | QT_WARNING_PUSH |
16 | QT_WARNING_DISABLE_GCC("-Wsuggest-override" ) |
17 | #if defined(Q_CC_CLANG) && Q_CC_CLANG >= 1100 |
18 | QT_WARNING_DISABLE_CLANG("-Wdeprecated-copy" ) |
19 | #endif |
20 | #include "vk_mem_alloc.h" |
21 | QT_WARNING_POP |
22 | |
23 | #include <qmath.h> |
24 | #include <QVulkanFunctions> |
25 | #include <QtGui/qwindow.h> |
26 | #include <optional> |
27 | |
28 | QT_BEGIN_NAMESPACE |
29 | |
30 | /* |
31 | Vulkan 1.0 backend. Provides a double-buffered swapchain that throttles the |
32 | rendering thread to vsync. Textures and "static" buffers are device local, |
33 | and a separate, host visible staging buffer is used to upload data to them. |
34 | "Dynamic" buffers are in host visible memory and are duplicated (since there |
35 | can be 2 frames in flight). This is handled transparently to the application. |
36 | |
37 | Barriers are generated automatically for each render or compute pass, based |
38 | on the resources that are used in that pass (in QRhiShaderResourceBindings, |
39 | vertex inputs, etc.). This implies deferring the recording of the command |
40 | buffer since the barriers have to be placed at the right place (before the |
41 | pass), and that can only be done once we know all the things the pass does. |
42 | |
43 | This in turn has implications for integrating external commands |
44 | (beginExternal() - direct Vulkan calls - endExternal()) because that is |
45 | incompatible with this approach by nature. Therefore we support another mode |
46 | of operation, where each render or compute pass uses one or more secondary |
47 | command buffers (recorded right away), with each beginExternal() leading to |
48 | closing the current secondary cb, creating a new secondary cb for the |
49 | external content, and then starting yet another one in endExternal() for |
50 | whatever comes afterwards in the pass. This way the primary command buffer |
51 | only has vkCmdExecuteCommand(s) within a renderpass instance |
52 | (Begin-EndRenderPass). (i.e. our only subpass is then |
53 | VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS instead of |
54 | VK_SUBPASS_CONTENTS_INLINE) |
55 | |
56 | The command buffer management mode is decided on a per frame basis, |
57 | controlled by the ExternalContentsInPass flag of beginFrame(). |
58 | */ |
59 | |
60 | /*! |
61 | \class QRhiVulkanInitParams |
62 | \inmodule QtGuiPrivate |
63 | \inheaderfile rhi/qrhi.h |
64 | \since 6.6 |
65 | \brief Vulkan specific initialization parameters. |
66 | |
67 | \note This is a RHI API with limited compatibility guarantees, see \l QRhi |
68 | for details. |
69 | |
70 | A Vulkan-based QRhi needs at minimum a valid QVulkanInstance. It is up to |
71 | the user to ensure this is available and initialized. This is typically |
72 | done in main() similarly to the following: |
73 | |
74 | \badcode |
75 | int main(int argc, char **argv) |
76 | { |
77 | ... |
78 | |
79 | QVulkanInstance inst; |
80 | inst.setLayers({ "VK_LAYER_KHRONOS_validation" }); // for debugging only, not for release builds |
81 | inst.setExtensions(QRhiVulkanInitParams::preferredInstanceExtensions()); |
82 | if (!inst.create()) |
83 | qFatal("Vulkan not available"); |
84 | |
85 | ... |
86 | } |
87 | \endcode |
88 | |
89 | This example enables the |
90 | \l{https://github.com/KhronosGroup/Vulkan-ValidationLayers}{Vulkan |
91 | validation layers}, when they are available, and also enables the |
92 | instance-level extensions QRhi reports as desirable (such as, |
93 | VK_KHR_get_physical_device_properties2), as long as they are supported by |
94 | the Vulkan implementation at run time. |
95 | |
96 | The former is optional, and is useful during the development phase |
97 | QVulkanInstance conveniently redirects messages and warnings to qDebug. |
98 | Avoid enabling it in production builds, however. The latter is strongly |
99 | recommended, and is important in order to make certain features functional |
100 | (for example, QRhi::CustomInstanceStepRate). |
101 | |
102 | Once this is done, a Vulkan-based QRhi can be created by passing the |
103 | instance and a QWindow with its surface type set to |
104 | QSurface::VulkanSurface: |
105 | |
106 | \badcode |
107 | QRhiVulkanInitParams params; |
108 | params.inst = vulkanInstance; |
109 | params.window = window; |
110 | rhi = QRhi::create(QRhi::Vulkan, ¶ms); |
111 | \endcode |
112 | |
113 | The window is optional and can be omitted. This is not recommended however |
114 | because there is then no way to ensure presenting is supported while |
115 | choosing a graphics queue. |
116 | |
117 | \note Even when a window is specified, QRhiSwapChain objects can be created |
118 | for other windows as well, as long as they all have their |
119 | QWindow::surfaceType() set to QSurface::VulkanSurface. |
120 | |
121 | To request additional extensions to be enabled on the Vulkan device, list them |
122 | in deviceExtensions. This can be relevant when integrating with native Vulkan |
123 | rendering code. |
124 | |
125 | It is expected that the backend's desired list of instance extensions will |
126 | be queried by calling the static function preferredInstanceExtensions() |
127 | before initializing a QVulkanInstance. The returned list can be safely |
128 | passed to QVulkanInstance::setExtensions() as-is, because unsupported |
129 | extensions are filtered out automatically. If this is not done, certain |
130 | features, such as QRhi::CustomInstanceStepRate may be reported as |
131 | unsupported even when the Vulkan implementation on the system has support |
132 | for the relevant functionality. |
133 | |
134 | For full functionality the QVulkanInstance needs to have API 1.1 enabled, |
135 | when available. This means calling QVulkanInstance::setApiVersion() with |
136 | 1.1 or higher whenever QVulkanInstance::supportedApiVersion() reports that |
137 | at least Vulkan 1.1 is supported. If this is not done, certain features, |
138 | such as QRhi::RenderTo3DTextureSlice may be reported as unsupported even |
139 | when the Vulkan implementation on the system supports Vulkan 1.1 or newer. |
140 | |
141 | \section2 Working with existing Vulkan devices |
142 | |
143 | When interoperating with another graphics engine, it may be necessary to |
144 | get a QRhi instance that uses the same Vulkan device. This can be achieved |
145 | by passing a pointer to a QRhiVulkanNativeHandles to QRhi::create(). |
146 | |
147 | The physical device must always be set to a non-null value. If the |
148 | intention is to just specify a physical device, but leave the rest of the |
149 | VkDevice and queue creation to QRhi, then no other members need to be |
150 | filled out in the struct. For example, this is the case when working with |
151 | OpenXR. |
152 | |
153 | To adopt an existing \c VkDevice, the device field must be set to a |
154 | non-null value as well. In addition, the graphics queue family index is |
155 | required. The queue index is optional, as the default of 0 is often |
156 | suitable. |
157 | |
158 | Optionally, an existing command pool object can be specified as well. Also |
159 | optionally, vmemAllocator can be used to share the same |
160 | \l{https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator}{Vulkan |
161 | memory allocator} between two QRhi instances. |
162 | |
163 | The QRhi does not take ownership of any of the external objects. |
164 | |
165 | Applications are encouraged to query the list of desired device extensions |
166 | by calling the static function preferredExtensionsForImportedDevice(), and |
167 | enable them on the VkDevice. Otherwise certain QRhi features may not be |
168 | available. |
169 | */ |
170 | |
171 | /*! |
172 | \variable QRhiVulkanInitParams::inst |
173 | |
174 | The QVulkanInstance that has already been successfully |
175 | \l{QVulkanInstance::create()}{created}, required. |
176 | */ |
177 | |
178 | /*! |
179 | \variable QRhiVulkanInitParams::window |
180 | |
181 | Optional, but recommended when targeting a QWindow. |
182 | */ |
183 | |
184 | /*! |
185 | \variable QRhiVulkanInitParams::deviceExtensions |
186 | |
187 | Optional, empty by default. The list of Vulkan device extensions to enable. |
188 | Unsupported extensions are ignored gracefully. |
189 | */ |
190 | |
191 | /*! |
192 | \class QRhiVulkanNativeHandles |
193 | \inmodule QtGuiPrivate |
194 | \inheaderfile rhi/qrhi.h |
195 | \since 6.6 |
196 | \brief Collects device, queue, and other Vulkan objects that are used by the QRhi. |
197 | |
198 | \note Ownership of the Vulkan objects is never transferred. |
199 | |
200 | \note This is a RHI API with limited compatibility guarantees, see \l QRhi |
201 | for details. |
202 | */ |
203 | |
204 | /*! |
205 | \variable QRhiVulkanNativeHandles::physDev |
206 | |
207 | When different from \nullptr, specifies the Vulkan physical device to use. |
208 | */ |
209 | |
210 | /*! |
211 | \variable QRhiVulkanNativeHandles::dev |
212 | |
213 | When wanting to import not just a physical device, but also use an already |
214 | existing VkDevice, set this and the graphics queue index and family index. |
215 | */ |
216 | |
217 | /*! |
218 | \variable QRhiVulkanNativeHandles::gfxQueueFamilyIdx |
219 | |
220 | Graphics queue family index. |
221 | */ |
222 | |
223 | /*! |
224 | \variable QRhiVulkanNativeHandles::gfxQueueIdx |
225 | |
226 | Graphics queue index. |
227 | */ |
228 | |
229 | /*! |
230 | \variable QRhiVulkanNativeHandles::vmemAllocator |
231 | |
232 | Relevant only when importing an existing memory allocator object, |
233 | leave it set to \nullptr otherwise. |
234 | */ |
235 | |
236 | /*! |
237 | \variable QRhiVulkanNativeHandles::gfxQueue |
238 | |
239 | Output only, not used by QRhi::create(), only set by the |
240 | QRhi::nativeHandles() accessor. The graphics VkQueue used by the QRhi. |
241 | */ |
242 | |
243 | /*! |
244 | \variable QRhiVulkanNativeHandles::inst |
245 | |
246 | Output only, not used by QRhi::create(), only set by the |
247 | QRhi::nativeHandles() accessor. The QVulkanInstance used by the QRhi. |
248 | */ |
249 | |
250 | /*! |
251 | \class QRhiVulkanCommandBufferNativeHandles |
252 | \inmodule QtGuiPrivate |
253 | \inheaderfile rhi/qrhi.h |
254 | \since 6.6 |
255 | \brief Holds the Vulkan command buffer object that is backing a QRhiCommandBuffer. |
256 | |
257 | \note The Vulkan command buffer object is only guaranteed to be valid, and |
258 | in recording state, while recording a frame. That is, between a |
259 | \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or |
260 | \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} - |
261 | \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair. |
262 | |
263 | \note This is a RHI API with limited compatibility guarantees, see \l QRhi |
264 | for details. |
265 | */ |
266 | |
267 | /*! |
268 | \variable QRhiVulkanCommandBufferNativeHandles::commandBuffer |
269 | |
270 | The VkCommandBuffer object. |
271 | */ |
272 | |
273 | /*! |
274 | \class QRhiVulkanRenderPassNativeHandles |
275 | \inmodule QtGuiPrivate |
276 | \inheaderfile rhi/qrhi.h |
277 | \since 6.6 |
278 | \brief Holds the Vulkan render pass object backing a QRhiRenderPassDescriptor. |
279 | |
280 | \note This is a RHI API with limited compatibility guarantees, see \l QRhi |
281 | for details. |
282 | */ |
283 | |
284 | /*! |
285 | \variable QRhiVulkanRenderPassNativeHandles::renderPass |
286 | |
287 | The VkRenderPass object. |
288 | */ |
289 | |
290 | template <class Int> |
291 | inline Int aligned(Int v, Int byteAlign) |
292 | { |
293 | return (v + byteAlign - 1) & ~(byteAlign - 1); |
294 | } |
295 | |
296 | static QVulkanInstance *globalVulkanInstance; |
297 | |
298 | static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetInstanceProcAddr(VkInstance, const char *pName) |
299 | { |
300 | return globalVulkanInstance->getInstanceProcAddr(name: pName); |
301 | } |
302 | |
303 | static VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL wrap_vkGetDeviceProcAddr(VkDevice device, const char *pName) |
304 | { |
305 | return globalVulkanInstance->functions()->vkGetDeviceProcAddr(device, pName); |
306 | } |
307 | |
308 | static inline VmaAllocation toVmaAllocation(QVkAlloc a) |
309 | { |
310 | return reinterpret_cast<VmaAllocation>(a); |
311 | } |
312 | |
313 | static inline VmaAllocator toVmaAllocator(QVkAllocator a) |
314 | { |
315 | return reinterpret_cast<VmaAllocator>(a); |
316 | } |
317 | |
318 | /*! |
319 | \return the list of instance extensions that are expected to be enabled on |
320 | the QVulkanInstance that is used for the Vulkan-based QRhi. |
321 | |
322 | The returned list can be safely passed to QVulkanInstance::setExtensions() |
323 | as-is, because unsupported extensions are filtered out automatically. |
324 | */ |
325 | QByteArrayList QRhiVulkanInitParams::preferredInstanceExtensions() |
326 | { |
327 | return { |
328 | QByteArrayLiteral("VK_KHR_get_physical_device_properties2" ) |
329 | }; |
330 | } |
331 | |
332 | /*! |
333 | \return the list of device extensions that are expected to be enabled on the |
334 | \c VkDevice when creating a Vulkan-based QRhi with an externally created |
335 | \c VkDevice object. |
336 | */ |
337 | QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice() |
338 | { |
339 | return { |
340 | QByteArrayLiteral("VK_KHR_swapchain" ), |
341 | QByteArrayLiteral("VK_EXT_vertex_attribute_divisor" ), |
342 | QByteArrayLiteral("VK_KHR_create_renderpass2" ), |
343 | QByteArrayLiteral("VK_KHR_depth_stencil_resolve" ) |
344 | }; |
345 | } |
346 | |
347 | QRhiVulkan::QRhiVulkan(QRhiVulkanInitParams *params, QRhiVulkanNativeHandles *importParams) |
348 | : ofr(this) |
349 | { |
350 | inst = params->inst; |
351 | maybeWindow = params->window; // may be null |
352 | requestedDeviceExtensions = params->deviceExtensions; |
353 | |
354 | if (importParams) { |
355 | physDev = importParams->physDev; |
356 | dev = importParams->dev; |
357 | if (dev && physDev) { |
358 | importedDevice = true; |
359 | gfxQueueFamilyIdx = importParams->gfxQueueFamilyIdx; |
360 | gfxQueueIdx = importParams->gfxQueueIdx; |
361 | // gfxQueue is output only, no point in accepting it as input |
362 | if (importParams->vmemAllocator) { |
363 | importedAllocator = true; |
364 | allocator = importParams->vmemAllocator; |
365 | } |
366 | } |
367 | } |
368 | } |
369 | |
370 | static bool qvk_debug_filter(QVulkanInstance::DebugMessageSeverityFlags severity, |
371 | QVulkanInstance::DebugMessageTypeFlags type, |
372 | const void *callbackData) |
373 | { |
374 | Q_UNUSED(severity); |
375 | Q_UNUSED(type); |
376 | #ifdef VK_EXT_debug_utils |
377 | const VkDebugUtilsMessengerCallbackDataEXT *d = static_cast<const VkDebugUtilsMessengerCallbackDataEXT *>(callbackData); |
378 | |
379 | // Filter out certain misleading validation layer messages, as per |
380 | // VulkanMemoryAllocator documentation. |
381 | if (strstr(haystack: d->pMessage, needle: "Mapping an image with layout" ) |
382 | && strstr(haystack: d->pMessage, needle: "can result in undefined behavior if this memory is used by the device" )) |
383 | { |
384 | return true; |
385 | } |
386 | |
387 | // In certain cases allocateDescriptorSet() will attempt to allocate from a |
388 | // pool that does not have enough descriptors of a certain type. This makes |
389 | // the validation layer shout. However, this is not an error since we will |
390 | // then move on to another pool. If there is a real error, a qWarning |
391 | // message is shown by allocateDescriptorSet(), so the validation warning |
392 | // does not have any value and is just noise. |
393 | if (strstr(haystack: d->pMessage, needle: "VUID-VkDescriptorSetAllocateInfo-descriptorPool-00307" )) |
394 | return true; |
395 | #else |
396 | Q_UNUSED(callbackData); |
397 | #endif |
398 | return false; |
399 | } |
400 | |
401 | static inline QRhiDriverInfo::DeviceType toRhiDeviceType(VkPhysicalDeviceType type) |
402 | { |
403 | switch (type) { |
404 | case VK_PHYSICAL_DEVICE_TYPE_OTHER: |
405 | return QRhiDriverInfo::UnknownDevice; |
406 | case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: |
407 | return QRhiDriverInfo::IntegratedDevice; |
408 | case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: |
409 | return QRhiDriverInfo::DiscreteDevice; |
410 | case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: |
411 | return QRhiDriverInfo::VirtualDevice; |
412 | case VK_PHYSICAL_DEVICE_TYPE_CPU: |
413 | return QRhiDriverInfo::CpuDevice; |
414 | default: |
415 | return QRhiDriverInfo::UnknownDevice; |
416 | } |
417 | } |
418 | |
419 | bool QRhiVulkan::create(QRhi::Flags flags) |
420 | { |
421 | Q_ASSERT(inst); |
422 | if (!inst->isValid()) { |
423 | qWarning(msg: "Vulkan instance is not valid" ); |
424 | return false; |
425 | } |
426 | |
427 | rhiFlags = flags; |
428 | qCDebug(QRHI_LOG_INFO, "Initializing QRhi Vulkan backend %p with flags %d" , this, int(rhiFlags)); |
429 | |
430 | globalVulkanInstance = inst; // used for function resolving in vkmemalloc callbacks |
431 | f = inst->functions(); |
432 | if (QRHI_LOG_INFO().isEnabled(type: QtDebugMsg)) { |
433 | qCDebug(QRHI_LOG_INFO, "Enabled instance extensions:" ); |
434 | for (const char *ext : inst->extensions()) |
435 | qCDebug(QRHI_LOG_INFO, " %s" , ext); |
436 | } |
437 | |
438 | caps = {}; |
439 | caps.debugUtils = inst->extensions().contains(QByteArrayLiteral("VK_EXT_debug_utils" )); |
440 | |
441 | QList<VkQueueFamilyProperties> queueFamilyProps; |
442 | auto queryQueueFamilyProps = [this, &queueFamilyProps] { |
443 | uint32_t queueCount = 0; |
444 | f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr); |
445 | queueFamilyProps.resize(size: int(queueCount)); |
446 | f->vkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueFamilyProps.data()); |
447 | }; |
448 | |
449 | // Choose a physical device, unless one was provided in importParams. |
450 | if (!physDev) { |
451 | uint32_t physDevCount = 0; |
452 | f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, nullptr); |
453 | if (!physDevCount) { |
454 | qWarning(msg: "No physical devices" ); |
455 | return false; |
456 | } |
457 | QVarLengthArray<VkPhysicalDevice, 4> physDevs(physDevCount); |
458 | VkResult err = f->vkEnumeratePhysicalDevices(inst->vkInstance(), &physDevCount, physDevs.data()); |
459 | if (err != VK_SUCCESS || !physDevCount) { |
460 | qWarning(msg: "Failed to enumerate physical devices: %d" , err); |
461 | return false; |
462 | } |
463 | |
464 | int physDevIndex = -1; |
465 | int requestedPhysDevIndex = -1; |
466 | if (qEnvironmentVariableIsSet(varName: "QT_VK_PHYSICAL_DEVICE_INDEX" )) |
467 | requestedPhysDevIndex = qEnvironmentVariableIntValue(varName: "QT_VK_PHYSICAL_DEVICE_INDEX" ); |
468 | |
469 | if (requestedPhysDevIndex < 0 && flags.testFlag(flag: QRhi::PreferSoftwareRenderer)) { |
470 | for (int i = 0; i < int(physDevCount); ++i) { |
471 | f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties); |
472 | if (physDevProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) { |
473 | requestedPhysDevIndex = i; |
474 | break; |
475 | } |
476 | } |
477 | } |
478 | |
479 | for (int i = 0; i < int(physDevCount); ++i) { |
480 | f->vkGetPhysicalDeviceProperties(physDevs[i], &physDevProperties); |
481 | qCDebug(QRHI_LOG_INFO, "Physical device %d: '%s' %d.%d.%d (api %d.%d.%d vendor 0x%X device 0x%X type %d)" , |
482 | i, |
483 | physDevProperties.deviceName, |
484 | VK_VERSION_MAJOR(physDevProperties.driverVersion), |
485 | VK_VERSION_MINOR(physDevProperties.driverVersion), |
486 | VK_VERSION_PATCH(physDevProperties.driverVersion), |
487 | VK_VERSION_MAJOR(physDevProperties.apiVersion), |
488 | VK_VERSION_MINOR(physDevProperties.apiVersion), |
489 | VK_VERSION_PATCH(physDevProperties.apiVersion), |
490 | physDevProperties.vendorID, |
491 | physDevProperties.deviceID, |
492 | physDevProperties.deviceType); |
493 | if (physDevIndex < 0 && (requestedPhysDevIndex < 0 || requestedPhysDevIndex == int(i))) { |
494 | physDevIndex = i; |
495 | qCDebug(QRHI_LOG_INFO, " using this physical device" ); |
496 | } |
497 | } |
498 | |
499 | if (physDevIndex < 0) { |
500 | qWarning(msg: "No matching physical device" ); |
501 | return false; |
502 | } |
503 | physDev = physDevs[physDevIndex]; |
504 | f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties); |
505 | } else { |
506 | f->vkGetPhysicalDeviceProperties(physDev, &physDevProperties); |
507 | qCDebug(QRHI_LOG_INFO, "Using imported physical device '%s' %d.%d.%d (api %d.%d.%d vendor 0x%X device 0x%X type %d)" , |
508 | physDevProperties.deviceName, |
509 | VK_VERSION_MAJOR(physDevProperties.driverVersion), |
510 | VK_VERSION_MINOR(physDevProperties.driverVersion), |
511 | VK_VERSION_PATCH(physDevProperties.driverVersion), |
512 | VK_VERSION_MAJOR(physDevProperties.apiVersion), |
513 | VK_VERSION_MINOR(physDevProperties.apiVersion), |
514 | VK_VERSION_PATCH(physDevProperties.apiVersion), |
515 | physDevProperties.vendorID, |
516 | physDevProperties.deviceID, |
517 | physDevProperties.deviceType); |
518 | } |
519 | |
520 | caps.apiVersion = inst->apiVersion(); |
521 | |
522 | // Check the physical device API version against the instance API version, |
523 | // they do not have to match, which means whatever version was set in the |
524 | // QVulkanInstance may not be legally used with a given device if the |
525 | // physical device has a lower version. |
526 | const QVersionNumber physDevApiVersion(VK_VERSION_MAJOR(physDevProperties.apiVersion), |
527 | VK_VERSION_MINOR(physDevProperties.apiVersion)); // patch version left out intentionally |
528 | if (physDevApiVersion < caps.apiVersion) { |
529 | qCDebug(QRHI_LOG_INFO) << "Instance has api version" << caps.apiVersion |
530 | << "whereas the chosen physical device has" << physDevApiVersion |
531 | << "- restricting to the latter" ; |
532 | caps.apiVersion = physDevApiVersion; |
533 | } |
534 | |
535 | driverInfoStruct.deviceName = QByteArray(physDevProperties.deviceName); |
536 | driverInfoStruct.deviceId = physDevProperties.deviceID; |
537 | driverInfoStruct.vendorId = physDevProperties.vendorID; |
538 | driverInfoStruct.deviceType = toRhiDeviceType(type: physDevProperties.deviceType); |
539 | |
540 | bool featuresQueried = false; |
541 | #ifdef VK_VERSION_1_1 |
542 | VkPhysicalDeviceFeatures2 physDevFeaturesChainable = {}; |
543 | physDevFeaturesChainable.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; |
544 | #endif |
545 | |
546 | // Vulkan >=1.2 headers at build time, >=1.2 implementation at run time |
547 | #ifdef VK_VERSION_1_2 |
548 | if (!featuresQueried) { |
549 | // Vulkan11Features, Vulkan12Features, etc. are only in Vulkan 1.2 and newer. |
550 | if (caps.apiVersion >= QVersionNumber(1, 2)) { |
551 | physDevFeatures11IfApi12OrNewer = {}; |
552 | physDevFeatures11IfApi12OrNewer.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; |
553 | physDevFeatures12 = {}; |
554 | physDevFeatures12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; |
555 | #ifdef VK_VERSION_1_3 |
556 | physDevFeatures13 = {}; |
557 | physDevFeatures13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; |
558 | #endif |
559 | physDevFeaturesChainable.pNext = &physDevFeatures11IfApi12OrNewer; |
560 | physDevFeatures11IfApi12OrNewer.pNext = &physDevFeatures12; |
561 | #ifdef VK_VERSION_1_3 |
562 | if (caps.apiVersion >= QVersionNumber(1, 3)) |
563 | physDevFeatures12.pNext = &physDevFeatures13; |
564 | #endif |
565 | f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable); |
566 | memcpy(dest: &physDevFeatures, src: &physDevFeaturesChainable.features, n: sizeof(VkPhysicalDeviceFeatures)); |
567 | featuresQueried = true; |
568 | } |
569 | } |
570 | #endif // VK_VERSION_1_2 |
571 | |
572 | // Vulkan >=1.1 headers at build time, 1.1 implementation at run time |
573 | #ifdef VK_VERSION_1_1 |
574 | if (!featuresQueried) { |
575 | // Vulkan versioning nightmares: if the runtime API version is 1.1, |
576 | // there is no Vulkan11Features (introduced in 1.2+, the headers might |
577 | // have the types and structs, but the Vulkan implementation version at |
578 | // run time is what matters). But there are individual feature structs. |
579 | // For multiview, it is important to get this right since at the time of |
580 | // writing Quest 3 Android is a Vulkan 1.1 implementation at run time on |
581 | // the headset. |
582 | if (caps.apiVersion == QVersionNumber(1, 1)) { |
583 | multiviewFeaturesIfApi11 = {}; |
584 | multiviewFeaturesIfApi11.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; |
585 | physDevFeaturesChainable.pNext = &multiviewFeaturesIfApi11; |
586 | f->vkGetPhysicalDeviceFeatures2(physDev, &physDevFeaturesChainable); |
587 | memcpy(dest: &physDevFeatures, src: &physDevFeaturesChainable.features, n: sizeof(VkPhysicalDeviceFeatures)); |
588 | featuresQueried = true; |
589 | } |
590 | } |
591 | #endif |
592 | |
593 | if (!featuresQueried) { |
594 | // If the API version at run time is 1.0 (or we are building with |
595 | // ancient 1.0 headers), then do the Vulkan 1.0 query. |
596 | f->vkGetPhysicalDeviceFeatures(physDev, &physDevFeatures); |
597 | featuresQueried = true; |
598 | } |
599 | |
600 | // Choose queue and create device, unless the device was specified in importParams. |
601 | if (!importedDevice) { |
602 | // We only support combined graphics+present queues. When it comes to |
603 | // compute, only combined graphics+compute queue is used, compute gets |
604 | // disabled otherwise. |
605 | std::optional<uint32_t> gfxQueueFamilyIdxOpt; |
606 | std::optional<uint32_t> computelessGfxQueueCandidateIdxOpt; |
607 | queryQueueFamilyProps(); |
608 | const uint32_t queueFamilyCount = uint32_t(queueFamilyProps.size()); |
609 | for (uint32_t i = 0; i < queueFamilyCount; ++i) { |
610 | qCDebug(QRHI_LOG_INFO, "queue family %u: flags=0x%x count=%u" , |
611 | i, queueFamilyProps[i].queueFlags, queueFamilyProps[i].queueCount); |
612 | if (!gfxQueueFamilyIdxOpt.has_value() |
613 | && (queueFamilyProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) |
614 | && (!maybeWindow || inst->supportsPresent(physicalDevice: physDev, queueFamilyIndex: i, window: maybeWindow))) |
615 | { |
616 | if (queueFamilyProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) |
617 | gfxQueueFamilyIdxOpt = i; |
618 | else if (!computelessGfxQueueCandidateIdxOpt.has_value()) |
619 | computelessGfxQueueCandidateIdxOpt = i; |
620 | } |
621 | } |
622 | if (gfxQueueFamilyIdxOpt.has_value()) { |
623 | gfxQueueFamilyIdx = gfxQueueFamilyIdxOpt.value(); |
624 | } else { |
625 | if (computelessGfxQueueCandidateIdxOpt.has_value()) { |
626 | gfxQueueFamilyIdx = computelessGfxQueueCandidateIdxOpt.value(); |
627 | } else { |
628 | qWarning(msg: "No graphics (or no graphics+present) queue family found" ); |
629 | return false; |
630 | } |
631 | } |
632 | |
633 | VkDeviceQueueCreateInfo queueInfo = {}; |
634 | const float prio[] = { 0 }; |
635 | queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; |
636 | queueInfo.queueFamilyIndex = gfxQueueFamilyIdx; |
637 | queueInfo.queueCount = 1; |
638 | queueInfo.pQueuePriorities = prio; |
639 | |
640 | QList<const char *> devLayers; |
641 | if (inst->layers().contains(t: "VK_LAYER_KHRONOS_validation" )) |
642 | devLayers.append(t: "VK_LAYER_KHRONOS_validation" ); |
643 | |
644 | QVulkanInfoVector<QVulkanExtension> devExts; |
645 | uint32_t devExtCount = 0; |
646 | f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, nullptr); |
647 | if (devExtCount) { |
648 | QList<VkExtensionProperties> extProps(devExtCount); |
649 | f->vkEnumerateDeviceExtensionProperties(physDev, nullptr, &devExtCount, extProps.data()); |
650 | for (const VkExtensionProperties &p : std::as_const(t&: extProps)) |
651 | devExts.append(t: { .name: p.extensionName, .version: p.specVersion }); |
652 | } |
653 | qCDebug(QRHI_LOG_INFO, "%d device extensions available" , int(devExts.size())); |
654 | |
655 | QList<const char *> requestedDevExts; |
656 | requestedDevExts.append(t: "VK_KHR_swapchain" ); |
657 | |
658 | const bool hasPhysDevProp2 = inst->extensions().contains(QByteArrayLiteral("VK_KHR_get_physical_device_properties2" )); |
659 | |
660 | if (devExts.contains(QByteArrayLiteral("VK_KHR_portability_subset" ))) { |
661 | if (hasPhysDevProp2) { |
662 | requestedDevExts.append(t: "VK_KHR_portability_subset" ); |
663 | } else { |
664 | qWarning(msg: "VK_KHR_portability_subset should be enabled on the device " |
665 | "but the instance does not have VK_KHR_get_physical_device_properties2 enabled. " |
666 | "Expect problems." ); |
667 | } |
668 | } |
669 | |
670 | #ifdef VK_EXT_vertex_attribute_divisor |
671 | if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) { |
672 | if (hasPhysDevProp2) { |
673 | requestedDevExts.append(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME); |
674 | caps.vertexAttribDivisor = true; |
675 | } |
676 | } |
677 | #endif |
678 | |
679 | #ifdef VK_KHR_create_renderpass2 |
680 | if (devExts.contains(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) { |
681 | requestedDevExts.append(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); |
682 | caps.renderPass2KHR = true; |
683 | } |
684 | #endif |
685 | |
686 | #ifdef VK_KHR_depth_stencil_resolve |
687 | if (devExts.contains(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME)) { |
688 | requestedDevExts.append(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); |
689 | caps.depthStencilResolveKHR = true; |
690 | } |
691 | #endif |
692 | |
693 | for (const QByteArray &ext : requestedDeviceExtensions) { |
694 | if (!ext.isEmpty() && !requestedDevExts.contains(t: ext)) { |
695 | if (devExts.contains(name: ext)) { |
696 | requestedDevExts.append(t: ext.constData()); |
697 | } else { |
698 | qWarning(msg: "Device extension %s requested in QRhiVulkanInitParams is not supported" , |
699 | ext.constData()); |
700 | } |
701 | } |
702 | } |
703 | |
704 | QByteArrayList envExtList = qgetenv(varName: "QT_VULKAN_DEVICE_EXTENSIONS" ).split(sep: ';'); |
705 | for (const QByteArray &ext : envExtList) { |
706 | if (!ext.isEmpty() && !requestedDevExts.contains(t: ext)) { |
707 | if (devExts.contains(name: ext)) { |
708 | requestedDevExts.append(t: ext.constData()); |
709 | } else { |
710 | qWarning(msg: "Device extension %s requested in QT_VULKAN_DEVICE_EXTENSIONS is not supported" , |
711 | ext.constData()); |
712 | } |
713 | } |
714 | } |
715 | |
716 | if (QRHI_LOG_INFO().isEnabled(type: QtDebugMsg)) { |
717 | qCDebug(QRHI_LOG_INFO, "Enabling device extensions:" ); |
718 | for (const char *ext : requestedDevExts) |
719 | qCDebug(QRHI_LOG_INFO, " %s" , ext); |
720 | } |
721 | |
722 | VkDeviceCreateInfo devInfo = {}; |
723 | devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; |
724 | devInfo.queueCreateInfoCount = 1; |
725 | devInfo.pQueueCreateInfos = &queueInfo; |
726 | devInfo.enabledLayerCount = uint32_t(devLayers.size()); |
727 | devInfo.ppEnabledLayerNames = devLayers.constData(); |
728 | devInfo.enabledExtensionCount = uint32_t(requestedDevExts.size()); |
729 | devInfo.ppEnabledExtensionNames = requestedDevExts.constData(); |
730 | |
731 | // Enable all features that are reported as supported, except |
732 | // robustness because that potentially affects performance. |
733 | // |
734 | // Enabling all features mainly serves third-party renderers that may |
735 | // use the VkDevice created here. For the record, the backend here |
736 | // optionally relies on the following features, meaning just for our |
737 | // (QRhi/Quick/Quick 3D) purposes it would be sufficient to |
738 | // enable-if-supported only the following: |
739 | // |
740 | // wideLines, largePoints, fillModeNonSolid, |
741 | // tessellationShader, geometryShader |
742 | // textureCompressionETC2, textureCompressionASTC_LDR, textureCompressionBC |
743 | |
744 | #ifdef VK_VERSION_1_1 |
745 | physDevFeaturesChainable.features.robustBufferAccess = VK_FALSE; |
746 | #endif |
747 | #ifdef VK_VERSION_1_3 |
748 | physDevFeatures13.robustImageAccess = VK_FALSE; |
749 | #endif |
750 | |
751 | #ifdef VK_VERSION_1_1 |
752 | if (caps.apiVersion >= QVersionNumber(1, 1)) { |
753 | // For a >=1.2 implementation at run time, this will enable all |
754 | // (1.0-1.3) features reported as supported, except the ones we turn |
755 | // off explicitly above. For a 1.1 implementation at run time, this |
756 | // only enables the 1.0 and multiview features reported as |
757 | // supported. We will not be bothering with the Vulkan 1.1 |
758 | // individual feature struct nonsense. |
759 | devInfo.pNext = &physDevFeaturesChainable; |
760 | } else |
761 | #endif |
762 | { |
763 | physDevFeatures.robustBufferAccess = VK_FALSE; |
764 | devInfo.pEnabledFeatures = &physDevFeatures; |
765 | } |
766 | |
767 | VkResult err = f->vkCreateDevice(physDev, &devInfo, nullptr, &dev); |
768 | if (err != VK_SUCCESS) { |
769 | qWarning(msg: "Failed to create device: %d" , err); |
770 | return false; |
771 | } |
772 | } else { |
773 | qCDebug(QRHI_LOG_INFO, "Using imported device %p" , dev); |
774 | |
775 | // Here we have no way to tell if the extensions got enabled or not. |
776 | // Pretend it's all there and supported. If getProcAddress fails, we'll |
777 | // handle that gracefully. |
778 | caps.vertexAttribDivisor = true; |
779 | caps.renderPass2KHR = true; |
780 | caps.depthStencilResolveKHR = true; |
781 | } |
782 | |
783 | vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>( |
784 | inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfaceCapabilitiesKHR" )); |
785 | vkGetPhysicalDeviceSurfaceFormatsKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR>( |
786 | inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfaceFormatsKHR" )); |
787 | vkGetPhysicalDeviceSurfacePresentModesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>( |
788 | inst->getInstanceProcAddr(name: "vkGetPhysicalDeviceSurfacePresentModesKHR" )); |
789 | |
790 | df = inst->deviceFunctions(device: dev); |
791 | |
792 | VkCommandPoolCreateInfo poolInfo = {}; |
793 | poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; |
794 | poolInfo.queueFamilyIndex = gfxQueueFamilyIdx; |
795 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
796 | VkResult err = df->vkCreateCommandPool(dev, &poolInfo, nullptr, &cmdPool[i]); |
797 | if (err != VK_SUCCESS) { |
798 | qWarning(msg: "Failed to create command pool: %d" , err); |
799 | return false; |
800 | } |
801 | } |
802 | |
803 | qCDebug(QRHI_LOG_INFO, "Using queue family index %u and queue index %u" , |
804 | gfxQueueFamilyIdx, gfxQueueIdx); |
805 | |
806 | df->vkGetDeviceQueue(dev, gfxQueueFamilyIdx, gfxQueueIdx, &gfxQueue); |
807 | |
808 | if (queueFamilyProps.isEmpty()) |
809 | queryQueueFamilyProps(); |
810 | |
811 | caps.compute = (queueFamilyProps[gfxQueueFamilyIdx].queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; |
812 | timestampValidBits = queueFamilyProps[gfxQueueFamilyIdx].timestampValidBits; |
813 | |
814 | ubufAlign = physDevProperties.limits.minUniformBufferOffsetAlignment; |
815 | // helps little with an optimal offset of 1 (on some drivers) when the spec |
816 | // elsewhere states that the minimum bufferOffset is 4... |
817 | texbufAlign = qMax<VkDeviceSize>(a: 4, b: physDevProperties.limits.optimalBufferCopyOffsetAlignment); |
818 | |
819 | caps.wideLines = physDevFeatures.wideLines; |
820 | |
821 | caps.texture3DSliceAs2D = caps.apiVersion >= QVersionNumber(1, 1); |
822 | |
823 | caps.tessellation = physDevFeatures.tessellationShader; |
824 | caps.geometryShader = physDevFeatures.geometryShader; |
825 | |
826 | caps.nonFillPolygonMode = physDevFeatures.fillModeNonSolid; |
827 | |
828 | #ifdef VK_VERSION_1_2 |
829 | if (caps.apiVersion >= QVersionNumber(1, 2)) |
830 | caps.multiView = physDevFeatures11IfApi12OrNewer.multiview; |
831 | #endif |
832 | |
833 | #ifdef VK_VERSION_1_1 |
834 | if (caps.apiVersion == QVersionNumber(1, 1)) |
835 | caps.multiView = multiviewFeaturesIfApi11.multiview; |
836 | #endif |
837 | |
838 | // With Vulkan 1.2 renderpass2 and depth_stencil_resolve are core, but we |
839 | // have to support the case of 1.1 + extensions, in particular for the Quest |
840 | // 3 (Android, Vulkan 1.1 at the time of writing). Therefore, always rely on |
841 | // the KHR extension for now. |
842 | #ifdef VK_KHR_create_renderpass2 |
843 | if (caps.renderPass2KHR) { |
844 | vkCreateRenderPass2KHR = reinterpret_cast<PFN_vkCreateRenderPass2KHR>(f->vkGetDeviceProcAddr(dev, "vkCreateRenderPass2KHR" )); |
845 | if (!vkCreateRenderPass2KHR) // handle it gracefully, the caps flag may be incorrect when using an imported VkDevice |
846 | caps.renderPass2KHR = false; |
847 | } |
848 | #endif |
849 | |
850 | if (!importedAllocator) { |
851 | VmaVulkanFunctions funcs = {}; |
852 | funcs.vkGetInstanceProcAddr = wrap_vkGetInstanceProcAddr; |
853 | funcs.vkGetDeviceProcAddr = wrap_vkGetDeviceProcAddr; |
854 | |
855 | VmaAllocatorCreateInfo allocatorInfo = {}; |
856 | // A QRhi is supposed to be used from one single thread only. Disable |
857 | // the allocator's own mutexes. This gives a performance boost. |
858 | allocatorInfo.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; |
859 | allocatorInfo.physicalDevice = physDev; |
860 | allocatorInfo.device = dev; |
861 | allocatorInfo.pVulkanFunctions = &funcs; |
862 | allocatorInfo.instance = inst->vkInstance(); |
863 | allocatorInfo.vulkanApiVersion = VK_MAKE_VERSION(caps.apiVersion.majorVersion(), |
864 | caps.apiVersion.minorVersion(), |
865 | caps.apiVersion.microVersion()); |
866 | VmaAllocator vmaallocator; |
867 | VkResult err = vmaCreateAllocator(pCreateInfo: &allocatorInfo, pAllocator: &vmaallocator); |
868 | if (err != VK_SUCCESS) { |
869 | qWarning(msg: "Failed to create allocator: %d" , err); |
870 | return false; |
871 | } |
872 | allocator = vmaallocator; |
873 | } |
874 | |
875 | inst->installDebugOutputFilter(filter: qvk_debug_filter); |
876 | |
877 | VkDescriptorPool pool; |
878 | VkResult err = createDescriptorPool(pool: &pool); |
879 | if (err == VK_SUCCESS) |
880 | descriptorPools.append(t: pool); |
881 | else |
882 | qWarning(msg: "Failed to create initial descriptor pool: %d" , err); |
883 | |
884 | VkQueryPoolCreateInfo timestampQueryPoolInfo = {}; |
885 | timestampQueryPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; |
886 | timestampQueryPoolInfo.queryType = VK_QUERY_TYPE_TIMESTAMP; |
887 | timestampQueryPoolInfo.queryCount = QVK_MAX_ACTIVE_TIMESTAMP_PAIRS * 2; |
888 | err = df->vkCreateQueryPool(dev, ×tampQueryPoolInfo, nullptr, ×tampQueryPool); |
889 | if (err != VK_SUCCESS) { |
890 | qWarning(msg: "Failed to create timestamp query pool: %d" , err); |
891 | return false; |
892 | } |
893 | timestampQueryPoolMap.resize(size: QVK_MAX_ACTIVE_TIMESTAMP_PAIRS); // 1 bit per pair |
894 | timestampQueryPoolMap.fill(aval: false); |
895 | |
896 | #ifdef VK_EXT_debug_utils |
897 | if (caps.debugUtils) { |
898 | vkSetDebugUtilsObjectNameEXT = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>(f->vkGetDeviceProcAddr(dev, "vkSetDebugUtilsObjectNameEXT" )); |
899 | vkCmdBeginDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdBeginDebugUtilsLabelEXT" )); |
900 | vkCmdEndDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdEndDebugUtilsLabelEXT" )); |
901 | vkCmdInsertDebugUtilsLabelEXT = reinterpret_cast<PFN_vkCmdInsertDebugUtilsLabelEXT>(f->vkGetDeviceProcAddr(dev, "vkCmdInsertDebugUtilsLabelEXT" )); |
902 | } |
903 | #endif |
904 | |
905 | deviceLost = false; |
906 | |
907 | nativeHandlesStruct.physDev = physDev; |
908 | nativeHandlesStruct.dev = dev; |
909 | nativeHandlesStruct.gfxQueueFamilyIdx = gfxQueueFamilyIdx; |
910 | nativeHandlesStruct.gfxQueueIdx = gfxQueueIdx; |
911 | nativeHandlesStruct.gfxQueue = gfxQueue; |
912 | nativeHandlesStruct.vmemAllocator = allocator; |
913 | nativeHandlesStruct.inst = inst; |
914 | |
915 | return true; |
916 | } |
917 | |
918 | void QRhiVulkan::destroy() |
919 | { |
920 | if (!df) |
921 | return; |
922 | |
923 | if (!deviceLost) |
924 | df->vkDeviceWaitIdle(dev); |
925 | |
926 | executeDeferredReleases(forced: true); |
927 | finishActiveReadbacks(forced: true); |
928 | |
929 | if (ofr.cmdFence) { |
930 | df->vkDestroyFence(dev, ofr.cmdFence, nullptr); |
931 | ofr.cmdFence = VK_NULL_HANDLE; |
932 | } |
933 | |
934 | if (pipelineCache) { |
935 | df->vkDestroyPipelineCache(dev, pipelineCache, nullptr); |
936 | pipelineCache = VK_NULL_HANDLE; |
937 | } |
938 | |
939 | for (const DescriptorPoolData &pool : descriptorPools) |
940 | df->vkDestroyDescriptorPool(dev, pool.pool, nullptr); |
941 | |
942 | descriptorPools.clear(); |
943 | |
944 | if (timestampQueryPool) { |
945 | df->vkDestroyQueryPool(dev, timestampQueryPool, nullptr); |
946 | timestampQueryPool = VK_NULL_HANDLE; |
947 | } |
948 | |
949 | if (!importedAllocator && allocator) { |
950 | vmaDestroyAllocator(allocator: toVmaAllocator(a: allocator)); |
951 | allocator = nullptr; |
952 | } |
953 | |
954 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
955 | if (cmdPool[i]) { |
956 | df->vkDestroyCommandPool(dev, cmdPool[i], nullptr); |
957 | cmdPool[i] = VK_NULL_HANDLE; |
958 | } |
959 | freeSecondaryCbs[i].clear(); |
960 | ofr.cbWrapper[i]->cb = VK_NULL_HANDLE; |
961 | } |
962 | |
963 | if (!importedDevice && dev) { |
964 | df->vkDestroyDevice(dev, nullptr); |
965 | inst->resetDeviceFunctions(device: dev); |
966 | dev = VK_NULL_HANDLE; |
967 | } |
968 | |
969 | f = nullptr; |
970 | df = nullptr; |
971 | } |
972 | |
973 | VkResult QRhiVulkan::createDescriptorPool(VkDescriptorPool *pool) |
974 | { |
975 | VkDescriptorPoolSize descPoolSizes[] = { |
976 | { .type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount: QVK_UNIFORM_BUFFERS_PER_POOL }, |
977 | { .type: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, .descriptorCount: QVK_UNIFORM_BUFFERS_PER_POOL }, |
978 | { .type: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount: QVK_COMBINED_IMAGE_SAMPLERS_PER_POOL }, |
979 | { .type: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount: QVK_STORAGE_BUFFERS_PER_POOL }, |
980 | { .type: VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount: QVK_STORAGE_IMAGES_PER_POOL } |
981 | }; |
982 | VkDescriptorPoolCreateInfo descPoolInfo = {}; |
983 | descPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; |
984 | // Do not enable vkFreeDescriptorSets - sets are never freed on their own |
985 | // (good so no trouble with fragmentation), they just deref their pool |
986 | // which is then reset at some point (or not). |
987 | descPoolInfo.flags = 0; |
988 | descPoolInfo.maxSets = QVK_DESC_SETS_PER_POOL; |
989 | descPoolInfo.poolSizeCount = sizeof(descPoolSizes) / sizeof(descPoolSizes[0]); |
990 | descPoolInfo.pPoolSizes = descPoolSizes; |
991 | return df->vkCreateDescriptorPool(dev, &descPoolInfo, nullptr, pool); |
992 | } |
993 | |
994 | bool QRhiVulkan::allocateDescriptorSet(VkDescriptorSetAllocateInfo *allocInfo, VkDescriptorSet *result, int *resultPoolIndex) |
995 | { |
996 | auto tryAllocate = [this, allocInfo, result](int poolIndex) { |
997 | allocInfo->descriptorPool = descriptorPools[poolIndex].pool; |
998 | VkResult r = df->vkAllocateDescriptorSets(dev, allocInfo, result); |
999 | if (r == VK_SUCCESS) |
1000 | descriptorPools[poolIndex].refCount += 1; |
1001 | return r; |
1002 | }; |
1003 | |
1004 | int lastPoolIdx = descriptorPools.size() - 1; |
1005 | for (int i = lastPoolIdx; i >= 0; --i) { |
1006 | if (descriptorPools[i].refCount == 0) { |
1007 | df->vkResetDescriptorPool(dev, descriptorPools[i].pool, 0); |
1008 | descriptorPools[i].allocedDescSets = 0; |
1009 | } |
1010 | if (descriptorPools[i].allocedDescSets + int(allocInfo->descriptorSetCount) <= QVK_DESC_SETS_PER_POOL) { |
1011 | VkResult err = tryAllocate(i); |
1012 | if (err == VK_SUCCESS) { |
1013 | descriptorPools[i].allocedDescSets += allocInfo->descriptorSetCount; |
1014 | *resultPoolIndex = i; |
1015 | return true; |
1016 | } |
1017 | } |
1018 | } |
1019 | |
1020 | VkDescriptorPool newPool; |
1021 | VkResult poolErr = createDescriptorPool(pool: &newPool); |
1022 | if (poolErr == VK_SUCCESS) { |
1023 | descriptorPools.append(t: newPool); |
1024 | lastPoolIdx = descriptorPools.size() - 1; |
1025 | VkResult err = tryAllocate(lastPoolIdx); |
1026 | if (err != VK_SUCCESS) { |
1027 | qWarning(msg: "Failed to allocate descriptor set from new pool too, giving up: %d" , err); |
1028 | return false; |
1029 | } |
1030 | descriptorPools[lastPoolIdx].allocedDescSets += allocInfo->descriptorSetCount; |
1031 | *resultPoolIndex = lastPoolIdx; |
1032 | return true; |
1033 | } else { |
1034 | qWarning(msg: "Failed to allocate new descriptor pool: %d" , poolErr); |
1035 | return false; |
1036 | } |
1037 | } |
1038 | |
1039 | static inline VkFormat toVkTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags) |
1040 | { |
1041 | const bool srgb = flags.testFlag(flag: QRhiTexture::sRGB); |
1042 | switch (format) { |
1043 | case QRhiTexture::RGBA8: |
1044 | return srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; |
1045 | case QRhiTexture::BGRA8: |
1046 | return srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM; |
1047 | case QRhiTexture::R8: |
1048 | return srgb ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; |
1049 | case QRhiTexture::RG8: |
1050 | return srgb ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; |
1051 | case QRhiTexture::R16: |
1052 | return VK_FORMAT_R16_UNORM; |
1053 | case QRhiTexture::RG16: |
1054 | return VK_FORMAT_R16G16_UNORM; |
1055 | case QRhiTexture::RED_OR_ALPHA8: |
1056 | return VK_FORMAT_R8_UNORM; |
1057 | |
1058 | case QRhiTexture::RGBA16F: |
1059 | return VK_FORMAT_R16G16B16A16_SFLOAT; |
1060 | case QRhiTexture::RGBA32F: |
1061 | return VK_FORMAT_R32G32B32A32_SFLOAT; |
1062 | case QRhiTexture::R16F: |
1063 | return VK_FORMAT_R16_SFLOAT; |
1064 | case QRhiTexture::R32F: |
1065 | return VK_FORMAT_R32_SFLOAT; |
1066 | |
1067 | case QRhiTexture::RGB10A2: |
1068 | // intentionally A2B10G10R10, not A2R10G10B10 |
1069 | return VK_FORMAT_A2B10G10R10_UNORM_PACK32; |
1070 | |
1071 | case QRhiTexture::D16: |
1072 | return VK_FORMAT_D16_UNORM; |
1073 | case QRhiTexture::D24: |
1074 | return VK_FORMAT_X8_D24_UNORM_PACK32; |
1075 | case QRhiTexture::D24S8: |
1076 | return VK_FORMAT_D24_UNORM_S8_UINT; |
1077 | case QRhiTexture::D32F: |
1078 | return VK_FORMAT_D32_SFLOAT; |
1079 | |
1080 | case QRhiTexture::BC1: |
1081 | return srgb ? VK_FORMAT_BC1_RGB_SRGB_BLOCK : VK_FORMAT_BC1_RGB_UNORM_BLOCK; |
1082 | case QRhiTexture::BC2: |
1083 | return srgb ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK; |
1084 | case QRhiTexture::BC3: |
1085 | return srgb ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK; |
1086 | case QRhiTexture::BC4: |
1087 | return VK_FORMAT_BC4_UNORM_BLOCK; |
1088 | case QRhiTexture::BC5: |
1089 | return VK_FORMAT_BC5_UNORM_BLOCK; |
1090 | case QRhiTexture::BC6H: |
1091 | return VK_FORMAT_BC6H_UFLOAT_BLOCK; |
1092 | case QRhiTexture::BC7: |
1093 | return srgb ? VK_FORMAT_BC7_SRGB_BLOCK : VK_FORMAT_BC7_UNORM_BLOCK; |
1094 | |
1095 | case QRhiTexture::ETC2_RGB8: |
1096 | return srgb ? VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; |
1097 | case QRhiTexture::ETC2_RGB8A1: |
1098 | return srgb ? VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK; |
1099 | case QRhiTexture::ETC2_RGBA8: |
1100 | return srgb ? VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK : VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK; |
1101 | |
1102 | case QRhiTexture::ASTC_4x4: |
1103 | return srgb ? VK_FORMAT_ASTC_4x4_SRGB_BLOCK : VK_FORMAT_ASTC_4x4_UNORM_BLOCK; |
1104 | case QRhiTexture::ASTC_5x4: |
1105 | return srgb ? VK_FORMAT_ASTC_5x4_SRGB_BLOCK : VK_FORMAT_ASTC_5x4_UNORM_BLOCK; |
1106 | case QRhiTexture::ASTC_5x5: |
1107 | return srgb ? VK_FORMAT_ASTC_5x5_SRGB_BLOCK : VK_FORMAT_ASTC_5x5_UNORM_BLOCK; |
1108 | case QRhiTexture::ASTC_6x5: |
1109 | return srgb ? VK_FORMAT_ASTC_6x5_SRGB_BLOCK : VK_FORMAT_ASTC_6x5_UNORM_BLOCK; |
1110 | case QRhiTexture::ASTC_6x6: |
1111 | return srgb ? VK_FORMAT_ASTC_6x6_SRGB_BLOCK : VK_FORMAT_ASTC_6x6_UNORM_BLOCK; |
1112 | case QRhiTexture::ASTC_8x5: |
1113 | return srgb ? VK_FORMAT_ASTC_8x5_SRGB_BLOCK : VK_FORMAT_ASTC_8x5_UNORM_BLOCK; |
1114 | case QRhiTexture::ASTC_8x6: |
1115 | return srgb ? VK_FORMAT_ASTC_8x6_SRGB_BLOCK : VK_FORMAT_ASTC_8x6_UNORM_BLOCK; |
1116 | case QRhiTexture::ASTC_8x8: |
1117 | return srgb ? VK_FORMAT_ASTC_8x8_SRGB_BLOCK : VK_FORMAT_ASTC_8x8_UNORM_BLOCK; |
1118 | case QRhiTexture::ASTC_10x5: |
1119 | return srgb ? VK_FORMAT_ASTC_10x5_SRGB_BLOCK : VK_FORMAT_ASTC_10x5_UNORM_BLOCK; |
1120 | case QRhiTexture::ASTC_10x6: |
1121 | return srgb ? VK_FORMAT_ASTC_10x6_SRGB_BLOCK : VK_FORMAT_ASTC_10x6_UNORM_BLOCK; |
1122 | case QRhiTexture::ASTC_10x8: |
1123 | return srgb ? VK_FORMAT_ASTC_10x8_SRGB_BLOCK : VK_FORMAT_ASTC_10x8_UNORM_BLOCK; |
1124 | case QRhiTexture::ASTC_10x10: |
1125 | return srgb ? VK_FORMAT_ASTC_10x10_SRGB_BLOCK : VK_FORMAT_ASTC_10x10_UNORM_BLOCK; |
1126 | case QRhiTexture::ASTC_12x10: |
1127 | return srgb ? VK_FORMAT_ASTC_12x10_SRGB_BLOCK : VK_FORMAT_ASTC_12x10_UNORM_BLOCK; |
1128 | case QRhiTexture::ASTC_12x12: |
1129 | return srgb ? VK_FORMAT_ASTC_12x12_SRGB_BLOCK : VK_FORMAT_ASTC_12x12_UNORM_BLOCK; |
1130 | |
1131 | default: |
1132 | Q_UNREACHABLE_RETURN(VK_FORMAT_R8G8B8A8_UNORM); |
1133 | } |
1134 | } |
1135 | |
1136 | static inline QRhiTexture::Format swapchainReadbackTextureFormat(VkFormat format, QRhiTexture::Flags *flags) |
1137 | { |
1138 | switch (format) { |
1139 | case VK_FORMAT_R8G8B8A8_UNORM: |
1140 | return QRhiTexture::RGBA8; |
1141 | case VK_FORMAT_R8G8B8A8_SRGB: |
1142 | if (flags) |
1143 | (*flags) |= QRhiTexture::sRGB; |
1144 | return QRhiTexture::RGBA8; |
1145 | case VK_FORMAT_B8G8R8A8_UNORM: |
1146 | return QRhiTexture::BGRA8; |
1147 | case VK_FORMAT_B8G8R8A8_SRGB: |
1148 | if (flags) |
1149 | (*flags) |= QRhiTexture::sRGB; |
1150 | return QRhiTexture::BGRA8; |
1151 | case VK_FORMAT_R16G16B16A16_SFLOAT: |
1152 | return QRhiTexture::RGBA16F; |
1153 | case VK_FORMAT_R32G32B32A32_SFLOAT: |
1154 | return QRhiTexture::RGBA32F; |
1155 | case VK_FORMAT_A2B10G10R10_UNORM_PACK32: |
1156 | return QRhiTexture::RGB10A2; |
1157 | default: |
1158 | qWarning(msg: "VkFormat %d cannot be read back" , format); |
1159 | break; |
1160 | } |
1161 | return QRhiTexture::UnknownFormat; |
1162 | } |
1163 | |
1164 | static constexpr inline bool isDepthTextureFormat(QRhiTexture::Format format) |
1165 | { |
1166 | switch (format) { |
1167 | case QRhiTexture::Format::D16: |
1168 | case QRhiTexture::Format::D24: |
1169 | case QRhiTexture::Format::D24S8: |
1170 | case QRhiTexture::Format::D32F: |
1171 | return true; |
1172 | |
1173 | default: |
1174 | return false; |
1175 | } |
1176 | } |
1177 | |
1178 | static constexpr inline VkImageAspectFlags aspectMaskForTextureFormat(QRhiTexture::Format format) |
1179 | { |
1180 | return isDepthTextureFormat(format) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; |
1181 | } |
1182 | |
1183 | // Transient images ("render buffers") backed by lazily allocated memory are |
1184 | // managed manually without going through vk_mem_alloc since it does not offer |
1185 | // any support for such images. This should be ok since in practice there |
1186 | // should be very few of such images. |
1187 | |
1188 | uint32_t QRhiVulkan::chooseTransientImageMemType(VkImage img, uint32_t startIndex) |
1189 | { |
1190 | VkPhysicalDeviceMemoryProperties physDevMemProps; |
1191 | f->vkGetPhysicalDeviceMemoryProperties(physDev, &physDevMemProps); |
1192 | |
1193 | VkMemoryRequirements memReq; |
1194 | df->vkGetImageMemoryRequirements(dev, img, &memReq); |
1195 | uint32_t memTypeIndex = uint32_t(-1); |
1196 | |
1197 | if (memReq.memoryTypeBits) { |
1198 | // Find a device local + lazily allocated, or at least device local memtype. |
1199 | const VkMemoryType *memType = physDevMemProps.memoryTypes; |
1200 | bool foundDevLocal = false; |
1201 | for (uint32_t i = startIndex; i < physDevMemProps.memoryTypeCount; ++i) { |
1202 | if (memReq.memoryTypeBits & (1 << i)) { |
1203 | if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { |
1204 | if (!foundDevLocal) { |
1205 | foundDevLocal = true; |
1206 | memTypeIndex = i; |
1207 | } |
1208 | if (memType[i].propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) { |
1209 | memTypeIndex = i; |
1210 | break; |
1211 | } |
1212 | } |
1213 | } |
1214 | } |
1215 | } |
1216 | |
1217 | return memTypeIndex; |
1218 | } |
1219 | |
1220 | bool QRhiVulkan::createTransientImage(VkFormat format, |
1221 | const QSize &pixelSize, |
1222 | VkImageUsageFlags usage, |
1223 | VkImageAspectFlags aspectMask, |
1224 | VkSampleCountFlagBits samples, |
1225 | VkDeviceMemory *mem, |
1226 | VkImage *images, |
1227 | VkImageView *views, |
1228 | int count) |
1229 | { |
1230 | VkMemoryRequirements memReq; |
1231 | VkResult err; |
1232 | |
1233 | for (int i = 0; i < count; ++i) { |
1234 | VkImageCreateInfo imgInfo = {}; |
1235 | imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; |
1236 | imgInfo.imageType = VK_IMAGE_TYPE_2D; |
1237 | imgInfo.format = format; |
1238 | imgInfo.extent.width = uint32_t(pixelSize.width()); |
1239 | imgInfo.extent.height = uint32_t(pixelSize.height()); |
1240 | imgInfo.extent.depth = 1; |
1241 | imgInfo.mipLevels = imgInfo.arrayLayers = 1; |
1242 | imgInfo.samples = samples; |
1243 | imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL; |
1244 | imgInfo.usage = usage | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; |
1245 | imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1246 | |
1247 | err = df->vkCreateImage(dev, &imgInfo, nullptr, images + i); |
1248 | if (err != VK_SUCCESS) { |
1249 | qWarning(msg: "Failed to create image: %d" , err); |
1250 | return false; |
1251 | } |
1252 | |
1253 | // Assume the reqs are the same since the images are same in every way. |
1254 | // Still, call GetImageMemReq for every image, in order to prevent the |
1255 | // validation layer from complaining. |
1256 | df->vkGetImageMemoryRequirements(dev, images[i], &memReq); |
1257 | } |
1258 | |
1259 | VkMemoryAllocateInfo memInfo = {}; |
1260 | memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; |
1261 | memInfo.allocationSize = aligned(v: memReq.size, byteAlign: memReq.alignment) * VkDeviceSize(count); |
1262 | |
1263 | uint32_t startIndex = 0; |
1264 | do { |
1265 | memInfo.memoryTypeIndex = chooseTransientImageMemType(img: images[0], startIndex); |
1266 | if (memInfo.memoryTypeIndex == uint32_t(-1)) { |
1267 | qWarning(msg: "No suitable memory type found" ); |
1268 | return false; |
1269 | } |
1270 | startIndex = memInfo.memoryTypeIndex + 1; |
1271 | err = df->vkAllocateMemory(dev, &memInfo, nullptr, mem); |
1272 | if (err != VK_SUCCESS && err != VK_ERROR_OUT_OF_DEVICE_MEMORY) { |
1273 | qWarning(msg: "Failed to allocate image memory: %d" , err); |
1274 | return false; |
1275 | } |
1276 | } while (err != VK_SUCCESS); |
1277 | |
1278 | VkDeviceSize ofs = 0; |
1279 | for (int i = 0; i < count; ++i) { |
1280 | err = df->vkBindImageMemory(dev, images[i], *mem, ofs); |
1281 | if (err != VK_SUCCESS) { |
1282 | qWarning(msg: "Failed to bind image memory: %d" , err); |
1283 | return false; |
1284 | } |
1285 | ofs += aligned(v: memReq.size, byteAlign: memReq.alignment); |
1286 | |
1287 | VkImageViewCreateInfo imgViewInfo = {}; |
1288 | imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
1289 | imgViewInfo.image = images[i]; |
1290 | imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
1291 | imgViewInfo.format = format; |
1292 | imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
1293 | imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
1294 | imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
1295 | imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
1296 | imgViewInfo.subresourceRange.aspectMask = aspectMask; |
1297 | imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; |
1298 | |
1299 | err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, views + i); |
1300 | if (err != VK_SUCCESS) { |
1301 | qWarning(msg: "Failed to create image view: %d" , err); |
1302 | return false; |
1303 | } |
1304 | } |
1305 | |
1306 | return true; |
1307 | } |
1308 | |
1309 | VkFormat QRhiVulkan::optimalDepthStencilFormat() |
1310 | { |
1311 | if (optimalDsFormat != VK_FORMAT_UNDEFINED) |
1312 | return optimalDsFormat; |
1313 | |
1314 | const VkFormat dsFormatCandidates[] = { |
1315 | VK_FORMAT_D24_UNORM_S8_UINT, |
1316 | VK_FORMAT_D32_SFLOAT_S8_UINT, |
1317 | VK_FORMAT_D16_UNORM_S8_UINT |
1318 | }; |
1319 | const int dsFormatCandidateCount = sizeof(dsFormatCandidates) / sizeof(VkFormat); |
1320 | int dsFormatIdx = 0; |
1321 | while (dsFormatIdx < dsFormatCandidateCount) { |
1322 | optimalDsFormat = dsFormatCandidates[dsFormatIdx]; |
1323 | VkFormatProperties fmtProp; |
1324 | f->vkGetPhysicalDeviceFormatProperties(physDev, optimalDsFormat, &fmtProp); |
1325 | if (fmtProp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) |
1326 | break; |
1327 | ++dsFormatIdx; |
1328 | } |
1329 | if (dsFormatIdx == dsFormatCandidateCount) |
1330 | qWarning(msg: "Failed to find an optimal depth-stencil format" ); |
1331 | |
1332 | return optimalDsFormat; |
1333 | } |
1334 | |
1335 | static void fillRenderPassCreateInfo(VkRenderPassCreateInfo *rpInfo, |
1336 | VkSubpassDescription *subpassDesc, |
1337 | QVkRenderPassDescriptor *rpD) |
1338 | { |
1339 | memset(s: subpassDesc, c: 0, n: sizeof(VkSubpassDescription)); |
1340 | subpassDesc->pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
1341 | subpassDesc->colorAttachmentCount = uint32_t(rpD->colorRefs.size()); |
1342 | subpassDesc->pColorAttachments = !rpD->colorRefs.isEmpty() ? rpD->colorRefs.constData() : nullptr; |
1343 | subpassDesc->pDepthStencilAttachment = rpD->hasDepthStencil ? &rpD->dsRef : nullptr; |
1344 | subpassDesc->pResolveAttachments = !rpD->resolveRefs.isEmpty() ? rpD->resolveRefs.constData() : nullptr; |
1345 | |
1346 | memset(s: rpInfo, c: 0, n: sizeof(VkRenderPassCreateInfo)); |
1347 | rpInfo->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; |
1348 | rpInfo->attachmentCount = uint32_t(rpD->attDescs.size()); |
1349 | rpInfo->pAttachments = rpD->attDescs.constData(); |
1350 | rpInfo->subpassCount = 1; |
1351 | rpInfo->pSubpasses = subpassDesc; |
1352 | rpInfo->dependencyCount = uint32_t(rpD->subpassDeps.size()); |
1353 | rpInfo->pDependencies = !rpD->subpassDeps.isEmpty() ? rpD->subpassDeps.constData() : nullptr; |
1354 | } |
1355 | |
1356 | bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasDepthStencil, VkSampleCountFlagBits samples, VkFormat colorFormat) |
1357 | { |
1358 | // attachment list layout is color (1), ds (0-1), resolve (0-1) |
1359 | |
1360 | VkAttachmentDescription attDesc = {}; |
1361 | attDesc.format = colorFormat; |
1362 | attDesc.samples = samples; |
1363 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1364 | attDesc.storeOp = samples > VK_SAMPLE_COUNT_1_BIT ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; |
1365 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1366 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1367 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1368 | attDesc.finalLayout = samples > VK_SAMPLE_COUNT_1_BIT ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
1369 | rpD->attDescs.append(t: attDesc); |
1370 | |
1371 | rpD->colorRefs.append(t: { .attachment: 0, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); |
1372 | |
1373 | rpD->hasDepthStencil = hasDepthStencil; |
1374 | rpD->hasDepthStencilResolve = false; |
1375 | rpD->multiViewCount = 0; |
1376 | |
1377 | if (hasDepthStencil) { |
1378 | // clear on load + no store + lazy alloc + transient image should play |
1379 | // nicely with tiled GPUs (no physical backing necessary for ds buffer) |
1380 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1381 | attDesc.format = optimalDepthStencilFormat(); |
1382 | attDesc.samples = samples; |
1383 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1384 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1385 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1386 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1387 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1388 | attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
1389 | rpD->attDescs.append(t: attDesc); |
1390 | |
1391 | rpD->dsRef = { .attachment: 1, .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; |
1392 | } |
1393 | |
1394 | if (samples > VK_SAMPLE_COUNT_1_BIT) { |
1395 | memset(s: &attDesc, c: 0, n: sizeof(attDesc)); |
1396 | attDesc.format = colorFormat; |
1397 | attDesc.samples = VK_SAMPLE_COUNT_1_BIT; |
1398 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; |
1399 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; |
1400 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1401 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1402 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1403 | attDesc.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
1404 | rpD->attDescs.append(t: attDesc); |
1405 | |
1406 | rpD->resolveRefs.append(t: { .attachment: 2, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }); |
1407 | } |
1408 | |
1409 | // Replace the first implicit dep (TOP_OF_PIPE / ALL_COMMANDS) with our own. |
1410 | VkSubpassDependency subpassDep = {}; |
1411 | subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; |
1412 | subpassDep.dstSubpass = 0; |
1413 | subpassDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
1414 | subpassDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
1415 | subpassDep.srcAccessMask = 0; |
1416 | subpassDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
1417 | rpD->subpassDeps.append(t: subpassDep); |
1418 | if (hasDepthStencil) { |
1419 | memset(s: &subpassDep, c: 0, n: sizeof(subpassDep)); |
1420 | subpassDep.srcSubpass = VK_SUBPASS_EXTERNAL; |
1421 | subpassDep.dstSubpass = 0; |
1422 | subpassDep.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
1423 | | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; |
1424 | subpassDep.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
1425 | | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; |
1426 | subpassDep.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; |
1427 | subpassDep.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
1428 | | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; |
1429 | rpD->subpassDeps.append(t: subpassDep); |
1430 | } |
1431 | |
1432 | VkRenderPassCreateInfo rpInfo; |
1433 | VkSubpassDescription subpassDesc; |
1434 | fillRenderPassCreateInfo(rpInfo: &rpInfo, subpassDesc: &subpassDesc, rpD); |
1435 | |
1436 | VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); |
1437 | if (err != VK_SUCCESS) { |
1438 | qWarning(msg: "Failed to create renderpass: %d" , err); |
1439 | return false; |
1440 | } |
1441 | |
1442 | return true; |
1443 | } |
1444 | |
1445 | struct MultiViewRenderPassSetupHelper |
1446 | { |
1447 | bool prepare(VkRenderPassCreateInfo *rpInfo, int multiViewCount, bool multiViewCap) |
1448 | { |
1449 | if (multiViewCount < 2) |
1450 | return true; |
1451 | if (!multiViewCap) { |
1452 | qWarning(msg: "Cannot create multiview render pass without support for the Vulkan 1.1 multiview feature" ); |
1453 | return false; |
1454 | } |
1455 | #ifdef VK_VERSION_1_1 |
1456 | uint32_t allViewsMask = 0; |
1457 | for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i) |
1458 | allViewsMask |= (1 << i); |
1459 | multiViewMask = allViewsMask; |
1460 | multiViewCorrelationMask = allViewsMask; |
1461 | multiViewInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO; |
1462 | multiViewInfo.subpassCount = 1; |
1463 | multiViewInfo.pViewMasks = &multiViewMask; |
1464 | multiViewInfo.correlationMaskCount = 1; |
1465 | multiViewInfo.pCorrelationMasks = &multiViewCorrelationMask; |
1466 | rpInfo->pNext = &multiViewInfo; |
1467 | #endif |
1468 | return true; |
1469 | } |
1470 | |
1471 | #ifdef VK_VERSION_1_1 |
1472 | VkRenderPassMultiviewCreateInfo multiViewInfo = {}; |
1473 | uint32_t multiViewMask = 0; |
1474 | uint32_t multiViewCorrelationMask = 0; |
1475 | #endif |
1476 | }; |
1477 | |
1478 | #ifdef VK_KHR_create_renderpass2 |
1479 | // Effectively converts a VkRenderPassCreateInfo into a VkRenderPassCreateInfo2, |
1480 | // adding depth-stencil resolve support. Assumes a single subpass and no subpass |
1481 | // dependencies. |
1482 | struct RenderPass2SetupHelper |
1483 | { |
1484 | bool prepare(VkRenderPassCreateInfo2 *rpInfo2, const VkRenderPassCreateInfo *rpInfo, const QVkRenderPassDescriptor *rpD, int multiViewCount) { |
1485 | *rpInfo2 = {}; |
1486 | |
1487 | viewMask = 0; |
1488 | if (multiViewCount >= 2) { |
1489 | for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i) |
1490 | viewMask |= (1 << i); |
1491 | } |
1492 | |
1493 | attDescs2.resize(sz: rpInfo->attachmentCount); |
1494 | for (qsizetype i = 0; i < attDescs2.count(); ++i) { |
1495 | VkAttachmentDescription2KHR &att2(attDescs2[i]); |
1496 | const VkAttachmentDescription &att(rpInfo->pAttachments[i]); |
1497 | att2 = {}; |
1498 | att2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; |
1499 | att2.flags = att.flags; |
1500 | att2.format = att.format; |
1501 | att2.samples = att.samples; |
1502 | att2.loadOp = att.loadOp; |
1503 | att2.storeOp = att.storeOp; |
1504 | att2.stencilLoadOp = att.stencilLoadOp; |
1505 | att2.stencilStoreOp = att.stencilStoreOp; |
1506 | att2.initialLayout = att.initialLayout; |
1507 | att2.finalLayout = att.finalLayout; |
1508 | } |
1509 | |
1510 | attRefs2.clear(); |
1511 | subpass2 = {}; |
1512 | subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR; |
1513 | const VkSubpassDescription &subpassDesc(rpInfo->pSubpasses[0]); |
1514 | subpass2.flags = subpassDesc.flags; |
1515 | subpass2.pipelineBindPoint = subpassDesc.pipelineBindPoint; |
1516 | if (multiViewCount >= 2) |
1517 | subpass2.viewMask = viewMask; |
1518 | |
1519 | // color attachment refs |
1520 | qsizetype startIndex = attRefs2.count(); |
1521 | for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) { |
1522 | attRefs2.append(t: {}); |
1523 | VkAttachmentReference2KHR &attref2(attRefs2.last()); |
1524 | const VkAttachmentReference &attref(subpassDesc.pColorAttachments[j]); |
1525 | attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; |
1526 | attref2.attachment = attref.attachment; |
1527 | attref2.layout = attref.layout; |
1528 | attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
1529 | } |
1530 | subpass2.colorAttachmentCount = subpassDesc.colorAttachmentCount; |
1531 | subpass2.pColorAttachments = attRefs2.constData() + startIndex; |
1532 | |
1533 | // color resolve refs |
1534 | if (subpassDesc.pResolveAttachments) { |
1535 | startIndex = attRefs2.count(); |
1536 | for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) { |
1537 | attRefs2.append(t: {}); |
1538 | VkAttachmentReference2KHR &attref2(attRefs2.last()); |
1539 | const VkAttachmentReference &attref(subpassDesc.pResolveAttachments[j]); |
1540 | attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; |
1541 | attref2.attachment = attref.attachment; |
1542 | attref2.layout = attref.layout; |
1543 | attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
1544 | } |
1545 | subpass2.pResolveAttachments = attRefs2.constData() + startIndex; |
1546 | } |
1547 | |
1548 | // depth-stencil ref |
1549 | if (subpassDesc.pDepthStencilAttachment) { |
1550 | startIndex = attRefs2.count(); |
1551 | attRefs2.append(t: {}); |
1552 | VkAttachmentReference2KHR &attref2(attRefs2.last()); |
1553 | const VkAttachmentReference &attref(*subpassDesc.pDepthStencilAttachment); |
1554 | attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; |
1555 | attref2.attachment = attref.attachment; |
1556 | attref2.layout = attref.layout; |
1557 | attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; |
1558 | subpass2.pDepthStencilAttachment = attRefs2.constData() + startIndex; |
1559 | } |
1560 | |
1561 | // depth-stencil resolve ref |
1562 | #ifdef VK_KHR_depth_stencil_resolve |
1563 | dsResolveDesc = {}; |
1564 | if (rpD->hasDepthStencilResolve) { |
1565 | startIndex = attRefs2.count(); |
1566 | attRefs2.append(t: {}); |
1567 | VkAttachmentReference2KHR &attref2(attRefs2.last()); |
1568 | attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR; |
1569 | attref2.attachment = rpD->dsResolveRef.attachment; |
1570 | attref2.layout = rpD->dsResolveRef.layout; |
1571 | attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; |
1572 | dsResolveDesc.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR; |
1573 | dsResolveDesc.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; |
1574 | dsResolveDesc.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; |
1575 | dsResolveDesc.pDepthStencilResolveAttachment = attRefs2.constData() + startIndex; |
1576 | subpass2.pNext = &dsResolveDesc; |
1577 | } |
1578 | #endif |
1579 | |
1580 | rpInfo2->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR; |
1581 | rpInfo2->pNext = nullptr; // the 1.1 VkRenderPassMultiviewCreateInfo is part of the '2' structs |
1582 | rpInfo2->flags = rpInfo->flags; |
1583 | rpInfo2->attachmentCount = rpInfo->attachmentCount; |
1584 | rpInfo2->pAttachments = attDescs2.constData(); |
1585 | rpInfo2->subpassCount = 1; |
1586 | rpInfo2->pSubpasses = &subpass2; |
1587 | if (multiViewCount >= 2) { |
1588 | rpInfo2->correlatedViewMaskCount = 1; |
1589 | rpInfo2->pCorrelatedViewMasks = &viewMask; |
1590 | } |
1591 | return true; |
1592 | } |
1593 | |
1594 | QVarLengthArray<VkAttachmentDescription2KHR, 8> attDescs2; |
1595 | QVarLengthArray<VkAttachmentReference2KHR, 8> attRefs2; |
1596 | VkSubpassDescription2KHR subpass2; |
1597 | #ifdef VK_KHR_depth_stencil_resolve |
1598 | VkSubpassDescriptionDepthStencilResolveKHR dsResolveDesc; |
1599 | #endif |
1600 | uint32_t viewMask; |
1601 | }; |
1602 | #endif // VK_KHR_create_renderpass2 |
1603 | |
1604 | bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD, |
1605 | const QRhiColorAttachment *colorAttachmentsBegin, |
1606 | const QRhiColorAttachment *colorAttachmentsEnd, |
1607 | bool preserveColor, |
1608 | bool preserveDs, |
1609 | bool storeDs, |
1610 | QRhiRenderBuffer *depthStencilBuffer, |
1611 | QRhiTexture *depthTexture, |
1612 | QRhiTexture *depthResolveTexture) |
1613 | { |
1614 | // attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1) |
1615 | |
1616 | int multiViewCount = 0; |
1617 | for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) { |
1618 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
1619 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
1620 | Q_ASSERT(texD || rbD); |
1621 | const VkFormat vkformat = texD ? texD->viewFormat : rbD->vkformat; |
1622 | const VkSampleCountFlagBits samples = texD ? texD->samples : rbD->samples; |
1623 | |
1624 | VkAttachmentDescription attDesc = {}; |
1625 | attDesc.format = vkformat; |
1626 | attDesc.samples = samples; |
1627 | attDesc.loadOp = preserveColor ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR; |
1628 | attDesc.storeOp = (it->resolveTexture() && !preserveColor) ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE; |
1629 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1630 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1631 | // this has to interact correctly with activateTextureRenderTarget(), hence leaving in COLOR_ATT |
1632 | attDesc.initialLayout = preserveColor ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; |
1633 | attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
1634 | rpD->attDescs.append(t: attDesc); |
1635 | |
1636 | const VkAttachmentReference ref = { .attachment: uint32_t(rpD->attDescs.size() - 1), .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
1637 | rpD->colorRefs.append(t: ref); |
1638 | |
1639 | if (it->multiViewCount() >= 2) { |
1640 | if (multiViewCount > 0 && multiViewCount != it->multiViewCount()) |
1641 | qWarning(msg: "Inconsistent multiViewCount in color attachment set" ); |
1642 | else |
1643 | multiViewCount = it->multiViewCount(); |
1644 | } else if (multiViewCount > 0) { |
1645 | qWarning(msg: "Mixing non-multiview color attachments within a multiview render pass" ); |
1646 | } |
1647 | } |
1648 | Q_ASSERT(multiViewCount == 0 || multiViewCount >= 2); |
1649 | rpD->multiViewCount = uint32_t(multiViewCount); |
1650 | |
1651 | rpD->hasDepthStencil = depthStencilBuffer || depthTexture; |
1652 | if (rpD->hasDepthStencil) { |
1653 | const VkFormat dsFormat = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->viewFormat |
1654 | : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->vkformat; |
1655 | const VkSampleCountFlagBits samples = depthTexture ? QRHI_RES(QVkTexture, depthTexture)->samples |
1656 | : QRHI_RES(QVkRenderBuffer, depthStencilBuffer)->samples; |
1657 | const VkAttachmentLoadOp loadOp = preserveDs ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_CLEAR; |
1658 | const VkAttachmentStoreOp storeOp = storeDs ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1659 | VkAttachmentDescription attDesc = {}; |
1660 | attDesc.format = dsFormat; |
1661 | attDesc.samples = samples; |
1662 | attDesc.loadOp = loadOp; |
1663 | attDesc.storeOp = storeOp; |
1664 | attDesc.stencilLoadOp = loadOp; |
1665 | attDesc.stencilStoreOp = storeOp; |
1666 | attDesc.initialLayout = preserveDs ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED; |
1667 | attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
1668 | rpD->attDescs.append(t: attDesc); |
1669 | if (depthTexture && depthTexture->arraySize() >= 2 && colorAttachmentsBegin == colorAttachmentsEnd) { |
1670 | multiViewCount = depthTexture->arraySize(); |
1671 | rpD->multiViewCount = multiViewCount; |
1672 | } |
1673 | } |
1674 | rpD->dsRef = { .attachment: uint32_t(rpD->attDescs.size() - 1), .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; |
1675 | |
1676 | for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) { |
1677 | if (it->resolveTexture()) { |
1678 | QVkTexture *rtexD = QRHI_RES(QVkTexture, it->resolveTexture()); |
1679 | const VkFormat dstFormat = rtexD->vkformat; |
1680 | if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT) |
1681 | qWarning(msg: "Resolving into a multisample texture is not supported" ); |
1682 | |
1683 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
1684 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
1685 | const VkFormat srcFormat = texD ? texD->vkformat : rbD->vkformat; |
1686 | if (srcFormat != dstFormat) { |
1687 | // This is a validation error. But some implementations survive, |
1688 | // actually. Warn about it however, because it's an error with |
1689 | // some other backends (like D3D) as well. |
1690 | qWarning(msg: "Multisample resolve between different formats (%d and %d) is not supported." , |
1691 | int(srcFormat), int(dstFormat)); |
1692 | } |
1693 | |
1694 | VkAttachmentDescription attDesc = {}; |
1695 | attDesc.format = rtexD->viewFormat; |
1696 | attDesc.samples = VK_SAMPLE_COUNT_1_BIT; |
1697 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored |
1698 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; |
1699 | attDesc.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; |
1700 | attDesc.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; |
1701 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1702 | attDesc.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
1703 | rpD->attDescs.append(t: attDesc); |
1704 | |
1705 | const VkAttachmentReference ref = { .attachment: uint32_t(rpD->attDescs.size() - 1), .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
1706 | rpD->resolveRefs.append(t: ref); |
1707 | } else { |
1708 | const VkAttachmentReference ref = { VK_ATTACHMENT_UNUSED, .layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; |
1709 | rpD->resolveRefs.append(t: ref); |
1710 | } |
1711 | } |
1712 | Q_ASSERT(rpD->colorRefs.size() == rpD->resolveRefs.size()); |
1713 | |
1714 | rpD->hasDepthStencilResolve = rpD->hasDepthStencil && depthResolveTexture; |
1715 | if (rpD->hasDepthStencilResolve) { |
1716 | QVkTexture *rtexD = QRHI_RES(QVkTexture, depthResolveTexture); |
1717 | if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT) |
1718 | qWarning(msg: "Resolving into a multisample depth texture is not supported" ); |
1719 | |
1720 | QVkTexture *texD = QRHI_RES(QVkTexture, depthResolveTexture); |
1721 | if (texD->vkformat != rtexD->vkformat) { |
1722 | qWarning(msg: "Multisample resolve between different depth-stencil formats (%d and %d) is not supported." , |
1723 | int(texD->vkformat), int(rtexD->vkformat)); |
1724 | } |
1725 | |
1726 | VkAttachmentDescription attDesc = {}; |
1727 | attDesc.format = rtexD->viewFormat; |
1728 | attDesc.samples = VK_SAMPLE_COUNT_1_BIT; |
1729 | attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored |
1730 | attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE; |
1731 | attDesc.stencilLoadOp = attDesc.loadOp; |
1732 | attDesc.stencilStoreOp = attDesc.storeOp; |
1733 | attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
1734 | attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
1735 | rpD->attDescs.append(t: attDesc); |
1736 | } |
1737 | rpD->dsResolveRef = { .attachment: uint32_t(rpD->attDescs.size() - 1), .layout: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL }; |
1738 | |
1739 | // rpD->subpassDeps stays empty: don't yet know the correct initial/final |
1740 | // access and stage stuff for the implicit deps at this point, so leave it |
1741 | // to the resource tracking and activateTextureRenderTarget() to generate |
1742 | // barriers. |
1743 | |
1744 | VkRenderPassCreateInfo rpInfo; |
1745 | VkSubpassDescription subpassDesc; |
1746 | fillRenderPassCreateInfo(rpInfo: &rpInfo, subpassDesc: &subpassDesc, rpD); |
1747 | |
1748 | MultiViewRenderPassSetupHelper multiViewHelper; |
1749 | if (!multiViewHelper.prepare(rpInfo: &rpInfo, multiViewCount, multiViewCap: caps.multiView)) |
1750 | return false; |
1751 | |
1752 | #ifdef VK_KHR_create_renderpass2 |
1753 | if (rpD->hasDepthStencilResolve && caps.renderPass2KHR) { |
1754 | // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1. |
1755 | VkRenderPassCreateInfo2KHR rpInfo2; |
1756 | RenderPass2SetupHelper rp2Helper; |
1757 | if (!rp2Helper.prepare(rpInfo2: &rpInfo2, rpInfo: &rpInfo, rpD, multiViewCount)) |
1758 | return false; |
1759 | |
1760 | VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp); |
1761 | if (err != VK_SUCCESS) { |
1762 | qWarning(msg: "Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d" , err); |
1763 | return false; |
1764 | } |
1765 | } else |
1766 | #endif |
1767 | { |
1768 | if (rpD->hasDepthStencilResolve) { |
1769 | qWarning(msg: "Resolving multisample depth-stencil buffers is not supported without " |
1770 | "VK_KHR_depth_stencil_resolve and VK_KHR_create_renderpass2" ); |
1771 | } |
1772 | VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp); |
1773 | if (err != VK_SUCCESS) { |
1774 | qWarning(msg: "Failed to create renderpass: %d" , err); |
1775 | return false; |
1776 | } |
1777 | } |
1778 | |
1779 | return true; |
1780 | } |
1781 | |
1782 | bool QRhiVulkan::recreateSwapChain(QRhiSwapChain *swapChain) |
1783 | { |
1784 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
1785 | if (swapChainD->pixelSize.isEmpty()) { |
1786 | qWarning(msg: "Surface size is 0, cannot create swapchain" ); |
1787 | return false; |
1788 | } |
1789 | |
1790 | df->vkDeviceWaitIdle(dev); |
1791 | |
1792 | if (!vkCreateSwapchainKHR) { |
1793 | vkCreateSwapchainKHR = reinterpret_cast<PFN_vkCreateSwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkCreateSwapchainKHR" )); |
1794 | vkDestroySwapchainKHR = reinterpret_cast<PFN_vkDestroySwapchainKHR>(f->vkGetDeviceProcAddr(dev, "vkDestroySwapchainKHR" )); |
1795 | vkGetSwapchainImagesKHR = reinterpret_cast<PFN_vkGetSwapchainImagesKHR>(f->vkGetDeviceProcAddr(dev, "vkGetSwapchainImagesKHR" )); |
1796 | vkAcquireNextImageKHR = reinterpret_cast<PFN_vkAcquireNextImageKHR>(f->vkGetDeviceProcAddr(dev, "vkAcquireNextImageKHR" )); |
1797 | vkQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(f->vkGetDeviceProcAddr(dev, "vkQueuePresentKHR" )); |
1798 | if (!vkCreateSwapchainKHR || !vkDestroySwapchainKHR || !vkGetSwapchainImagesKHR || !vkAcquireNextImageKHR || !vkQueuePresentKHR) { |
1799 | qWarning(msg: "Swapchain functions not available" ); |
1800 | return false; |
1801 | } |
1802 | } |
1803 | |
1804 | VkSurfaceCapabilitiesKHR surfaceCaps; |
1805 | vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physDev, swapChainD->surface, &surfaceCaps); |
1806 | quint32 reqBufferCount; |
1807 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::MinimalBufferCount) || surfaceCaps.maxImageCount == 0) { |
1808 | reqBufferCount = qMax<quint32>(a: 2, b: surfaceCaps.minImageCount); |
1809 | } else { |
1810 | reqBufferCount = qMax(a: qMin<quint32>(a: surfaceCaps.maxImageCount, b: 3), b: surfaceCaps.minImageCount); |
1811 | } |
1812 | VkSurfaceTransformFlagBitsKHR preTransform = |
1813 | (surfaceCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) |
1814 | ? VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR |
1815 | : surfaceCaps.currentTransform; |
1816 | |
1817 | // This looks odd but matches how platforms work in practice. |
1818 | // |
1819 | // On Windows with NVIDIA for example, the only supportedCompositeAlpha |
1820 | // value reported is OPAQUE, nothing else. Yet transparency works |
1821 | // regardless, as long as the native window is set up correctly, so that's |
1822 | // not something we need to handle here. |
1823 | // |
1824 | // On Linux with Intel and Mesa and running on xcb reports, on one |
1825 | // particular system, INHERIT+PRE_MULTIPLIED. Tranparency works, regardless, |
1826 | // presumably due to setting INHERIT. |
1827 | // |
1828 | // On the same setup with Wayland instead of xcb we see |
1829 | // OPAQUE+PRE_MULTIPLIED reported. Here transparency won't work unless |
1830 | // PRE_MULTIPLIED is set. |
1831 | // |
1832 | // Therefore our rules are: |
1833 | // - Prefer INHERIT over OPAQUE. |
1834 | // - Then based on the request, try the requested alpha mode, but if |
1835 | // that's not reported as supported, try also the other (PRE/POST, |
1836 | // POST/PRE) as that is better than nothing. This is not different from |
1837 | // some other backends, e.g. D3D11 with DirectComposition there is also |
1838 | // no control over being straight or pre-multiplied. Whereas with |
1839 | // WGL/GLX/EGL we never had that sort of control. |
1840 | |
1841 | VkCompositeAlphaFlagBitsKHR compositeAlpha = |
1842 | (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) |
1843 | ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR |
1844 | : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; |
1845 | |
1846 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::SurfaceHasPreMulAlpha)) { |
1847 | if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) |
1848 | compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; |
1849 | else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) |
1850 | compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; |
1851 | } else if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::SurfaceHasNonPreMulAlpha)) { |
1852 | if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) |
1853 | compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; |
1854 | else if (surfaceCaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) |
1855 | compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; |
1856 | } |
1857 | |
1858 | VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
1859 | swapChainD->supportsReadback = (surfaceCaps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT); |
1860 | if (swapChainD->supportsReadback && swapChainD->m_flags.testFlag(flag: QRhiSwapChain::UsedAsTransferSource)) |
1861 | usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
1862 | |
1863 | const bool stereo = bool(swapChainD->m_window) && (swapChainD->m_window->format().stereo()) |
1864 | && surfaceCaps.maxImageArrayLayers > 1; |
1865 | swapChainD->stereo = stereo; |
1866 | |
1867 | VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; |
1868 | if (swapChainD->m_flags.testFlag(flag: QRhiSwapChain::NoVSync)) { |
1869 | // Stereo has a weird bug, when using VK_PRESENT_MODE_MAILBOX_KHR, |
1870 | // black screen is shown, but there is no validation error. |
1871 | // Detected on Windows, with NVidia RTX A series (at least 4000 and 6000) driver 535.98 |
1872 | if (swapChainD->supportedPresentationModes.contains(t: VK_PRESENT_MODE_MAILBOX_KHR) && !stereo) |
1873 | presentMode = VK_PRESENT_MODE_MAILBOX_KHR; |
1874 | else if (swapChainD->supportedPresentationModes.contains(t: VK_PRESENT_MODE_IMMEDIATE_KHR)) |
1875 | presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; |
1876 | } |
1877 | |
1878 | // If the surface is different than before, then passing in the old |
1879 | // swapchain associated with the old surface can fail the swapchain |
1880 | // creation. (for example, Android loses the surface when backgrounding and |
1881 | // restoring applications, and it also enforces failing swapchain creation |
1882 | // with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR if the old swapchain is provided) |
1883 | const bool reuseExisting = swapChainD->sc && swapChainD->lastConnectedSurface == swapChainD->surface; |
1884 | |
1885 | qCDebug(QRHI_LOG_INFO, "Creating %s swapchain of %u buffers, size %dx%d, presentation mode %d" , |
1886 | reuseExisting ? "recycled" : "new" , |
1887 | reqBufferCount, swapChainD->pixelSize.width(), swapChainD->pixelSize.height(), presentMode); |
1888 | |
1889 | VkSwapchainCreateInfoKHR swapChainInfo = {}; |
1890 | swapChainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; |
1891 | swapChainInfo.surface = swapChainD->surface; |
1892 | swapChainInfo.minImageCount = reqBufferCount; |
1893 | swapChainInfo.imageFormat = swapChainD->colorFormat; |
1894 | swapChainInfo.imageColorSpace = swapChainD->colorSpace; |
1895 | swapChainInfo.imageExtent = VkExtent2D { .width: uint32_t(swapChainD->pixelSize.width()), .height: uint32_t(swapChainD->pixelSize.height()) }; |
1896 | swapChainInfo.imageArrayLayers = stereo ? 2u : 1u; |
1897 | swapChainInfo.imageUsage = usage; |
1898 | swapChainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; |
1899 | swapChainInfo.preTransform = preTransform; |
1900 | swapChainInfo.compositeAlpha = compositeAlpha; |
1901 | swapChainInfo.presentMode = presentMode; |
1902 | swapChainInfo.clipped = true; |
1903 | swapChainInfo.oldSwapchain = reuseExisting ? swapChainD->sc : VK_NULL_HANDLE; |
1904 | |
1905 | VkSwapchainKHR newSwapChain; |
1906 | VkResult err = vkCreateSwapchainKHR(dev, &swapChainInfo, nullptr, &newSwapChain); |
1907 | if (err != VK_SUCCESS) { |
1908 | qWarning(msg: "Failed to create swapchain: %d" , err); |
1909 | return false; |
1910 | } |
1911 | |
1912 | if (swapChainD->sc) |
1913 | releaseSwapChainResources(swapChain); |
1914 | |
1915 | swapChainD->sc = newSwapChain; |
1916 | swapChainD->lastConnectedSurface = swapChainD->surface; |
1917 | |
1918 | quint32 actualSwapChainBufferCount = 0; |
1919 | err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, nullptr); |
1920 | if (err != VK_SUCCESS || actualSwapChainBufferCount == 0) { |
1921 | qWarning(msg: "Failed to get swapchain images: %d" , err); |
1922 | return false; |
1923 | } |
1924 | |
1925 | if (actualSwapChainBufferCount != reqBufferCount) |
1926 | qCDebug(QRHI_LOG_INFO, "Actual swapchain buffer count is %u" , actualSwapChainBufferCount); |
1927 | swapChainD->bufferCount = int(actualSwapChainBufferCount); |
1928 | |
1929 | QVarLengthArray<VkImage, QVkSwapChain::EXPECTED_MAX_BUFFER_COUNT> swapChainImages(actualSwapChainBufferCount); |
1930 | err = vkGetSwapchainImagesKHR(dev, swapChainD->sc, &actualSwapChainBufferCount, swapChainImages.data()); |
1931 | if (err != VK_SUCCESS) { |
1932 | qWarning(msg: "Failed to get swapchain images: %d" , err); |
1933 | return false; |
1934 | } |
1935 | |
1936 | QVarLengthArray<VkImage, QVkSwapChain::EXPECTED_MAX_BUFFER_COUNT> msaaImages(swapChainD->bufferCount); |
1937 | QVarLengthArray<VkImageView, QVkSwapChain::EXPECTED_MAX_BUFFER_COUNT> msaaViews(swapChainD->bufferCount); |
1938 | if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) { |
1939 | if (!createTransientImage(format: swapChainD->colorFormat, |
1940 | pixelSize: swapChainD->pixelSize, |
1941 | usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, |
1942 | aspectMask: VK_IMAGE_ASPECT_COLOR_BIT, |
1943 | samples: swapChainD->samples, |
1944 | mem: &swapChainD->msaaImageMem, |
1945 | images: msaaImages.data(), |
1946 | views: msaaViews.data(), |
1947 | count: swapChainD->bufferCount)) |
1948 | { |
1949 | qWarning(msg: "Failed to create transient image for MSAA color buffer" ); |
1950 | return false; |
1951 | } |
1952 | } |
1953 | |
1954 | VkFenceCreateInfo fenceInfo = {}; |
1955 | fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; |
1956 | fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; |
1957 | |
1958 | // Double up for stereo |
1959 | swapChainD->imageRes.resize(sz: swapChainD->bufferCount * (stereo ? 2u : 1u)); |
1960 | |
1961 | for (int i = 0; i < swapChainD->bufferCount; ++i) { |
1962 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]); |
1963 | image.image = swapChainImages[i]; |
1964 | if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) { |
1965 | image.msaaImage = msaaImages[i]; |
1966 | image.msaaImageView = msaaViews[i]; |
1967 | } |
1968 | |
1969 | VkImageViewCreateInfo imgViewInfo = {}; |
1970 | imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
1971 | imgViewInfo.image = swapChainImages[i]; |
1972 | imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
1973 | imgViewInfo.format = swapChainD->colorFormat; |
1974 | imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
1975 | imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
1976 | imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
1977 | imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
1978 | imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
1979 | imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; |
1980 | err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView); |
1981 | if (err != VK_SUCCESS) { |
1982 | qWarning(msg: "Failed to create swapchain image view %d: %d" , i, err); |
1983 | return false; |
1984 | } |
1985 | |
1986 | image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone; |
1987 | } |
1988 | if (stereo) { |
1989 | for (int i = 0; i < swapChainD->bufferCount; ++i) { |
1990 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[i + swapChainD->bufferCount]); |
1991 | image.image = swapChainImages[i]; |
1992 | if (swapChainD->samples > VK_SAMPLE_COUNT_1_BIT) { |
1993 | image.msaaImage = msaaImages[i]; |
1994 | image.msaaImageView = msaaViews[i]; |
1995 | } |
1996 | |
1997 | VkImageViewCreateInfo imgViewInfo = {}; |
1998 | imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
1999 | imgViewInfo.image = swapChainImages[i]; |
2000 | imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; |
2001 | imgViewInfo.format = swapChainD->colorFormat; |
2002 | imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
2003 | imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
2004 | imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
2005 | imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
2006 | imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
2007 | imgViewInfo.subresourceRange.baseArrayLayer = 1; |
2008 | imgViewInfo.subresourceRange.levelCount = imgViewInfo.subresourceRange.layerCount = 1; |
2009 | err = df->vkCreateImageView(dev, &imgViewInfo, nullptr, &image.imageView); |
2010 | if (err != VK_SUCCESS) { |
2011 | qWarning(msg: "Failed to create swapchain image view %d: %d" , i, err); |
2012 | return false; |
2013 | } |
2014 | |
2015 | image.lastUse = QVkSwapChain::ImageResources::ScImageUseNone; |
2016 | } |
2017 | } |
2018 | |
2019 | swapChainD->currentImageIndex = 0; |
2020 | |
2021 | VkSemaphoreCreateInfo semInfo = {}; |
2022 | semInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; |
2023 | |
2024 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
2025 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]); |
2026 | |
2027 | frame.imageAcquired = false; |
2028 | frame.imageSemWaitable = false; |
2029 | |
2030 | df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.imageFence); |
2031 | frame.imageFenceWaitable = true; // fence was created in signaled state |
2032 | |
2033 | df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.imageSem); |
2034 | df->vkCreateSemaphore(dev, &semInfo, nullptr, &frame.drawSem); |
2035 | |
2036 | err = df->vkCreateFence(dev, &fenceInfo, nullptr, &frame.cmdFence); |
2037 | if (err != VK_SUCCESS) { |
2038 | qWarning(msg: "Failed to create command buffer fence: %d" , err); |
2039 | return false; |
2040 | } |
2041 | frame.cmdFenceWaitable = true; // fence was created in signaled state |
2042 | } |
2043 | |
2044 | swapChainD->currentFrameSlot = 0; |
2045 | |
2046 | return true; |
2047 | } |
2048 | |
2049 | void QRhiVulkan::releaseSwapChainResources(QRhiSwapChain *swapChain) |
2050 | { |
2051 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
2052 | |
2053 | if (swapChainD->sc == VK_NULL_HANDLE) |
2054 | return; |
2055 | |
2056 | if (!deviceLost) |
2057 | df->vkDeviceWaitIdle(dev); |
2058 | |
2059 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
2060 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[i]); |
2061 | if (frame.cmdFence) { |
2062 | if (frame.cmdFenceWaitable) |
2063 | df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX); |
2064 | df->vkDestroyFence(dev, frame.cmdFence, nullptr); |
2065 | frame.cmdFence = VK_NULL_HANDLE; |
2066 | frame.cmdFenceWaitable = false; |
2067 | } |
2068 | if (frame.imageFence) { |
2069 | if (frame.imageFenceWaitable) |
2070 | df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX); |
2071 | df->vkDestroyFence(dev, frame.imageFence, nullptr); |
2072 | frame.imageFence = VK_NULL_HANDLE; |
2073 | frame.imageFenceWaitable = false; |
2074 | } |
2075 | if (frame.imageSem) { |
2076 | df->vkDestroySemaphore(dev, frame.imageSem, nullptr); |
2077 | frame.imageSem = VK_NULL_HANDLE; |
2078 | } |
2079 | if (frame.drawSem) { |
2080 | df->vkDestroySemaphore(dev, frame.drawSem, nullptr); |
2081 | frame.drawSem = VK_NULL_HANDLE; |
2082 | } |
2083 | } |
2084 | |
2085 | for (int i = 0; i < swapChainD->bufferCount * (swapChainD->stereo ? 2 : 1); ++i) { |
2086 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[i]); |
2087 | if (image.fb) { |
2088 | df->vkDestroyFramebuffer(dev, image.fb, nullptr); |
2089 | image.fb = VK_NULL_HANDLE; |
2090 | } |
2091 | if (image.imageView) { |
2092 | df->vkDestroyImageView(dev, image.imageView, nullptr); |
2093 | image.imageView = VK_NULL_HANDLE; |
2094 | } |
2095 | if (image.msaaImageView) { |
2096 | df->vkDestroyImageView(dev, image.msaaImageView, nullptr); |
2097 | image.msaaImageView = VK_NULL_HANDLE; |
2098 | } |
2099 | if (image.msaaImage) { |
2100 | df->vkDestroyImage(dev, image.msaaImage, nullptr); |
2101 | image.msaaImage = VK_NULL_HANDLE; |
2102 | } |
2103 | } |
2104 | |
2105 | if (swapChainD->msaaImageMem) { |
2106 | df->vkFreeMemory(dev, swapChainD->msaaImageMem, nullptr); |
2107 | swapChainD->msaaImageMem = VK_NULL_HANDLE; |
2108 | } |
2109 | |
2110 | vkDestroySwapchainKHR(dev, swapChainD->sc, nullptr); |
2111 | swapChainD->sc = VK_NULL_HANDLE; |
2112 | |
2113 | // NB! surface and similar must remain intact |
2114 | } |
2115 | |
2116 | void QRhiVulkan::ensureCommandPoolForNewFrame() |
2117 | { |
2118 | VkCommandPoolResetFlags flags = 0; |
2119 | |
2120 | // While not clear what "recycles all of the resources from the command |
2121 | // pool back to the system" really means in practice, set it when there was |
2122 | // a call to releaseCachedResources() recently. |
2123 | if (releaseCachedResourcesCalledBeforeFrameStart) |
2124 | flags |= VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT; |
2125 | |
2126 | // put all command buffers allocated from this slot's pool to initial state |
2127 | df->vkResetCommandPool(dev, cmdPool[currentFrameSlot], flags); |
2128 | } |
2129 | |
2130 | double QRhiVulkan::elapsedSecondsFromTimestamp(quint64 timestamp[2], bool *ok) |
2131 | { |
2132 | quint64 mask = 0; |
2133 | for (quint64 i = 0; i < timestampValidBits; i += 8) |
2134 | mask |= 0xFFULL << i; |
2135 | const quint64 ts0 = timestamp[0] & mask; |
2136 | const quint64 ts1 = timestamp[1] & mask; |
2137 | const float nsecsPerTick = physDevProperties.limits.timestampPeriod; |
2138 | if (!qFuzzyIsNull(f: nsecsPerTick)) { |
2139 | const float elapsedMs = float(ts1 - ts0) * nsecsPerTick / 1000000.0f; |
2140 | const double elapsedSec = elapsedMs / 1000.0; |
2141 | *ok = true; |
2142 | return elapsedSec; |
2143 | } |
2144 | *ok = false; |
2145 | return 0; |
2146 | } |
2147 | |
2148 | QRhi::FrameOpResult QRhiVulkan::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags) |
2149 | { |
2150 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
2151 | const int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; |
2152 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); |
2153 | |
2154 | inst->handle()->beginFrame(window: swapChainD->window); |
2155 | |
2156 | if (!frame.imageAcquired) { |
2157 | // Wait if we are too far ahead, i.e. the thread gets throttled based on the presentation rate |
2158 | // (note that we are using FIFO mode -> vsync) |
2159 | if (frame.imageFenceWaitable) { |
2160 | df->vkWaitForFences(dev, 1, &frame.imageFence, VK_TRUE, UINT64_MAX); |
2161 | df->vkResetFences(dev, 1, &frame.imageFence); |
2162 | frame.imageFenceWaitable = false; |
2163 | } |
2164 | |
2165 | // move on to next swapchain image |
2166 | uint32_t imageIndex = 0; |
2167 | VkResult err = vkAcquireNextImageKHR(dev, swapChainD->sc, UINT64_MAX, |
2168 | frame.imageSem, frame.imageFence, &imageIndex); |
2169 | if (err == VK_SUCCESS || err == VK_SUBOPTIMAL_KHR) { |
2170 | swapChainD->currentImageIndex = imageIndex; |
2171 | frame.imageSemWaitable = true; |
2172 | frame.imageAcquired = true; |
2173 | frame.imageFenceWaitable = true; |
2174 | } else if (err == VK_ERROR_OUT_OF_DATE_KHR) { |
2175 | return QRhi::FrameOpSwapChainOutOfDate; |
2176 | } else { |
2177 | if (err == VK_ERROR_DEVICE_LOST) { |
2178 | qWarning(msg: "Device loss detected in vkAcquireNextImageKHR()" ); |
2179 | deviceLost = true; |
2180 | return QRhi::FrameOpDeviceLost; |
2181 | } |
2182 | qWarning(msg: "Failed to acquire next swapchain image: %d" , err); |
2183 | return QRhi::FrameOpError; |
2184 | } |
2185 | } |
2186 | |
2187 | // Make sure the previous commands for the same image have finished. (note |
2188 | // that this is based on the fence from the command buffer submit, nothing |
2189 | // to do with the Present) |
2190 | // |
2191 | // Do this also for any other swapchain's commands with the same frame slot |
2192 | // While this reduces concurrency, it keeps resource usage safe: swapchain |
2193 | // A starting its frame 0, followed by swapchain B starting its own frame 0 |
2194 | // will make B wait for A's frame 0 commands, so if a resource is written |
2195 | // in B's frame or when B checks for pending resource releases, that won't |
2196 | // mess up A's in-flight commands (as they are not in flight anymore). |
2197 | waitCommandCompletion(frameSlot: frameResIndex); |
2198 | |
2199 | currentFrameSlot = int(swapChainD->currentFrameSlot); |
2200 | currentSwapChain = swapChainD; |
2201 | if (swapChainD->ds) |
2202 | swapChainD->ds->lastActiveFrameSlot = currentFrameSlot; |
2203 | |
2204 | // reset the command pool |
2205 | ensureCommandPoolForNewFrame(); |
2206 | |
2207 | // start recording to this frame's command buffer |
2208 | QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(cb: &frame.cmdBuf); |
2209 | if (cbres != QRhi::FrameOpSuccess) |
2210 | return cbres; |
2211 | |
2212 | swapChainD->cbWrapper.cb = frame.cmdBuf; |
2213 | |
2214 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); |
2215 | swapChainD->rtWrapper.d.fb = image.fb; |
2216 | |
2217 | if (swapChainD->stereo) { |
2218 | QVkSwapChain::ImageResources &image( |
2219 | swapChainD->imageRes[swapChainD->currentImageIndex + swapChainD->bufferCount]); |
2220 | swapChainD->rtWrapperRight.d.fb = image.fb; |
2221 | } |
2222 | |
2223 | prepareNewFrame(cb: &swapChainD->cbWrapper); |
2224 | |
2225 | // Read the timestamps for the previous frame for this slot. |
2226 | if (frame.timestampQueryIndex >= 0) { |
2227 | quint64 timestamp[2] = { 0, 0 }; |
2228 | VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(frame.timestampQueryIndex), 2, |
2229 | 2 * sizeof(quint64), timestamp, sizeof(quint64), |
2230 | VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); |
2231 | timestampQueryPoolMap.clearBit(i: frame.timestampQueryIndex / 2); |
2232 | frame.timestampQueryIndex = -1; |
2233 | if (err == VK_SUCCESS) { |
2234 | bool ok = false; |
2235 | const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, ok: &ok); |
2236 | if (ok) |
2237 | swapChainD->cbWrapper.lastGpuTime = elapsedSec; |
2238 | } else { |
2239 | qWarning(msg: "Failed to query timestamp: %d" , err); |
2240 | } |
2241 | } |
2242 | |
2243 | // No timestamps if the client did not opt in, or when not having at least 2 frames in flight. |
2244 | if (rhiFlags.testFlag(flag: QRhi::EnableTimestamps) && swapChainD->bufferCount > 1) { |
2245 | int timestampQueryIdx = -1; |
2246 | for (int i = 0; i < timestampQueryPoolMap.size(); ++i) { |
2247 | if (!timestampQueryPoolMap.testBit(i)) { |
2248 | timestampQueryPoolMap.setBit(i); |
2249 | timestampQueryIdx = i * 2; |
2250 | break; |
2251 | } |
2252 | } |
2253 | if (timestampQueryIdx >= 0) { |
2254 | df->vkCmdResetQueryPool(frame.cmdBuf, timestampQueryPool, uint32_t(timestampQueryIdx), 2); |
2255 | // record timestamp at the start of the command buffer |
2256 | df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, |
2257 | timestampQueryPool, uint32_t(timestampQueryIdx)); |
2258 | frame.timestampQueryIndex = timestampQueryIdx; |
2259 | } |
2260 | } |
2261 | |
2262 | return QRhi::FrameOpSuccess; |
2263 | } |
2264 | |
2265 | QRhi::FrameOpResult QRhiVulkan::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) |
2266 | { |
2267 | QVkSwapChain *swapChainD = QRHI_RES(QVkSwapChain, swapChain); |
2268 | Q_ASSERT(currentSwapChain == swapChainD); |
2269 | |
2270 | auto cleanup = qScopeGuard(f: [this, swapChainD] { |
2271 | inst->handle()->endFrame(window: swapChainD->window); |
2272 | }); |
2273 | |
2274 | recordPrimaryCommandBuffer(cbD: &swapChainD->cbWrapper); |
2275 | |
2276 | int frameResIndex = swapChainD->bufferCount > 1 ? swapChainD->currentFrameSlot : 0; |
2277 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[frameResIndex]); |
2278 | QVkSwapChain::ImageResources &image(swapChainD->imageRes[swapChainD->currentImageIndex]); |
2279 | |
2280 | if (image.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { |
2281 | VkImageMemoryBarrier presTrans = {}; |
2282 | presTrans.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
2283 | presTrans.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
2284 | presTrans.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
2285 | presTrans.image = image.image; |
2286 | presTrans.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
2287 | presTrans.subresourceRange.levelCount = presTrans.subresourceRange.layerCount = 1; |
2288 | |
2289 | if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseNone) { |
2290 | // was not used at all (no render pass), just transition from undefined to presentable |
2291 | presTrans.srcAccessMask = 0; |
2292 | presTrans.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
2293 | df->vkCmdPipelineBarrier(frame.cmdBuf, |
2294 | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
2295 | 0, 0, nullptr, 0, nullptr, |
2296 | 1, &presTrans); |
2297 | } else if (image.lastUse == QVkSwapChain::ImageResources::ScImageUseTransferSource) { |
2298 | // was used in a readback as transfer source, go back to presentable layout |
2299 | presTrans.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; |
2300 | presTrans.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
2301 | df->vkCmdPipelineBarrier(frame.cmdBuf, |
2302 | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
2303 | 0, 0, nullptr, 0, nullptr, |
2304 | 1, &presTrans); |
2305 | } |
2306 | image.lastUse = QVkSwapChain::ImageResources::ScImageUseRender; |
2307 | } |
2308 | |
2309 | // record another timestamp, when enabled |
2310 | if (frame.timestampQueryIndex >= 0) { |
2311 | df->vkCmdWriteTimestamp(frame.cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, |
2312 | timestampQueryPool, uint32_t(frame.timestampQueryIndex + 1)); |
2313 | } |
2314 | |
2315 | // stop recording and submit to the queue |
2316 | Q_ASSERT(!frame.cmdFenceWaitable); |
2317 | const bool needsPresent = !flags.testFlag(flag: QRhi::SkipPresent); |
2318 | QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb: frame.cmdBuf, |
2319 | cmdFence: frame.cmdFence, |
2320 | waitSem: frame.imageSemWaitable ? &frame.imageSem : nullptr, |
2321 | signalSem: needsPresent ? &frame.drawSem : nullptr); |
2322 | if (submitres != QRhi::FrameOpSuccess) |
2323 | return submitres; |
2324 | |
2325 | frame.imageSemWaitable = false; |
2326 | frame.cmdFenceWaitable = true; |
2327 | |
2328 | if (needsPresent) { |
2329 | // add the Present to the queue |
2330 | VkPresentInfoKHR presInfo = {}; |
2331 | presInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; |
2332 | presInfo.swapchainCount = 1; |
2333 | presInfo.pSwapchains = &swapChainD->sc; |
2334 | presInfo.pImageIndices = &swapChainD->currentImageIndex; |
2335 | presInfo.waitSemaphoreCount = 1; |
2336 | presInfo.pWaitSemaphores = &frame.drawSem; // gfxQueueFamilyIdx == presQueueFamilyIdx ? &frame.drawSem : &frame.presTransSem; |
2337 | |
2338 | // Do platform-specific WM notification. F.ex. essential on Wayland in |
2339 | // order to circumvent driver frame callbacks |
2340 | inst->presentAboutToBeQueued(window: swapChainD->window); |
2341 | |
2342 | VkResult err = vkQueuePresentKHR(gfxQueue, &presInfo); |
2343 | if (err != VK_SUCCESS) { |
2344 | if (err == VK_ERROR_OUT_OF_DATE_KHR) { |
2345 | return QRhi::FrameOpSwapChainOutOfDate; |
2346 | } else if (err != VK_SUBOPTIMAL_KHR) { |
2347 | if (err == VK_ERROR_DEVICE_LOST) { |
2348 | qWarning(msg: "Device loss detected in vkQueuePresentKHR()" ); |
2349 | deviceLost = true; |
2350 | return QRhi::FrameOpDeviceLost; |
2351 | } |
2352 | qWarning(msg: "Failed to present: %d" , err); |
2353 | return QRhi::FrameOpError; |
2354 | } |
2355 | } |
2356 | |
2357 | // Do platform-specific WM notification. F.ex. essential on X11 in |
2358 | // order to prevent glitches on resizing the window. |
2359 | inst->presentQueued(window: swapChainD->window); |
2360 | |
2361 | // mark the current swapchain buffer as unused from our side |
2362 | frame.imageAcquired = false; |
2363 | // and move on to the next buffer |
2364 | swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT; |
2365 | } |
2366 | |
2367 | swapChainD->frameCount += 1; |
2368 | currentSwapChain = nullptr; |
2369 | return QRhi::FrameOpSuccess; |
2370 | } |
2371 | |
2372 | void QRhiVulkan::prepareNewFrame(QRhiCommandBuffer *cb) |
2373 | { |
2374 | // Now is the time to do things for frame N-F, where N is the current one, |
2375 | // F is QVK_FRAMES_IN_FLIGHT, because only here it is guaranteed that that |
2376 | // frame has completed on the GPU (due to the fence wait in beginFrame). To |
2377 | // decide if something is safe to handle now a simple "lastActiveFrameSlot |
2378 | // == currentFrameSlot" is sufficient (remember that e.g. with F==2 |
2379 | // currentFrameSlot goes 0, 1, 0, 1, 0, ...) |
2380 | // |
2381 | // With multiple swapchains on the same QRhi things get more convoluted |
2382 | // (and currentFrameSlot strictly alternating is not true anymore) but |
2383 | // begin(Offscreen)Frame() blocks anyway waiting for its current frame |
2384 | // slot's previous commands to complete so this here is safe regardless. |
2385 | |
2386 | executeDeferredReleases(); |
2387 | |
2388 | QRHI_RES(QVkCommandBuffer, cb)->resetState(); |
2389 | |
2390 | finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls |
2391 | |
2392 | releaseCachedResourcesCalledBeforeFrameStart = false; |
2393 | } |
2394 | |
2395 | QRhi::FrameOpResult QRhiVulkan::startPrimaryCommandBuffer(VkCommandBuffer *cb) |
2396 | { |
2397 | if (!*cb) { |
2398 | VkCommandBufferAllocateInfo cmdBufInfo = {}; |
2399 | cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; |
2400 | cmdBufInfo.commandPool = cmdPool[currentFrameSlot]; |
2401 | cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; |
2402 | cmdBufInfo.commandBufferCount = 1; |
2403 | |
2404 | VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, cb); |
2405 | if (err != VK_SUCCESS) { |
2406 | if (err == VK_ERROR_DEVICE_LOST) { |
2407 | qWarning(msg: "Device loss detected in vkAllocateCommandBuffers()" ); |
2408 | deviceLost = true; |
2409 | return QRhi::FrameOpDeviceLost; |
2410 | } |
2411 | qWarning(msg: "Failed to allocate frame command buffer: %d" , err); |
2412 | return QRhi::FrameOpError; |
2413 | } |
2414 | } |
2415 | |
2416 | VkCommandBufferBeginInfo cmdBufBeginInfo = {}; |
2417 | cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; |
2418 | |
2419 | VkResult err = df->vkBeginCommandBuffer(*cb, &cmdBufBeginInfo); |
2420 | if (err != VK_SUCCESS) { |
2421 | if (err == VK_ERROR_DEVICE_LOST) { |
2422 | qWarning(msg: "Device loss detected in vkBeginCommandBuffer()" ); |
2423 | deviceLost = true; |
2424 | return QRhi::FrameOpDeviceLost; |
2425 | } |
2426 | qWarning(msg: "Failed to begin frame command buffer: %d" , err); |
2427 | return QRhi::FrameOpError; |
2428 | } |
2429 | |
2430 | return QRhi::FrameOpSuccess; |
2431 | } |
2432 | |
2433 | QRhi::FrameOpResult QRhiVulkan::endAndSubmitPrimaryCommandBuffer(VkCommandBuffer cb, VkFence cmdFence, |
2434 | VkSemaphore *waitSem, VkSemaphore *signalSem) |
2435 | { |
2436 | VkResult err = df->vkEndCommandBuffer(cb); |
2437 | if (err != VK_SUCCESS) { |
2438 | if (err == VK_ERROR_DEVICE_LOST) { |
2439 | qWarning(msg: "Device loss detected in vkEndCommandBuffer()" ); |
2440 | deviceLost = true; |
2441 | return QRhi::FrameOpDeviceLost; |
2442 | } |
2443 | qWarning(msg: "Failed to end frame command buffer: %d" , err); |
2444 | return QRhi::FrameOpError; |
2445 | } |
2446 | |
2447 | VkSubmitInfo submitInfo = {}; |
2448 | submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; |
2449 | submitInfo.commandBufferCount = 1; |
2450 | submitInfo.pCommandBuffers = &cb; |
2451 | if (waitSem) { |
2452 | submitInfo.waitSemaphoreCount = 1; |
2453 | submitInfo.pWaitSemaphores = waitSem; |
2454 | } |
2455 | if (signalSem) { |
2456 | submitInfo.signalSemaphoreCount = 1; |
2457 | submitInfo.pSignalSemaphores = signalSem; |
2458 | } |
2459 | VkPipelineStageFlags psf = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
2460 | submitInfo.pWaitDstStageMask = &psf; |
2461 | |
2462 | err = df->vkQueueSubmit(gfxQueue, 1, &submitInfo, cmdFence); |
2463 | if (err != VK_SUCCESS) { |
2464 | if (err == VK_ERROR_DEVICE_LOST) { |
2465 | qWarning(msg: "Device loss detected in vkQueueSubmit()" ); |
2466 | deviceLost = true; |
2467 | return QRhi::FrameOpDeviceLost; |
2468 | } |
2469 | qWarning(msg: "Failed to submit to graphics queue: %d" , err); |
2470 | return QRhi::FrameOpError; |
2471 | } |
2472 | |
2473 | return QRhi::FrameOpSuccess; |
2474 | } |
2475 | |
2476 | void QRhiVulkan::waitCommandCompletion(int frameSlot) |
2477 | { |
2478 | for (QVkSwapChain *sc : std::as_const(t&: swapchains)) { |
2479 | const int frameResIndex = sc->bufferCount > 1 ? frameSlot : 0; |
2480 | QVkSwapChain::FrameResources &frame(sc->frameRes[frameResIndex]); |
2481 | if (frame.cmdFenceWaitable) { |
2482 | df->vkWaitForFences(dev, 1, &frame.cmdFence, VK_TRUE, UINT64_MAX); |
2483 | df->vkResetFences(dev, 1, &frame.cmdFence); |
2484 | frame.cmdFenceWaitable = false; |
2485 | } |
2486 | } |
2487 | } |
2488 | |
2489 | QRhi::FrameOpResult QRhiVulkan::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags) |
2490 | { |
2491 | // Switch to the next slot manually. Swapchains do not know about this |
2492 | // which is good. So for example an onscreen, onscreen, offscreen, |
2493 | // onscreen, onscreen, onscreen sequence of frames leads to 0, 1, 0, 0, 1, |
2494 | // 0. (no strict alternation anymore) But this is not different from what |
2495 | // happens when multiple swapchains are involved. Offscreen frames are |
2496 | // synchronous anyway in the sense that they wait for execution to complete |
2497 | // in endOffscreenFrame, so no resources used in that frame are busy |
2498 | // anymore in the next frame. |
2499 | |
2500 | currentFrameSlot = (currentFrameSlot + 1) % QVK_FRAMES_IN_FLIGHT; |
2501 | |
2502 | waitCommandCompletion(frameSlot: currentFrameSlot); |
2503 | |
2504 | ensureCommandPoolForNewFrame(); |
2505 | |
2506 | QVkCommandBuffer *cbWrapper = ofr.cbWrapper[currentFrameSlot]; |
2507 | QRhi::FrameOpResult cbres = startPrimaryCommandBuffer(cb: &cbWrapper->cb); |
2508 | if (cbres != QRhi::FrameOpSuccess) |
2509 | return cbres; |
2510 | |
2511 | prepareNewFrame(cb: cbWrapper); |
2512 | ofr.active = true; |
2513 | |
2514 | if (rhiFlags.testFlag(flag: QRhi::EnableTimestamps)) { |
2515 | int timestampQueryIdx = -1; |
2516 | for (int i = 0; i < timestampQueryPoolMap.size(); ++i) { |
2517 | if (!timestampQueryPoolMap.testBit(i)) { |
2518 | timestampQueryPoolMap.setBit(i); |
2519 | timestampQueryIdx = i * 2; |
2520 | break; |
2521 | } |
2522 | } |
2523 | if (timestampQueryIdx >= 0) { |
2524 | df->vkCmdResetQueryPool(cbWrapper->cb, timestampQueryPool, uint32_t(timestampQueryIdx), 2); |
2525 | // record timestamp at the start of the command buffer |
2526 | df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, |
2527 | timestampQueryPool, uint32_t(timestampQueryIdx)); |
2528 | ofr.timestampQueryIndex = timestampQueryIdx; |
2529 | } |
2530 | } |
2531 | |
2532 | *cb = cbWrapper; |
2533 | return QRhi::FrameOpSuccess; |
2534 | } |
2535 | |
2536 | QRhi::FrameOpResult QRhiVulkan::endOffscreenFrame(QRhi::EndFrameFlags flags) |
2537 | { |
2538 | Q_UNUSED(flags); |
2539 | Q_ASSERT(ofr.active); |
2540 | ofr.active = false; |
2541 | |
2542 | QVkCommandBuffer *cbWrapper(ofr.cbWrapper[currentFrameSlot]); |
2543 | recordPrimaryCommandBuffer(cbD: cbWrapper); |
2544 | |
2545 | // record another timestamp, when enabled |
2546 | if (ofr.timestampQueryIndex >= 0) { |
2547 | df->vkCmdWriteTimestamp(cbWrapper->cb, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, |
2548 | timestampQueryPool, uint32_t(ofr.timestampQueryIndex + 1)); |
2549 | } |
2550 | |
2551 | if (!ofr.cmdFence) { |
2552 | VkFenceCreateInfo fenceInfo = {}; |
2553 | fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; |
2554 | VkResult err = df->vkCreateFence(dev, &fenceInfo, nullptr, &ofr.cmdFence); |
2555 | if (err != VK_SUCCESS) { |
2556 | qWarning(msg: "Failed to create command buffer fence: %d" , err); |
2557 | return QRhi::FrameOpError; |
2558 | } |
2559 | } |
2560 | |
2561 | QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb: cbWrapper->cb, cmdFence: ofr.cmdFence, waitSem: nullptr, signalSem: nullptr); |
2562 | if (submitres != QRhi::FrameOpSuccess) |
2563 | return submitres; |
2564 | |
2565 | // wait for completion |
2566 | df->vkWaitForFences(dev, 1, &ofr.cmdFence, VK_TRUE, UINT64_MAX); |
2567 | df->vkResetFences(dev, 1, &ofr.cmdFence); |
2568 | |
2569 | // Here we know that executing the host-side reads for this (or any |
2570 | // previous) frame is safe since we waited for completion above. |
2571 | finishActiveReadbacks(forced: true); |
2572 | |
2573 | // Read the timestamps, if we wrote them. |
2574 | if (ofr.timestampQueryIndex >= 0) { |
2575 | quint64 timestamp[2] = { 0, 0 }; |
2576 | VkResult err = df->vkGetQueryPoolResults(dev, timestampQueryPool, uint32_t(ofr.timestampQueryIndex), 2, |
2577 | 2 * sizeof(quint64), timestamp, sizeof(quint64), |
2578 | VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); |
2579 | timestampQueryPoolMap.clearBit(i: ofr.timestampQueryIndex / 2); |
2580 | ofr.timestampQueryIndex = -1; |
2581 | if (err == VK_SUCCESS) { |
2582 | bool ok = false; |
2583 | const double elapsedSec = elapsedSecondsFromTimestamp(timestamp, ok: &ok); |
2584 | if (ok) |
2585 | cbWrapper->lastGpuTime = elapsedSec; |
2586 | } else { |
2587 | qWarning(msg: "Failed to query timestamp: %d" , err); |
2588 | } |
2589 | } |
2590 | |
2591 | return QRhi::FrameOpSuccess; |
2592 | } |
2593 | |
2594 | QRhi::FrameOpResult QRhiVulkan::finish() |
2595 | { |
2596 | QVkSwapChain *swapChainD = nullptr; |
2597 | if (inFrame) { |
2598 | // There is either a swapchain or an offscreen frame on-going. |
2599 | // End command recording and submit what we have. |
2600 | VkCommandBuffer cb; |
2601 | if (ofr.active) { |
2602 | Q_ASSERT(!currentSwapChain); |
2603 | QVkCommandBuffer *cbWrapper(ofr.cbWrapper[currentFrameSlot]); |
2604 | Q_ASSERT(cbWrapper->recordingPass == QVkCommandBuffer::NoPass); |
2605 | recordPrimaryCommandBuffer(cbD: cbWrapper); |
2606 | cbWrapper->resetCommands(); |
2607 | cb = cbWrapper->cb; |
2608 | } else { |
2609 | Q_ASSERT(currentSwapChain); |
2610 | Q_ASSERT(currentSwapChain->cbWrapper.recordingPass == QVkCommandBuffer::NoPass); |
2611 | swapChainD = currentSwapChain; |
2612 | recordPrimaryCommandBuffer(cbD: &swapChainD->cbWrapper); |
2613 | swapChainD->cbWrapper.resetCommands(); |
2614 | cb = swapChainD->cbWrapper.cb; |
2615 | } |
2616 | QRhi::FrameOpResult submitres = endAndSubmitPrimaryCommandBuffer(cb, VK_NULL_HANDLE, waitSem: nullptr, signalSem: nullptr); |
2617 | if (submitres != QRhi::FrameOpSuccess) |
2618 | return submitres; |
2619 | } |
2620 | |
2621 | df->vkQueueWaitIdle(gfxQueue); |
2622 | |
2623 | if (inFrame) { |
2624 | // The current frame slot's command pool needs to be reset. |
2625 | ensureCommandPoolForNewFrame(); |
2626 | // Allocate and begin recording on a new command buffer. |
2627 | if (ofr.active) { |
2628 | startPrimaryCommandBuffer(cb: &ofr.cbWrapper[currentFrameSlot]->cb); |
2629 | } else { |
2630 | QVkSwapChain::FrameResources &frame(swapChainD->frameRes[swapChainD->currentFrameSlot]); |
2631 | startPrimaryCommandBuffer(cb: &frame.cmdBuf); |
2632 | swapChainD->cbWrapper.cb = frame.cmdBuf; |
2633 | } |
2634 | } |
2635 | |
2636 | executeDeferredReleases(forced: true); |
2637 | finishActiveReadbacks(forced: true); |
2638 | |
2639 | return QRhi::FrameOpSuccess; |
2640 | } |
2641 | |
2642 | static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkBuffer::UsageState &bufUsage) |
2643 | { |
2644 | QRhiPassResourceTracker::UsageState u; |
2645 | u.layout = 0; // unused with buffers |
2646 | u.access = int(bufUsage.access); |
2647 | u.stage = int(bufUsage.stage); |
2648 | return u; |
2649 | } |
2650 | |
2651 | static inline QRhiPassResourceTracker::UsageState toPassTrackerUsageState(const QVkTexture::UsageState &texUsage) |
2652 | { |
2653 | QRhiPassResourceTracker::UsageState u; |
2654 | u.layout = texUsage.layout; |
2655 | u.access = int(texUsage.access); |
2656 | u.stage = int(texUsage.stage); |
2657 | return u; |
2658 | } |
2659 | |
2660 | void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRenderTarget *rtD) |
2661 | { |
2662 | if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(desc: rtD->description(), currentResIdList: rtD->d.currentResIdList)) |
2663 | rtD->create(); |
2664 | |
2665 | rtD->lastActiveFrameSlot = currentFrameSlot; |
2666 | rtD->d.rp->lastActiveFrameSlot = currentFrameSlot; |
2667 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
2668 | for (auto it = rtD->m_desc.cbeginColorAttachments(), itEnd = rtD->m_desc.cendColorAttachments(); it != itEnd; ++it) { |
2669 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
2670 | QVkTexture *resolveTexD = QRHI_RES(QVkTexture, it->resolveTexture()); |
2671 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
2672 | if (texD) { |
2673 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
2674 | access: QRhiPassResourceTracker::TexColorOutput, |
2675 | stage: QRhiPassResourceTracker::TexColorOutputStage); |
2676 | texD->lastActiveFrameSlot = currentFrameSlot; |
2677 | } else if (rbD) { |
2678 | // Won't register rbD->backingTexture because it cannot be used for |
2679 | // anything in a renderpass, its use makes only sense in |
2680 | // combination with a resolveTexture. |
2681 | rbD->lastActiveFrameSlot = currentFrameSlot; |
2682 | } |
2683 | if (resolveTexD) { |
2684 | trackedRegisterTexture(passResTracker: &passResTracker, texD: resolveTexD, |
2685 | access: QRhiPassResourceTracker::TexColorOutput, |
2686 | stage: QRhiPassResourceTracker::TexColorOutputStage); |
2687 | resolveTexD->lastActiveFrameSlot = currentFrameSlot; |
2688 | } |
2689 | } |
2690 | if (rtD->m_desc.depthStencilBuffer()) { |
2691 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, rtD->m_desc.depthStencilBuffer()); |
2692 | Q_ASSERT(rbD->m_type == QRhiRenderBuffer::DepthStencil); |
2693 | // We specify no explicit VkSubpassDependency for an offscreen render |
2694 | // target, meaning we need an explicit barrier for the depth-stencil |
2695 | // buffer to avoid a write-after-write hazard (as the implicit one is |
2696 | // not sufficient). Textures are taken care of by the resource tracking |
2697 | // but that excludes the (content-wise) throwaway depth-stencil buffer. |
2698 | depthStencilExplicitBarrier(cbD, rbD); |
2699 | rbD->lastActiveFrameSlot = currentFrameSlot; |
2700 | } |
2701 | if (rtD->m_desc.depthTexture()) { |
2702 | QVkTexture *depthTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthTexture()); |
2703 | trackedRegisterTexture(passResTracker: &passResTracker, texD: depthTexD, |
2704 | access: QRhiPassResourceTracker::TexDepthOutput, |
2705 | stage: QRhiPassResourceTracker::TexDepthOutputStage); |
2706 | depthTexD->lastActiveFrameSlot = currentFrameSlot; |
2707 | } |
2708 | if (rtD->m_desc.depthResolveTexture()) { |
2709 | QVkTexture *depthResolveTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthResolveTexture()); |
2710 | trackedRegisterTexture(passResTracker: &passResTracker, texD: depthResolveTexD, |
2711 | access: QRhiPassResourceTracker::TexDepthOutput, |
2712 | stage: QRhiPassResourceTracker::TexDepthOutputStage); |
2713 | depthResolveTexD->lastActiveFrameSlot = currentFrameSlot; |
2714 | } |
2715 | } |
2716 | |
2717 | void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2718 | { |
2719 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2720 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2721 | |
2722 | enqueueResourceUpdates(cbD, resourceUpdates); |
2723 | } |
2724 | |
2725 | VkCommandBuffer QRhiVulkan::startSecondaryCommandBuffer(QVkRenderTargetData *rtD) |
2726 | { |
2727 | VkCommandBuffer secondaryCb; |
2728 | |
2729 | if (!freeSecondaryCbs[currentFrameSlot].isEmpty()) { |
2730 | secondaryCb = freeSecondaryCbs[currentFrameSlot].last(); |
2731 | freeSecondaryCbs[currentFrameSlot].removeLast(); |
2732 | } else { |
2733 | VkCommandBufferAllocateInfo cmdBufInfo = {}; |
2734 | cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; |
2735 | cmdBufInfo.commandPool = cmdPool[currentFrameSlot]; |
2736 | cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; |
2737 | cmdBufInfo.commandBufferCount = 1; |
2738 | |
2739 | VkResult err = df->vkAllocateCommandBuffers(dev, &cmdBufInfo, &secondaryCb); |
2740 | if (err != VK_SUCCESS) { |
2741 | qWarning(msg: "Failed to create secondary command buffer: %d" , err); |
2742 | return VK_NULL_HANDLE; |
2743 | } |
2744 | } |
2745 | |
2746 | VkCommandBufferBeginInfo cmdBufBeginInfo = {}; |
2747 | cmdBufBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; |
2748 | cmdBufBeginInfo.flags = rtD ? VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT : 0; |
2749 | VkCommandBufferInheritanceInfo cmdBufInheritInfo = {}; |
2750 | cmdBufInheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; |
2751 | cmdBufInheritInfo.subpass = 0; |
2752 | if (rtD) { |
2753 | cmdBufInheritInfo.renderPass = rtD->rp->rp; |
2754 | cmdBufInheritInfo.framebuffer = rtD->fb; |
2755 | } |
2756 | cmdBufBeginInfo.pInheritanceInfo = &cmdBufInheritInfo; |
2757 | |
2758 | VkResult err = df->vkBeginCommandBuffer(secondaryCb, &cmdBufBeginInfo); |
2759 | if (err != VK_SUCCESS) { |
2760 | qWarning(msg: "Failed to begin secondary command buffer: %d" , err); |
2761 | return VK_NULL_HANDLE; |
2762 | } |
2763 | |
2764 | return secondaryCb; |
2765 | } |
2766 | |
2767 | void QRhiVulkan::endAndEnqueueSecondaryCommandBuffer(VkCommandBuffer cb, QVkCommandBuffer *cbD) |
2768 | { |
2769 | VkResult err = df->vkEndCommandBuffer(cb); |
2770 | if (err != VK_SUCCESS) |
2771 | qWarning(msg: "Failed to end secondary command buffer: %d" , err); |
2772 | |
2773 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
2774 | cmd.cmd = QVkCommandBuffer::Command::ExecuteSecondary; |
2775 | cmd.args.executeSecondary.cb = cb; |
2776 | |
2777 | QRhiVulkan::DeferredReleaseEntry e; |
2778 | e.type = QRhiVulkan::DeferredReleaseEntry::SecondaryCommandBuffer; |
2779 | e.lastActiveFrameSlot = currentFrameSlot; |
2780 | e.secondaryCommandBuffer.cb = cb; |
2781 | releaseQueue.append(t: e); |
2782 | } |
2783 | |
2784 | void QRhiVulkan::beginPass(QRhiCommandBuffer *cb, |
2785 | QRhiRenderTarget *rt, |
2786 | const QColor &colorClearValue, |
2787 | const QRhiDepthStencilClearValue &depthStencilClearValue, |
2788 | QRhiResourceUpdateBatch *resourceUpdates, |
2789 | QRhiCommandBuffer::BeginPassFlags flags) |
2790 | { |
2791 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2792 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2793 | |
2794 | if (resourceUpdates) |
2795 | enqueueResourceUpdates(cbD, resourceUpdates); |
2796 | |
2797 | // Insert a TransitionPassResources into the command stream, pointing to |
2798 | // the tracker this pass is going to use. That's how we generate the |
2799 | // barriers later during recording the real VkCommandBuffer, right before |
2800 | // the vkCmdBeginRenderPass. |
2801 | enqueueTransitionPassResources(cbD); |
2802 | |
2803 | QVkRenderTargetData *rtD = nullptr; |
2804 | switch (rt->resourceType()) { |
2805 | case QRhiResource::SwapChainRenderTarget: |
2806 | rtD = &QRHI_RES(QVkSwapChainRenderTarget, rt)->d; |
2807 | rtD->rp->lastActiveFrameSlot = currentFrameSlot; |
2808 | Q_ASSERT(currentSwapChain); |
2809 | currentSwapChain->imageRes[currentSwapChain->currentImageIndex].lastUse = |
2810 | QVkSwapChain::ImageResources::ScImageUseRender; |
2811 | break; |
2812 | case QRhiResource::TextureRenderTarget: |
2813 | { |
2814 | QVkTextureRenderTarget *rtTex = QRHI_RES(QVkTextureRenderTarget, rt); |
2815 | rtD = &rtTex->d; |
2816 | activateTextureRenderTarget(cbD, rtD: rtTex); |
2817 | } |
2818 | break; |
2819 | default: |
2820 | Q_UNREACHABLE(); |
2821 | break; |
2822 | } |
2823 | |
2824 | cbD->recordingPass = QVkCommandBuffer::RenderPass; |
2825 | cbD->passUsesSecondaryCb = flags.testFlag(flag: QRhiCommandBuffer::ExternalContent); |
2826 | cbD->currentTarget = rt; |
2827 | |
2828 | // No copy operations or image layout transitions allowed after this point |
2829 | // (up until endPass) as we are going to begin the renderpass. |
2830 | |
2831 | VkRenderPassBeginInfo rpBeginInfo = {}; |
2832 | rpBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; |
2833 | rpBeginInfo.renderPass = rtD->rp->rp; |
2834 | rpBeginInfo.framebuffer = rtD->fb; |
2835 | rpBeginInfo.renderArea.extent.width = uint32_t(rtD->pixelSize.width()); |
2836 | rpBeginInfo.renderArea.extent.height = uint32_t(rtD->pixelSize.height()); |
2837 | |
2838 | QVarLengthArray<VkClearValue, 4> cvs; |
2839 | for (int i = 0; i < rtD->colorAttCount; ++i) { |
2840 | VkClearValue cv; |
2841 | cv.color = { .float32: { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()), |
2842 | float(colorClearValue.alphaF()) } }; |
2843 | cvs.append(t: cv); |
2844 | } |
2845 | for (int i = 0; i < rtD->dsAttCount; ++i) { |
2846 | VkClearValue cv; |
2847 | cv.depthStencil = { .depth: depthStencilClearValue.depthClearValue(), .stencil: depthStencilClearValue.stencilClearValue() }; |
2848 | cvs.append(t: cv); |
2849 | } |
2850 | for (int i = 0; i < rtD->resolveAttCount; ++i) { |
2851 | VkClearValue cv; |
2852 | cv.color = { .float32: { float(colorClearValue.redF()), float(colorClearValue.greenF()), float(colorClearValue.blueF()), |
2853 | float(colorClearValue.alphaF()) } }; |
2854 | cvs.append(t: cv); |
2855 | } |
2856 | for (int i = 0; i < rtD->dsResolveAttCount; ++i) { |
2857 | VkClearValue cv; |
2858 | cv.depthStencil = { .depth: depthStencilClearValue.depthClearValue(), .stencil: depthStencilClearValue.stencilClearValue() }; |
2859 | cvs.append(t: cv); |
2860 | } |
2861 | rpBeginInfo.clearValueCount = uint32_t(cvs.size()); |
2862 | |
2863 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
2864 | cmd.cmd = QVkCommandBuffer::Command::BeginRenderPass; |
2865 | cmd.args.beginRenderPass.desc = rpBeginInfo; |
2866 | cmd.args.beginRenderPass.clearValueIndex = cbD->pools.clearValue.size(); |
2867 | cmd.args.beginRenderPass.useSecondaryCb = cbD->passUsesSecondaryCb; |
2868 | cbD->pools.clearValue.append(buf: cvs.constData(), sz: cvs.size()); |
2869 | |
2870 | if (cbD->passUsesSecondaryCb) |
2871 | cbD->activeSecondaryCbStack.append(t: startSecondaryCommandBuffer(rtD)); |
2872 | |
2873 | cbD->resetCachedState(); |
2874 | } |
2875 | |
2876 | void QRhiVulkan::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2877 | { |
2878 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2879 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
2880 | |
2881 | if (cbD->passUsesSecondaryCb) { |
2882 | VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last(); |
2883 | cbD->activeSecondaryCbStack.removeLast(); |
2884 | endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD); |
2885 | } |
2886 | |
2887 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
2888 | cmd.cmd = QVkCommandBuffer::Command::EndRenderPass; |
2889 | |
2890 | cbD->recordingPass = QVkCommandBuffer::NoPass; |
2891 | cbD->currentTarget = nullptr; |
2892 | |
2893 | if (resourceUpdates) |
2894 | enqueueResourceUpdates(cbD, resourceUpdates); |
2895 | } |
2896 | |
2897 | void QRhiVulkan::beginComputePass(QRhiCommandBuffer *cb, |
2898 | QRhiResourceUpdateBatch *resourceUpdates, |
2899 | QRhiCommandBuffer::BeginPassFlags flags) |
2900 | { |
2901 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2902 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
2903 | |
2904 | if (resourceUpdates) |
2905 | enqueueResourceUpdates(cbD, resourceUpdates); |
2906 | |
2907 | enqueueTransitionPassResources(cbD); |
2908 | |
2909 | cbD->recordingPass = QVkCommandBuffer::ComputePass; |
2910 | cbD->passUsesSecondaryCb = flags.testFlag(flag: QRhiCommandBuffer::ExternalContent); |
2911 | |
2912 | cbD->computePassState.reset(); |
2913 | |
2914 | if (cbD->passUsesSecondaryCb) |
2915 | cbD->activeSecondaryCbStack.append(t: startSecondaryCommandBuffer()); |
2916 | |
2917 | cbD->resetCachedState(); |
2918 | } |
2919 | |
2920 | void QRhiVulkan::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) |
2921 | { |
2922 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2923 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); |
2924 | |
2925 | if (cbD->passUsesSecondaryCb) { |
2926 | VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last(); |
2927 | cbD->activeSecondaryCbStack.removeLast(); |
2928 | endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD); |
2929 | } |
2930 | |
2931 | cbD->recordingPass = QVkCommandBuffer::NoPass; |
2932 | |
2933 | if (resourceUpdates) |
2934 | enqueueResourceUpdates(cbD, resourceUpdates); |
2935 | } |
2936 | |
2937 | void QRhiVulkan::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) |
2938 | { |
2939 | QVkComputePipeline *psD = QRHI_RES(QVkComputePipeline, ps); |
2940 | Q_ASSERT(psD->pipeline); |
2941 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2942 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); |
2943 | |
2944 | if (cbD->currentComputePipeline != ps || cbD->currentPipelineGeneration != psD->generation) { |
2945 | if (cbD->passUsesSecondaryCb) { |
2946 | df->vkCmdBindPipeline(cbD->activeSecondaryCbStack.last(), VK_PIPELINE_BIND_POINT_COMPUTE, psD->pipeline); |
2947 | } else { |
2948 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
2949 | cmd.cmd = QVkCommandBuffer::Command::BindPipeline; |
2950 | cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE; |
2951 | cmd.args.bindPipeline.pipeline = psD->pipeline; |
2952 | } |
2953 | |
2954 | cbD->currentGraphicsPipeline = nullptr; |
2955 | cbD->currentComputePipeline = ps; |
2956 | cbD->currentPipelineGeneration = psD->generation; |
2957 | } |
2958 | |
2959 | psD->lastActiveFrameSlot = currentFrameSlot; |
2960 | } |
2961 | |
2962 | template<typename T> |
2963 | inline void qrhivk_accumulateComputeResource(T *writtenResources, QRhiResource *resource, |
2964 | QRhiShaderResourceBinding::Type bindingType, |
2965 | int loadTypeVal, int storeTypeVal, int loadStoreTypeVal) |
2966 | { |
2967 | VkAccessFlags access = 0; |
2968 | if (bindingType == loadTypeVal) { |
2969 | access = VK_ACCESS_SHADER_READ_BIT; |
2970 | } else { |
2971 | access = VK_ACCESS_SHADER_WRITE_BIT; |
2972 | if (bindingType == loadStoreTypeVal) |
2973 | access |= VK_ACCESS_SHADER_READ_BIT; |
2974 | } |
2975 | auto it = writtenResources->find(resource); |
2976 | if (it != writtenResources->end()) |
2977 | it->first |= access; |
2978 | else if (bindingType == storeTypeVal || bindingType == loadStoreTypeVal) |
2979 | writtenResources->insert(resource, { access, true }); |
2980 | } |
2981 | |
2982 | void QRhiVulkan::dispatch(QRhiCommandBuffer *cb, int x, int y, int z) |
2983 | { |
2984 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
2985 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::ComputePass); |
2986 | |
2987 | // When there are multiple dispatches, read-after-write and |
2988 | // write-after-write need a barrier. |
2989 | QVarLengthArray<VkImageMemoryBarrier, 8> imageBarriers; |
2990 | QVarLengthArray<VkBufferMemoryBarrier, 8> bufferBarriers; |
2991 | if (cbD->currentComputeSrb) { |
2992 | // The key in the writtenResources map indicates that the resource was |
2993 | // written in a previous dispatch, whereas the value accumulates the |
2994 | // access mask in the current one. |
2995 | for (auto &accessAndIsNewFlag : cbD->computePassState.writtenResources) |
2996 | accessAndIsNewFlag = { 0, false }; |
2997 | |
2998 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, cbD->currentComputeSrb); |
2999 | const int bindingCount = srbD->m_bindings.size(); |
3000 | for (int i = 0; i < bindingCount; ++i) { |
3001 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->m_bindings.at(idx: i)); |
3002 | switch (b->type) { |
3003 | case QRhiShaderResourceBinding::ImageLoad: |
3004 | case QRhiShaderResourceBinding::ImageStore: |
3005 | case QRhiShaderResourceBinding::ImageLoadStore: |
3006 | qrhivk_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources, |
3007 | resource: b->u.simage.tex, |
3008 | bindingType: b->type, |
3009 | loadTypeVal: QRhiShaderResourceBinding::ImageLoad, |
3010 | storeTypeVal: QRhiShaderResourceBinding::ImageStore, |
3011 | loadStoreTypeVal: QRhiShaderResourceBinding::ImageLoadStore); |
3012 | break; |
3013 | case QRhiShaderResourceBinding::BufferLoad: |
3014 | case QRhiShaderResourceBinding::BufferStore: |
3015 | case QRhiShaderResourceBinding::BufferLoadStore: |
3016 | qrhivk_accumulateComputeResource(writtenResources: &cbD->computePassState.writtenResources, |
3017 | resource: b->u.sbuf.buf, |
3018 | bindingType: b->type, |
3019 | loadTypeVal: QRhiShaderResourceBinding::BufferLoad, |
3020 | storeTypeVal: QRhiShaderResourceBinding::BufferStore, |
3021 | loadStoreTypeVal: QRhiShaderResourceBinding::BufferLoadStore); |
3022 | break; |
3023 | default: |
3024 | break; |
3025 | } |
3026 | } |
3027 | |
3028 | for (auto it = cbD->computePassState.writtenResources.begin(); it != cbD->computePassState.writtenResources.end(); ) { |
3029 | const int accessInThisDispatch = it->first; |
3030 | const bool isNewInThisDispatch = it->second; |
3031 | if (accessInThisDispatch && !isNewInThisDispatch) { |
3032 | if (it.key()->resourceType() == QRhiResource::Texture) { |
3033 | QVkTexture *texD = QRHI_RES(QVkTexture, it.key()); |
3034 | VkImageMemoryBarrier barrier = {}; |
3035 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
3036 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3037 | // won't care about subresources, pretend the whole resource was written |
3038 | barrier.subresourceRange.baseMipLevel = 0; |
3039 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
3040 | barrier.subresourceRange.baseArrayLayer = 0; |
3041 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
3042 | barrier.oldLayout = texD->usageState.layout; |
3043 | barrier.newLayout = texD->usageState.layout; |
3044 | barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; |
3045 | barrier.dstAccessMask = accessInThisDispatch; |
3046 | barrier.image = texD->image; |
3047 | imageBarriers.append(t: barrier); |
3048 | } else { |
3049 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key()); |
3050 | VkBufferMemoryBarrier barrier = {}; |
3051 | barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; |
3052 | barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
3053 | barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
3054 | barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; |
3055 | barrier.dstAccessMask = accessInThisDispatch; |
3056 | barrier.buffer = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0]; |
3057 | barrier.size = VK_WHOLE_SIZE; |
3058 | bufferBarriers.append(t: barrier); |
3059 | } |
3060 | } |
3061 | // Anything that was previously written, but is only read now, can be |
3062 | // removed from the written list (because that previous write got a |
3063 | // corresponding barrier now). |
3064 | if (accessInThisDispatch == VK_ACCESS_SHADER_READ_BIT) |
3065 | it = cbD->computePassState.writtenResources.erase(it); |
3066 | else |
3067 | ++it; |
3068 | } |
3069 | } |
3070 | |
3071 | if (cbD->passUsesSecondaryCb) { |
3072 | VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last(); |
3073 | if (!imageBarriers.isEmpty()) { |
3074 | df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, |
3075 | 0, 0, nullptr, |
3076 | 0, nullptr, |
3077 | imageBarriers.size(), imageBarriers.constData()); |
3078 | } |
3079 | if (!bufferBarriers.isEmpty()) { |
3080 | df->vkCmdPipelineBarrier(secondaryCb, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, |
3081 | 0, 0, nullptr, |
3082 | bufferBarriers.size(), bufferBarriers.constData(), |
3083 | 0, nullptr); |
3084 | } |
3085 | df->vkCmdDispatch(secondaryCb, uint32_t(x), uint32_t(y), uint32_t(z)); |
3086 | } else { |
3087 | if (!imageBarriers.isEmpty()) { |
3088 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3089 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
3090 | cmd.args.imageBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
3091 | cmd.args.imageBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
3092 | cmd.args.imageBarrier.count = imageBarriers.size(); |
3093 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); |
3094 | cbD->pools.imageBarrier.append(buf: imageBarriers.constData(), sz: imageBarriers.size()); |
3095 | } |
3096 | if (!bufferBarriers.isEmpty()) { |
3097 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3098 | cmd.cmd = QVkCommandBuffer::Command::BufferBarrier; |
3099 | cmd.args.bufferBarrier.srcStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
3100 | cmd.args.bufferBarrier.dstStageMask = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
3101 | cmd.args.bufferBarrier.count = bufferBarriers.size(); |
3102 | cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.size(); |
3103 | cbD->pools.bufferBarrier.append(buf: bufferBarriers.constData(), sz: bufferBarriers.size()); |
3104 | } |
3105 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3106 | cmd.cmd = QVkCommandBuffer::Command::Dispatch; |
3107 | cmd.args.dispatch.x = x; |
3108 | cmd.args.dispatch.y = y; |
3109 | cmd.args.dispatch.z = z; |
3110 | } |
3111 | } |
3112 | |
3113 | VkShaderModule QRhiVulkan::createShader(const QByteArray &spirv) |
3114 | { |
3115 | VkShaderModuleCreateInfo shaderInfo = {}; |
3116 | shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; |
3117 | shaderInfo.codeSize = size_t(spirv.size()); |
3118 | shaderInfo.pCode = reinterpret_cast<const quint32 *>(spirv.constData()); |
3119 | VkShaderModule shaderModule; |
3120 | VkResult err = df->vkCreateShaderModule(dev, &shaderInfo, nullptr, &shaderModule); |
3121 | if (err != VK_SUCCESS) { |
3122 | qWarning(msg: "Failed to create shader module: %d" , err); |
3123 | return VK_NULL_HANDLE; |
3124 | } |
3125 | return shaderModule; |
3126 | } |
3127 | |
3128 | bool QRhiVulkan::ensurePipelineCache(const void *initialData, size_t initialDataSize) |
3129 | { |
3130 | if (pipelineCache) |
3131 | return true; |
3132 | |
3133 | VkPipelineCacheCreateInfo pipelineCacheInfo = {}; |
3134 | pipelineCacheInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; |
3135 | pipelineCacheInfo.initialDataSize = initialDataSize; |
3136 | pipelineCacheInfo.pInitialData = initialData; |
3137 | VkResult err = df->vkCreatePipelineCache(dev, &pipelineCacheInfo, nullptr, &pipelineCache); |
3138 | if (err != VK_SUCCESS) { |
3139 | qWarning(msg: "Failed to create pipeline cache: %d" , err); |
3140 | return false; |
3141 | } |
3142 | return true; |
3143 | } |
3144 | |
3145 | void QRhiVulkan::updateShaderResourceBindings(QRhiShaderResourceBindings *srb, int descSetIdx) |
3146 | { |
3147 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb); |
3148 | |
3149 | QVarLengthArray<VkDescriptorBufferInfo, 8> bufferInfos; |
3150 | using ArrayOfImageDesc = QVarLengthArray<VkDescriptorImageInfo, 8>; |
3151 | QVarLengthArray<ArrayOfImageDesc, 8> imageInfos; |
3152 | QVarLengthArray<VkWriteDescriptorSet, 12> writeInfos; |
3153 | QVarLengthArray<QPair<int, int>, 12> infoIndices; |
3154 | |
3155 | const bool updateAll = descSetIdx < 0; |
3156 | int frameSlot = updateAll ? 0 : descSetIdx; |
3157 | while (frameSlot < (updateAll ? QVK_FRAMES_IN_FLIGHT : descSetIdx + 1)) { |
3158 | for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) { |
3159 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding: srbD->sortedBindings.at(idx: i)); |
3160 | QVkShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[frameSlot][i]); |
3161 | |
3162 | VkWriteDescriptorSet writeInfo = {}; |
3163 | writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; |
3164 | writeInfo.dstSet = srbD->descSets[frameSlot]; |
3165 | writeInfo.dstBinding = uint32_t(b->binding); |
3166 | writeInfo.descriptorCount = 1; |
3167 | |
3168 | int bufferInfoIndex = -1; |
3169 | int imageInfoIndex = -1; |
3170 | |
3171 | switch (b->type) { |
3172 | case QRhiShaderResourceBinding::UniformBuffer: |
3173 | { |
3174 | writeInfo.descriptorType = b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC |
3175 | : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
3176 | QRhiBuffer *buf = b->u.ubuf.buf; |
3177 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, buf); |
3178 | bd.ubuf.id = bufD->m_id; |
3179 | bd.ubuf.generation = bufD->generation; |
3180 | VkDescriptorBufferInfo bufInfo; |
3181 | bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; |
3182 | bufInfo.offset = b->u.ubuf.offset; |
3183 | bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size; |
3184 | // be nice and assert when we know the vulkan device would die a horrible death due to non-aligned reads |
3185 | Q_ASSERT(aligned(bufInfo.offset, ubufAlign) == bufInfo.offset); |
3186 | bufferInfoIndex = bufferInfos.size(); |
3187 | bufferInfos.append(t: bufInfo); |
3188 | } |
3189 | break; |
3190 | case QRhiShaderResourceBinding::SampledTexture: |
3191 | { |
3192 | const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; |
3193 | writeInfo.descriptorCount = data->count; // arrays of combined image samplers are supported |
3194 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; |
3195 | ArrayOfImageDesc imageInfo(data->count); |
3196 | for (int elem = 0; elem < data->count; ++elem) { |
3197 | QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); |
3198 | QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler); |
3199 | bd.stex.d[elem].texId = texD->m_id; |
3200 | bd.stex.d[elem].texGeneration = texD->generation; |
3201 | bd.stex.d[elem].samplerId = samplerD->m_id; |
3202 | bd.stex.d[elem].samplerGeneration = samplerD->generation; |
3203 | imageInfo[elem].sampler = samplerD->sampler; |
3204 | imageInfo[elem].imageView = texD->imageView; |
3205 | imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; |
3206 | } |
3207 | bd.stex.count = data->count; |
3208 | imageInfoIndex = imageInfos.size(); |
3209 | imageInfos.append(t: imageInfo); |
3210 | } |
3211 | break; |
3212 | case QRhiShaderResourceBinding::Texture: |
3213 | { |
3214 | const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; |
3215 | writeInfo.descriptorCount = data->count; // arrays of (separate) images are supported |
3216 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; |
3217 | ArrayOfImageDesc imageInfo(data->count); |
3218 | for (int elem = 0; elem < data->count; ++elem) { |
3219 | QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); |
3220 | bd.stex.d[elem].texId = texD->m_id; |
3221 | bd.stex.d[elem].texGeneration = texD->generation; |
3222 | bd.stex.d[elem].samplerId = 0; |
3223 | bd.stex.d[elem].samplerGeneration = 0; |
3224 | imageInfo[elem].sampler = VK_NULL_HANDLE; |
3225 | imageInfo[elem].imageView = texD->imageView; |
3226 | imageInfo[elem].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; |
3227 | } |
3228 | bd.stex.count = data->count; |
3229 | imageInfoIndex = imageInfos.size(); |
3230 | imageInfos.append(t: imageInfo); |
3231 | } |
3232 | break; |
3233 | case QRhiShaderResourceBinding::Sampler: |
3234 | { |
3235 | QVkSampler *samplerD = QRHI_RES(QVkSampler, b->u.stex.texSamplers[0].sampler); |
3236 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; |
3237 | bd.stex.d[0].texId = 0; |
3238 | bd.stex.d[0].texGeneration = 0; |
3239 | bd.stex.d[0].samplerId = samplerD->m_id; |
3240 | bd.stex.d[0].samplerGeneration = samplerD->generation; |
3241 | ArrayOfImageDesc imageInfo(1); |
3242 | imageInfo[0].sampler = samplerD->sampler; |
3243 | imageInfo[0].imageView = VK_NULL_HANDLE; |
3244 | imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; |
3245 | imageInfoIndex = imageInfos.size(); |
3246 | imageInfos.append(t: imageInfo); |
3247 | } |
3248 | break; |
3249 | case QRhiShaderResourceBinding::ImageLoad: |
3250 | case QRhiShaderResourceBinding::ImageStore: |
3251 | case QRhiShaderResourceBinding::ImageLoadStore: |
3252 | { |
3253 | QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); |
3254 | VkImageView view = texD->perLevelImageViewForLoadStore(level: b->u.simage.level); |
3255 | if (view) { |
3256 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; |
3257 | bd.simage.id = texD->m_id; |
3258 | bd.simage.generation = texD->generation; |
3259 | ArrayOfImageDesc imageInfo(1); |
3260 | imageInfo[0].sampler = VK_NULL_HANDLE; |
3261 | imageInfo[0].imageView = view; |
3262 | imageInfo[0].imageLayout = VK_IMAGE_LAYOUT_GENERAL; |
3263 | imageInfoIndex = imageInfos.size(); |
3264 | imageInfos.append(t: imageInfo); |
3265 | } |
3266 | } |
3267 | break; |
3268 | case QRhiShaderResourceBinding::BufferLoad: |
3269 | case QRhiShaderResourceBinding::BufferStore: |
3270 | case QRhiShaderResourceBinding::BufferLoadStore: |
3271 | { |
3272 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf); |
3273 | writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
3274 | bd.sbuf.id = bufD->m_id; |
3275 | bd.sbuf.generation = bufD->generation; |
3276 | VkDescriptorBufferInfo bufInfo; |
3277 | bufInfo.buffer = bufD->m_type == QRhiBuffer::Dynamic ? bufD->buffers[frameSlot] : bufD->buffers[0]; |
3278 | bufInfo.offset = b->u.ubuf.offset; |
3279 | bufInfo.range = b->u.ubuf.maybeSize ? b->u.ubuf.maybeSize : bufD->m_size; |
3280 | bufferInfoIndex = bufferInfos.size(); |
3281 | bufferInfos.append(t: bufInfo); |
3282 | } |
3283 | break; |
3284 | default: |
3285 | continue; |
3286 | } |
3287 | |
3288 | writeInfos.append(t: writeInfo); |
3289 | infoIndices.append(t: { bufferInfoIndex, imageInfoIndex }); |
3290 | } |
3291 | ++frameSlot; |
3292 | } |
3293 | |
3294 | for (int i = 0, writeInfoCount = writeInfos.size(); i < writeInfoCount; ++i) { |
3295 | const int bufferInfoIndex = infoIndices[i].first; |
3296 | const int imageInfoIndex = infoIndices[i].second; |
3297 | if (bufferInfoIndex >= 0) |
3298 | writeInfos[i].pBufferInfo = &bufferInfos[bufferInfoIndex]; |
3299 | else if (imageInfoIndex >= 0) |
3300 | writeInfos[i].pImageInfo = imageInfos[imageInfoIndex].constData(); |
3301 | } |
3302 | |
3303 | df->vkUpdateDescriptorSets(dev, uint32_t(writeInfos.size()), writeInfos.constData(), 0, nullptr); |
3304 | } |
3305 | |
3306 | static inline bool accessIsWrite(VkAccessFlags access) |
3307 | { |
3308 | return (access & VK_ACCESS_SHADER_WRITE_BIT) != 0 |
3309 | || (access & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0 |
3310 | || (access & VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) != 0 |
3311 | || (access & VK_ACCESS_TRANSFER_WRITE_BIT) != 0 |
3312 | || (access & VK_ACCESS_HOST_WRITE_BIT) != 0 |
3313 | || (access & VK_ACCESS_MEMORY_WRITE_BIT) != 0; |
3314 | } |
3315 | |
3316 | void QRhiVulkan::trackedBufferBarrier(QVkCommandBuffer *cbD, QVkBuffer *bufD, int slot, |
3317 | VkAccessFlags access, VkPipelineStageFlags stage) |
3318 | { |
3319 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
3320 | Q_ASSERT(access && stage); |
3321 | QVkBuffer::UsageState &s(bufD->usageState[slot]); |
3322 | if (!s.stage) { |
3323 | s.access = access; |
3324 | s.stage = stage; |
3325 | return; |
3326 | } |
3327 | |
3328 | if (s.access == access && s.stage == stage) { |
3329 | // No need to flood with unnecessary read-after-read barriers. |
3330 | // Write-after-write is a different matter, however. |
3331 | if (!accessIsWrite(access)) |
3332 | return; |
3333 | } |
3334 | |
3335 | VkBufferMemoryBarrier bufMemBarrier = {}; |
3336 | bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; |
3337 | bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
3338 | bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
3339 | bufMemBarrier.srcAccessMask = s.access; |
3340 | bufMemBarrier.dstAccessMask = access; |
3341 | bufMemBarrier.buffer = bufD->buffers[slot]; |
3342 | bufMemBarrier.size = VK_WHOLE_SIZE; |
3343 | |
3344 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3345 | cmd.cmd = QVkCommandBuffer::Command::BufferBarrier; |
3346 | cmd.args.bufferBarrier.srcStageMask = s.stage; |
3347 | cmd.args.bufferBarrier.dstStageMask = stage; |
3348 | cmd.args.bufferBarrier.count = 1; |
3349 | cmd.args.bufferBarrier.index = cbD->pools.bufferBarrier.size(); |
3350 | cbD->pools.bufferBarrier.append(t: bufMemBarrier); |
3351 | |
3352 | s.access = access; |
3353 | s.stage = stage; |
3354 | } |
3355 | |
3356 | void QRhiVulkan::trackedImageBarrier(QVkCommandBuffer *cbD, QVkTexture *texD, |
3357 | VkImageLayout layout, VkAccessFlags access, VkPipelineStageFlags stage) |
3358 | { |
3359 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
3360 | Q_ASSERT(layout && access && stage); |
3361 | QVkTexture::UsageState &s(texD->usageState); |
3362 | if (s.access == access && s.stage == stage && s.layout == layout) { |
3363 | if (!accessIsWrite(access)) |
3364 | return; |
3365 | } |
3366 | |
3367 | VkImageMemoryBarrier barrier = {}; |
3368 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
3369 | barrier.subresourceRange.aspectMask = aspectMaskForTextureFormat(format: texD->m_format); |
3370 | barrier.subresourceRange.baseMipLevel = 0; |
3371 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
3372 | barrier.subresourceRange.baseArrayLayer = 0; |
3373 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
3374 | barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED |
3375 | barrier.newLayout = layout; |
3376 | barrier.srcAccessMask = s.access; // may be 0 but that's fine |
3377 | barrier.dstAccessMask = access; |
3378 | barrier.image = texD->image; |
3379 | |
3380 | VkPipelineStageFlags srcStage = s.stage; |
3381 | // stage mask cannot be 0 |
3382 | if (!srcStage) |
3383 | srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; |
3384 | |
3385 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3386 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
3387 | cmd.args.imageBarrier.srcStageMask = srcStage; |
3388 | cmd.args.imageBarrier.dstStageMask = stage; |
3389 | cmd.args.imageBarrier.count = 1; |
3390 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); |
3391 | cbD->pools.imageBarrier.append(t: barrier); |
3392 | |
3393 | s.layout = layout; |
3394 | s.access = access; |
3395 | s.stage = stage; |
3396 | } |
3397 | |
3398 | void QRhiVulkan::depthStencilExplicitBarrier(QVkCommandBuffer *cbD, QVkRenderBuffer *rbD) |
3399 | { |
3400 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
3401 | |
3402 | VkImageMemoryBarrier barrier = {}; |
3403 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
3404 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; |
3405 | barrier.subresourceRange.baseMipLevel = 0; |
3406 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
3407 | barrier.subresourceRange.baseArrayLayer = 0; |
3408 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
3409 | barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
3410 | barrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
3411 | barrier.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; |
3412 | barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
3413 | | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; |
3414 | barrier.image = rbD->image; |
3415 | |
3416 | const VkPipelineStageFlags stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
3417 | | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; |
3418 | |
3419 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3420 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
3421 | cmd.args.imageBarrier.srcStageMask = stages; |
3422 | cmd.args.imageBarrier.dstStageMask = stages; |
3423 | cmd.args.imageBarrier.count = 1; |
3424 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); |
3425 | cbD->pools.imageBarrier.append(t: barrier); |
3426 | } |
3427 | |
3428 | void QRhiVulkan::subresourceBarrier(QVkCommandBuffer *cbD, VkImage image, |
3429 | VkImageLayout oldLayout, VkImageLayout newLayout, |
3430 | VkAccessFlags srcAccess, VkAccessFlags dstAccess, |
3431 | VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, |
3432 | int startLayer, int layerCount, |
3433 | int startLevel, int levelCount) |
3434 | { |
3435 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
3436 | VkImageMemoryBarrier barrier = {}; |
3437 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
3438 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3439 | barrier.subresourceRange.baseMipLevel = uint32_t(startLevel); |
3440 | barrier.subresourceRange.levelCount = uint32_t(levelCount); |
3441 | barrier.subresourceRange.baseArrayLayer = uint32_t(startLayer); |
3442 | barrier.subresourceRange.layerCount = uint32_t(layerCount); |
3443 | barrier.oldLayout = oldLayout; |
3444 | barrier.newLayout = newLayout; |
3445 | barrier.srcAccessMask = srcAccess; |
3446 | barrier.dstAccessMask = dstAccess; |
3447 | barrier.image = image; |
3448 | |
3449 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3450 | cmd.cmd = QVkCommandBuffer::Command::ImageBarrier; |
3451 | cmd.args.imageBarrier.srcStageMask = srcStage; |
3452 | cmd.args.imageBarrier.dstStageMask = dstStage; |
3453 | cmd.args.imageBarrier.count = 1; |
3454 | cmd.args.imageBarrier.index = cbD->pools.imageBarrier.size(); |
3455 | cbD->pools.imageBarrier.append(t: barrier); |
3456 | } |
3457 | |
3458 | VkDeviceSize QRhiVulkan::subresUploadByteSize(const QRhiTextureSubresourceUploadDescription &subresDesc) const |
3459 | { |
3460 | VkDeviceSize size = 0; |
3461 | const qsizetype imageSizeBytes = subresDesc.image().isNull() ? |
3462 | subresDesc.data().size() : subresDesc.image().sizeInBytes(); |
3463 | if (imageSizeBytes > 0) |
3464 | size += aligned(v: VkDeviceSize(imageSizeBytes), byteAlign: texbufAlign); |
3465 | return size; |
3466 | } |
3467 | |
3468 | void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level, |
3469 | const QRhiTextureSubresourceUploadDescription &subresDesc, |
3470 | size_t *curOfs, void *mp, |
3471 | BufferImageCopyList *copyInfos) |
3472 | { |
3473 | qsizetype copySizeBytes = 0; |
3474 | qsizetype imageSizeBytes = 0; |
3475 | const void *src = nullptr; |
3476 | const bool is3D = texD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional); |
3477 | const bool is1D = texD->m_flags.testFlag(flag: QRhiTexture::OneDimensional); |
3478 | |
3479 | VkBufferImageCopy copyInfo = {}; |
3480 | copyInfo.bufferOffset = *curOfs; |
3481 | copyInfo.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3482 | copyInfo.imageSubresource.mipLevel = uint32_t(level); |
3483 | copyInfo.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(layer); |
3484 | copyInfo.imageSubresource.layerCount = 1; |
3485 | copyInfo.imageExtent.depth = 1; |
3486 | if (is3D) |
3487 | copyInfo.imageOffset.z = uint32_t(layer); |
3488 | if (is1D) |
3489 | copyInfo.imageOffset.y = uint32_t(layer); |
3490 | |
3491 | const QByteArray rawData = subresDesc.data(); |
3492 | const QPoint dp = subresDesc.destinationTopLeft(); |
3493 | QImage image = subresDesc.image(); |
3494 | if (!image.isNull()) { |
3495 | copySizeBytes = imageSizeBytes = image.sizeInBytes(); |
3496 | QSize size = image.size(); |
3497 | src = image.constBits(); |
3498 | // Scanlines in QImage are 4 byte aligned so bpl must |
3499 | // be taken into account for bufferRowLength. |
3500 | int bpc = qMax(a: 1, b: image.depth() / 8); |
3501 | // this is in pixels, not bytes, to make it more complicated... |
3502 | copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc); |
3503 | if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) { |
3504 | const int sx = subresDesc.sourceTopLeft().x(); |
3505 | const int sy = subresDesc.sourceTopLeft().y(); |
3506 | if (!subresDesc.sourceSize().isEmpty()) |
3507 | size = subresDesc.sourceSize(); |
3508 | |
3509 | if (size.width() == image.width()) { |
3510 | // No need to make a QImage copy here, can copy from the source |
3511 | // QImage into staging directly. |
3512 | src = image.constBits() + sy * image.bytesPerLine() + sx * bpc; |
3513 | copySizeBytes = size.height() * image.bytesPerLine(); |
3514 | } else { |
3515 | image = image.copy(x: sx, y: sy, w: size.width(), h: size.height()); |
3516 | src = image.constBits(); |
3517 | // The staging buffer gets the slice only. The rest of the |
3518 | // space reserved for this mip will be unused. |
3519 | copySizeBytes = image.sizeInBytes(); |
3520 | bpc = qMax(a: 1, b: image.depth() / 8); |
3521 | copyInfo.bufferRowLength = uint32_t(image.bytesPerLine() / bpc); |
3522 | } |
3523 | } |
3524 | copyInfo.imageOffset.x = dp.x(); |
3525 | copyInfo.imageOffset.y = dp.y(); |
3526 | copyInfo.imageExtent.width = uint32_t(size.width()); |
3527 | copyInfo.imageExtent.height = uint32_t(size.height()); |
3528 | copyInfos->append(t: copyInfo); |
3529 | } else if (!rawData.isEmpty() && isCompressedFormat(format: texD->m_format)) { |
3530 | copySizeBytes = imageSizeBytes = rawData.size(); |
3531 | src = rawData.constData(); |
3532 | QSize size = q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize); |
3533 | const int subresw = size.width(); |
3534 | const int subresh = size.height(); |
3535 | if (!subresDesc.sourceSize().isEmpty()) |
3536 | size = subresDesc.sourceSize(); |
3537 | const int w = size.width(); |
3538 | const int h = size.height(); |
3539 | QSize blockDim; |
3540 | compressedFormatInfo(format: texD->m_format, size: QSize(w, h), bpl: nullptr, byteSize: nullptr, blockDim: &blockDim); |
3541 | // x and y must be multiples of the block width and height |
3542 | copyInfo.imageOffset.x = aligned(v: dp.x(), byteAlign: blockDim.width()); |
3543 | copyInfo.imageOffset.y = aligned(v: dp.y(), byteAlign: blockDim.height()); |
3544 | // width and height must be multiples of the block width and height |
3545 | // or x + width and y + height must equal the subresource width and height |
3546 | copyInfo.imageExtent.width = uint32_t(dp.x() + w == subresw ? w : aligned(v: w, byteAlign: blockDim.width())); |
3547 | copyInfo.imageExtent.height = uint32_t(dp.y() + h == subresh ? h : aligned(v: h, byteAlign: blockDim.height())); |
3548 | copyInfos->append(t: copyInfo); |
3549 | } else if (!rawData.isEmpty()) { |
3550 | copySizeBytes = imageSizeBytes = rawData.size(); |
3551 | src = rawData.constData(); |
3552 | QSize size = q->sizeForMipLevel(mipLevel: level, baseLevelSize: texD->m_pixelSize); |
3553 | if (subresDesc.dataStride()) { |
3554 | quint32 bytesPerPixel = 0; |
3555 | textureFormatInfo(format: texD->m_format, size, bpl: nullptr, byteSize: nullptr, bytesPerPixel: &bytesPerPixel); |
3556 | if (bytesPerPixel) |
3557 | copyInfo.bufferRowLength = subresDesc.dataStride() / bytesPerPixel; |
3558 | } |
3559 | if (!subresDesc.sourceSize().isEmpty()) |
3560 | size = subresDesc.sourceSize(); |
3561 | copyInfo.imageOffset.x = dp.x(); |
3562 | copyInfo.imageOffset.y = dp.y(); |
3563 | copyInfo.imageExtent.width = uint32_t(size.width()); |
3564 | copyInfo.imageExtent.height = uint32_t(size.height()); |
3565 | copyInfos->append(t: copyInfo); |
3566 | } else { |
3567 | qWarning(msg: "Invalid texture upload for %p layer=%d mip=%d" , texD, layer, level); |
3568 | } |
3569 | |
3570 | if (src) { |
3571 | memcpy(dest: reinterpret_cast<char *>(mp) + *curOfs, src: src, n: size_t(copySizeBytes)); |
3572 | *curOfs += aligned(v: VkDeviceSize(imageSizeBytes), byteAlign: texbufAlign); |
3573 | } |
3574 | } |
3575 | |
3576 | void QRhiVulkan::(VkResult err) |
3577 | { |
3578 | if (err == VK_ERROR_OUT_OF_DEVICE_MEMORY) |
3579 | qWarning() << "Out of device memory, current allocator statistics are" << statistics(); |
3580 | } |
3581 | |
3582 | void QRhiVulkan::enqueueResourceUpdates(QVkCommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates) |
3583 | { |
3584 | QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(b: resourceUpdates); |
3585 | |
3586 | for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) { |
3587 | const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]); |
3588 | if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) { |
3589 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); |
3590 | Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); |
3591 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
3592 | if (u.offset == 0 && u.data.size() == bufD->m_size) |
3593 | bufD->pendingDynamicUpdates[i].clear(); |
3594 | bufD->pendingDynamicUpdates[i].append(t: { .offset: u.offset, .data: u.data }); |
3595 | } |
3596 | } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) { |
3597 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); |
3598 | Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic); |
3599 | Q_ASSERT(u.offset + u.data.size() <= bufD->m_size); |
3600 | |
3601 | if (!bufD->stagingBuffers[currentFrameSlot]) { |
3602 | VkBufferCreateInfo bufferInfo = {}; |
3603 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
3604 | // must cover the entire buffer - this way multiple, partial updates per frame |
3605 | // are supported even when the staging buffer is reused (Static) |
3606 | bufferInfo.size = bufD->m_size; |
3607 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; |
3608 | |
3609 | VmaAllocationCreateInfo allocInfo = {}; |
3610 | allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY; |
3611 | |
3612 | VmaAllocation allocation; |
3613 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, |
3614 | pBuffer: &bufD->stagingBuffers[currentFrameSlot], pAllocation: &allocation, pAllocationInfo: nullptr); |
3615 | if (err == VK_SUCCESS) { |
3616 | bufD->stagingAllocations[currentFrameSlot] = allocation; |
3617 | } else { |
3618 | qWarning(msg: "Failed to create staging buffer of size %u: %d" , bufD->m_size, err); |
3619 | printExtraErrorInfo(err); |
3620 | continue; |
3621 | } |
3622 | } |
3623 | |
3624 | void *p = nullptr; |
3625 | VmaAllocation a = toVmaAllocation(a: bufD->stagingAllocations[currentFrameSlot]); |
3626 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
3627 | if (err != VK_SUCCESS) { |
3628 | qWarning(msg: "Failed to map buffer: %d" , err); |
3629 | continue; |
3630 | } |
3631 | memcpy(dest: static_cast<uchar *>(p) + u.offset, src: u.data.constData(), n: u.data.size()); |
3632 | vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: u.offset, size: u.data.size()); |
3633 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3634 | |
3635 | trackedBufferBarrier(cbD, bufD, slot: 0, |
3636 | access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3637 | |
3638 | VkBufferCopy copyInfo = {}; |
3639 | copyInfo.srcOffset = u.offset; |
3640 | copyInfo.dstOffset = u.offset; |
3641 | copyInfo.size = u.data.size(); |
3642 | |
3643 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3644 | cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; |
3645 | cmd.args.copyBuffer.src = bufD->stagingBuffers[currentFrameSlot]; |
3646 | cmd.args.copyBuffer.dst = bufD->buffers[0]; |
3647 | cmd.args.copyBuffer.desc = copyInfo; |
3648 | |
3649 | // Where's the barrier for read-after-write? (assuming the common case |
3650 | // of binding this buffer as vertex/index, or, less likely, as uniform |
3651 | // buffer, in a renderpass later on) That is handled by the pass |
3652 | // resource tracking: the appropriate pipeline barrier will be |
3653 | // generated and recorded right before the renderpass, that binds this |
3654 | // buffer in one of its commands, gets its BeginRenderPass recorded. |
3655 | |
3656 | bufD->lastActiveFrameSlot = currentFrameSlot; |
3657 | |
3658 | if (bufD->m_type == QRhiBuffer::Immutable) { |
3659 | QRhiVulkan::DeferredReleaseEntry e; |
3660 | e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; |
3661 | e.lastActiveFrameSlot = currentFrameSlot; |
3662 | e.stagingBuffer.stagingBuffer = bufD->stagingBuffers[currentFrameSlot]; |
3663 | e.stagingBuffer.stagingAllocation = bufD->stagingAllocations[currentFrameSlot]; |
3664 | bufD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; |
3665 | bufD->stagingAllocations[currentFrameSlot] = nullptr; |
3666 | releaseQueue.append(t: e); |
3667 | } |
3668 | } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) { |
3669 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, u.buf); |
3670 | if (bufD->m_type == QRhiBuffer::Dynamic) { |
3671 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
3672 | void *p = nullptr; |
3673 | VmaAllocation a = toVmaAllocation(a: bufD->allocations[currentFrameSlot]); |
3674 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
3675 | if (err == VK_SUCCESS) { |
3676 | u.result->data.resize(size: u.readSize); |
3677 | memcpy(dest: u.result->data.data(), src: reinterpret_cast<char *>(p) + u.offset, n: u.readSize); |
3678 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3679 | } |
3680 | if (u.result->completed) |
3681 | u.result->completed(); |
3682 | } else { |
3683 | // Non-Dynamic buffers may not be host visible, so have to |
3684 | // create a readback buffer, enqueue a copy from |
3685 | // bufD->buffers[0] to this buffer, and then once the command |
3686 | // buffer completes, copy the data out of the host visible |
3687 | // readback buffer. Quite similar to what we do for texture |
3688 | // readbacks. |
3689 | BufferReadback readback; |
3690 | readback.activeFrameSlot = currentFrameSlot; |
3691 | readback.result = u.result; |
3692 | readback.byteSize = u.readSize; |
3693 | |
3694 | VkBufferCreateInfo bufferInfo = {}; |
3695 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
3696 | bufferInfo.size = readback.byteSize; |
3697 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
3698 | |
3699 | VmaAllocationCreateInfo allocInfo = {}; |
3700 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; |
3701 | |
3702 | VmaAllocation allocation; |
3703 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &readback.stagingBuf, pAllocation: &allocation, pAllocationInfo: nullptr); |
3704 | if (err == VK_SUCCESS) { |
3705 | readback.stagingAlloc = allocation; |
3706 | } else { |
3707 | qWarning(msg: "Failed to create readback buffer of size %u: %d" , readback.byteSize, err); |
3708 | printExtraErrorInfo(err); |
3709 | continue; |
3710 | } |
3711 | |
3712 | trackedBufferBarrier(cbD, bufD, slot: 0, access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3713 | |
3714 | VkBufferCopy copyInfo = {}; |
3715 | copyInfo.srcOffset = u.offset; |
3716 | copyInfo.size = u.readSize; |
3717 | |
3718 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3719 | cmd.cmd = QVkCommandBuffer::Command::CopyBuffer; |
3720 | cmd.args.copyBuffer.src = bufD->buffers[0]; |
3721 | cmd.args.copyBuffer.dst = readback.stagingBuf; |
3722 | cmd.args.copyBuffer.desc = copyInfo; |
3723 | |
3724 | bufD->lastActiveFrameSlot = currentFrameSlot; |
3725 | |
3726 | activeBufferReadbacks.append(t: readback); |
3727 | } |
3728 | } |
3729 | } |
3730 | |
3731 | for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) { |
3732 | const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]); |
3733 | if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) { |
3734 | QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); |
3735 | // batch into a single staging buffer and a single CopyBufferToImage with multiple copyInfos |
3736 | VkDeviceSize stagingSize = 0; |
3737 | for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) { |
3738 | for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { |
3739 | for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(t: u.subresDesc[layer][level])) |
3740 | stagingSize += subresUploadByteSize(subresDesc); |
3741 | } |
3742 | } |
3743 | |
3744 | Q_ASSERT(!utexD->stagingBuffers[currentFrameSlot]); |
3745 | VkBufferCreateInfo bufferInfo = {}; |
3746 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
3747 | bufferInfo.size = stagingSize; |
3748 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; |
3749 | |
3750 | VmaAllocationCreateInfo allocInfo = {}; |
3751 | allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; |
3752 | |
3753 | VmaAllocation allocation; |
3754 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, |
3755 | pBuffer: &utexD->stagingBuffers[currentFrameSlot], pAllocation: &allocation, pAllocationInfo: nullptr); |
3756 | if (err != VK_SUCCESS) { |
3757 | qWarning(msg: "Failed to create image staging buffer of size %d: %d" , int(stagingSize), err); |
3758 | printExtraErrorInfo(err); |
3759 | continue; |
3760 | } |
3761 | utexD->stagingAllocations[currentFrameSlot] = allocation; |
3762 | |
3763 | BufferImageCopyList copyInfos; |
3764 | size_t curOfs = 0; |
3765 | void *mp = nullptr; |
3766 | VmaAllocation a = toVmaAllocation(a: utexD->stagingAllocations[currentFrameSlot]); |
3767 | err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &mp); |
3768 | if (err != VK_SUCCESS) { |
3769 | qWarning(msg: "Failed to map image data: %d" , err); |
3770 | continue; |
3771 | } |
3772 | |
3773 | for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) { |
3774 | for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) { |
3775 | const QList<QRhiTextureSubresourceUploadDescription> &srd(u.subresDesc[layer][level]); |
3776 | if (srd.isEmpty()) |
3777 | continue; |
3778 | for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(t: srd)) { |
3779 | prepareUploadSubres(texD: utexD, layer, level, |
3780 | subresDesc, curOfs: &curOfs, mp, copyInfos: ©Infos); |
3781 | } |
3782 | } |
3783 | } |
3784 | vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: 0, size: stagingSize); |
3785 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
3786 | |
3787 | trackedImageBarrier(cbD, texD: utexD, layout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
3788 | access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3789 | |
3790 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3791 | cmd.cmd = QVkCommandBuffer::Command::CopyBufferToImage; |
3792 | cmd.args.copyBufferToImage.src = utexD->stagingBuffers[currentFrameSlot]; |
3793 | cmd.args.copyBufferToImage.dst = utexD->image; |
3794 | cmd.args.copyBufferToImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
3795 | cmd.args.copyBufferToImage.count = copyInfos.size(); |
3796 | cmd.args.copyBufferToImage.bufferImageCopyIndex = cbD->pools.bufferImageCopy.size(); |
3797 | cbD->pools.bufferImageCopy.append(buf: copyInfos.constData(), sz: copyInfos.size()); |
3798 | |
3799 | // no reuse of staging, this is intentional |
3800 | QRhiVulkan::DeferredReleaseEntry e; |
3801 | e.type = QRhiVulkan::DeferredReleaseEntry::StagingBuffer; |
3802 | e.lastActiveFrameSlot = currentFrameSlot; |
3803 | e.stagingBuffer.stagingBuffer = utexD->stagingBuffers[currentFrameSlot]; |
3804 | e.stagingBuffer.stagingAllocation = utexD->stagingAllocations[currentFrameSlot]; |
3805 | utexD->stagingBuffers[currentFrameSlot] = VK_NULL_HANDLE; |
3806 | utexD->stagingAllocations[currentFrameSlot] = nullptr; |
3807 | releaseQueue.append(t: e); |
3808 | |
3809 | // Similarly to buffers, transitioning away from DST is done later, |
3810 | // when a renderpass using the texture is encountered. |
3811 | |
3812 | utexD->lastActiveFrameSlot = currentFrameSlot; |
3813 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) { |
3814 | Q_ASSERT(u.src && u.dst); |
3815 | if (u.src == u.dst) { |
3816 | qWarning(msg: "Texture copy with matching source and destination is not supported" ); |
3817 | continue; |
3818 | } |
3819 | QVkTexture *srcD = QRHI_RES(QVkTexture, u.src); |
3820 | QVkTexture *dstD = QRHI_RES(QVkTexture, u.dst); |
3821 | const bool srcIs3D = srcD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional); |
3822 | const bool dstIs3D = dstD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional); |
3823 | |
3824 | VkImageCopy region = {}; |
3825 | region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3826 | region.srcSubresource.mipLevel = uint32_t(u.desc.sourceLevel()); |
3827 | region.srcSubresource.baseArrayLayer = srcIs3D ? 0 : uint32_t(u.desc.sourceLayer()); |
3828 | region.srcSubresource.layerCount = 1; |
3829 | |
3830 | region.srcOffset.x = u.desc.sourceTopLeft().x(); |
3831 | region.srcOffset.y = u.desc.sourceTopLeft().y(); |
3832 | if (srcIs3D) |
3833 | region.srcOffset.z = uint32_t(u.desc.sourceLayer()); |
3834 | |
3835 | region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3836 | region.dstSubresource.mipLevel = uint32_t(u.desc.destinationLevel()); |
3837 | region.dstSubresource.baseArrayLayer = dstIs3D ? 0 : uint32_t(u.desc.destinationLayer()); |
3838 | region.dstSubresource.layerCount = 1; |
3839 | |
3840 | region.dstOffset.x = u.desc.destinationTopLeft().x(); |
3841 | region.dstOffset.y = u.desc.destinationTopLeft().y(); |
3842 | if (dstIs3D) |
3843 | region.dstOffset.z = uint32_t(u.desc.destinationLayer()); |
3844 | |
3845 | const QSize mipSize = q->sizeForMipLevel(mipLevel: u.desc.sourceLevel(), baseLevelSize: srcD->m_pixelSize); |
3846 | const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize(); |
3847 | region.extent.width = uint32_t(copySize.width()); |
3848 | region.extent.height = uint32_t(copySize.height()); |
3849 | region.extent.depth = 1; |
3850 | |
3851 | trackedImageBarrier(cbD, texD: srcD, layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3852 | access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3853 | trackedImageBarrier(cbD, texD: dstD, layout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
3854 | access: VK_ACCESS_TRANSFER_WRITE_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3855 | |
3856 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3857 | cmd.cmd = QVkCommandBuffer::Command::CopyImage; |
3858 | cmd.args.copyImage.src = srcD->image; |
3859 | cmd.args.copyImage.srcLayout = srcD->usageState.layout; |
3860 | cmd.args.copyImage.dst = dstD->image; |
3861 | cmd.args.copyImage.dstLayout = dstD->usageState.layout; |
3862 | cmd.args.copyImage.desc = region; |
3863 | |
3864 | srcD->lastActiveFrameSlot = dstD->lastActiveFrameSlot = currentFrameSlot; |
3865 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) { |
3866 | TextureReadback readback; |
3867 | readback.activeFrameSlot = currentFrameSlot; |
3868 | readback.desc = u.rb; |
3869 | readback.result = u.result; |
3870 | |
3871 | QVkTexture *texD = QRHI_RES(QVkTexture, u.rb.texture()); |
3872 | QVkSwapChain *swapChainD = nullptr; |
3873 | bool is3D = false; |
3874 | if (texD) { |
3875 | if (texD->samples > VK_SAMPLE_COUNT_1_BIT) { |
3876 | qWarning(msg: "Multisample texture cannot be read back" ); |
3877 | continue; |
3878 | } |
3879 | is3D = texD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional); |
3880 | readback.pixelSize = q->sizeForMipLevel(mipLevel: u.rb.level(), baseLevelSize: texD->m_pixelSize); |
3881 | readback.format = texD->m_format; |
3882 | texD->lastActiveFrameSlot = currentFrameSlot; |
3883 | } else { |
3884 | Q_ASSERT(currentSwapChain); |
3885 | swapChainD = QRHI_RES(QVkSwapChain, currentSwapChain); |
3886 | if (!swapChainD->supportsReadback) { |
3887 | qWarning(msg: "Swapchain does not support readback" ); |
3888 | continue; |
3889 | } |
3890 | readback.pixelSize = swapChainD->pixelSize; |
3891 | readback.format = swapchainReadbackTextureFormat(format: swapChainD->colorFormat, flags: nullptr); |
3892 | if (readback.format == QRhiTexture::UnknownFormat) |
3893 | continue; |
3894 | |
3895 | // Multisample swapchains need nothing special since resolving |
3896 | // happens when ending a renderpass. |
3897 | } |
3898 | textureFormatInfo(format: readback.format, size: readback.pixelSize, bpl: nullptr, byteSize: &readback.byteSize, bytesPerPixel: nullptr); |
3899 | |
3900 | // Create a host visible readback buffer. |
3901 | VkBufferCreateInfo bufferInfo = {}; |
3902 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
3903 | bufferInfo.size = readback.byteSize; |
3904 | bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
3905 | |
3906 | VmaAllocationCreateInfo allocInfo = {}; |
3907 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_TO_CPU; |
3908 | |
3909 | VmaAllocation allocation; |
3910 | VkResult err = vmaCreateBuffer(allocator: toVmaAllocator(a: allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &readback.stagingBuf, pAllocation: &allocation, pAllocationInfo: nullptr); |
3911 | if (err == VK_SUCCESS) { |
3912 | readback.stagingAlloc = allocation; |
3913 | } else { |
3914 | qWarning(msg: "Failed to create readback buffer of size %u: %d" , readback.byteSize, err); |
3915 | printExtraErrorInfo(err); |
3916 | continue; |
3917 | } |
3918 | |
3919 | // Copy from the (optimal and not host visible) image into the buffer. |
3920 | VkBufferImageCopy copyDesc = {}; |
3921 | copyDesc.bufferOffset = 0; |
3922 | copyDesc.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
3923 | copyDesc.imageSubresource.mipLevel = uint32_t(u.rb.level()); |
3924 | copyDesc.imageSubresource.baseArrayLayer = is3D ? 0 : uint32_t(u.rb.layer()); |
3925 | copyDesc.imageSubresource.layerCount = 1; |
3926 | if (is3D) |
3927 | copyDesc.imageOffset.z = u.rb.layer(); |
3928 | copyDesc.imageExtent.width = uint32_t(readback.pixelSize.width()); |
3929 | copyDesc.imageExtent.height = uint32_t(readback.pixelSize.height()); |
3930 | copyDesc.imageExtent.depth = 1; |
3931 | |
3932 | if (texD) { |
3933 | trackedImageBarrier(cbD, texD, layout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3934 | access: VK_ACCESS_TRANSFER_READ_BIT, stage: VK_PIPELINE_STAGE_TRANSFER_BIT); |
3935 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3936 | cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; |
3937 | cmd.args.copyImageToBuffer.src = texD->image; |
3938 | cmd.args.copyImageToBuffer.srcLayout = texD->usageState.layout; |
3939 | cmd.args.copyImageToBuffer.dst = readback.stagingBuf; |
3940 | cmd.args.copyImageToBuffer.desc = copyDesc; |
3941 | } else { |
3942 | // use the swapchain image |
3943 | QVkSwapChain::ImageResources &imageRes(swapChainD->imageRes[swapChainD->currentImageIndex]); |
3944 | VkImage image = imageRes.image; |
3945 | if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseTransferSource) { |
3946 | if (imageRes.lastUse != QVkSwapChain::ImageResources::ScImageUseRender) { |
3947 | qWarning(msg: "Attempted to read back undefined swapchain image content, " |
3948 | "results are undefined. (do a render pass first)" ); |
3949 | } |
3950 | subresourceBarrier(cbD, image, |
3951 | oldLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3952 | srcAccess: VK_ACCESS_MEMORY_READ_BIT, dstAccess: VK_ACCESS_TRANSFER_READ_BIT, |
3953 | srcStage: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3954 | startLayer: 0, layerCount: 1, |
3955 | startLevel: 0, levelCount: 1); |
3956 | imageRes.lastUse = QVkSwapChain::ImageResources::ScImageUseTransferSource; |
3957 | } |
3958 | |
3959 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
3960 | cmd.cmd = QVkCommandBuffer::Command::CopyImageToBuffer; |
3961 | cmd.args.copyImageToBuffer.src = image; |
3962 | cmd.args.copyImageToBuffer.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
3963 | cmd.args.copyImageToBuffer.dst = readback.stagingBuf; |
3964 | cmd.args.copyImageToBuffer.desc = copyDesc; |
3965 | } |
3966 | |
3967 | activeTextureReadbacks.append(t: readback); |
3968 | } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) { |
3969 | QVkTexture *utexD = QRHI_RES(QVkTexture, u.dst); |
3970 | Q_ASSERT(utexD->m_flags.testFlag(QRhiTexture::UsedWithGenerateMips)); |
3971 | const bool isCube = utexD->m_flags.testFlag(flag: QRhiTexture::CubeMap); |
3972 | const bool isArray = utexD->m_flags.testFlag(flag: QRhiTexture::TextureArray); |
3973 | const bool is3D = utexD->m_flags.testFlag(flag: QRhiTexture::ThreeDimensional); |
3974 | |
3975 | VkImageLayout origLayout = utexD->usageState.layout; |
3976 | VkAccessFlags origAccess = utexD->usageState.access; |
3977 | VkPipelineStageFlags origStage = utexD->usageState.stage; |
3978 | if (!origStage) |
3979 | origStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; |
3980 | |
3981 | for (int layer = 0; layer < (isCube ? 6 : (isArray ? qMax(a: 0, b: utexD->m_arraySize) : 1)); ++layer) { |
3982 | int w = utexD->m_pixelSize.width(); |
3983 | int h = utexD->m_pixelSize.height(); |
3984 | int depth = is3D ? qMax(a: 1, b: utexD->m_depth) : 1; |
3985 | for (int level = 1; level < int(utexD->mipLevelCount); ++level) { |
3986 | if (level == 1) { |
3987 | subresourceBarrier(cbD, image: utexD->image, |
3988 | oldLayout: origLayout, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3989 | srcAccess: origAccess, dstAccess: VK_ACCESS_TRANSFER_READ_BIT, |
3990 | srcStage: origStage, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3991 | startLayer: layer, layerCount: 1, |
3992 | startLevel: level - 1, levelCount: 1); |
3993 | } else { |
3994 | subresourceBarrier(cbD, image: utexD->image, |
3995 | oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, |
3996 | srcAccess: VK_ACCESS_TRANSFER_WRITE_BIT, dstAccess: VK_ACCESS_TRANSFER_READ_BIT, |
3997 | srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
3998 | startLayer: layer, layerCount: 1, |
3999 | startLevel: level - 1, levelCount: 1); |
4000 | } |
4001 | |
4002 | subresourceBarrier(cbD, image: utexD->image, |
4003 | oldLayout: origLayout, newLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, |
4004 | srcAccess: origAccess, dstAccess: VK_ACCESS_TRANSFER_WRITE_BIT, |
4005 | srcStage: origStage, dstStage: VK_PIPELINE_STAGE_TRANSFER_BIT, |
4006 | startLayer: layer, layerCount: 1, |
4007 | startLevel: level, levelCount: 1); |
4008 | |
4009 | VkImageBlit region = {}; |
4010 | region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
4011 | region.srcSubresource.mipLevel = uint32_t(level) - 1; |
4012 | region.srcSubresource.baseArrayLayer = uint32_t(layer); |
4013 | region.srcSubresource.layerCount = 1; |
4014 | |
4015 | region.srcOffsets[1].x = qMax(a: 1, b: w); |
4016 | region.srcOffsets[1].y = qMax(a: 1, b: h); |
4017 | region.srcOffsets[1].z = qMax(a: 1, b: depth); |
4018 | |
4019 | region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
4020 | region.dstSubresource.mipLevel = uint32_t(level); |
4021 | region.dstSubresource.baseArrayLayer = uint32_t(layer); |
4022 | region.dstSubresource.layerCount = 1; |
4023 | |
4024 | region.dstOffsets[1].x = qMax(a: 1, b: w >> 1); |
4025 | region.dstOffsets[1].y = qMax(a: 1, b: h >> 1); |
4026 | region.dstOffsets[1].z = qMax(a: 1, b: depth >> 1); |
4027 | |
4028 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
4029 | cmd.cmd = QVkCommandBuffer::Command::BlitImage; |
4030 | cmd.args.blitImage.src = utexD->image; |
4031 | cmd.args.blitImage.srcLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
4032 | cmd.args.blitImage.dst = utexD->image; |
4033 | cmd.args.blitImage.dstLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
4034 | cmd.args.blitImage.filter = VK_FILTER_LINEAR; |
4035 | cmd.args.blitImage.desc = region; |
4036 | |
4037 | w >>= 1; |
4038 | h >>= 1; |
4039 | depth >>= 1; |
4040 | } |
4041 | |
4042 | if (utexD->mipLevelCount > 1) { |
4043 | subresourceBarrier(cbD, image: utexD->image, |
4044 | oldLayout: VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, newLayout: origLayout, |
4045 | srcAccess: VK_ACCESS_TRANSFER_READ_BIT, dstAccess: origAccess, |
4046 | srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: origStage, |
4047 | startLayer: layer, layerCount: 1, |
4048 | startLevel: 0, levelCount: int(utexD->mipLevelCount) - 1); |
4049 | subresourceBarrier(cbD, image: utexD->image, |
4050 | oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, newLayout: origLayout, |
4051 | srcAccess: VK_ACCESS_TRANSFER_WRITE_BIT, dstAccess: origAccess, |
4052 | srcStage: VK_PIPELINE_STAGE_TRANSFER_BIT, dstStage: origStage, |
4053 | startLayer: layer, layerCount: 1, |
4054 | startLevel: int(utexD->mipLevelCount) - 1, levelCount: 1); |
4055 | } |
4056 | } |
4057 | utexD->lastActiveFrameSlot = currentFrameSlot; |
4058 | } |
4059 | } |
4060 | |
4061 | ud->free(); |
4062 | } |
4063 | |
4064 | void QRhiVulkan::executeBufferHostWritesForSlot(QVkBuffer *bufD, int slot) |
4065 | { |
4066 | if (bufD->pendingDynamicUpdates[slot].isEmpty()) |
4067 | return; |
4068 | |
4069 | Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic); |
4070 | void *p = nullptr; |
4071 | VmaAllocation a = toVmaAllocation(a: bufD->allocations[slot]); |
4072 | // The vmaMap/Unmap are basically a no-op when persistently mapped since it |
4073 | // refcounts; this is great because we don't need to care if the allocation |
4074 | // was created as persistently mapped or not. |
4075 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
4076 | if (err != VK_SUCCESS) { |
4077 | qWarning(msg: "Failed to map buffer: %d" , err); |
4078 | return; |
4079 | } |
4080 | quint32 changeBegin = UINT32_MAX; |
4081 | quint32 changeEnd = 0; |
4082 | for (const QVkBuffer::DynamicUpdate &u : std::as_const(t&: bufD->pendingDynamicUpdates[slot])) { |
4083 | memcpy(dest: static_cast<char *>(p) + u.offset, src: u.data.constData(), n: u.data.size()); |
4084 | if (u.offset < changeBegin) |
4085 | changeBegin = u.offset; |
4086 | if (u.offset + u.data.size() > changeEnd) |
4087 | changeEnd = u.offset + u.data.size(); |
4088 | } |
4089 | if (changeBegin < UINT32_MAX && changeBegin < changeEnd) |
4090 | vmaFlushAllocation(allocator: toVmaAllocator(a: allocator), allocation: a, offset: changeBegin, size: changeEnd - changeBegin); |
4091 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
4092 | |
4093 | bufD->pendingDynamicUpdates[slot].clear(); |
4094 | } |
4095 | |
4096 | static void qrhivk_releaseBuffer(const QRhiVulkan::DeferredReleaseEntry &e, void *allocator) |
4097 | { |
4098 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
4099 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.buffer.buffers[i], allocation: toVmaAllocation(a: e.buffer.allocations[i])); |
4100 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.buffer.stagingBuffers[i], allocation: toVmaAllocation(a: e.buffer.stagingAllocations[i])); |
4101 | } |
4102 | } |
4103 | |
4104 | static void qrhivk_releaseRenderBuffer(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df) |
4105 | { |
4106 | df->vkDestroyImageView(dev, e.renderBuffer.imageView, nullptr); |
4107 | df->vkDestroyImage(dev, e.renderBuffer.image, nullptr); |
4108 | df->vkFreeMemory(dev, e.renderBuffer.memory, nullptr); |
4109 | } |
4110 | |
4111 | static void qrhivk_releaseTexture(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df, void *allocator) |
4112 | { |
4113 | df->vkDestroyImageView(dev, e.texture.imageView, nullptr); |
4114 | vmaDestroyImage(allocator: toVmaAllocator(a: allocator), image: e.texture.image, allocation: toVmaAllocation(a: e.texture.allocation)); |
4115 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
4116 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.texture.stagingBuffers[i], allocation: toVmaAllocation(a: e.texture.stagingAllocations[i])); |
4117 | for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) { |
4118 | if (e.texture.extraImageViews[i]) |
4119 | df->vkDestroyImageView(dev, e.texture.extraImageViews[i], nullptr); |
4120 | } |
4121 | } |
4122 | |
4123 | static void qrhivk_releaseSampler(const QRhiVulkan::DeferredReleaseEntry &e, VkDevice dev, QVulkanDeviceFunctions *df) |
4124 | { |
4125 | df->vkDestroySampler(dev, e.sampler.sampler, nullptr); |
4126 | } |
4127 | |
4128 | void QRhiVulkan::executeDeferredReleases(bool forced) |
4129 | { |
4130 | for (int i = releaseQueue.size() - 1; i >= 0; --i) { |
4131 | const QRhiVulkan::DeferredReleaseEntry &e(releaseQueue[i]); |
4132 | if (forced || currentFrameSlot == e.lastActiveFrameSlot || e.lastActiveFrameSlot < 0) { |
4133 | switch (e.type) { |
4134 | case QRhiVulkan::DeferredReleaseEntry::Pipeline: |
4135 | df->vkDestroyPipeline(dev, e.pipelineState.pipeline, nullptr); |
4136 | df->vkDestroyPipelineLayout(dev, e.pipelineState.layout, nullptr); |
4137 | break; |
4138 | case QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings: |
4139 | df->vkDestroyDescriptorSetLayout(dev, e.shaderResourceBindings.layout, nullptr); |
4140 | if (e.shaderResourceBindings.poolIndex >= 0) { |
4141 | descriptorPools[e.shaderResourceBindings.poolIndex].refCount -= 1; |
4142 | Q_ASSERT(descriptorPools[e.shaderResourceBindings.poolIndex].refCount >= 0); |
4143 | } |
4144 | break; |
4145 | case QRhiVulkan::DeferredReleaseEntry::Buffer: |
4146 | qrhivk_releaseBuffer(e, allocator); |
4147 | break; |
4148 | case QRhiVulkan::DeferredReleaseEntry::RenderBuffer: |
4149 | qrhivk_releaseRenderBuffer(e, dev, df); |
4150 | break; |
4151 | case QRhiVulkan::DeferredReleaseEntry::Texture: |
4152 | qrhivk_releaseTexture(e, dev, df, allocator); |
4153 | break; |
4154 | case QRhiVulkan::DeferredReleaseEntry::Sampler: |
4155 | qrhivk_releaseSampler(e, dev, df); |
4156 | break; |
4157 | case QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget: |
4158 | df->vkDestroyFramebuffer(dev, e.textureRenderTarget.fb, nullptr); |
4159 | for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) { |
4160 | df->vkDestroyImageView(dev, e.textureRenderTarget.rtv[att], nullptr); |
4161 | df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr); |
4162 | } |
4163 | df->vkDestroyImageView(dev, e.textureRenderTarget.dsv, nullptr); |
4164 | df->vkDestroyImageView(dev, e.textureRenderTarget.resdsv, nullptr); |
4165 | break; |
4166 | case QRhiVulkan::DeferredReleaseEntry::RenderPass: |
4167 | df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr); |
4168 | break; |
4169 | case QRhiVulkan::DeferredReleaseEntry::StagingBuffer: |
4170 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: e.stagingBuffer.stagingBuffer, allocation: toVmaAllocation(a: e.stagingBuffer.stagingAllocation)); |
4171 | break; |
4172 | case QRhiVulkan::DeferredReleaseEntry::SecondaryCommandBuffer: |
4173 | freeSecondaryCbs[e.lastActiveFrameSlot].append(t: e.secondaryCommandBuffer.cb); |
4174 | break; |
4175 | default: |
4176 | Q_UNREACHABLE(); |
4177 | break; |
4178 | } |
4179 | releaseQueue.removeAt(i); |
4180 | } |
4181 | } |
4182 | } |
4183 | |
4184 | void QRhiVulkan::finishActiveReadbacks(bool forced) |
4185 | { |
4186 | QVarLengthArray<std::function<void()>, 4> completedCallbacks; |
4187 | |
4188 | for (int i = activeTextureReadbacks.size() - 1; i >= 0; --i) { |
4189 | const QRhiVulkan::TextureReadback &readback(activeTextureReadbacks[i]); |
4190 | if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { |
4191 | readback.result->format = readback.format; |
4192 | readback.result->pixelSize = readback.pixelSize; |
4193 | VmaAllocation a = toVmaAllocation(a: readback.stagingAlloc); |
4194 | void *p = nullptr; |
4195 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
4196 | if (err == VK_SUCCESS && p) { |
4197 | readback.result->data.resize(size: int(readback.byteSize)); |
4198 | memcpy(dest: readback.result->data.data(), src: p, n: readback.byteSize); |
4199 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
4200 | } else { |
4201 | qWarning(msg: "Failed to map texture readback buffer of size %u: %d" , readback.byteSize, err); |
4202 | } |
4203 | |
4204 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: readback.stagingBuf, allocation: a); |
4205 | |
4206 | if (readback.result->completed) |
4207 | completedCallbacks.append(t: readback.result->completed); |
4208 | |
4209 | activeTextureReadbacks.removeLast(); |
4210 | } |
4211 | } |
4212 | |
4213 | for (int i = activeBufferReadbacks.size() - 1; i >= 0; --i) { |
4214 | const QRhiVulkan::BufferReadback &readback(activeBufferReadbacks[i]); |
4215 | if (forced || currentFrameSlot == readback.activeFrameSlot || readback.activeFrameSlot < 0) { |
4216 | VmaAllocation a = toVmaAllocation(a: readback.stagingAlloc); |
4217 | void *p = nullptr; |
4218 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: allocator), allocation: a, ppData: &p); |
4219 | if (err == VK_SUCCESS && p) { |
4220 | readback.result->data.resize(size: readback.byteSize); |
4221 | memcpy(dest: readback.result->data.data(), src: p, n: readback.byteSize); |
4222 | vmaUnmapMemory(allocator: toVmaAllocator(a: allocator), allocation: a); |
4223 | } else { |
4224 | qWarning(msg: "Failed to map buffer readback buffer of size %d: %d" , readback.byteSize, err); |
4225 | } |
4226 | |
4227 | vmaDestroyBuffer(allocator: toVmaAllocator(a: allocator), buffer: readback.stagingBuf, allocation: a); |
4228 | |
4229 | if (readback.result->completed) |
4230 | completedCallbacks.append(t: readback.result->completed); |
4231 | |
4232 | activeBufferReadbacks.removeLast(); |
4233 | } |
4234 | } |
4235 | |
4236 | for (auto f : completedCallbacks) |
4237 | f(); |
4238 | } |
4239 | |
4240 | static struct { |
4241 | VkSampleCountFlagBits mask; |
4242 | int count; |
4243 | } qvk_sampleCounts[] = { |
4244 | // keep this sorted by 'count' |
4245 | { .mask: VK_SAMPLE_COUNT_1_BIT, .count: 1 }, |
4246 | { .mask: VK_SAMPLE_COUNT_2_BIT, .count: 2 }, |
4247 | { .mask: VK_SAMPLE_COUNT_4_BIT, .count: 4 }, |
4248 | { .mask: VK_SAMPLE_COUNT_8_BIT, .count: 8 }, |
4249 | { .mask: VK_SAMPLE_COUNT_16_BIT, .count: 16 }, |
4250 | { .mask: VK_SAMPLE_COUNT_32_BIT, .count: 32 }, |
4251 | { .mask: VK_SAMPLE_COUNT_64_BIT, .count: 64 } |
4252 | }; |
4253 | |
4254 | QList<int> QRhiVulkan::supportedSampleCounts() const |
4255 | { |
4256 | const VkPhysicalDeviceLimits *limits = &physDevProperties.limits; |
4257 | VkSampleCountFlags color = limits->framebufferColorSampleCounts; |
4258 | VkSampleCountFlags depth = limits->framebufferDepthSampleCounts; |
4259 | VkSampleCountFlags stencil = limits->framebufferStencilSampleCounts; |
4260 | QList<int> result; |
4261 | |
4262 | for (const auto &qvk_sampleCount : qvk_sampleCounts) { |
4263 | if ((color & qvk_sampleCount.mask) |
4264 | && (depth & qvk_sampleCount.mask) |
4265 | && (stencil & qvk_sampleCount.mask)) |
4266 | { |
4267 | result.append(t: qvk_sampleCount.count); |
4268 | } |
4269 | } |
4270 | |
4271 | return result; |
4272 | } |
4273 | |
4274 | VkSampleCountFlagBits QRhiVulkan::effectiveSampleCountBits(int sampleCount) |
4275 | { |
4276 | const int s = effectiveSampleCount(sampleCount); |
4277 | |
4278 | for (const auto &qvk_sampleCount : qvk_sampleCounts) { |
4279 | if (qvk_sampleCount.count == s) |
4280 | return qvk_sampleCount.mask; |
4281 | } |
4282 | |
4283 | Q_UNREACHABLE_RETURN(VK_SAMPLE_COUNT_1_BIT); |
4284 | } |
4285 | |
4286 | void QRhiVulkan::enqueueTransitionPassResources(QVkCommandBuffer *cbD) |
4287 | { |
4288 | cbD->passResTrackers.append(t: QRhiPassResourceTracker()); |
4289 | cbD->currentPassResTrackerIndex = cbD->passResTrackers.size() - 1; |
4290 | |
4291 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
4292 | cmd.cmd = QVkCommandBuffer::Command::TransitionPassResources; |
4293 | cmd.args.transitionResources.trackerIndex = cbD->passResTrackers.size() - 1; |
4294 | } |
4295 | |
4296 | void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD) |
4297 | { |
4298 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::NoPass); |
4299 | |
4300 | for (auto it = cbD->commands.begin(), end = cbD->commands.end(); it != end; ++it) { |
4301 | QVkCommandBuffer::Command &cmd(*it); |
4302 | switch (cmd.cmd) { |
4303 | case QVkCommandBuffer::Command::CopyBuffer: |
4304 | df->vkCmdCopyBuffer(cbD->cb, cmd.args.copyBuffer.src, cmd.args.copyBuffer.dst, |
4305 | 1, &cmd.args.copyBuffer.desc); |
4306 | break; |
4307 | case QVkCommandBuffer::Command::CopyBufferToImage: |
4308 | df->vkCmdCopyBufferToImage(cbD->cb, cmd.args.copyBufferToImage.src, cmd.args.copyBufferToImage.dst, |
4309 | cmd.args.copyBufferToImage.dstLayout, |
4310 | uint32_t(cmd.args.copyBufferToImage.count), |
4311 | cbD->pools.bufferImageCopy.constData() + cmd.args.copyBufferToImage.bufferImageCopyIndex); |
4312 | break; |
4313 | case QVkCommandBuffer::Command::CopyImage: |
4314 | df->vkCmdCopyImage(cbD->cb, cmd.args.copyImage.src, cmd.args.copyImage.srcLayout, |
4315 | cmd.args.copyImage.dst, cmd.args.copyImage.dstLayout, |
4316 | 1, &cmd.args.copyImage.desc); |
4317 | break; |
4318 | case QVkCommandBuffer::Command::CopyImageToBuffer: |
4319 | df->vkCmdCopyImageToBuffer(cbD->cb, cmd.args.copyImageToBuffer.src, cmd.args.copyImageToBuffer.srcLayout, |
4320 | cmd.args.copyImageToBuffer.dst, |
4321 | 1, &cmd.args.copyImageToBuffer.desc); |
4322 | break; |
4323 | case QVkCommandBuffer::Command::ImageBarrier: |
4324 | df->vkCmdPipelineBarrier(cbD->cb, cmd.args.imageBarrier.srcStageMask, cmd.args.imageBarrier.dstStageMask, |
4325 | 0, 0, nullptr, 0, nullptr, |
4326 | cmd.args.imageBarrier.count, cbD->pools.imageBarrier.constData() + cmd.args.imageBarrier.index); |
4327 | break; |
4328 | case QVkCommandBuffer::Command::BufferBarrier: |
4329 | df->vkCmdPipelineBarrier(cbD->cb, cmd.args.bufferBarrier.srcStageMask, cmd.args.bufferBarrier.dstStageMask, |
4330 | 0, 0, nullptr, |
4331 | cmd.args.bufferBarrier.count, cbD->pools.bufferBarrier.constData() + cmd.args.bufferBarrier.index, |
4332 | 0, nullptr); |
4333 | break; |
4334 | case QVkCommandBuffer::Command::BlitImage: |
4335 | df->vkCmdBlitImage(cbD->cb, cmd.args.blitImage.src, cmd.args.blitImage.srcLayout, |
4336 | cmd.args.blitImage.dst, cmd.args.blitImage.dstLayout, |
4337 | 1, &cmd.args.blitImage.desc, |
4338 | cmd.args.blitImage.filter); |
4339 | break; |
4340 | case QVkCommandBuffer::Command::BeginRenderPass: |
4341 | cmd.args.beginRenderPass.desc.pClearValues = cbD->pools.clearValue.constData() + cmd.args.beginRenderPass.clearValueIndex; |
4342 | df->vkCmdBeginRenderPass(cbD->cb, &cmd.args.beginRenderPass.desc, |
4343 | cmd.args.beginRenderPass.useSecondaryCb ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS |
4344 | : VK_SUBPASS_CONTENTS_INLINE); |
4345 | break; |
4346 | case QVkCommandBuffer::Command::EndRenderPass: |
4347 | df->vkCmdEndRenderPass(cbD->cb); |
4348 | break; |
4349 | case QVkCommandBuffer::Command::BindPipeline: |
4350 | df->vkCmdBindPipeline(cbD->cb, cmd.args.bindPipeline.bindPoint, cmd.args.bindPipeline.pipeline); |
4351 | break; |
4352 | case QVkCommandBuffer::Command::BindDescriptorSet: |
4353 | { |
4354 | const uint32_t *offsets = nullptr; |
4355 | if (cmd.args.bindDescriptorSet.dynamicOffsetCount > 0) |
4356 | offsets = cbD->pools.dynamicOffset.constData() + cmd.args.bindDescriptorSet.dynamicOffsetIndex; |
4357 | df->vkCmdBindDescriptorSets(cbD->cb, cmd.args.bindDescriptorSet.bindPoint, |
4358 | cmd.args.bindDescriptorSet.pipelineLayout, |
4359 | 0, 1, &cmd.args.bindDescriptorSet.descSet, |
4360 | uint32_t(cmd.args.bindDescriptorSet.dynamicOffsetCount), |
4361 | offsets); |
4362 | } |
4363 | break; |
4364 | case QVkCommandBuffer::Command::BindVertexBuffer: |
4365 | df->vkCmdBindVertexBuffers(cbD->cb, uint32_t(cmd.args.bindVertexBuffer.startBinding), |
4366 | uint32_t(cmd.args.bindVertexBuffer.count), |
4367 | cbD->pools.vertexBuffer.constData() + cmd.args.bindVertexBuffer.vertexBufferIndex, |
4368 | cbD->pools.vertexBufferOffset.constData() + cmd.args.bindVertexBuffer.vertexBufferOffsetIndex); |
4369 | break; |
4370 | case QVkCommandBuffer::Command::BindIndexBuffer: |
4371 | df->vkCmdBindIndexBuffer(cbD->cb, cmd.args.bindIndexBuffer.buf, |
4372 | cmd.args.bindIndexBuffer.ofs, cmd.args.bindIndexBuffer.type); |
4373 | break; |
4374 | case QVkCommandBuffer::Command::SetViewport: |
4375 | df->vkCmdSetViewport(cbD->cb, 0, 1, &cmd.args.setViewport.viewport); |
4376 | break; |
4377 | case QVkCommandBuffer::Command::SetScissor: |
4378 | df->vkCmdSetScissor(cbD->cb, 0, 1, &cmd.args.setScissor.scissor); |
4379 | break; |
4380 | case QVkCommandBuffer::Command::SetBlendConstants: |
4381 | df->vkCmdSetBlendConstants(cbD->cb, cmd.args.setBlendConstants.c); |
4382 | break; |
4383 | case QVkCommandBuffer::Command::SetStencilRef: |
4384 | df->vkCmdSetStencilReference(cbD->cb, VK_STENCIL_FRONT_AND_BACK, cmd.args.setStencilRef.ref); |
4385 | break; |
4386 | case QVkCommandBuffer::Command::Draw: |
4387 | df->vkCmdDraw(cbD->cb, cmd.args.draw.vertexCount, cmd.args.draw.instanceCount, |
4388 | cmd.args.draw.firstVertex, cmd.args.draw.firstInstance); |
4389 | break; |
4390 | case QVkCommandBuffer::Command::DrawIndexed: |
4391 | df->vkCmdDrawIndexed(cbD->cb, cmd.args.drawIndexed.indexCount, cmd.args.drawIndexed.instanceCount, |
4392 | cmd.args.drawIndexed.firstIndex, cmd.args.drawIndexed.vertexOffset, |
4393 | cmd.args.drawIndexed.firstInstance); |
4394 | break; |
4395 | case QVkCommandBuffer::Command::DebugMarkerBegin: |
4396 | #ifdef VK_EXT_debug_utils |
4397 | cmd.args.debugMarkerBegin.label.pLabelName = |
4398 | cbD->pools.debugMarkerData[cmd.args.debugMarkerBegin.labelNameIndex].constData(); |
4399 | vkCmdBeginDebugUtilsLabelEXT(cbD->cb, &cmd.args.debugMarkerBegin.label); |
4400 | #endif |
4401 | break; |
4402 | case QVkCommandBuffer::Command::DebugMarkerEnd: |
4403 | #ifdef VK_EXT_debug_utils |
4404 | vkCmdEndDebugUtilsLabelEXT(cbD->cb); |
4405 | #endif |
4406 | break; |
4407 | case QVkCommandBuffer::Command::DebugMarkerInsert: |
4408 | #ifdef VK_EXT_debug_utils |
4409 | cmd.args.debugMarkerInsert.label.pLabelName = |
4410 | cbD->pools.debugMarkerData[cmd.args.debugMarkerInsert.labelNameIndex].constData(); |
4411 | vkCmdInsertDebugUtilsLabelEXT(cbD->cb, &cmd.args.debugMarkerInsert.label); |
4412 | #endif |
4413 | break; |
4414 | case QVkCommandBuffer::Command::TransitionPassResources: |
4415 | recordTransitionPassResources(cbD, tracker: cbD->passResTrackers[cmd.args.transitionResources.trackerIndex]); |
4416 | break; |
4417 | case QVkCommandBuffer::Command::Dispatch: |
4418 | df->vkCmdDispatch(cbD->cb, uint32_t(cmd.args.dispatch.x), uint32_t(cmd.args.dispatch.y), uint32_t(cmd.args.dispatch.z)); |
4419 | break; |
4420 | case QVkCommandBuffer::Command::ExecuteSecondary: |
4421 | df->vkCmdExecuteCommands(cbD->cb, 1, &cmd.args.executeSecondary.cb); |
4422 | break; |
4423 | default: |
4424 | break; |
4425 | } |
4426 | } |
4427 | } |
4428 | |
4429 | static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::BufferAccess access) |
4430 | { |
4431 | switch (access) { |
4432 | case QRhiPassResourceTracker::BufVertexInput: |
4433 | return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; |
4434 | case QRhiPassResourceTracker::BufIndexRead: |
4435 | return VK_ACCESS_INDEX_READ_BIT; |
4436 | case QRhiPassResourceTracker::BufUniformRead: |
4437 | return VK_ACCESS_UNIFORM_READ_BIT; |
4438 | case QRhiPassResourceTracker::BufStorageLoad: |
4439 | return VK_ACCESS_SHADER_READ_BIT; |
4440 | case QRhiPassResourceTracker::BufStorageStore: |
4441 | return VK_ACCESS_SHADER_WRITE_BIT; |
4442 | case QRhiPassResourceTracker::BufStorageLoadStore: |
4443 | return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; |
4444 | default: |
4445 | Q_UNREACHABLE(); |
4446 | break; |
4447 | } |
4448 | return 0; |
4449 | } |
4450 | |
4451 | static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::BufferStage stage) |
4452 | { |
4453 | switch (stage) { |
4454 | case QRhiPassResourceTracker::BufVertexInputStage: |
4455 | return VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; |
4456 | case QRhiPassResourceTracker::BufVertexStage: |
4457 | return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; |
4458 | case QRhiPassResourceTracker::BufTCStage: |
4459 | return VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT; |
4460 | case QRhiPassResourceTracker::BufTEStage: |
4461 | return VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT; |
4462 | case QRhiPassResourceTracker::BufFragmentStage: |
4463 | return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; |
4464 | case QRhiPassResourceTracker::BufComputeStage: |
4465 | return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
4466 | case QRhiPassResourceTracker::BufGeometryStage: |
4467 | return VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; |
4468 | default: |
4469 | Q_UNREACHABLE(); |
4470 | break; |
4471 | } |
4472 | return 0; |
4473 | } |
4474 | |
4475 | static inline QVkBuffer::UsageState toVkBufferUsageState(QRhiPassResourceTracker::UsageState usage) |
4476 | { |
4477 | QVkBuffer::UsageState u; |
4478 | u.access = VkAccessFlags(usage.access); |
4479 | u.stage = VkPipelineStageFlags(usage.stage); |
4480 | return u; |
4481 | } |
4482 | |
4483 | static inline VkImageLayout toVkLayout(QRhiPassResourceTracker::TextureAccess access) |
4484 | { |
4485 | switch (access) { |
4486 | case QRhiPassResourceTracker::TexSample: |
4487 | return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; |
4488 | case QRhiPassResourceTracker::TexColorOutput: |
4489 | return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
4490 | case QRhiPassResourceTracker::TexDepthOutput: |
4491 | return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; |
4492 | case QRhiPassResourceTracker::TexStorageLoad: |
4493 | case QRhiPassResourceTracker::TexStorageStore: |
4494 | case QRhiPassResourceTracker::TexStorageLoadStore: |
4495 | return VK_IMAGE_LAYOUT_GENERAL; |
4496 | default: |
4497 | Q_UNREACHABLE(); |
4498 | break; |
4499 | } |
4500 | return VK_IMAGE_LAYOUT_GENERAL; |
4501 | } |
4502 | |
4503 | static inline VkAccessFlags toVkAccess(QRhiPassResourceTracker::TextureAccess access) |
4504 | { |
4505 | switch (access) { |
4506 | case QRhiPassResourceTracker::TexSample: |
4507 | return VK_ACCESS_SHADER_READ_BIT; |
4508 | case QRhiPassResourceTracker::TexColorOutput: |
4509 | return VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
4510 | case QRhiPassResourceTracker::TexDepthOutput: |
4511 | return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; |
4512 | case QRhiPassResourceTracker::TexStorageLoad: |
4513 | return VK_ACCESS_SHADER_READ_BIT; |
4514 | case QRhiPassResourceTracker::TexStorageStore: |
4515 | return VK_ACCESS_SHADER_WRITE_BIT; |
4516 | case QRhiPassResourceTracker::TexStorageLoadStore: |
4517 | return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; |
4518 | default: |
4519 | Q_UNREACHABLE(); |
4520 | break; |
4521 | } |
4522 | return 0; |
4523 | } |
4524 | |
4525 | static inline VkPipelineStageFlags toVkPipelineStage(QRhiPassResourceTracker::TextureStage stage) |
4526 | { |
4527 | switch (stage) { |
4528 | case QRhiPassResourceTracker::TexVertexStage: |
4529 | return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; |
4530 | case QRhiPassResourceTracker::TexTCStage: |
4531 | return VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT; |
4532 | case QRhiPassResourceTracker::TexTEStage: |
4533 | return VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT; |
4534 | case QRhiPassResourceTracker::TexFragmentStage: |
4535 | return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; |
4536 | case QRhiPassResourceTracker::TexColorOutputStage: |
4537 | return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
4538 | case QRhiPassResourceTracker::TexDepthOutputStage: |
4539 | return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; |
4540 | case QRhiPassResourceTracker::TexComputeStage: |
4541 | return VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; |
4542 | case QRhiPassResourceTracker::TexGeometryStage: |
4543 | return VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; |
4544 | default: |
4545 | Q_UNREACHABLE(); |
4546 | break; |
4547 | } |
4548 | return 0; |
4549 | } |
4550 | |
4551 | static inline QVkTexture::UsageState toVkTextureUsageState(QRhiPassResourceTracker::UsageState usage) |
4552 | { |
4553 | QVkTexture::UsageState u; |
4554 | u.layout = VkImageLayout(usage.layout); |
4555 | u.access = VkAccessFlags(usage.access); |
4556 | u.stage = VkPipelineStageFlags(usage.stage); |
4557 | return u; |
4558 | } |
4559 | |
4560 | void QRhiVulkan::trackedRegisterBuffer(QRhiPassResourceTracker *passResTracker, |
4561 | QVkBuffer *bufD, |
4562 | int slot, |
4563 | QRhiPassResourceTracker::BufferAccess access, |
4564 | QRhiPassResourceTracker::BufferStage stage) |
4565 | { |
4566 | QVkBuffer::UsageState &u(bufD->usageState[slot]); |
4567 | const VkAccessFlags newAccess = toVkAccess(access); |
4568 | const VkPipelineStageFlags newStage = toVkPipelineStage(stage); |
4569 | if (u.access == newAccess && u.stage == newStage) { |
4570 | if (!accessIsWrite(access)) |
4571 | return; |
4572 | } |
4573 | passResTracker->registerBuffer(buf: bufD, slot, access: &access, stage: &stage, state: toPassTrackerUsageState(bufUsage: u)); |
4574 | u.access = newAccess; |
4575 | u.stage = newStage; |
4576 | } |
4577 | |
4578 | void QRhiVulkan::trackedRegisterTexture(QRhiPassResourceTracker *passResTracker, |
4579 | QVkTexture *texD, |
4580 | QRhiPassResourceTracker::TextureAccess access, |
4581 | QRhiPassResourceTracker::TextureStage stage) |
4582 | { |
4583 | QVkTexture::UsageState &u(texD->usageState); |
4584 | const VkAccessFlags newAccess = toVkAccess(access); |
4585 | const VkPipelineStageFlags newStage = toVkPipelineStage(stage); |
4586 | const VkImageLayout newLayout = toVkLayout(access); |
4587 | if (u.access == newAccess && u.stage == newStage && u.layout == newLayout) { |
4588 | if (!accessIsWrite(access)) |
4589 | return; |
4590 | } |
4591 | passResTracker->registerTexture(tex: texD, access: &access, stage: &stage, state: toPassTrackerUsageState(texUsage: u)); |
4592 | u.layout = newLayout; |
4593 | u.access = newAccess; |
4594 | u.stage = newStage; |
4595 | } |
4596 | |
4597 | void QRhiVulkan::recordTransitionPassResources(QVkCommandBuffer *cbD, const QRhiPassResourceTracker &tracker) |
4598 | { |
4599 | if (tracker.isEmpty()) |
4600 | return; |
4601 | |
4602 | for (auto it = tracker.cbeginBuffers(), itEnd = tracker.cendBuffers(); it != itEnd; ++it) { |
4603 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, it.key()); |
4604 | VkAccessFlags access = toVkAccess(access: it->access); |
4605 | VkPipelineStageFlags stage = toVkPipelineStage(stage: it->stage); |
4606 | QVkBuffer::UsageState s = toVkBufferUsageState(usage: it->stateAtPassBegin); |
4607 | if (!s.stage) |
4608 | continue; |
4609 | if (s.access == access && s.stage == stage) { |
4610 | if (!accessIsWrite(access)) |
4611 | continue; |
4612 | } |
4613 | VkBufferMemoryBarrier bufMemBarrier = {}; |
4614 | bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; |
4615 | bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
4616 | bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
4617 | bufMemBarrier.srcAccessMask = s.access; |
4618 | bufMemBarrier.dstAccessMask = access; |
4619 | bufMemBarrier.buffer = bufD->buffers[it->slot]; |
4620 | bufMemBarrier.size = VK_WHOLE_SIZE; |
4621 | df->vkCmdPipelineBarrier(cbD->cb, s.stage, stage, 0, |
4622 | 0, nullptr, |
4623 | 1, &bufMemBarrier, |
4624 | 0, nullptr); |
4625 | } |
4626 | |
4627 | for (auto it = tracker.cbeginTextures(), itEnd = tracker.cendTextures(); it != itEnd; ++it) { |
4628 | QVkTexture *texD = QRHI_RES(QVkTexture, it.key()); |
4629 | VkImageLayout layout = toVkLayout(access: it->access); |
4630 | VkAccessFlags access = toVkAccess(access: it->access); |
4631 | VkPipelineStageFlags stage = toVkPipelineStage(stage: it->stage); |
4632 | QVkTexture::UsageState s = toVkTextureUsageState(usage: it->stateAtPassBegin); |
4633 | if (s.access == access && s.stage == stage && s.layout == layout) { |
4634 | if (!accessIsWrite(access)) |
4635 | continue; |
4636 | } |
4637 | VkImageMemoryBarrier barrier = {}; |
4638 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; |
4639 | barrier.subresourceRange.aspectMask = aspectMaskForTextureFormat(format: texD->m_format); |
4640 | barrier.subresourceRange.baseMipLevel = 0; |
4641 | barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; |
4642 | barrier.subresourceRange.baseArrayLayer = 0; |
4643 | barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; |
4644 | barrier.oldLayout = s.layout; // new textures have this set to PREINITIALIZED |
4645 | barrier.newLayout = layout; |
4646 | barrier.srcAccessMask = s.access; // may be 0 but that's fine |
4647 | barrier.dstAccessMask = access; |
4648 | barrier.image = texD->image; |
4649 | VkPipelineStageFlags srcStage = s.stage; |
4650 | // stage mask cannot be 0 |
4651 | if (!srcStage) |
4652 | srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; |
4653 | df->vkCmdPipelineBarrier(cbD->cb, srcStage, stage, 0, |
4654 | 0, nullptr, |
4655 | 0, nullptr, |
4656 | 1, &barrier); |
4657 | } |
4658 | } |
4659 | |
4660 | QRhiSwapChain *QRhiVulkan::createSwapChain() |
4661 | { |
4662 | if (!vkGetPhysicalDeviceSurfaceCapabilitiesKHR |
4663 | || !vkGetPhysicalDeviceSurfaceFormatsKHR |
4664 | || !vkGetPhysicalDeviceSurfacePresentModesKHR) |
4665 | { |
4666 | qWarning(msg: "Physical device surface queries not available" ); |
4667 | return nullptr; |
4668 | } |
4669 | |
4670 | return new QVkSwapChain(this); |
4671 | } |
4672 | |
4673 | QRhiBuffer *QRhiVulkan::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size) |
4674 | { |
4675 | return new QVkBuffer(this, type, usage, size); |
4676 | } |
4677 | |
4678 | int QRhiVulkan::ubufAlignment() const |
4679 | { |
4680 | return int(ubufAlign); // typically 256 (bytes) |
4681 | } |
4682 | |
4683 | bool QRhiVulkan::isYUpInFramebuffer() const |
4684 | { |
4685 | return false; |
4686 | } |
4687 | |
4688 | bool QRhiVulkan::isYUpInNDC() const |
4689 | { |
4690 | return false; |
4691 | } |
4692 | |
4693 | bool QRhiVulkan::isClipDepthZeroToOne() const |
4694 | { |
4695 | return true; |
4696 | } |
4697 | |
4698 | QMatrix4x4 QRhiVulkan::clipSpaceCorrMatrix() const |
4699 | { |
4700 | // See https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ |
4701 | |
4702 | static QMatrix4x4 m; |
4703 | if (m.isIdentity()) { |
4704 | // NB the ctor takes row-major |
4705 | m = QMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f, |
4706 | 0.0f, -1.0f, 0.0f, 0.0f, |
4707 | 0.0f, 0.0f, 0.5f, 0.5f, |
4708 | 0.0f, 0.0f, 0.0f, 1.0f); |
4709 | } |
4710 | return m; |
4711 | } |
4712 | |
4713 | bool QRhiVulkan::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const |
4714 | { |
4715 | // Note that with some SDKs the validation layer gives an odd warning about |
4716 | // BC not being supported, even when our check here succeeds. Not much we |
4717 | // can do about that. |
4718 | if (format >= QRhiTexture::BC1 && format <= QRhiTexture::BC7) { |
4719 | if (!physDevFeatures.textureCompressionBC) |
4720 | return false; |
4721 | } |
4722 | |
4723 | if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ETC2_RGBA8) { |
4724 | if (!physDevFeatures.textureCompressionETC2) |
4725 | return false; |
4726 | } |
4727 | |
4728 | if (format >= QRhiTexture::ASTC_4x4 && format <= QRhiTexture::ASTC_12x12) { |
4729 | if (!physDevFeatures.textureCompressionASTC_LDR) |
4730 | return false; |
4731 | } |
4732 | |
4733 | VkFormat vkformat = toVkTextureFormat(format, flags); |
4734 | VkFormatProperties props; |
4735 | f->vkGetPhysicalDeviceFormatProperties(physDev, vkformat, &props); |
4736 | return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0; |
4737 | } |
4738 | |
4739 | bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const |
4740 | { |
4741 | switch (feature) { |
4742 | case QRhi::MultisampleTexture: |
4743 | return true; |
4744 | case QRhi::MultisampleRenderBuffer: |
4745 | return true; |
4746 | case QRhi::DebugMarkers: |
4747 | return caps.debugUtils; |
4748 | case QRhi::Timestamps: |
4749 | return timestampValidBits != 0; |
4750 | case QRhi::Instancing: |
4751 | return true; |
4752 | case QRhi::CustomInstanceStepRate: |
4753 | return caps.vertexAttribDivisor; |
4754 | case QRhi::PrimitiveRestart: |
4755 | return true; |
4756 | case QRhi::NonDynamicUniformBuffers: |
4757 | return true; |
4758 | case QRhi::NonFourAlignedEffectiveIndexBufferOffset: |
4759 | return true; |
4760 | case QRhi::NPOTTextureRepeat: |
4761 | return true; |
4762 | case QRhi::RedOrAlpha8IsRed: |
4763 | return true; |
4764 | case QRhi::ElementIndexUint: |
4765 | return true; |
4766 | case QRhi::Compute: |
4767 | return caps.compute; |
4768 | case QRhi::WideLines: |
4769 | return caps.wideLines; |
4770 | case QRhi::VertexShaderPointSize: |
4771 | return true; |
4772 | case QRhi::BaseVertex: |
4773 | return true; |
4774 | case QRhi::BaseInstance: |
4775 | return true; |
4776 | case QRhi::TriangleFanTopology: |
4777 | return true; |
4778 | case QRhi::ReadBackNonUniformBuffer: |
4779 | return true; |
4780 | case QRhi::ReadBackNonBaseMipLevel: |
4781 | return true; |
4782 | case QRhi::TexelFetch: |
4783 | return true; |
4784 | case QRhi::RenderToNonBaseMipLevel: |
4785 | return true; |
4786 | case QRhi::IntAttributes: |
4787 | return true; |
4788 | case QRhi::ScreenSpaceDerivatives: |
4789 | return true; |
4790 | case QRhi::ReadBackAnyTextureFormat: |
4791 | return true; |
4792 | case QRhi::PipelineCacheDataLoadSave: |
4793 | return true; |
4794 | case QRhi::ImageDataStride: |
4795 | return true; |
4796 | case QRhi::RenderBufferImport: |
4797 | return false; |
4798 | case QRhi::ThreeDimensionalTextures: |
4799 | return true; |
4800 | case QRhi::RenderTo3DTextureSlice: |
4801 | return caps.texture3DSliceAs2D; |
4802 | case QRhi::TextureArrays: |
4803 | return true; |
4804 | case QRhi::Tessellation: |
4805 | return caps.tessellation; |
4806 | case QRhi::GeometryShader: |
4807 | return caps.geometryShader; |
4808 | case QRhi::TextureArrayRange: |
4809 | return true; |
4810 | case QRhi::NonFillPolygonMode: |
4811 | return caps.nonFillPolygonMode; |
4812 | case QRhi::OneDimensionalTextures: |
4813 | return true; |
4814 | case QRhi::OneDimensionalTextureMipmaps: |
4815 | return true; |
4816 | case QRhi::HalfAttributes: |
4817 | return true; |
4818 | case QRhi::RenderToOneDimensionalTexture: |
4819 | return true; |
4820 | case QRhi::ThreeDimensionalTextureMipmaps: |
4821 | return true; |
4822 | case QRhi::MultiView: |
4823 | return caps.multiView; |
4824 | case QRhi::TextureViewFormat: |
4825 | return true; |
4826 | case QRhi::ResolveDepthStencil: |
4827 | return caps.renderPass2KHR && caps.depthStencilResolveKHR; |
4828 | default: |
4829 | Q_UNREACHABLE_RETURN(false); |
4830 | } |
4831 | } |
4832 | |
4833 | int QRhiVulkan::resourceLimit(QRhi::ResourceLimit limit) const |
4834 | { |
4835 | switch (limit) { |
4836 | case QRhi::TextureSizeMin: |
4837 | return 1; |
4838 | case QRhi::TextureSizeMax: |
4839 | return int(physDevProperties.limits.maxImageDimension2D); |
4840 | case QRhi::MaxColorAttachments: |
4841 | return int(physDevProperties.limits.maxColorAttachments); |
4842 | case QRhi::FramesInFlight: |
4843 | return QVK_FRAMES_IN_FLIGHT; |
4844 | case QRhi::MaxAsyncReadbackFrames: |
4845 | return QVK_FRAMES_IN_FLIGHT; |
4846 | case QRhi::MaxThreadGroupsPerDimension: |
4847 | return int(qMin(a: physDevProperties.limits.maxComputeWorkGroupCount[0], |
4848 | b: qMin(a: physDevProperties.limits.maxComputeWorkGroupCount[1], |
4849 | b: physDevProperties.limits.maxComputeWorkGroupCount[2]))); |
4850 | case QRhi::MaxThreadsPerThreadGroup: |
4851 | return int(physDevProperties.limits.maxComputeWorkGroupInvocations); |
4852 | case QRhi::MaxThreadGroupX: |
4853 | return int(physDevProperties.limits.maxComputeWorkGroupSize[0]); |
4854 | case QRhi::MaxThreadGroupY: |
4855 | return int(physDevProperties.limits.maxComputeWorkGroupSize[1]); |
4856 | case QRhi::MaxThreadGroupZ: |
4857 | return int(physDevProperties.limits.maxComputeWorkGroupSize[2]); |
4858 | case QRhi::TextureArraySizeMax: |
4859 | return int(physDevProperties.limits.maxImageArrayLayers); |
4860 | case QRhi::MaxUniformBufferRange: |
4861 | return int(qMin<uint32_t>(INT_MAX, b: physDevProperties.limits.maxUniformBufferRange)); |
4862 | case QRhi::MaxVertexInputs: |
4863 | return physDevProperties.limits.maxVertexInputAttributes; |
4864 | case QRhi::MaxVertexOutputs: |
4865 | return physDevProperties.limits.maxVertexOutputComponents / 4; |
4866 | default: |
4867 | Q_UNREACHABLE_RETURN(0); |
4868 | } |
4869 | } |
4870 | |
4871 | const QRhiNativeHandles *QRhiVulkan::nativeHandles() |
4872 | { |
4873 | return &nativeHandlesStruct; |
4874 | } |
4875 | |
4876 | QRhiDriverInfo QRhiVulkan::driverInfo() const |
4877 | { |
4878 | return driverInfoStruct; |
4879 | } |
4880 | |
4881 | QRhiStats QRhiVulkan::statistics() |
4882 | { |
4883 | QRhiStats result; |
4884 | result.totalPipelineCreationTime = totalPipelineCreationTime(); |
4885 | |
4886 | VmaBudget budgets[VK_MAX_MEMORY_HEAPS]; |
4887 | vmaGetHeapBudgets(allocator: toVmaAllocator(a: allocator), pBudgets: budgets); |
4888 | |
4889 | uint32_t count = toVmaAllocator(a: allocator)->GetMemoryHeapCount(); |
4890 | for (uint32_t i = 0; i < count; ++i) { |
4891 | const VmaStatistics &stats(budgets[i].statistics); |
4892 | result.blockCount += stats.blockCount; |
4893 | result.allocCount += stats.allocationCount; |
4894 | result.usedBytes += stats.allocationBytes; |
4895 | result.unusedBytes += stats.blockBytes - stats.allocationBytes; |
4896 | } |
4897 | |
4898 | return result; |
4899 | } |
4900 | |
4901 | bool QRhiVulkan::makeThreadLocalNativeContextCurrent() |
4902 | { |
4903 | // not applicable |
4904 | return false; |
4905 | } |
4906 | |
4907 | void QRhiVulkan::releaseCachedResources() |
4908 | { |
4909 | releaseCachedResourcesCalledBeforeFrameStart = true; |
4910 | } |
4911 | |
4912 | bool QRhiVulkan::isDeviceLost() const |
4913 | { |
4914 | return deviceLost; |
4915 | } |
4916 | |
4917 | struct |
4918 | { |
4919 | quint32 ; |
4920 | quint32 ; |
4921 | quint32 ; |
4922 | quint32 ; |
4923 | quint32 ; |
4924 | quint32 ; |
4925 | quint32 ; |
4926 | quint32 ; |
4927 | }; |
4928 | |
4929 | QByteArray QRhiVulkan::pipelineCacheData() |
4930 | { |
4931 | Q_STATIC_ASSERT(sizeof(QVkPipelineCacheDataHeader) == 32); |
4932 | |
4933 | QByteArray data; |
4934 | if (!pipelineCache || !rhiFlags.testFlag(flag: QRhi::EnablePipelineCacheDataSave)) |
4935 | return data; |
4936 | |
4937 | size_t dataSize = 0; |
4938 | VkResult err = df->vkGetPipelineCacheData(dev, pipelineCache, &dataSize, nullptr); |
4939 | if (err != VK_SUCCESS) { |
4940 | qCDebug(QRHI_LOG_INFO, "Failed to get pipeline cache data size: %d" , err); |
4941 | return QByteArray(); |
4942 | } |
4943 | const size_t = sizeof(QVkPipelineCacheDataHeader); |
4944 | const size_t dataOffset = headerSize + VK_UUID_SIZE; |
4945 | data.resize(size: dataOffset + dataSize); |
4946 | err = df->vkGetPipelineCacheData(dev, pipelineCache, &dataSize, data.data() + dataOffset); |
4947 | if (err != VK_SUCCESS) { |
4948 | qCDebug(QRHI_LOG_INFO, "Failed to get pipeline cache data of %d bytes: %d" , int(dataSize), err); |
4949 | return QByteArray(); |
4950 | } |
4951 | |
4952 | QVkPipelineCacheDataHeader ; |
4953 | header.rhiId = pipelineCacheRhiId(); |
4954 | header.arch = quint32(sizeof(void*)); |
4955 | header.driverVersion = physDevProperties.driverVersion; |
4956 | header.vendorId = physDevProperties.vendorID; |
4957 | header.deviceId = physDevProperties.deviceID; |
4958 | header.dataSize = quint32(dataSize); |
4959 | header.uuidSize = VK_UUID_SIZE; |
4960 | header.reserved = 0; |
4961 | memcpy(dest: data.data(), src: &header, n: headerSize); |
4962 | memcpy(dest: data.data() + headerSize, src: physDevProperties.pipelineCacheUUID, VK_UUID_SIZE); |
4963 | |
4964 | return data; |
4965 | } |
4966 | |
4967 | void QRhiVulkan::setPipelineCacheData(const QByteArray &data) |
4968 | { |
4969 | if (data.isEmpty()) |
4970 | return; |
4971 | |
4972 | const size_t = sizeof(QVkPipelineCacheDataHeader); |
4973 | if (data.size() < qsizetype(headerSize)) { |
4974 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size" ); |
4975 | return; |
4976 | } |
4977 | QVkPipelineCacheDataHeader ; |
4978 | memcpy(dest: &header, src: data.constData(), n: headerSize); |
4979 | |
4980 | const quint32 rhiId = pipelineCacheRhiId(); |
4981 | if (header.rhiId != rhiId) { |
4982 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)" , |
4983 | rhiId, header.rhiId); |
4984 | return; |
4985 | } |
4986 | const quint32 arch = quint32(sizeof(void*)); |
4987 | if (header.arch != arch) { |
4988 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)" , |
4989 | arch, header.arch); |
4990 | return; |
4991 | } |
4992 | if (header.driverVersion != physDevProperties.driverVersion) { |
4993 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: driverVersion does not match (%u, %u)" , |
4994 | physDevProperties.driverVersion, header.driverVersion); |
4995 | return; |
4996 | } |
4997 | if (header.vendorId != physDevProperties.vendorID) { |
4998 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: vendorID does not match (%u, %u)" , |
4999 | physDevProperties.vendorID, header.vendorId); |
5000 | return; |
5001 | } |
5002 | if (header.deviceId != physDevProperties.deviceID) { |
5003 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: deviceID does not match (%u, %u)" , |
5004 | physDevProperties.deviceID, header.deviceId); |
5005 | return; |
5006 | } |
5007 | if (header.uuidSize != VK_UUID_SIZE) { |
5008 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: VK_UUID_SIZE does not match (%u, %u)" , |
5009 | quint32(VK_UUID_SIZE), header.uuidSize); |
5010 | return; |
5011 | } |
5012 | |
5013 | if (data.size() < qsizetype(headerSize + VK_UUID_SIZE)) { |
5014 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, no uuid" ); |
5015 | return; |
5016 | } |
5017 | if (memcmp(s1: data.constData() + headerSize, s2: physDevProperties.pipelineCacheUUID, VK_UUID_SIZE)) { |
5018 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: pipelineCacheUUID does not match" ); |
5019 | return; |
5020 | } |
5021 | |
5022 | const size_t dataOffset = headerSize + VK_UUID_SIZE; |
5023 | if (data.size() < qsizetype(dataOffset + header.dataSize)) { |
5024 | qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, data missing" ); |
5025 | return; |
5026 | } |
5027 | |
5028 | if (pipelineCache) { |
5029 | df->vkDestroyPipelineCache(dev, pipelineCache, nullptr); |
5030 | pipelineCache = VK_NULL_HANDLE; |
5031 | } |
5032 | |
5033 | if (ensurePipelineCache(initialData: data.constData() + dataOffset, initialDataSize: header.dataSize)) { |
5034 | qCDebug(QRHI_LOG_INFO, "Created pipeline cache with initial data of %d bytes" , |
5035 | int(header.dataSize)); |
5036 | } else { |
5037 | qCDebug(QRHI_LOG_INFO, "Failed to create pipeline cache with initial data specified" ); |
5038 | } |
5039 | } |
5040 | |
5041 | QRhiRenderBuffer *QRhiVulkan::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, |
5042 | int sampleCount, QRhiRenderBuffer::Flags flags, |
5043 | QRhiTexture::Format backingFormatHint) |
5044 | { |
5045 | return new QVkRenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint); |
5046 | } |
5047 | |
5048 | QRhiTexture *QRhiVulkan::createTexture(QRhiTexture::Format format, |
5049 | const QSize &pixelSize, int depth, int arraySize, |
5050 | int sampleCount, QRhiTexture::Flags flags) |
5051 | { |
5052 | return new QVkTexture(this, format, pixelSize, depth, arraySize, sampleCount, flags); |
5053 | } |
5054 | |
5055 | QRhiSampler *QRhiVulkan::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, |
5056 | QRhiSampler::Filter mipmapMode, |
5057 | QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) |
5058 | { |
5059 | return new QVkSampler(this, magFilter, minFilter, mipmapMode, u, v, w); |
5060 | } |
5061 | |
5062 | QRhiTextureRenderTarget *QRhiVulkan::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, |
5063 | QRhiTextureRenderTarget::Flags flags) |
5064 | { |
5065 | return new QVkTextureRenderTarget(this, desc, flags); |
5066 | } |
5067 | |
5068 | QRhiGraphicsPipeline *QRhiVulkan::createGraphicsPipeline() |
5069 | { |
5070 | return new QVkGraphicsPipeline(this); |
5071 | } |
5072 | |
5073 | QRhiComputePipeline *QRhiVulkan::createComputePipeline() |
5074 | { |
5075 | return new QVkComputePipeline(this); |
5076 | } |
5077 | |
5078 | QRhiShaderResourceBindings *QRhiVulkan::createShaderResourceBindings() |
5079 | { |
5080 | return new QVkShaderResourceBindings(this); |
5081 | } |
5082 | |
5083 | void QRhiVulkan::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) |
5084 | { |
5085 | QVkGraphicsPipeline *psD = QRHI_RES(QVkGraphicsPipeline, ps); |
5086 | Q_ASSERT(psD->pipeline); |
5087 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5088 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5089 | |
5090 | if (cbD->currentGraphicsPipeline != ps || cbD->currentPipelineGeneration != psD->generation) { |
5091 | if (cbD->passUsesSecondaryCb) { |
5092 | df->vkCmdBindPipeline(cbD->activeSecondaryCbStack.last(), VK_PIPELINE_BIND_POINT_GRAPHICS, psD->pipeline); |
5093 | } else { |
5094 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5095 | cmd.cmd = QVkCommandBuffer::Command::BindPipeline; |
5096 | cmd.args.bindPipeline.bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
5097 | cmd.args.bindPipeline.pipeline = psD->pipeline; |
5098 | } |
5099 | |
5100 | cbD->currentGraphicsPipeline = ps; |
5101 | cbD->currentComputePipeline = nullptr; |
5102 | cbD->currentPipelineGeneration = psD->generation; |
5103 | } |
5104 | |
5105 | psD->lastActiveFrameSlot = currentFrameSlot; |
5106 | } |
5107 | |
5108 | void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, |
5109 | int dynamicOffsetCount, |
5110 | const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) |
5111 | { |
5112 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5113 | Q_ASSERT(cbD->recordingPass != QVkCommandBuffer::NoPass); |
5114 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
5115 | QVkGraphicsPipeline *gfxPsD = QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline); |
5116 | QVkComputePipeline *compPsD = QRHI_RES(QVkComputePipeline, cbD->currentComputePipeline); |
5117 | |
5118 | if (!srb) { |
5119 | if (gfxPsD) |
5120 | srb = gfxPsD->m_shaderResourceBindings; |
5121 | else |
5122 | srb = compPsD->m_shaderResourceBindings; |
5123 | } |
5124 | |
5125 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb); |
5126 | const int descSetIdx = srbD->hasSlottedResource ? currentFrameSlot : 0; |
5127 | auto &descSetBd(srbD->boundResourceData[descSetIdx]); |
5128 | bool rewriteDescSet = false; |
5129 | |
5130 | // Do host writes and mark referenced shader resources as in-use. |
5131 | // Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects. |
5132 | for (int i = 0, ie = srbD->sortedBindings.size(); i != ie; ++i) { |
5133 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding&: srbD->sortedBindings[i]); |
5134 | QVkShaderResourceBindings::BoundResourceData &bd(descSetBd[i]); |
5135 | switch (b->type) { |
5136 | case QRhiShaderResourceBinding::UniformBuffer: |
5137 | { |
5138 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.ubuf.buf); |
5139 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer)); |
5140 | |
5141 | if (bufD->m_type == QRhiBuffer::Dynamic) |
5142 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
5143 | |
5144 | bufD->lastActiveFrameSlot = currentFrameSlot; |
5145 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot: bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0, |
5146 | access: QRhiPassResourceTracker::BufUniformRead, |
5147 | stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage)); |
5148 | |
5149 | // Check both the "local" id (the generation counter) and the |
5150 | // global id. The latter is relevant when a newly allocated |
5151 | // QRhiResource ends up with the same pointer as a previous one. |
5152 | // (and that previous one could have been in an srb...) |
5153 | if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) { |
5154 | rewriteDescSet = true; |
5155 | bd.ubuf.id = bufD->m_id; |
5156 | bd.ubuf.generation = bufD->generation; |
5157 | } |
5158 | } |
5159 | break; |
5160 | case QRhiShaderResourceBinding::SampledTexture: |
5161 | case QRhiShaderResourceBinding::Texture: |
5162 | case QRhiShaderResourceBinding::Sampler: |
5163 | { |
5164 | const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex; |
5165 | if (bd.stex.count != data->count) { |
5166 | bd.stex.count = data->count; |
5167 | rewriteDescSet = true; |
5168 | } |
5169 | for (int elem = 0; elem < data->count; ++elem) { |
5170 | QVkTexture *texD = QRHI_RES(QVkTexture, data->texSamplers[elem].tex); |
5171 | QVkSampler *samplerD = QRHI_RES(QVkSampler, data->texSamplers[elem].sampler); |
5172 | // We use the same code path for both combined and separate |
5173 | // images and samplers, so tex or sampler (but not both) can be |
5174 | // null here. |
5175 | Q_ASSERT(texD || samplerD); |
5176 | if (texD) { |
5177 | texD->lastActiveFrameSlot = currentFrameSlot; |
5178 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
5179 | access: QRhiPassResourceTracker::TexSample, |
5180 | stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage)); |
5181 | } |
5182 | if (samplerD) |
5183 | samplerD->lastActiveFrameSlot = currentFrameSlot; |
5184 | const quint64 texId = texD ? texD->m_id : 0; |
5185 | const uint texGen = texD ? texD->generation : 0; |
5186 | const quint64 samplerId = samplerD ? samplerD->m_id : 0; |
5187 | const uint samplerGen = samplerD ? samplerD->generation : 0; |
5188 | if (texGen != bd.stex.d[elem].texGeneration |
5189 | || texId != bd.stex.d[elem].texId |
5190 | || samplerGen != bd.stex.d[elem].samplerGeneration |
5191 | || samplerId != bd.stex.d[elem].samplerId) |
5192 | { |
5193 | rewriteDescSet = true; |
5194 | bd.stex.d[elem].texId = texId; |
5195 | bd.stex.d[elem].texGeneration = texGen; |
5196 | bd.stex.d[elem].samplerId = samplerId; |
5197 | bd.stex.d[elem].samplerGeneration = samplerGen; |
5198 | } |
5199 | } |
5200 | } |
5201 | break; |
5202 | case QRhiShaderResourceBinding::ImageLoad: |
5203 | case QRhiShaderResourceBinding::ImageStore: |
5204 | case QRhiShaderResourceBinding::ImageLoadStore: |
5205 | { |
5206 | QVkTexture *texD = QRHI_RES(QVkTexture, b->u.simage.tex); |
5207 | Q_ASSERT(texD->m_flags.testFlag(QRhiTexture::UsedWithLoadStore)); |
5208 | texD->lastActiveFrameSlot = currentFrameSlot; |
5209 | QRhiPassResourceTracker::TextureAccess access; |
5210 | if (b->type == QRhiShaderResourceBinding::ImageLoad) |
5211 | access = QRhiPassResourceTracker::TexStorageLoad; |
5212 | else if (b->type == QRhiShaderResourceBinding::ImageStore) |
5213 | access = QRhiPassResourceTracker::TexStorageStore; |
5214 | else |
5215 | access = QRhiPassResourceTracker::TexStorageLoadStore; |
5216 | trackedRegisterTexture(passResTracker: &passResTracker, texD, |
5217 | access, |
5218 | stage: QRhiPassResourceTracker::toPassTrackerTextureStage(stages: b->stage)); |
5219 | |
5220 | if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) { |
5221 | rewriteDescSet = true; |
5222 | bd.simage.id = texD->m_id; |
5223 | bd.simage.generation = texD->generation; |
5224 | } |
5225 | } |
5226 | break; |
5227 | case QRhiShaderResourceBinding::BufferLoad: |
5228 | case QRhiShaderResourceBinding::BufferStore: |
5229 | case QRhiShaderResourceBinding::BufferLoadStore: |
5230 | { |
5231 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, b->u.sbuf.buf); |
5232 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer)); |
5233 | |
5234 | if (bufD->m_type == QRhiBuffer::Dynamic) |
5235 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
5236 | |
5237 | bufD->lastActiveFrameSlot = currentFrameSlot; |
5238 | QRhiPassResourceTracker::BufferAccess access; |
5239 | if (b->type == QRhiShaderResourceBinding::BufferLoad) |
5240 | access = QRhiPassResourceTracker::BufStorageLoad; |
5241 | else if (b->type == QRhiShaderResourceBinding::BufferStore) |
5242 | access = QRhiPassResourceTracker::BufStorageStore; |
5243 | else |
5244 | access = QRhiPassResourceTracker::BufStorageLoadStore; |
5245 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot: bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0, |
5246 | access, |
5247 | stage: QRhiPassResourceTracker::toPassTrackerBufferStage(stages: b->stage)); |
5248 | |
5249 | if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) { |
5250 | rewriteDescSet = true; |
5251 | bd.sbuf.id = bufD->m_id; |
5252 | bd.sbuf.generation = bufD->generation; |
5253 | } |
5254 | } |
5255 | break; |
5256 | default: |
5257 | Q_UNREACHABLE(); |
5258 | break; |
5259 | } |
5260 | } |
5261 | |
5262 | // write descriptor sets, if needed |
5263 | if (rewriteDescSet) |
5264 | updateShaderResourceBindings(srb, descSetIdx); |
5265 | |
5266 | // make sure the descriptors for the correct slot will get bound. |
5267 | // also, dynamic offsets always need a bind. |
5268 | const bool forceRebind = (srbD->hasSlottedResource && cbD->currentDescSetSlot != descSetIdx) || srbD->hasDynamicOffset; |
5269 | |
5270 | const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb); |
5271 | |
5272 | if (forceRebind || rewriteDescSet || srbChanged || cbD->currentSrbGeneration != srbD->generation) { |
5273 | QVarLengthArray<uint32_t, 4> dynOfs; |
5274 | if (srbD->hasDynamicOffset) { |
5275 | // Filling out dynOfs based on the sorted bindings is important |
5276 | // because dynOfs has to be ordered based on the binding numbers, |
5277 | // and neither srb nor dynamicOffsets has any such ordering |
5278 | // requirement. |
5279 | for (const QRhiShaderResourceBinding &binding : std::as_const(t&: srbD->sortedBindings)) { |
5280 | const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(binding); |
5281 | if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.hasDynamicOffset) { |
5282 | uint32_t offset = 0; |
5283 | for (int i = 0; i < dynamicOffsetCount; ++i) { |
5284 | const QRhiCommandBuffer::DynamicOffset &bindingOffsetPair(dynamicOffsets[i]); |
5285 | if (bindingOffsetPair.first == b->binding) { |
5286 | offset = bindingOffsetPair.second; |
5287 | break; |
5288 | } |
5289 | } |
5290 | dynOfs.append(t: offset); // use 0 if dynamicOffsets did not contain this binding |
5291 | } |
5292 | } |
5293 | } |
5294 | |
5295 | if (cbD->passUsesSecondaryCb) { |
5296 | df->vkCmdBindDescriptorSets(cbD->activeSecondaryCbStack.last(), |
5297 | gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE, |
5298 | gfxPsD ? gfxPsD->layout : compPsD->layout, |
5299 | 0, 1, &srbD->descSets[descSetIdx], |
5300 | uint32_t(dynOfs.size()), |
5301 | dynOfs.size() ? dynOfs.constData() : nullptr); |
5302 | } else { |
5303 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5304 | cmd.cmd = QVkCommandBuffer::Command::BindDescriptorSet; |
5305 | cmd.args.bindDescriptorSet.bindPoint = gfxPsD ? VK_PIPELINE_BIND_POINT_GRAPHICS |
5306 | : VK_PIPELINE_BIND_POINT_COMPUTE; |
5307 | cmd.args.bindDescriptorSet.pipelineLayout = gfxPsD ? gfxPsD->layout : compPsD->layout; |
5308 | cmd.args.bindDescriptorSet.descSet = srbD->descSets[descSetIdx]; |
5309 | cmd.args.bindDescriptorSet.dynamicOffsetCount = dynOfs.size(); |
5310 | cmd.args.bindDescriptorSet.dynamicOffsetIndex = cbD->pools.dynamicOffset.size(); |
5311 | cbD->pools.dynamicOffset.append(buf: dynOfs.constData(), sz: dynOfs.size()); |
5312 | } |
5313 | |
5314 | if (gfxPsD) { |
5315 | cbD->currentGraphicsSrb = srb; |
5316 | cbD->currentComputeSrb = nullptr; |
5317 | } else { |
5318 | cbD->currentGraphicsSrb = nullptr; |
5319 | cbD->currentComputeSrb = srb; |
5320 | } |
5321 | cbD->currentSrbGeneration = srbD->generation; |
5322 | cbD->currentDescSetSlot = descSetIdx; |
5323 | } |
5324 | |
5325 | srbD->lastActiveFrameSlot = currentFrameSlot; |
5326 | } |
5327 | |
5328 | void QRhiVulkan::setVertexInput(QRhiCommandBuffer *cb, |
5329 | int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, |
5330 | QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) |
5331 | { |
5332 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5333 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5334 | QRhiPassResourceTracker &passResTracker(cbD->passResTrackers[cbD->currentPassResTrackerIndex]); |
5335 | |
5336 | bool needsBindVBuf = false; |
5337 | for (int i = 0; i < bindingCount; ++i) { |
5338 | const int inputSlot = startBinding + i; |
5339 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first); |
5340 | Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer)); |
5341 | bufD->lastActiveFrameSlot = currentFrameSlot; |
5342 | if (bufD->m_type == QRhiBuffer::Dynamic) |
5343 | executeBufferHostWritesForSlot(bufD, slot: currentFrameSlot); |
5344 | |
5345 | const VkBuffer vkvertexbuf = bufD->buffers[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0]; |
5346 | if (cbD->currentVertexBuffers[inputSlot] != vkvertexbuf |
5347 | || cbD->currentVertexOffsets[inputSlot] != bindings[i].second) |
5348 | { |
5349 | needsBindVBuf = true; |
5350 | cbD->currentVertexBuffers[inputSlot] = vkvertexbuf; |
5351 | cbD->currentVertexOffsets[inputSlot] = bindings[i].second; |
5352 | } |
5353 | } |
5354 | |
5355 | if (needsBindVBuf) { |
5356 | QVarLengthArray<VkBuffer, 4> bufs; |
5357 | QVarLengthArray<VkDeviceSize, 4> ofs; |
5358 | for (int i = 0; i < bindingCount; ++i) { |
5359 | QVkBuffer *bufD = QRHI_RES(QVkBuffer, bindings[i].first); |
5360 | const int slot = bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0; |
5361 | bufs.append(t: bufD->buffers[slot]); |
5362 | ofs.append(t: bindings[i].second); |
5363 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD, slot, |
5364 | access: QRhiPassResourceTracker::BufVertexInput, |
5365 | stage: QRhiPassResourceTracker::BufVertexInputStage); |
5366 | } |
5367 | |
5368 | if (cbD->passUsesSecondaryCb) { |
5369 | df->vkCmdBindVertexBuffers(cbD->activeSecondaryCbStack.last(), uint32_t(startBinding), |
5370 | uint32_t(bufs.size()), bufs.constData(), ofs.constData()); |
5371 | } else { |
5372 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5373 | cmd.cmd = QVkCommandBuffer::Command::BindVertexBuffer; |
5374 | cmd.args.bindVertexBuffer.startBinding = startBinding; |
5375 | cmd.args.bindVertexBuffer.count = bufs.size(); |
5376 | cmd.args.bindVertexBuffer.vertexBufferIndex = cbD->pools.vertexBuffer.size(); |
5377 | cbD->pools.vertexBuffer.append(buf: bufs.constData(), sz: bufs.size()); |
5378 | cmd.args.bindVertexBuffer.vertexBufferOffsetIndex = cbD->pools.vertexBufferOffset.size(); |
5379 | cbD->pools.vertexBufferOffset.append(buf: ofs.constData(), sz: ofs.size()); |
5380 | } |
5381 | } |
5382 | |
5383 | if (indexBuf) { |
5384 | QVkBuffer *ibufD = QRHI_RES(QVkBuffer, indexBuf); |
5385 | Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer)); |
5386 | ibufD->lastActiveFrameSlot = currentFrameSlot; |
5387 | if (ibufD->m_type == QRhiBuffer::Dynamic) |
5388 | executeBufferHostWritesForSlot(bufD: ibufD, slot: currentFrameSlot); |
5389 | |
5390 | const int slot = ibufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0; |
5391 | const VkBuffer vkindexbuf = ibufD->buffers[slot]; |
5392 | const VkIndexType type = indexFormat == QRhiCommandBuffer::IndexUInt16 ? VK_INDEX_TYPE_UINT16 |
5393 | : VK_INDEX_TYPE_UINT32; |
5394 | |
5395 | if (cbD->currentIndexBuffer != vkindexbuf |
5396 | || cbD->currentIndexOffset != indexOffset |
5397 | || cbD->currentIndexFormat != type) |
5398 | { |
5399 | cbD->currentIndexBuffer = vkindexbuf; |
5400 | cbD->currentIndexOffset = indexOffset; |
5401 | cbD->currentIndexFormat = type; |
5402 | |
5403 | if (cbD->passUsesSecondaryCb) { |
5404 | df->vkCmdBindIndexBuffer(cbD->activeSecondaryCbStack.last(), vkindexbuf, indexOffset, type); |
5405 | } else { |
5406 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5407 | cmd.cmd = QVkCommandBuffer::Command::BindIndexBuffer; |
5408 | cmd.args.bindIndexBuffer.buf = vkindexbuf; |
5409 | cmd.args.bindIndexBuffer.ofs = indexOffset; |
5410 | cmd.args.bindIndexBuffer.type = type; |
5411 | } |
5412 | |
5413 | trackedRegisterBuffer(passResTracker: &passResTracker, bufD: ibufD, slot, |
5414 | access: QRhiPassResourceTracker::BufIndexRead, |
5415 | stage: QRhiPassResourceTracker::BufVertexInputStage); |
5416 | } |
5417 | } |
5418 | } |
5419 | |
5420 | void QRhiVulkan::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) |
5421 | { |
5422 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5423 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5424 | const QSize outputSize = cbD->currentTarget->pixelSize(); |
5425 | |
5426 | // x,y is top-left in VkViewport but bottom-left in QRhiViewport |
5427 | float x, y, w, h; |
5428 | if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, r: viewport.viewport(), x: &x, y: &y, w: &w, h: &h)) |
5429 | return; |
5430 | |
5431 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5432 | VkViewport *vp = &cmd.args.setViewport.viewport; |
5433 | vp->x = x; |
5434 | vp->y = y; |
5435 | vp->width = w; |
5436 | vp->height = h; |
5437 | vp->minDepth = viewport.minDepth(); |
5438 | vp->maxDepth = viewport.maxDepth(); |
5439 | |
5440 | if (cbD->passUsesSecondaryCb) { |
5441 | df->vkCmdSetViewport(cbD->activeSecondaryCbStack.last(), 0, 1, vp); |
5442 | cbD->commands.unget(); |
5443 | } else { |
5444 | cmd.cmd = QVkCommandBuffer::Command::SetViewport; |
5445 | } |
5446 | |
5447 | if (cbD->currentGraphicsPipeline |
5448 | && !QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline) |
5449 | ->m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesScissor)) { |
5450 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5451 | VkRect2D *s = &cmd.args.setScissor.scissor; |
5452 | qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, r: viewport.viewport(), x: &x, y: &y, w: &w, h: &h); |
5453 | s->offset.x = int32_t(x); |
5454 | s->offset.y = int32_t(y); |
5455 | s->extent.width = uint32_t(w); |
5456 | s->extent.height = uint32_t(h); |
5457 | if (cbD->passUsesSecondaryCb) { |
5458 | df->vkCmdSetScissor(cbD->activeSecondaryCbStack.last(), 0, 1, s); |
5459 | cbD->commands.unget(); |
5460 | } else { |
5461 | cmd.cmd = QVkCommandBuffer::Command::SetScissor; |
5462 | } |
5463 | } |
5464 | } |
5465 | |
5466 | void QRhiVulkan::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) |
5467 | { |
5468 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5469 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5470 | Q_ASSERT(QRHI_RES(QVkGraphicsPipeline, cbD->currentGraphicsPipeline)->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor)); |
5471 | const QSize outputSize = cbD->currentTarget->pixelSize(); |
5472 | |
5473 | // x,y is top-left in VkRect2D but bottom-left in QRhiScissor |
5474 | int x, y, w, h; |
5475 | if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, r: scissor.scissor(), x: &x, y: &y, w: &w, h: &h)) |
5476 | return; |
5477 | |
5478 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5479 | VkRect2D *s = &cmd.args.setScissor.scissor; |
5480 | s->offset.x = x; |
5481 | s->offset.y = y; |
5482 | s->extent.width = uint32_t(w); |
5483 | s->extent.height = uint32_t(h); |
5484 | |
5485 | if (cbD->passUsesSecondaryCb) { |
5486 | df->vkCmdSetScissor(cbD->activeSecondaryCbStack.last(), 0, 1, s); |
5487 | cbD->commands.unget(); |
5488 | } else { |
5489 | cmd.cmd = QVkCommandBuffer::Command::SetScissor; |
5490 | } |
5491 | } |
5492 | |
5493 | void QRhiVulkan::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) |
5494 | { |
5495 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5496 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5497 | |
5498 | if (cbD->passUsesSecondaryCb) { |
5499 | float constants[] = { float(c.redF()), float(c.greenF()), float(c.blueF()), float(c.alphaF()) }; |
5500 | df->vkCmdSetBlendConstants(cbD->activeSecondaryCbStack.last(), constants); |
5501 | } else { |
5502 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5503 | cmd.cmd = QVkCommandBuffer::Command::SetBlendConstants; |
5504 | cmd.args.setBlendConstants.c[0] = float(c.redF()); |
5505 | cmd.args.setBlendConstants.c[1] = float(c.greenF()); |
5506 | cmd.args.setBlendConstants.c[2] = float(c.blueF()); |
5507 | cmd.args.setBlendConstants.c[3] = float(c.alphaF()); |
5508 | } |
5509 | } |
5510 | |
5511 | void QRhiVulkan::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) |
5512 | { |
5513 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5514 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5515 | |
5516 | if (cbD->passUsesSecondaryCb) { |
5517 | df->vkCmdSetStencilReference(cbD->activeSecondaryCbStack.last(), VK_STENCIL_FRONT_AND_BACK, refValue); |
5518 | } else { |
5519 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5520 | cmd.cmd = QVkCommandBuffer::Command::SetStencilRef; |
5521 | cmd.args.setStencilRef.ref = refValue; |
5522 | } |
5523 | } |
5524 | |
5525 | void QRhiVulkan::draw(QRhiCommandBuffer *cb, quint32 vertexCount, |
5526 | quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) |
5527 | { |
5528 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5529 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5530 | |
5531 | if (cbD->passUsesSecondaryCb) { |
5532 | df->vkCmdDraw(cbD->activeSecondaryCbStack.last(), vertexCount, instanceCount, firstVertex, firstInstance); |
5533 | } else { |
5534 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5535 | cmd.cmd = QVkCommandBuffer::Command::Draw; |
5536 | cmd.args.draw.vertexCount = vertexCount; |
5537 | cmd.args.draw.instanceCount = instanceCount; |
5538 | cmd.args.draw.firstVertex = firstVertex; |
5539 | cmd.args.draw.firstInstance = firstInstance; |
5540 | } |
5541 | } |
5542 | |
5543 | void QRhiVulkan::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, |
5544 | quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) |
5545 | { |
5546 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5547 | Q_ASSERT(cbD->recordingPass == QVkCommandBuffer::RenderPass); |
5548 | |
5549 | if (cbD->passUsesSecondaryCb) { |
5550 | df->vkCmdDrawIndexed(cbD->activeSecondaryCbStack.last(), indexCount, instanceCount, |
5551 | firstIndex, vertexOffset, firstInstance); |
5552 | } else { |
5553 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5554 | cmd.cmd = QVkCommandBuffer::Command::DrawIndexed; |
5555 | cmd.args.drawIndexed.indexCount = indexCount; |
5556 | cmd.args.drawIndexed.instanceCount = instanceCount; |
5557 | cmd.args.drawIndexed.firstIndex = firstIndex; |
5558 | cmd.args.drawIndexed.vertexOffset = vertexOffset; |
5559 | cmd.args.drawIndexed.firstInstance = firstInstance; |
5560 | } |
5561 | } |
5562 | |
5563 | void QRhiVulkan::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) |
5564 | { |
5565 | #ifdef VK_EXT_debug_utils |
5566 | if (!debugMarkers || !caps.debugUtils) |
5567 | return; |
5568 | |
5569 | VkDebugUtilsLabelEXT label = {}; |
5570 | label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; |
5571 | |
5572 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5573 | if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) { |
5574 | label.pLabelName = name.constData(); |
5575 | vkCmdBeginDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last(), &label); |
5576 | } else { |
5577 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5578 | cmd.cmd = QVkCommandBuffer::Command::DebugMarkerBegin; |
5579 | cmd.args.debugMarkerBegin.label = label; |
5580 | cmd.args.debugMarkerBegin.labelNameIndex = cbD->pools.debugMarkerData.size(); |
5581 | cbD->pools.debugMarkerData.append(t: name); |
5582 | } |
5583 | #else |
5584 | Q_UNUSED(cb); |
5585 | Q_UNUSED(name); |
5586 | #endif |
5587 | } |
5588 | |
5589 | void QRhiVulkan::debugMarkEnd(QRhiCommandBuffer *cb) |
5590 | { |
5591 | #ifdef VK_EXT_debug_utils |
5592 | if (!debugMarkers || !caps.debugUtils) |
5593 | return; |
5594 | |
5595 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5596 | if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) { |
5597 | vkCmdEndDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last()); |
5598 | } else { |
5599 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5600 | cmd.cmd = QVkCommandBuffer::Command::DebugMarkerEnd; |
5601 | } |
5602 | #else |
5603 | Q_UNUSED(cb); |
5604 | #endif |
5605 | } |
5606 | |
5607 | void QRhiVulkan::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) |
5608 | { |
5609 | #ifdef VK_EXT_debug_utils |
5610 | if (!debugMarkers || !caps.debugUtils) |
5611 | return; |
5612 | |
5613 | VkDebugUtilsLabelEXT label = {}; |
5614 | label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; |
5615 | |
5616 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5617 | if (cbD->recordingPass != QVkCommandBuffer::NoPass && cbD->passUsesSecondaryCb) { |
5618 | label.pLabelName = msg.constData(); |
5619 | vkCmdInsertDebugUtilsLabelEXT(cbD->activeSecondaryCbStack.last(), &label); |
5620 | } else { |
5621 | QVkCommandBuffer::Command &cmd(cbD->commands.get()); |
5622 | cmd.cmd = QVkCommandBuffer::Command::DebugMarkerInsert; |
5623 | cmd.args.debugMarkerInsert.label = label; |
5624 | cmd.args.debugMarkerInsert.labelNameIndex = cbD->pools.debugMarkerData.size(); |
5625 | cbD->pools.debugMarkerData.append(t: msg); |
5626 | } |
5627 | #else |
5628 | Q_UNUSED(cb); |
5629 | Q_UNUSED(msg); |
5630 | #endif |
5631 | } |
5632 | |
5633 | const QRhiNativeHandles *QRhiVulkan::nativeHandles(QRhiCommandBuffer *cb) |
5634 | { |
5635 | return QRHI_RES(QVkCommandBuffer, cb)->nativeHandles(); |
5636 | } |
5637 | |
5638 | static inline QVkRenderTargetData *maybeRenderTargetData(QVkCommandBuffer *cbD) |
5639 | { |
5640 | Q_ASSERT(cbD->currentTarget); |
5641 | QVkRenderTargetData *rtD = nullptr; |
5642 | if (cbD->recordingPass == QVkCommandBuffer::RenderPass) { |
5643 | switch (cbD->currentTarget->resourceType()) { |
5644 | case QRhiResource::SwapChainRenderTarget: |
5645 | rtD = &QRHI_RES(QVkSwapChainRenderTarget, cbD->currentTarget)->d; |
5646 | break; |
5647 | case QRhiResource::TextureRenderTarget: |
5648 | rtD = &QRHI_RES(QVkTextureRenderTarget, cbD->currentTarget)->d; |
5649 | break; |
5650 | default: |
5651 | Q_UNREACHABLE(); |
5652 | break; |
5653 | } |
5654 | } |
5655 | return rtD; |
5656 | } |
5657 | |
5658 | void QRhiVulkan::beginExternal(QRhiCommandBuffer *cb) |
5659 | { |
5660 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5661 | |
5662 | // When not in a pass, it is simple: record what we have (but do not |
5663 | // submit), the cb can then be used to record more external commands. |
5664 | if (cbD->recordingPass == QVkCommandBuffer::NoPass) { |
5665 | recordPrimaryCommandBuffer(cbD); |
5666 | cbD->resetCommands(); |
5667 | return; |
5668 | } |
5669 | |
5670 | // Otherwise, inside a pass, have a secondary command buffer (with |
5671 | // RENDER_PASS_CONTINUE). Using the main one is not acceptable since we |
5672 | // cannot just record at this stage, that would mess up the resource |
5673 | // tracking and commands like TransitionPassResources. |
5674 | |
5675 | if (cbD->inExternal) |
5676 | return; |
5677 | |
5678 | if (!cbD->passUsesSecondaryCb) { |
5679 | qWarning(msg: "beginExternal() within a pass is only supported with secondary command buffers. " |
5680 | "This can be enabled by passing QRhiCommandBuffer::ExternalContent to beginPass()." ); |
5681 | return; |
5682 | } |
5683 | |
5684 | VkCommandBuffer secondaryCb = cbD->activeSecondaryCbStack.last(); |
5685 | cbD->activeSecondaryCbStack.removeLast(); |
5686 | endAndEnqueueSecondaryCommandBuffer(cb: secondaryCb, cbD); |
5687 | |
5688 | VkCommandBuffer extCb = startSecondaryCommandBuffer(rtD: maybeRenderTargetData(cbD)); |
5689 | if (extCb) { |
5690 | cbD->activeSecondaryCbStack.append(t: extCb); |
5691 | cbD->inExternal = true; |
5692 | } |
5693 | } |
5694 | |
5695 | void QRhiVulkan::endExternal(QRhiCommandBuffer *cb) |
5696 | { |
5697 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5698 | |
5699 | if (cbD->recordingPass == QVkCommandBuffer::NoPass) { |
5700 | Q_ASSERT(cbD->commands.isEmpty() && cbD->currentPassResTrackerIndex == -1); |
5701 | } else if (cbD->inExternal) { |
5702 | VkCommandBuffer extCb = cbD->activeSecondaryCbStack.last(); |
5703 | cbD->activeSecondaryCbStack.removeLast(); |
5704 | endAndEnqueueSecondaryCommandBuffer(cb: extCb, cbD); |
5705 | cbD->activeSecondaryCbStack.append(t: startSecondaryCommandBuffer(rtD: maybeRenderTargetData(cbD))); |
5706 | } |
5707 | |
5708 | cbD->resetCachedState(); |
5709 | } |
5710 | |
5711 | double QRhiVulkan::lastCompletedGpuTime(QRhiCommandBuffer *cb) |
5712 | { |
5713 | QVkCommandBuffer *cbD = QRHI_RES(QVkCommandBuffer, cb); |
5714 | return cbD->lastGpuTime; |
5715 | } |
5716 | |
5717 | void QRhiVulkan::setObjectName(uint64_t object, VkObjectType type, const QByteArray &name, int slot) |
5718 | { |
5719 | #ifdef VK_EXT_debug_utils |
5720 | if (!debugMarkers || !caps.debugUtils || name.isEmpty()) |
5721 | return; |
5722 | |
5723 | VkDebugUtilsObjectNameInfoEXT nameInfo = {}; |
5724 | nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; |
5725 | nameInfo.objectType = type; |
5726 | nameInfo.objectHandle = object; |
5727 | QByteArray decoratedName = name; |
5728 | if (slot >= 0) { |
5729 | decoratedName += '/'; |
5730 | decoratedName += QByteArray::number(slot); |
5731 | } |
5732 | nameInfo.pObjectName = decoratedName.constData(); |
5733 | vkSetDebugUtilsObjectNameEXT(dev, &nameInfo); |
5734 | #else |
5735 | Q_UNUSED(object); |
5736 | Q_UNUSED(type); |
5737 | Q_UNUSED(name); |
5738 | Q_UNUSED(slot); |
5739 | #endif |
5740 | } |
5741 | |
5742 | static inline VkBufferUsageFlagBits toVkBufferUsage(QRhiBuffer::UsageFlags usage) |
5743 | { |
5744 | int u = 0; |
5745 | if (usage.testFlag(flag: QRhiBuffer::VertexBuffer)) |
5746 | u |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; |
5747 | if (usage.testFlag(flag: QRhiBuffer::IndexBuffer)) |
5748 | u |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; |
5749 | if (usage.testFlag(flag: QRhiBuffer::UniformBuffer)) |
5750 | u |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; |
5751 | if (usage.testFlag(flag: QRhiBuffer::StorageBuffer)) |
5752 | u |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; |
5753 | return VkBufferUsageFlagBits(u); |
5754 | } |
5755 | |
5756 | static inline VkFilter toVkFilter(QRhiSampler::Filter f) |
5757 | { |
5758 | switch (f) { |
5759 | case QRhiSampler::Nearest: |
5760 | return VK_FILTER_NEAREST; |
5761 | case QRhiSampler::Linear: |
5762 | return VK_FILTER_LINEAR; |
5763 | default: |
5764 | Q_UNREACHABLE_RETURN(VK_FILTER_NEAREST); |
5765 | } |
5766 | } |
5767 | |
5768 | static inline VkSamplerMipmapMode toVkMipmapMode(QRhiSampler::Filter f) |
5769 | { |
5770 | switch (f) { |
5771 | case QRhiSampler::None: |
5772 | return VK_SAMPLER_MIPMAP_MODE_NEAREST; |
5773 | case QRhiSampler::Nearest: |
5774 | return VK_SAMPLER_MIPMAP_MODE_NEAREST; |
5775 | case QRhiSampler::Linear: |
5776 | return VK_SAMPLER_MIPMAP_MODE_LINEAR; |
5777 | default: |
5778 | Q_UNREACHABLE_RETURN(VK_SAMPLER_MIPMAP_MODE_NEAREST); |
5779 | } |
5780 | } |
5781 | |
5782 | static inline VkSamplerAddressMode toVkAddressMode(QRhiSampler::AddressMode m) |
5783 | { |
5784 | switch (m) { |
5785 | case QRhiSampler::Repeat: |
5786 | return VK_SAMPLER_ADDRESS_MODE_REPEAT; |
5787 | case QRhiSampler::ClampToEdge: |
5788 | return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; |
5789 | case QRhiSampler::Mirror: |
5790 | return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; |
5791 | default: |
5792 | Q_UNREACHABLE_RETURN(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); |
5793 | } |
5794 | } |
5795 | |
5796 | static inline VkShaderStageFlagBits toVkShaderStage(QRhiShaderStage::Type type) |
5797 | { |
5798 | switch (type) { |
5799 | case QRhiShaderStage::Vertex: |
5800 | return VK_SHADER_STAGE_VERTEX_BIT; |
5801 | case QRhiShaderStage::TessellationControl: |
5802 | return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; |
5803 | case QRhiShaderStage::TessellationEvaluation: |
5804 | return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; |
5805 | case QRhiShaderStage::Fragment: |
5806 | return VK_SHADER_STAGE_FRAGMENT_BIT; |
5807 | case QRhiShaderStage::Compute: |
5808 | return VK_SHADER_STAGE_COMPUTE_BIT; |
5809 | case QRhiShaderStage::Geometry: |
5810 | return VK_SHADER_STAGE_GEOMETRY_BIT; |
5811 | default: |
5812 | Q_UNREACHABLE_RETURN(VK_SHADER_STAGE_VERTEX_BIT); |
5813 | } |
5814 | } |
5815 | |
5816 | static inline VkFormat toVkAttributeFormat(QRhiVertexInputAttribute::Format format) |
5817 | { |
5818 | switch (format) { |
5819 | case QRhiVertexInputAttribute::Float4: |
5820 | return VK_FORMAT_R32G32B32A32_SFLOAT; |
5821 | case QRhiVertexInputAttribute::Float3: |
5822 | return VK_FORMAT_R32G32B32_SFLOAT; |
5823 | case QRhiVertexInputAttribute::Float2: |
5824 | return VK_FORMAT_R32G32_SFLOAT; |
5825 | case QRhiVertexInputAttribute::Float: |
5826 | return VK_FORMAT_R32_SFLOAT; |
5827 | case QRhiVertexInputAttribute::UNormByte4: |
5828 | return VK_FORMAT_R8G8B8A8_UNORM; |
5829 | case QRhiVertexInputAttribute::UNormByte2: |
5830 | return VK_FORMAT_R8G8_UNORM; |
5831 | case QRhiVertexInputAttribute::UNormByte: |
5832 | return VK_FORMAT_R8_UNORM; |
5833 | case QRhiVertexInputAttribute::UInt4: |
5834 | return VK_FORMAT_R32G32B32A32_UINT; |
5835 | case QRhiVertexInputAttribute::UInt3: |
5836 | return VK_FORMAT_R32G32B32_UINT; |
5837 | case QRhiVertexInputAttribute::UInt2: |
5838 | return VK_FORMAT_R32G32_UINT; |
5839 | case QRhiVertexInputAttribute::UInt: |
5840 | return VK_FORMAT_R32_UINT; |
5841 | case QRhiVertexInputAttribute::SInt4: |
5842 | return VK_FORMAT_R32G32B32A32_SINT; |
5843 | case QRhiVertexInputAttribute::SInt3: |
5844 | return VK_FORMAT_R32G32B32_SINT; |
5845 | case QRhiVertexInputAttribute::SInt2: |
5846 | return VK_FORMAT_R32G32_SINT; |
5847 | case QRhiVertexInputAttribute::SInt: |
5848 | return VK_FORMAT_R32_SINT; |
5849 | case QRhiVertexInputAttribute::Half4: |
5850 | return VK_FORMAT_R16G16B16A16_SFLOAT; |
5851 | case QRhiVertexInputAttribute::Half3: |
5852 | return VK_FORMAT_R16G16B16_SFLOAT; |
5853 | case QRhiVertexInputAttribute::Half2: |
5854 | return VK_FORMAT_R16G16_SFLOAT; |
5855 | case QRhiVertexInputAttribute::Half: |
5856 | return VK_FORMAT_R16_SFLOAT; |
5857 | case QRhiVertexInputAttribute::UShort4: |
5858 | return VK_FORMAT_R16G16B16A16_UINT; |
5859 | case QRhiVertexInputAttribute::UShort3: |
5860 | return VK_FORMAT_R16G16B16_UINT; |
5861 | case QRhiVertexInputAttribute::UShort2: |
5862 | return VK_FORMAT_R16G16_UINT; |
5863 | case QRhiVertexInputAttribute::UShort: |
5864 | return VK_FORMAT_R16_UINT; |
5865 | case QRhiVertexInputAttribute::SShort4: |
5866 | return VK_FORMAT_R16G16B16A16_SINT; |
5867 | case QRhiVertexInputAttribute::SShort3: |
5868 | return VK_FORMAT_R16G16B16_SINT; |
5869 | case QRhiVertexInputAttribute::SShort2: |
5870 | return VK_FORMAT_R16G16_SINT; |
5871 | case QRhiVertexInputAttribute::SShort: |
5872 | return VK_FORMAT_R16_SINT; |
5873 | default: |
5874 | Q_UNREACHABLE_RETURN(VK_FORMAT_R32G32B32A32_SFLOAT); |
5875 | } |
5876 | } |
5877 | |
5878 | static inline VkPrimitiveTopology toVkTopology(QRhiGraphicsPipeline::Topology t) |
5879 | { |
5880 | switch (t) { |
5881 | case QRhiGraphicsPipeline::Triangles: |
5882 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; |
5883 | case QRhiGraphicsPipeline::TriangleStrip: |
5884 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; |
5885 | case QRhiGraphicsPipeline::TriangleFan: |
5886 | return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; |
5887 | case QRhiGraphicsPipeline::Lines: |
5888 | return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; |
5889 | case QRhiGraphicsPipeline::LineStrip: |
5890 | return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; |
5891 | case QRhiGraphicsPipeline::Points: |
5892 | return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; |
5893 | case QRhiGraphicsPipeline::Patches: |
5894 | return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST; |
5895 | default: |
5896 | Q_UNREACHABLE_RETURN(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); |
5897 | } |
5898 | } |
5899 | |
5900 | static inline VkCullModeFlags toVkCullMode(QRhiGraphicsPipeline::CullMode c) |
5901 | { |
5902 | switch (c) { |
5903 | case QRhiGraphicsPipeline::None: |
5904 | return VK_CULL_MODE_NONE; |
5905 | case QRhiGraphicsPipeline::Front: |
5906 | return VK_CULL_MODE_FRONT_BIT; |
5907 | case QRhiGraphicsPipeline::Back: |
5908 | return VK_CULL_MODE_BACK_BIT; |
5909 | default: |
5910 | Q_UNREACHABLE_RETURN(VK_CULL_MODE_NONE); |
5911 | } |
5912 | } |
5913 | |
5914 | static inline VkFrontFace toVkFrontFace(QRhiGraphicsPipeline::FrontFace f) |
5915 | { |
5916 | switch (f) { |
5917 | case QRhiGraphicsPipeline::CCW: |
5918 | return VK_FRONT_FACE_COUNTER_CLOCKWISE; |
5919 | case QRhiGraphicsPipeline::CW: |
5920 | return VK_FRONT_FACE_CLOCKWISE; |
5921 | default: |
5922 | Q_UNREACHABLE_RETURN(VK_FRONT_FACE_COUNTER_CLOCKWISE); |
5923 | } |
5924 | } |
5925 | |
5926 | static inline VkColorComponentFlags toVkColorComponents(QRhiGraphicsPipeline::ColorMask c) |
5927 | { |
5928 | int f = 0; |
5929 | if (c.testFlag(flag: QRhiGraphicsPipeline::R)) |
5930 | f |= VK_COLOR_COMPONENT_R_BIT; |
5931 | if (c.testFlag(flag: QRhiGraphicsPipeline::G)) |
5932 | f |= VK_COLOR_COMPONENT_G_BIT; |
5933 | if (c.testFlag(flag: QRhiGraphicsPipeline::B)) |
5934 | f |= VK_COLOR_COMPONENT_B_BIT; |
5935 | if (c.testFlag(flag: QRhiGraphicsPipeline::A)) |
5936 | f |= VK_COLOR_COMPONENT_A_BIT; |
5937 | return VkColorComponentFlags(f); |
5938 | } |
5939 | |
5940 | static inline VkBlendFactor toVkBlendFactor(QRhiGraphicsPipeline::BlendFactor f) |
5941 | { |
5942 | switch (f) { |
5943 | case QRhiGraphicsPipeline::Zero: |
5944 | return VK_BLEND_FACTOR_ZERO; |
5945 | case QRhiGraphicsPipeline::One: |
5946 | return VK_BLEND_FACTOR_ONE; |
5947 | case QRhiGraphicsPipeline::SrcColor: |
5948 | return VK_BLEND_FACTOR_SRC_COLOR; |
5949 | case QRhiGraphicsPipeline::OneMinusSrcColor: |
5950 | return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; |
5951 | case QRhiGraphicsPipeline::DstColor: |
5952 | return VK_BLEND_FACTOR_DST_COLOR; |
5953 | case QRhiGraphicsPipeline::OneMinusDstColor: |
5954 | return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; |
5955 | case QRhiGraphicsPipeline::SrcAlpha: |
5956 | return VK_BLEND_FACTOR_SRC_ALPHA; |
5957 | case QRhiGraphicsPipeline::OneMinusSrcAlpha: |
5958 | return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; |
5959 | case QRhiGraphicsPipeline::DstAlpha: |
5960 | return VK_BLEND_FACTOR_DST_ALPHA; |
5961 | case QRhiGraphicsPipeline::OneMinusDstAlpha: |
5962 | return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; |
5963 | case QRhiGraphicsPipeline::ConstantColor: |
5964 | return VK_BLEND_FACTOR_CONSTANT_COLOR; |
5965 | case QRhiGraphicsPipeline::OneMinusConstantColor: |
5966 | return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; |
5967 | case QRhiGraphicsPipeline::ConstantAlpha: |
5968 | return VK_BLEND_FACTOR_CONSTANT_ALPHA; |
5969 | case QRhiGraphicsPipeline::OneMinusConstantAlpha: |
5970 | return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; |
5971 | case QRhiGraphicsPipeline::SrcAlphaSaturate: |
5972 | return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; |
5973 | case QRhiGraphicsPipeline::Src1Color: |
5974 | return VK_BLEND_FACTOR_SRC1_COLOR; |
5975 | case QRhiGraphicsPipeline::OneMinusSrc1Color: |
5976 | return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; |
5977 | case QRhiGraphicsPipeline::Src1Alpha: |
5978 | return VK_BLEND_FACTOR_SRC1_ALPHA; |
5979 | case QRhiGraphicsPipeline::OneMinusSrc1Alpha: |
5980 | return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; |
5981 | default: |
5982 | Q_UNREACHABLE_RETURN(VK_BLEND_FACTOR_ZERO); |
5983 | } |
5984 | } |
5985 | |
5986 | static inline VkBlendOp toVkBlendOp(QRhiGraphicsPipeline::BlendOp op) |
5987 | { |
5988 | switch (op) { |
5989 | case QRhiGraphicsPipeline::Add: |
5990 | return VK_BLEND_OP_ADD; |
5991 | case QRhiGraphicsPipeline::Subtract: |
5992 | return VK_BLEND_OP_SUBTRACT; |
5993 | case QRhiGraphicsPipeline::ReverseSubtract: |
5994 | return VK_BLEND_OP_REVERSE_SUBTRACT; |
5995 | case QRhiGraphicsPipeline::Min: |
5996 | return VK_BLEND_OP_MIN; |
5997 | case QRhiGraphicsPipeline::Max: |
5998 | return VK_BLEND_OP_MAX; |
5999 | default: |
6000 | Q_UNREACHABLE_RETURN(VK_BLEND_OP_ADD); |
6001 | } |
6002 | } |
6003 | |
6004 | static inline VkCompareOp toVkCompareOp(QRhiGraphicsPipeline::CompareOp op) |
6005 | { |
6006 | switch (op) { |
6007 | case QRhiGraphicsPipeline::Never: |
6008 | return VK_COMPARE_OP_NEVER; |
6009 | case QRhiGraphicsPipeline::Less: |
6010 | return VK_COMPARE_OP_LESS; |
6011 | case QRhiGraphicsPipeline::Equal: |
6012 | return VK_COMPARE_OP_EQUAL; |
6013 | case QRhiGraphicsPipeline::LessOrEqual: |
6014 | return VK_COMPARE_OP_LESS_OR_EQUAL; |
6015 | case QRhiGraphicsPipeline::Greater: |
6016 | return VK_COMPARE_OP_GREATER; |
6017 | case QRhiGraphicsPipeline::NotEqual: |
6018 | return VK_COMPARE_OP_NOT_EQUAL; |
6019 | case QRhiGraphicsPipeline::GreaterOrEqual: |
6020 | return VK_COMPARE_OP_GREATER_OR_EQUAL; |
6021 | case QRhiGraphicsPipeline::Always: |
6022 | return VK_COMPARE_OP_ALWAYS; |
6023 | default: |
6024 | Q_UNREACHABLE_RETURN(VK_COMPARE_OP_ALWAYS); |
6025 | } |
6026 | } |
6027 | |
6028 | static inline VkStencilOp toVkStencilOp(QRhiGraphicsPipeline::StencilOp op) |
6029 | { |
6030 | switch (op) { |
6031 | case QRhiGraphicsPipeline::StencilZero: |
6032 | return VK_STENCIL_OP_ZERO; |
6033 | case QRhiGraphicsPipeline::Keep: |
6034 | return VK_STENCIL_OP_KEEP; |
6035 | case QRhiGraphicsPipeline::Replace: |
6036 | return VK_STENCIL_OP_REPLACE; |
6037 | case QRhiGraphicsPipeline::IncrementAndClamp: |
6038 | return VK_STENCIL_OP_INCREMENT_AND_CLAMP; |
6039 | case QRhiGraphicsPipeline::DecrementAndClamp: |
6040 | return VK_STENCIL_OP_DECREMENT_AND_CLAMP; |
6041 | case QRhiGraphicsPipeline::Invert: |
6042 | return VK_STENCIL_OP_INVERT; |
6043 | case QRhiGraphicsPipeline::IncrementAndWrap: |
6044 | return VK_STENCIL_OP_INCREMENT_AND_WRAP; |
6045 | case QRhiGraphicsPipeline::DecrementAndWrap: |
6046 | return VK_STENCIL_OP_DECREMENT_AND_WRAP; |
6047 | default: |
6048 | Q_UNREACHABLE_RETURN(VK_STENCIL_OP_KEEP); |
6049 | } |
6050 | } |
6051 | |
6052 | static inline VkPolygonMode toVkPolygonMode(QRhiGraphicsPipeline::PolygonMode mode) |
6053 | { |
6054 | switch (mode) { |
6055 | case QRhiGraphicsPipeline::Fill: |
6056 | return VK_POLYGON_MODE_FILL; |
6057 | case QRhiGraphicsPipeline::Line: |
6058 | return VK_POLYGON_MODE_LINE; |
6059 | default: |
6060 | Q_UNREACHABLE_RETURN(VK_POLYGON_MODE_FILL); |
6061 | } |
6062 | } |
6063 | |
6064 | static inline void fillVkStencilOpState(VkStencilOpState *dst, const QRhiGraphicsPipeline::StencilOpState &src) |
6065 | { |
6066 | dst->failOp = toVkStencilOp(op: src.failOp); |
6067 | dst->passOp = toVkStencilOp(op: src.passOp); |
6068 | dst->depthFailOp = toVkStencilOp(op: src.depthFailOp); |
6069 | dst->compareOp = toVkCompareOp(op: src.compareOp); |
6070 | } |
6071 | |
6072 | static inline VkDescriptorType toVkDescriptorType(const QRhiShaderResourceBinding::Data *b) |
6073 | { |
6074 | switch (b->type) { |
6075 | case QRhiShaderResourceBinding::UniformBuffer: |
6076 | return b->u.ubuf.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC |
6077 | : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; |
6078 | |
6079 | case QRhiShaderResourceBinding::SampledTexture: |
6080 | return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; |
6081 | |
6082 | case QRhiShaderResourceBinding::Texture: |
6083 | return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; |
6084 | |
6085 | case QRhiShaderResourceBinding::Sampler: |
6086 | return VK_DESCRIPTOR_TYPE_SAMPLER; |
6087 | |
6088 | case QRhiShaderResourceBinding::ImageLoad: |
6089 | case QRhiShaderResourceBinding::ImageStore: |
6090 | case QRhiShaderResourceBinding::ImageLoadStore: |
6091 | return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; |
6092 | |
6093 | case QRhiShaderResourceBinding::BufferLoad: |
6094 | case QRhiShaderResourceBinding::BufferStore: |
6095 | case QRhiShaderResourceBinding::BufferLoadStore: |
6096 | return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; |
6097 | |
6098 | default: |
6099 | Q_UNREACHABLE_RETURN(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); |
6100 | } |
6101 | } |
6102 | |
6103 | static inline VkShaderStageFlags toVkShaderStageFlags(QRhiShaderResourceBinding::StageFlags stage) |
6104 | { |
6105 | int s = 0; |
6106 | if (stage.testFlag(flag: QRhiShaderResourceBinding::VertexStage)) |
6107 | s |= VK_SHADER_STAGE_VERTEX_BIT; |
6108 | if (stage.testFlag(flag: QRhiShaderResourceBinding::FragmentStage)) |
6109 | s |= VK_SHADER_STAGE_FRAGMENT_BIT; |
6110 | if (stage.testFlag(flag: QRhiShaderResourceBinding::ComputeStage)) |
6111 | s |= VK_SHADER_STAGE_COMPUTE_BIT; |
6112 | if (stage.testFlag(flag: QRhiShaderResourceBinding::TessellationControlStage)) |
6113 | s |= VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; |
6114 | if (stage.testFlag(flag: QRhiShaderResourceBinding::TessellationEvaluationStage)) |
6115 | s |= VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; |
6116 | if (stage.testFlag(flag: QRhiShaderResourceBinding::GeometryStage)) |
6117 | s |= VK_SHADER_STAGE_GEOMETRY_BIT; |
6118 | return VkShaderStageFlags(s); |
6119 | } |
6120 | |
6121 | static inline VkCompareOp toVkTextureCompareOp(QRhiSampler::CompareOp op) |
6122 | { |
6123 | switch (op) { |
6124 | case QRhiSampler::Never: |
6125 | return VK_COMPARE_OP_NEVER; |
6126 | case QRhiSampler::Less: |
6127 | return VK_COMPARE_OP_LESS; |
6128 | case QRhiSampler::Equal: |
6129 | return VK_COMPARE_OP_EQUAL; |
6130 | case QRhiSampler::LessOrEqual: |
6131 | return VK_COMPARE_OP_LESS_OR_EQUAL; |
6132 | case QRhiSampler::Greater: |
6133 | return VK_COMPARE_OP_GREATER; |
6134 | case QRhiSampler::NotEqual: |
6135 | return VK_COMPARE_OP_NOT_EQUAL; |
6136 | case QRhiSampler::GreaterOrEqual: |
6137 | return VK_COMPARE_OP_GREATER_OR_EQUAL; |
6138 | case QRhiSampler::Always: |
6139 | return VK_COMPARE_OP_ALWAYS; |
6140 | default: |
6141 | Q_UNREACHABLE_RETURN(VK_COMPARE_OP_NEVER); |
6142 | } |
6143 | } |
6144 | |
6145 | QVkBuffer::QVkBuffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size) |
6146 | : QRhiBuffer(rhi, type, usage, size) |
6147 | { |
6148 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
6149 | buffers[i] = stagingBuffers[i] = VK_NULL_HANDLE; |
6150 | allocations[i] = stagingAllocations[i] = nullptr; |
6151 | } |
6152 | } |
6153 | |
6154 | QVkBuffer::~QVkBuffer() |
6155 | { |
6156 | destroy(); |
6157 | } |
6158 | |
6159 | void QVkBuffer::destroy() |
6160 | { |
6161 | if (!buffers[0]) |
6162 | return; |
6163 | |
6164 | QRhiVulkan::DeferredReleaseEntry e; |
6165 | e.type = QRhiVulkan::DeferredReleaseEntry::Buffer; |
6166 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6167 | |
6168 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
6169 | e.buffer.buffers[i] = buffers[i]; |
6170 | e.buffer.allocations[i] = allocations[i]; |
6171 | e.buffer.stagingBuffers[i] = stagingBuffers[i]; |
6172 | e.buffer.stagingAllocations[i] = stagingAllocations[i]; |
6173 | |
6174 | buffers[i] = VK_NULL_HANDLE; |
6175 | allocations[i] = nullptr; |
6176 | stagingBuffers[i] = VK_NULL_HANDLE; |
6177 | stagingAllocations[i] = nullptr; |
6178 | pendingDynamicUpdates[i].clear(); |
6179 | } |
6180 | |
6181 | QRHI_RES_RHI(QRhiVulkan); |
6182 | // destroy() implementations, unlike other functions, are expected to test |
6183 | // for m_rhi being null, to allow surviving in case one attempts to destroy |
6184 | // a (leaked) resource after the QRhi. |
6185 | if (rhiD) { |
6186 | rhiD->releaseQueue.append(t: e); |
6187 | rhiD->unregisterResource(res: this); |
6188 | } |
6189 | } |
6190 | |
6191 | bool QVkBuffer::create() |
6192 | { |
6193 | if (buffers[0]) |
6194 | destroy(); |
6195 | |
6196 | if (m_usage.testFlag(flag: QRhiBuffer::StorageBuffer) && m_type == Dynamic) { |
6197 | qWarning(msg: "StorageBuffer cannot be combined with Dynamic" ); |
6198 | return false; |
6199 | } |
6200 | |
6201 | const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size; |
6202 | |
6203 | VkBufferCreateInfo bufferInfo = {}; |
6204 | bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; |
6205 | bufferInfo.size = nonZeroSize; |
6206 | bufferInfo.usage = toVkBufferUsage(usage: m_usage); |
6207 | |
6208 | VmaAllocationCreateInfo allocInfo = {}; |
6209 | |
6210 | if (m_type == Dynamic) { |
6211 | #ifndef Q_OS_DARWIN // not for MoltenVK |
6212 | // Keep mapped all the time. Essential f.ex. with some mobile GPUs, |
6213 | // where mapping and unmapping an entire allocation every time updating |
6214 | // a suballocated buffer presents a significant perf. hit. |
6215 | allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; |
6216 | #endif |
6217 | // host visible, frequent changes |
6218 | allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; |
6219 | } else { |
6220 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; |
6221 | bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
6222 | } |
6223 | |
6224 | QRHI_RES_RHI(QRhiVulkan); |
6225 | VkResult err = VK_SUCCESS; |
6226 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
6227 | buffers[i] = VK_NULL_HANDLE; |
6228 | allocations[i] = nullptr; |
6229 | usageState[i].access = usageState[i].stage = 0; |
6230 | if (i == 0 || m_type == Dynamic) { |
6231 | VmaAllocation allocation; |
6232 | err = vmaCreateBuffer(allocator: toVmaAllocator(a: rhiD->allocator), pBufferCreateInfo: &bufferInfo, pAllocationCreateInfo: &allocInfo, pBuffer: &buffers[i], pAllocation: &allocation, pAllocationInfo: nullptr); |
6233 | if (err != VK_SUCCESS) |
6234 | break; |
6235 | allocations[i] = allocation; |
6236 | rhiD->setObjectName(object: uint64_t(buffers[i]), type: VK_OBJECT_TYPE_BUFFER, name: m_objectName, |
6237 | slot: m_type == Dynamic ? i : -1); |
6238 | } |
6239 | } |
6240 | |
6241 | if (err != VK_SUCCESS) { |
6242 | qWarning(msg: "Failed to create buffer of size %u: %d" , nonZeroSize, err); |
6243 | rhiD->printExtraErrorInfo(err); |
6244 | return false; |
6245 | } |
6246 | |
6247 | lastActiveFrameSlot = -1; |
6248 | generation += 1; |
6249 | rhiD->registerResource(res: this); |
6250 | return true; |
6251 | } |
6252 | |
6253 | QRhiBuffer::NativeBuffer QVkBuffer::nativeBuffer() |
6254 | { |
6255 | if (m_type == Dynamic) { |
6256 | QRHI_RES_RHI(QRhiVulkan); |
6257 | NativeBuffer b; |
6258 | Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QVK_FRAMES_IN_FLIGHT)); |
6259 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
6260 | rhiD->executeBufferHostWritesForSlot(bufD: this, slot: i); |
6261 | b.objects[i] = &buffers[i]; |
6262 | } |
6263 | b.slotCount = QVK_FRAMES_IN_FLIGHT; |
6264 | return b; |
6265 | } |
6266 | return { .objects: { &buffers[0] }, .slotCount: 1 }; |
6267 | } |
6268 | |
6269 | char *QVkBuffer::beginFullDynamicBufferUpdateForCurrentFrame() |
6270 | { |
6271 | // Shortcut the entire buffer update mechanism and allow the client to do |
6272 | // the host writes directly to the buffer. This will lead to unexpected |
6273 | // results when combined with QRhiResourceUpdateBatch-based updates for the |
6274 | // buffer, but provides a fast path for dynamic buffers that have all their |
6275 | // content changed in every frame. |
6276 | Q_ASSERT(m_type == Dynamic); |
6277 | QRHI_RES_RHI(QRhiVulkan); |
6278 | Q_ASSERT(rhiD->inFrame); |
6279 | const int slot = rhiD->currentFrameSlot; |
6280 | void *p = nullptr; |
6281 | VmaAllocation a = toVmaAllocation(a: allocations[slot]); |
6282 | VkResult err = vmaMapMemory(allocator: toVmaAllocator(a: rhiD->allocator), allocation: a, ppData: &p); |
6283 | if (err != VK_SUCCESS) { |
6284 | qWarning(msg: "Failed to map buffer: %d" , err); |
6285 | return nullptr; |
6286 | } |
6287 | return static_cast<char *>(p); |
6288 | } |
6289 | |
6290 | void QVkBuffer::endFullDynamicBufferUpdateForCurrentFrame() |
6291 | { |
6292 | QRHI_RES_RHI(QRhiVulkan); |
6293 | const int slot = rhiD->currentFrameSlot; |
6294 | VmaAllocation a = toVmaAllocation(a: allocations[slot]); |
6295 | vmaFlushAllocation(allocator: toVmaAllocator(a: rhiD->allocator), allocation: a, offset: 0, size: m_size); |
6296 | vmaUnmapMemory(allocator: toVmaAllocator(a: rhiD->allocator), allocation: a); |
6297 | } |
6298 | |
6299 | QVkRenderBuffer::QVkRenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, |
6300 | int sampleCount, Flags flags, |
6301 | QRhiTexture::Format backingFormatHint) |
6302 | : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint) |
6303 | { |
6304 | } |
6305 | |
6306 | QVkRenderBuffer::~QVkRenderBuffer() |
6307 | { |
6308 | destroy(); |
6309 | delete backingTexture; |
6310 | } |
6311 | |
6312 | void QVkRenderBuffer::destroy() |
6313 | { |
6314 | if (!memory && !backingTexture) |
6315 | return; |
6316 | |
6317 | QRhiVulkan::DeferredReleaseEntry e; |
6318 | e.type = QRhiVulkan::DeferredReleaseEntry::RenderBuffer; |
6319 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6320 | |
6321 | e.renderBuffer.memory = memory; |
6322 | e.renderBuffer.image = image; |
6323 | e.renderBuffer.imageView = imageView; |
6324 | |
6325 | memory = VK_NULL_HANDLE; |
6326 | image = VK_NULL_HANDLE; |
6327 | imageView = VK_NULL_HANDLE; |
6328 | |
6329 | if (backingTexture) { |
6330 | Q_ASSERT(backingTexture->lastActiveFrameSlot == -1); |
6331 | backingTexture->lastActiveFrameSlot = e.lastActiveFrameSlot; |
6332 | backingTexture->destroy(); |
6333 | } |
6334 | |
6335 | QRHI_RES_RHI(QRhiVulkan); |
6336 | if (rhiD) { |
6337 | rhiD->releaseQueue.append(t: e); |
6338 | rhiD->unregisterResource(res: this); |
6339 | } |
6340 | } |
6341 | |
6342 | bool QVkRenderBuffer::create() |
6343 | { |
6344 | if (memory || backingTexture) |
6345 | destroy(); |
6346 | |
6347 | if (m_pixelSize.isEmpty()) |
6348 | return false; |
6349 | |
6350 | QRHI_RES_RHI(QRhiVulkan); |
6351 | samples = rhiD->effectiveSampleCountBits(sampleCount: m_sampleCount); |
6352 | |
6353 | switch (m_type) { |
6354 | case QRhiRenderBuffer::Color: |
6355 | { |
6356 | if (!backingTexture) { |
6357 | backingTexture = QRHI_RES(QVkTexture, rhiD->createTexture(backingFormat(), |
6358 | m_pixelSize, |
6359 | 1, |
6360 | 0, |
6361 | m_sampleCount, |
6362 | QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource)); |
6363 | } else { |
6364 | backingTexture->setPixelSize(m_pixelSize); |
6365 | backingTexture->setSampleCount(m_sampleCount); |
6366 | } |
6367 | backingTexture->setName(m_objectName); |
6368 | if (!backingTexture->create()) |
6369 | return false; |
6370 | vkformat = backingTexture->vkformat; |
6371 | } |
6372 | break; |
6373 | case QRhiRenderBuffer::DepthStencil: |
6374 | vkformat = rhiD->optimalDepthStencilFormat(); |
6375 | if (!rhiD->createTransientImage(format: vkformat, |
6376 | pixelSize: m_pixelSize, |
6377 | usage: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, |
6378 | aspectMask: VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, |
6379 | samples, |
6380 | mem: &memory, |
6381 | images: &image, |
6382 | views: &imageView, |
6383 | count: 1)) |
6384 | { |
6385 | return false; |
6386 | } |
6387 | rhiD->setObjectName(object: uint64_t(image), type: VK_OBJECT_TYPE_IMAGE, name: m_objectName); |
6388 | break; |
6389 | default: |
6390 | Q_UNREACHABLE(); |
6391 | break; |
6392 | } |
6393 | |
6394 | lastActiveFrameSlot = -1; |
6395 | generation += 1; |
6396 | rhiD->registerResource(res: this); |
6397 | return true; |
6398 | } |
6399 | |
6400 | QRhiTexture::Format QVkRenderBuffer::backingFormat() const |
6401 | { |
6402 | if (m_backingFormatHint != QRhiTexture::UnknownFormat) |
6403 | return m_backingFormatHint; |
6404 | else |
6405 | return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat; |
6406 | } |
6407 | |
6408 | QVkTexture::QVkTexture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, |
6409 | int arraySize, int sampleCount, Flags flags) |
6410 | : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags) |
6411 | { |
6412 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
6413 | stagingBuffers[i] = VK_NULL_HANDLE; |
6414 | stagingAllocations[i] = nullptr; |
6415 | } |
6416 | for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) |
6417 | perLevelImageViews[i] = VK_NULL_HANDLE; |
6418 | } |
6419 | |
6420 | QVkTexture::~QVkTexture() |
6421 | { |
6422 | destroy(); |
6423 | } |
6424 | |
6425 | void QVkTexture::destroy() |
6426 | { |
6427 | if (!image) |
6428 | return; |
6429 | |
6430 | QRhiVulkan::DeferredReleaseEntry e; |
6431 | e.type = QRhiVulkan::DeferredReleaseEntry::Texture; |
6432 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6433 | |
6434 | e.texture.image = owns ? image : VK_NULL_HANDLE; |
6435 | e.texture.imageView = imageView; |
6436 | e.texture.allocation = owns ? imageAlloc : nullptr; |
6437 | |
6438 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
6439 | e.texture.stagingBuffers[i] = stagingBuffers[i]; |
6440 | e.texture.stagingAllocations[i] = stagingAllocations[i]; |
6441 | |
6442 | stagingBuffers[i] = VK_NULL_HANDLE; |
6443 | stagingAllocations[i] = nullptr; |
6444 | } |
6445 | |
6446 | for (int i = 0; i < QRhi::MAX_MIP_LEVELS; ++i) { |
6447 | e.texture.extraImageViews[i] = perLevelImageViews[i]; |
6448 | perLevelImageViews[i] = VK_NULL_HANDLE; |
6449 | } |
6450 | |
6451 | image = VK_NULL_HANDLE; |
6452 | imageView = VK_NULL_HANDLE; |
6453 | imageAlloc = nullptr; |
6454 | |
6455 | QRHI_RES_RHI(QRhiVulkan); |
6456 | if (rhiD) { |
6457 | rhiD->releaseQueue.append(t: e); |
6458 | rhiD->unregisterResource(res: this); |
6459 | } |
6460 | } |
6461 | |
6462 | bool QVkTexture::prepareCreate(QSize *adjustedSize) |
6463 | { |
6464 | if (image) |
6465 | destroy(); |
6466 | |
6467 | QRHI_RES_RHI(QRhiVulkan); |
6468 | vkformat = toVkTextureFormat(format: m_format, flags: m_flags); |
6469 | if (m_writeViewFormat.format != UnknownFormat) |
6470 | viewFormat = toVkTextureFormat(format: m_writeViewFormat.format, flags: m_writeViewFormat.srgb ? sRGB : Flags()); |
6471 | else |
6472 | viewFormat = vkformat; |
6473 | if (m_readViewFormat.format != UnknownFormat) |
6474 | viewFormatForSampling = toVkTextureFormat(format: m_readViewFormat.format, flags: m_readViewFormat.srgb ? sRGB : Flags()); |
6475 | else |
6476 | viewFormatForSampling = vkformat; |
6477 | |
6478 | VkFormatProperties props; |
6479 | rhiD->f->vkGetPhysicalDeviceFormatProperties(rhiD->physDev, vkformat, &props); |
6480 | const bool canSampleOptimal = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); |
6481 | if (!canSampleOptimal) { |
6482 | qWarning(msg: "Texture sampling with optimal tiling for format %d not supported" , vkformat); |
6483 | return false; |
6484 | } |
6485 | |
6486 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
6487 | const bool isArray = m_flags.testFlag(flag: TextureArray); |
6488 | const bool is3D = m_flags.testFlag(flag: ThreeDimensional); |
6489 | const bool is1D = m_flags.testFlag(flag: OneDimensional); |
6490 | const bool hasMipMaps = m_flags.testFlag(flag: MipMapped); |
6491 | |
6492 | const QSize size = is1D ? QSize(qMax(a: 1, b: m_pixelSize.width()), 1) |
6493 | : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize); |
6494 | |
6495 | mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1); |
6496 | const int maxLevels = QRhi::MAX_MIP_LEVELS; |
6497 | if (mipLevelCount > maxLevels) { |
6498 | qWarning(msg: "Too many mip levels (%d, max is %d), truncating mip chain" , mipLevelCount, maxLevels); |
6499 | mipLevelCount = maxLevels; |
6500 | } |
6501 | samples = rhiD->effectiveSampleCountBits(sampleCount: m_sampleCount); |
6502 | if (samples > VK_SAMPLE_COUNT_1_BIT) { |
6503 | if (isCube) { |
6504 | qWarning(msg: "Cubemap texture cannot be multisample" ); |
6505 | return false; |
6506 | } |
6507 | if (is3D) { |
6508 | qWarning(msg: "3D texture cannot be multisample" ); |
6509 | return false; |
6510 | } |
6511 | if (hasMipMaps) { |
6512 | qWarning(msg: "Multisample texture cannot have mipmaps" ); |
6513 | return false; |
6514 | } |
6515 | } |
6516 | if (isCube && is3D) { |
6517 | qWarning(msg: "Texture cannot be both cube and 3D" ); |
6518 | return false; |
6519 | } |
6520 | if (isArray && is3D) { |
6521 | qWarning(msg: "Texture cannot be both array and 3D" ); |
6522 | return false; |
6523 | } |
6524 | if (isCube && is1D) { |
6525 | qWarning(msg: "Texture cannot be both cube and 1D" ); |
6526 | return false; |
6527 | } |
6528 | if (is1D && is3D) { |
6529 | qWarning(msg: "Texture cannot be both 1D and 3D" ); |
6530 | return false; |
6531 | } |
6532 | if (m_depth > 1 && !is3D) { |
6533 | qWarning(msg: "Texture cannot have a depth of %d when it is not 3D" , m_depth); |
6534 | return false; |
6535 | } |
6536 | if (m_arraySize > 0 && !isArray) { |
6537 | qWarning(msg: "Texture cannot have an array size of %d when it is not an array" , m_arraySize); |
6538 | return false; |
6539 | } |
6540 | if (m_arraySize < 1 && isArray) { |
6541 | qWarning(msg: "Texture is an array but array size is %d" , m_arraySize); |
6542 | return false; |
6543 | } |
6544 | |
6545 | usageState.layout = VK_IMAGE_LAYOUT_PREINITIALIZED; |
6546 | usageState.access = 0; |
6547 | usageState.stage = 0; |
6548 | |
6549 | if (adjustedSize) |
6550 | *adjustedSize = size; |
6551 | |
6552 | return true; |
6553 | } |
6554 | |
6555 | bool QVkTexture::finishCreate() |
6556 | { |
6557 | QRHI_RES_RHI(QRhiVulkan); |
6558 | |
6559 | const auto aspectMask = aspectMaskForTextureFormat(format: m_format); |
6560 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
6561 | const bool isArray = m_flags.testFlag(flag: TextureArray); |
6562 | const bool is3D = m_flags.testFlag(flag: ThreeDimensional); |
6563 | const bool is1D = m_flags.testFlag(flag: OneDimensional); |
6564 | |
6565 | VkImageViewCreateInfo viewInfo = {}; |
6566 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
6567 | viewInfo.image = image; |
6568 | viewInfo.viewType = isCube |
6569 | ? VK_IMAGE_VIEW_TYPE_CUBE |
6570 | : (is3D ? VK_IMAGE_VIEW_TYPE_3D |
6571 | : (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D) |
6572 | : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D))); |
6573 | viewInfo.format = viewFormatForSampling; |
6574 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
6575 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
6576 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
6577 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
6578 | viewInfo.subresourceRange.aspectMask = aspectMask; |
6579 | viewInfo.subresourceRange.levelCount = mipLevelCount; |
6580 | if (isArray && m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) { |
6581 | viewInfo.subresourceRange.baseArrayLayer = uint32_t(m_arrayRangeStart); |
6582 | viewInfo.subresourceRange.layerCount = uint32_t(m_arrayRangeLength); |
6583 | } else { |
6584 | viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(a: 0, b: m_arraySize) : 1); |
6585 | } |
6586 | |
6587 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &imageView); |
6588 | if (err != VK_SUCCESS) { |
6589 | qWarning(msg: "Failed to create image view: %d" , err); |
6590 | return false; |
6591 | } |
6592 | |
6593 | lastActiveFrameSlot = -1; |
6594 | generation += 1; |
6595 | |
6596 | return true; |
6597 | } |
6598 | |
6599 | bool QVkTexture::create() |
6600 | { |
6601 | QSize size; |
6602 | if (!prepareCreate(adjustedSize: &size)) |
6603 | return false; |
6604 | |
6605 | QRHI_RES_RHI(QRhiVulkan); |
6606 | const bool isRenderTarget = m_flags.testFlag(flag: QRhiTexture::RenderTarget); |
6607 | const bool isDepth = isDepthTextureFormat(format: m_format); |
6608 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
6609 | const bool isArray = m_flags.testFlag(flag: TextureArray); |
6610 | const bool is3D = m_flags.testFlag(flag: ThreeDimensional); |
6611 | const bool is1D = m_flags.testFlag(flag: OneDimensional); |
6612 | |
6613 | VkImageCreateInfo imageInfo = {}; |
6614 | imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; |
6615 | imageInfo.flags = 0; |
6616 | if (isCube) |
6617 | imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; |
6618 | |
6619 | if (is3D && isRenderTarget) { |
6620 | // This relies on a Vulkan 1.1 constant. For guaranteed proper behavior |
6621 | // this also requires that at run time the VkInstance has at least API 1.1 |
6622 | // enabled. (though it works as expected with some Vulkan (1.2) |
6623 | // implementations regardless of the requested API version, but f.ex. the |
6624 | // validation layer complains when using this without enabling >=1.1) |
6625 | if (!rhiD->caps.texture3DSliceAs2D) |
6626 | qWarning(msg: "QRhiVulkan: Rendering to 3D texture slice may not be functional without API 1.1 on the VkInstance" ); |
6627 | #ifdef VK_VERSION_1_1 |
6628 | imageInfo.flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; |
6629 | #else |
6630 | imageInfo.flags |= 0x00000020; |
6631 | #endif |
6632 | } |
6633 | |
6634 | imageInfo.imageType = is1D ? VK_IMAGE_TYPE_1D : is3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D; |
6635 | imageInfo.format = vkformat; |
6636 | imageInfo.extent.width = uint32_t(size.width()); |
6637 | imageInfo.extent.height = uint32_t(size.height()); |
6638 | imageInfo.extent.depth = is3D ? qMax(a: 1, b: m_depth) : 1; |
6639 | imageInfo.mipLevels = mipLevelCount; |
6640 | imageInfo.arrayLayers = isCube ? 6 : (isArray ? qMax(a: 0, b: m_arraySize) : 1); |
6641 | imageInfo.samples = samples; |
6642 | imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; |
6643 | imageInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; |
6644 | |
6645 | imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
6646 | if (isRenderTarget) { |
6647 | if (isDepth) |
6648 | imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; |
6649 | else |
6650 | imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
6651 | } |
6652 | if (m_flags.testFlag(flag: QRhiTexture::UsedAsTransferSource)) |
6653 | imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
6654 | if (m_flags.testFlag(flag: QRhiTexture::UsedWithGenerateMips)) |
6655 | imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; |
6656 | if (m_flags.testFlag(flag: QRhiTexture::UsedWithLoadStore)) |
6657 | imageInfo.usage |= VK_IMAGE_USAGE_STORAGE_BIT; |
6658 | |
6659 | VmaAllocationCreateInfo allocInfo = {}; |
6660 | allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; |
6661 | |
6662 | VmaAllocation allocation; |
6663 | VkResult err = vmaCreateImage(allocator: toVmaAllocator(a: rhiD->allocator), pImageCreateInfo: &imageInfo, pAllocationCreateInfo: &allocInfo, pImage: &image, pAllocation: &allocation, pAllocationInfo: nullptr); |
6664 | if (err != VK_SUCCESS) { |
6665 | qWarning(msg: "Failed to create image (with VkImageCreateInfo %ux%u depth %u vkformat 0x%X mips %u layers %u vksamples 0x%X): %d" , |
6666 | imageInfo.extent.width, imageInfo.extent.height, imageInfo.extent.depth, |
6667 | int(imageInfo.format), |
6668 | imageInfo.mipLevels, |
6669 | imageInfo.arrayLayers, |
6670 | int(imageInfo.samples), |
6671 | err); |
6672 | rhiD->printExtraErrorInfo(err); |
6673 | return false; |
6674 | } |
6675 | imageAlloc = allocation; |
6676 | |
6677 | if (!finishCreate()) |
6678 | return false; |
6679 | |
6680 | rhiD->setObjectName(object: uint64_t(image), type: VK_OBJECT_TYPE_IMAGE, name: m_objectName); |
6681 | |
6682 | owns = true; |
6683 | rhiD->registerResource(res: this); |
6684 | return true; |
6685 | } |
6686 | |
6687 | bool QVkTexture::createFrom(QRhiTexture::NativeTexture src) |
6688 | { |
6689 | VkImage img = VkImage(src.object); |
6690 | if (img == 0) |
6691 | return false; |
6692 | |
6693 | if (!prepareCreate()) |
6694 | return false; |
6695 | |
6696 | image = img; |
6697 | |
6698 | if (!finishCreate()) |
6699 | return false; |
6700 | |
6701 | usageState.layout = VkImageLayout(src.layout); |
6702 | |
6703 | owns = false; |
6704 | QRHI_RES_RHI(QRhiVulkan); |
6705 | rhiD->registerResource(res: this); |
6706 | return true; |
6707 | } |
6708 | |
6709 | QRhiTexture::NativeTexture QVkTexture::nativeTexture() |
6710 | { |
6711 | return {.object: quint64(image), .layout: usageState.layout}; |
6712 | } |
6713 | |
6714 | void QVkTexture::setNativeLayout(int layout) |
6715 | { |
6716 | usageState.layout = VkImageLayout(layout); |
6717 | } |
6718 | |
6719 | VkImageView QVkTexture::perLevelImageViewForLoadStore(int level) |
6720 | { |
6721 | Q_ASSERT(level >= 0 && level < int(mipLevelCount)); |
6722 | if (perLevelImageViews[level] != VK_NULL_HANDLE) |
6723 | return perLevelImageViews[level]; |
6724 | |
6725 | const VkImageAspectFlags aspectMask = aspectMaskForTextureFormat(format: m_format); |
6726 | const bool isCube = m_flags.testFlag(flag: CubeMap); |
6727 | const bool isArray = m_flags.testFlag(flag: TextureArray); |
6728 | const bool is3D = m_flags.testFlag(flag: ThreeDimensional); |
6729 | const bool is1D = m_flags.testFlag(flag: OneDimensional); |
6730 | |
6731 | VkImageViewCreateInfo viewInfo = {}; |
6732 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
6733 | viewInfo.image = image; |
6734 | viewInfo.viewType = isCube |
6735 | ? VK_IMAGE_VIEW_TYPE_CUBE |
6736 | : (is3D ? VK_IMAGE_VIEW_TYPE_3D |
6737 | : (is1D ? (isArray ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D) |
6738 | : (isArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D))); |
6739 | viewInfo.format = viewFormat; // this is writeViewFormat, regardless of Load, Store, or LoadStore; intentional |
6740 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
6741 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
6742 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
6743 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
6744 | viewInfo.subresourceRange.aspectMask = aspectMask; |
6745 | viewInfo.subresourceRange.baseMipLevel = uint32_t(level); |
6746 | viewInfo.subresourceRange.levelCount = 1; |
6747 | viewInfo.subresourceRange.baseArrayLayer = 0; |
6748 | viewInfo.subresourceRange.layerCount = isCube ? 6 : (isArray ? qMax(a: 0, b: m_arraySize) : 1); |
6749 | |
6750 | VkImageView v = VK_NULL_HANDLE; |
6751 | QRHI_RES_RHI(QRhiVulkan); |
6752 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &v); |
6753 | if (err != VK_SUCCESS) { |
6754 | qWarning(msg: "Failed to create image view: %d" , err); |
6755 | return VK_NULL_HANDLE; |
6756 | } |
6757 | |
6758 | perLevelImageViews[level] = v; |
6759 | return v; |
6760 | } |
6761 | |
6762 | QVkSampler::QVkSampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, |
6763 | AddressMode u, AddressMode v, AddressMode w) |
6764 | : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w) |
6765 | { |
6766 | } |
6767 | |
6768 | QVkSampler::~QVkSampler() |
6769 | { |
6770 | destroy(); |
6771 | } |
6772 | |
6773 | void QVkSampler::destroy() |
6774 | { |
6775 | if (!sampler) |
6776 | return; |
6777 | |
6778 | QRhiVulkan::DeferredReleaseEntry e; |
6779 | e.type = QRhiVulkan::DeferredReleaseEntry::Sampler; |
6780 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6781 | |
6782 | e.sampler.sampler = sampler; |
6783 | sampler = VK_NULL_HANDLE; |
6784 | |
6785 | QRHI_RES_RHI(QRhiVulkan); |
6786 | if (rhiD) { |
6787 | rhiD->releaseQueue.append(t: e); |
6788 | rhiD->unregisterResource(res: this); |
6789 | } |
6790 | } |
6791 | |
6792 | bool QVkSampler::create() |
6793 | { |
6794 | if (sampler) |
6795 | destroy(); |
6796 | |
6797 | VkSamplerCreateInfo samplerInfo = {}; |
6798 | samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; |
6799 | samplerInfo.magFilter = toVkFilter(f: m_magFilter); |
6800 | samplerInfo.minFilter = toVkFilter(f: m_minFilter); |
6801 | samplerInfo.mipmapMode = toVkMipmapMode(f: m_mipmapMode); |
6802 | samplerInfo.addressModeU = toVkAddressMode(m: m_addressU); |
6803 | samplerInfo.addressModeV = toVkAddressMode(m: m_addressV); |
6804 | samplerInfo.addressModeW = toVkAddressMode(m: m_addressW); |
6805 | samplerInfo.maxAnisotropy = 1.0f; |
6806 | samplerInfo.compareEnable = m_compareOp != Never; |
6807 | samplerInfo.compareOp = toVkTextureCompareOp(op: m_compareOp); |
6808 | samplerInfo.maxLod = m_mipmapMode == None ? 0.25f : 1000.0f; |
6809 | |
6810 | QRHI_RES_RHI(QRhiVulkan); |
6811 | VkResult err = rhiD->df->vkCreateSampler(rhiD->dev, &samplerInfo, nullptr, &sampler); |
6812 | if (err != VK_SUCCESS) { |
6813 | qWarning(msg: "Failed to create sampler: %d" , err); |
6814 | return false; |
6815 | } |
6816 | |
6817 | lastActiveFrameSlot = -1; |
6818 | generation += 1; |
6819 | rhiD->registerResource(res: this); |
6820 | return true; |
6821 | } |
6822 | |
6823 | QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi) |
6824 | : QRhiRenderPassDescriptor(rhi) |
6825 | { |
6826 | serializedFormatData.reserve(asize: 64); |
6827 | } |
6828 | |
6829 | QVkRenderPassDescriptor::~QVkRenderPassDescriptor() |
6830 | { |
6831 | destroy(); |
6832 | } |
6833 | |
6834 | void QVkRenderPassDescriptor::destroy() |
6835 | { |
6836 | if (!rp) |
6837 | return; |
6838 | |
6839 | if (!ownsRp) { |
6840 | rp = VK_NULL_HANDLE; |
6841 | return; |
6842 | } |
6843 | |
6844 | QRhiVulkan::DeferredReleaseEntry e; |
6845 | e.type = QRhiVulkan::DeferredReleaseEntry::RenderPass; |
6846 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
6847 | |
6848 | e.renderPass.rp = rp; |
6849 | |
6850 | rp = VK_NULL_HANDLE; |
6851 | |
6852 | QRHI_RES_RHI(QRhiVulkan); |
6853 | if (rhiD) { |
6854 | rhiD->releaseQueue.append(t: e); |
6855 | rhiD->unregisterResource(res: this); |
6856 | } |
6857 | } |
6858 | |
6859 | static inline bool attachmentDescriptionEquals(const VkAttachmentDescription &a, const VkAttachmentDescription &b) |
6860 | { |
6861 | return a.format == b.format |
6862 | && a.samples == b.samples |
6863 | && a.loadOp == b.loadOp |
6864 | && a.storeOp == b.storeOp |
6865 | && a.stencilLoadOp == b.stencilLoadOp |
6866 | && a.stencilStoreOp == b.stencilStoreOp |
6867 | && a.initialLayout == b.initialLayout |
6868 | && a.finalLayout == b.finalLayout; |
6869 | } |
6870 | |
6871 | bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const |
6872 | { |
6873 | if (other == this) |
6874 | return true; |
6875 | |
6876 | if (!other) |
6877 | return false; |
6878 | |
6879 | const QVkRenderPassDescriptor *o = QRHI_RES(const QVkRenderPassDescriptor, other); |
6880 | |
6881 | if (attDescs.size() != o->attDescs.size()) |
6882 | return false; |
6883 | if (colorRefs.size() != o->colorRefs.size()) |
6884 | return false; |
6885 | if (resolveRefs.size() != o->resolveRefs.size()) |
6886 | return false; |
6887 | if (hasDepthStencil != o->hasDepthStencil) |
6888 | return false; |
6889 | if (hasDepthStencilResolve != o->hasDepthStencilResolve) |
6890 | return false; |
6891 | if (multiViewCount != o->multiViewCount) |
6892 | return false; |
6893 | |
6894 | for (int i = 0, ie = colorRefs.size(); i != ie; ++i) { |
6895 | const uint32_t attIdx = colorRefs[i].attachment; |
6896 | if (attIdx != o->colorRefs[i].attachment) |
6897 | return false; |
6898 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
6899 | return false; |
6900 | } |
6901 | |
6902 | if (hasDepthStencil) { |
6903 | const uint32_t attIdx = dsRef.attachment; |
6904 | if (attIdx != o->dsRef.attachment) |
6905 | return false; |
6906 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
6907 | return false; |
6908 | } |
6909 | |
6910 | for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) { |
6911 | const uint32_t attIdx = resolveRefs[i].attachment; |
6912 | if (attIdx != o->resolveRefs[i].attachment) |
6913 | return false; |
6914 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
6915 | return false; |
6916 | } |
6917 | |
6918 | if (hasDepthStencilResolve) { |
6919 | const uint32_t attIdx = dsResolveRef.attachment; |
6920 | if (attIdx != o->dsResolveRef.attachment) |
6921 | return false; |
6922 | if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(a: attDescs[attIdx], b: o->attDescs[attIdx])) |
6923 | return false; |
6924 | } |
6925 | |
6926 | // subpassDeps is not included |
6927 | |
6928 | return true; |
6929 | } |
6930 | |
6931 | void QVkRenderPassDescriptor::updateSerializedFormat() |
6932 | { |
6933 | serializedFormatData.clear(); |
6934 | auto p = std::back_inserter(x&: serializedFormatData); |
6935 | |
6936 | *p++ = attDescs.size(); |
6937 | *p++ = colorRefs.size(); |
6938 | *p++ = resolveRefs.size(); |
6939 | *p++ = hasDepthStencil; |
6940 | *p++ = hasDepthStencilResolve; |
6941 | *p++ = multiViewCount; |
6942 | |
6943 | auto serializeAttachmentData = [this, &p](uint32_t attIdx) { |
6944 | const bool used = attIdx != VK_ATTACHMENT_UNUSED; |
6945 | const VkAttachmentDescription *a = used ? &attDescs[attIdx] : nullptr; |
6946 | *p++ = used ? a->format : 0; |
6947 | *p++ = used ? a->samples : 0; |
6948 | *p++ = used ? a->loadOp : 0; |
6949 | *p++ = used ? a->storeOp : 0; |
6950 | *p++ = used ? a->stencilLoadOp : 0; |
6951 | *p++ = used ? a->stencilStoreOp : 0; |
6952 | *p++ = used ? a->initialLayout : 0; |
6953 | *p++ = used ? a->finalLayout : 0; |
6954 | }; |
6955 | |
6956 | for (int i = 0, ie = colorRefs.size(); i != ie; ++i) { |
6957 | const uint32_t attIdx = colorRefs[i].attachment; |
6958 | *p++ = attIdx; |
6959 | serializeAttachmentData(attIdx); |
6960 | } |
6961 | |
6962 | if (hasDepthStencil) { |
6963 | const uint32_t attIdx = dsRef.attachment; |
6964 | *p++ = attIdx; |
6965 | serializeAttachmentData(attIdx); |
6966 | } |
6967 | |
6968 | for (int i = 0, ie = resolveRefs.size(); i != ie; ++i) { |
6969 | const uint32_t attIdx = resolveRefs[i].attachment; |
6970 | *p++ = attIdx; |
6971 | serializeAttachmentData(attIdx); |
6972 | } |
6973 | |
6974 | if (hasDepthStencilResolve) { |
6975 | const uint32_t attIdx = dsResolveRef.attachment; |
6976 | *p++ = attIdx; |
6977 | serializeAttachmentData(attIdx); |
6978 | } |
6979 | } |
6980 | |
6981 | QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescriptor() const |
6982 | { |
6983 | QVkRenderPassDescriptor *rpD = new QVkRenderPassDescriptor(m_rhi); |
6984 | |
6985 | rpD->ownsRp = true; |
6986 | rpD->attDescs = attDescs; |
6987 | rpD->colorRefs = colorRefs; |
6988 | rpD->resolveRefs = resolveRefs; |
6989 | rpD->subpassDeps = subpassDeps; |
6990 | rpD->hasDepthStencil = hasDepthStencil; |
6991 | rpD->hasDepthStencilResolve = hasDepthStencilResolve; |
6992 | rpD->multiViewCount = multiViewCount; |
6993 | rpD->dsRef = dsRef; |
6994 | rpD->dsResolveRef = dsResolveRef; |
6995 | |
6996 | VkRenderPassCreateInfo rpInfo; |
6997 | VkSubpassDescription subpassDesc; |
6998 | fillRenderPassCreateInfo(rpInfo: &rpInfo, subpassDesc: &subpassDesc, rpD); |
6999 | |
7000 | QRHI_RES_RHI(QRhiVulkan); |
7001 | MultiViewRenderPassSetupHelper multiViewHelper; |
7002 | if (!multiViewHelper.prepare(rpInfo: &rpInfo, multiViewCount, multiViewCap: rhiD->caps.multiView)) { |
7003 | delete rpD; |
7004 | return nullptr; |
7005 | } |
7006 | |
7007 | VkResult err = rhiD->df->vkCreateRenderPass(rhiD->dev, &rpInfo, nullptr, &rpD->rp); |
7008 | if (err != VK_SUCCESS) { |
7009 | qWarning(msg: "Failed to create renderpass: %d" , err); |
7010 | delete rpD; |
7011 | return nullptr; |
7012 | } |
7013 | |
7014 | rpD->updateSerializedFormat(); |
7015 | rhiD->registerResource(res: rpD); |
7016 | return rpD; |
7017 | } |
7018 | |
7019 | QVector<quint32> QVkRenderPassDescriptor::serializedFormat() const |
7020 | { |
7021 | return serializedFormatData; |
7022 | } |
7023 | |
7024 | const QRhiNativeHandles *QVkRenderPassDescriptor::nativeHandles() |
7025 | { |
7026 | nativeHandlesStruct.renderPass = rp; |
7027 | return &nativeHandlesStruct; |
7028 | } |
7029 | |
7030 | QVkSwapChainRenderTarget::QVkSwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain) |
7031 | : QRhiSwapChainRenderTarget(rhi, swapchain) |
7032 | { |
7033 | } |
7034 | |
7035 | QVkSwapChainRenderTarget::~QVkSwapChainRenderTarget() |
7036 | { |
7037 | destroy(); |
7038 | } |
7039 | |
7040 | void QVkSwapChainRenderTarget::destroy() |
7041 | { |
7042 | // nothing to do here |
7043 | } |
7044 | |
7045 | QSize QVkSwapChainRenderTarget::pixelSize() const |
7046 | { |
7047 | return d.pixelSize; |
7048 | } |
7049 | |
7050 | float QVkSwapChainRenderTarget::devicePixelRatio() const |
7051 | { |
7052 | return d.dpr; |
7053 | } |
7054 | |
7055 | int QVkSwapChainRenderTarget::sampleCount() const |
7056 | { |
7057 | return d.sampleCount; |
7058 | } |
7059 | |
7060 | QVkTextureRenderTarget::QVkTextureRenderTarget(QRhiImplementation *rhi, |
7061 | const QRhiTextureRenderTargetDescription &desc, |
7062 | Flags flags) |
7063 | : QRhiTextureRenderTarget(rhi, desc, flags) |
7064 | { |
7065 | for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) { |
7066 | rtv[att] = VK_NULL_HANDLE; |
7067 | resrtv[att] = VK_NULL_HANDLE; |
7068 | } |
7069 | } |
7070 | |
7071 | QVkTextureRenderTarget::~QVkTextureRenderTarget() |
7072 | { |
7073 | destroy(); |
7074 | } |
7075 | |
7076 | void QVkTextureRenderTarget::destroy() |
7077 | { |
7078 | if (!d.fb) |
7079 | return; |
7080 | |
7081 | QRhiVulkan::DeferredReleaseEntry e; |
7082 | e.type = QRhiVulkan::DeferredReleaseEntry::TextureRenderTarget; |
7083 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
7084 | |
7085 | e.textureRenderTarget.fb = d.fb; |
7086 | d.fb = VK_NULL_HANDLE; |
7087 | |
7088 | for (int att = 0; att < QVkRenderTargetData::MAX_COLOR_ATTACHMENTS; ++att) { |
7089 | e.textureRenderTarget.rtv[att] = rtv[att]; |
7090 | e.textureRenderTarget.resrtv[att] = resrtv[att]; |
7091 | rtv[att] = VK_NULL_HANDLE; |
7092 | resrtv[att] = VK_NULL_HANDLE; |
7093 | } |
7094 | |
7095 | e.textureRenderTarget.dsv = dsv; |
7096 | dsv = VK_NULL_HANDLE; |
7097 | e.textureRenderTarget.resdsv = resdsv; |
7098 | resdsv = VK_NULL_HANDLE; |
7099 | |
7100 | QRHI_RES_RHI(QRhiVulkan); |
7101 | if (rhiD) { |
7102 | rhiD->releaseQueue.append(t: e); |
7103 | rhiD->unregisterResource(res: this); |
7104 | } |
7105 | } |
7106 | |
7107 | QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescriptor() |
7108 | { |
7109 | // not yet built so cannot rely on data computed in create() |
7110 | |
7111 | QRHI_RES_RHI(QRhiVulkan); |
7112 | QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi); |
7113 | if (!rhiD->createOffscreenRenderPass(rpD: rp, |
7114 | colorAttachmentsBegin: m_desc.cbeginColorAttachments(), |
7115 | colorAttachmentsEnd: m_desc.cendColorAttachments(), |
7116 | preserveColor: m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveColorContents), |
7117 | preserveDs: m_flags.testFlag(flag: QRhiTextureRenderTarget::PreserveDepthStencilContents), |
7118 | storeDs: m_desc.depthTexture() && !m_flags.testFlag(flag: DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(), |
7119 | depthStencilBuffer: m_desc.depthStencilBuffer(), |
7120 | depthTexture: m_desc.depthTexture(), |
7121 | depthResolveTexture: m_desc.depthResolveTexture())) |
7122 | { |
7123 | delete rp; |
7124 | return nullptr; |
7125 | } |
7126 | |
7127 | rp->ownsRp = true; |
7128 | rp->updateSerializedFormat(); |
7129 | rhiD->registerResource(res: rp); |
7130 | return rp; |
7131 | } |
7132 | |
7133 | bool QVkTextureRenderTarget::create() |
7134 | { |
7135 | if (d.fb) |
7136 | destroy(); |
7137 | |
7138 | Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture()); |
7139 | Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture()); |
7140 | const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture(); |
7141 | |
7142 | QRHI_RES_RHI(QRhiVulkan); |
7143 | QVarLengthArray<VkImageView, 8> views; |
7144 | d.multiViewCount = 0; |
7145 | |
7146 | d.colorAttCount = 0; |
7147 | int attIndex = 0; |
7148 | for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { |
7149 | d.colorAttCount += 1; |
7150 | QVkTexture *texD = QRHI_RES(QVkTexture, it->texture()); |
7151 | QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer()); |
7152 | Q_ASSERT(texD || rbD); |
7153 | if (texD) { |
7154 | Q_ASSERT(texD->flags().testFlag(QRhiTexture::RenderTarget)); |
7155 | const bool is1D = texD->flags().testFlag(flag: QRhiTexture::OneDimensional); |
7156 | const bool isMultiView = it->multiViewCount() >= 2; |
7157 | if (isMultiView && d.multiViewCount == 0) |
7158 | d.multiViewCount = it->multiViewCount(); |
7159 | VkImageViewCreateInfo viewInfo = {}; |
7160 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
7161 | viewInfo.image = texD->image; |
7162 | viewInfo.viewType = is1D ? VK_IMAGE_VIEW_TYPE_1D |
7163 | : (isMultiView ? VK_IMAGE_VIEW_TYPE_2D_ARRAY |
7164 | : VK_IMAGE_VIEW_TYPE_2D); |
7165 | viewInfo.format = texD->viewFormat; |
7166 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
7167 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
7168 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
7169 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
7170 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
7171 | viewInfo.subresourceRange.baseMipLevel = uint32_t(it->level()); |
7172 | viewInfo.subresourceRange.levelCount = 1; |
7173 | viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->layer()); |
7174 | viewInfo.subresourceRange.layerCount = uint32_t(isMultiView ? it->multiViewCount() : 1); |
7175 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &rtv[attIndex]); |
7176 | if (err != VK_SUCCESS) { |
7177 | qWarning(msg: "Failed to create render target image view: %d" , err); |
7178 | return false; |
7179 | } |
7180 | views.append(t: rtv[attIndex]); |
7181 | if (attIndex == 0) { |
7182 | d.pixelSize = rhiD->q->sizeForMipLevel(mipLevel: it->level(), baseLevelSize: texD->pixelSize()); |
7183 | d.sampleCount = texD->samples; |
7184 | } |
7185 | } else if (rbD) { |
7186 | Q_ASSERT(rbD->backingTexture); |
7187 | views.append(t: rbD->backingTexture->imageView); |
7188 | if (attIndex == 0) { |
7189 | d.pixelSize = rbD->pixelSize(); |
7190 | d.sampleCount = rbD->samples; |
7191 | } |
7192 | } |
7193 | } |
7194 | d.dpr = 1; |
7195 | |
7196 | if (hasDepthStencil) { |
7197 | if (m_desc.depthTexture()) { |
7198 | QVkTexture *depthTexD = QRHI_RES(QVkTexture, m_desc.depthTexture()); |
7199 | // need a dedicated view just because viewFormat may differ from vkformat |
7200 | VkImageViewCreateInfo viewInfo = {}; |
7201 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
7202 | viewInfo.image = depthTexD->image; |
7203 | viewInfo.viewType = d.multiViewCount > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; |
7204 | viewInfo.format = depthTexD->viewFormat; |
7205 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
7206 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
7207 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
7208 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
7209 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; |
7210 | viewInfo.subresourceRange.levelCount = 1; |
7211 | viewInfo.subresourceRange.layerCount = qMax<uint32_t>(a: 1, b: d.multiViewCount); |
7212 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &dsv); |
7213 | if (err != VK_SUCCESS) { |
7214 | qWarning(msg: "Failed to create depth-stencil image view for rt: %d" , err); |
7215 | return false; |
7216 | } |
7217 | views.append(t: dsv); |
7218 | if (d.colorAttCount == 0) { |
7219 | d.pixelSize = depthTexD->pixelSize(); |
7220 | d.sampleCount = depthTexD->samples; |
7221 | } |
7222 | } else { |
7223 | QVkRenderBuffer *depthRbD = QRHI_RES(QVkRenderBuffer, m_desc.depthStencilBuffer()); |
7224 | views.append(t: depthRbD->imageView); |
7225 | if (d.colorAttCount == 0) { |
7226 | d.pixelSize = depthRbD->pixelSize(); |
7227 | d.sampleCount = depthRbD->samples; |
7228 | } |
7229 | } |
7230 | d.dsAttCount = 1; |
7231 | } else { |
7232 | d.dsAttCount = 0; |
7233 | } |
7234 | |
7235 | d.resolveAttCount = 0; |
7236 | attIndex = 0; |
7237 | Q_ASSERT(d.multiViewCount == 0 || d.multiViewCount >= 2); |
7238 | for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) { |
7239 | if (it->resolveTexture()) { |
7240 | QVkTexture *resTexD = QRHI_RES(QVkTexture, it->resolveTexture()); |
7241 | Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget)); |
7242 | d.resolveAttCount += 1; |
7243 | |
7244 | VkImageViewCreateInfo viewInfo = {}; |
7245 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
7246 | viewInfo.image = resTexD->image; |
7247 | viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY |
7248 | : VK_IMAGE_VIEW_TYPE_2D; |
7249 | viewInfo.format = resTexD->viewFormat; |
7250 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
7251 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
7252 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
7253 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
7254 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
7255 | viewInfo.subresourceRange.baseMipLevel = uint32_t(it->resolveLevel()); |
7256 | viewInfo.subresourceRange.levelCount = 1; |
7257 | viewInfo.subresourceRange.baseArrayLayer = uint32_t(it->resolveLayer()); |
7258 | viewInfo.subresourceRange.layerCount = qMax<uint32_t>(a: 1, b: d.multiViewCount); |
7259 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resrtv[attIndex]); |
7260 | if (err != VK_SUCCESS) { |
7261 | qWarning(msg: "Failed to create render target resolve image view: %d" , err); |
7262 | return false; |
7263 | } |
7264 | views.append(t: resrtv[attIndex]); |
7265 | } |
7266 | } |
7267 | |
7268 | if (m_desc.depthResolveTexture()) { |
7269 | QVkTexture *resTexD = QRHI_RES(QVkTexture, m_desc.depthResolveTexture()); |
7270 | Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget)); |
7271 | |
7272 | VkImageViewCreateInfo viewInfo = {}; |
7273 | viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; |
7274 | viewInfo.image = resTexD->image; |
7275 | viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY |
7276 | : VK_IMAGE_VIEW_TYPE_2D; |
7277 | viewInfo.format = resTexD->viewFormat; |
7278 | viewInfo.components.r = VK_COMPONENT_SWIZZLE_R; |
7279 | viewInfo.components.g = VK_COMPONENT_SWIZZLE_G; |
7280 | viewInfo.components.b = VK_COMPONENT_SWIZZLE_B; |
7281 | viewInfo.components.a = VK_COMPONENT_SWIZZLE_A; |
7282 | viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; |
7283 | viewInfo.subresourceRange.baseMipLevel = 0; |
7284 | viewInfo.subresourceRange.levelCount = 1; |
7285 | viewInfo.subresourceRange.baseArrayLayer = 0; |
7286 | viewInfo.subresourceRange.layerCount = qMax<uint32_t>(a: 1, b: d.multiViewCount); |
7287 | VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resdsv); |
7288 | if (err != VK_SUCCESS) { |
7289 | qWarning(msg: "Failed to create render target depth resolve image view: %d" , err); |
7290 | return false; |
7291 | } |
7292 | views.append(t: resdsv); |
7293 | d.dsResolveAttCount = 1; |
7294 | } else { |
7295 | d.dsResolveAttCount = 0; |
7296 | } |
7297 | |
7298 | if (!m_renderPassDesc) |
7299 | qWarning(msg: "QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()." ); |
7300 | |
7301 | d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); |
7302 | Q_ASSERT(d.rp && d.rp->rp); |
7303 | |
7304 | VkFramebufferCreateInfo fbInfo = {}; |
7305 | fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; |
7306 | fbInfo.renderPass = d.rp->rp; |
7307 | fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount + d.dsResolveAttCount); |
7308 | fbInfo.pAttachments = views.constData(); |
7309 | fbInfo.width = uint32_t(d.pixelSize.width()); |
7310 | fbInfo.height = uint32_t(d.pixelSize.height()); |
7311 | fbInfo.layers = 1; |
7312 | |
7313 | VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &d.fb); |
7314 | if (err != VK_SUCCESS) { |
7315 | qWarning(msg: "Failed to create framebuffer: %d" , err); |
7316 | return false; |
7317 | } |
7318 | |
7319 | QRhiRenderTargetAttachmentTracker::updateResIdList<QVkTexture, QVkRenderBuffer>(desc: m_desc, dst: &d.currentResIdList); |
7320 | |
7321 | lastActiveFrameSlot = -1; |
7322 | rhiD->registerResource(res: this); |
7323 | return true; |
7324 | } |
7325 | |
7326 | QSize QVkTextureRenderTarget::pixelSize() const |
7327 | { |
7328 | if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QVkTexture, QVkRenderBuffer>(desc: m_desc, currentResIdList: d.currentResIdList)) |
7329 | const_cast<QVkTextureRenderTarget *>(this)->create(); |
7330 | |
7331 | return d.pixelSize; |
7332 | } |
7333 | |
7334 | float QVkTextureRenderTarget::devicePixelRatio() const |
7335 | { |
7336 | return d.dpr; |
7337 | } |
7338 | |
7339 | int QVkTextureRenderTarget::sampleCount() const |
7340 | { |
7341 | return d.sampleCount; |
7342 | } |
7343 | |
7344 | QVkShaderResourceBindings::QVkShaderResourceBindings(QRhiImplementation *rhi) |
7345 | : QRhiShaderResourceBindings(rhi) |
7346 | { |
7347 | } |
7348 | |
7349 | QVkShaderResourceBindings::~QVkShaderResourceBindings() |
7350 | { |
7351 | destroy(); |
7352 | } |
7353 | |
7354 | void QVkShaderResourceBindings::destroy() |
7355 | { |
7356 | if (!layout) |
7357 | return; |
7358 | |
7359 | sortedBindings.clear(); |
7360 | |
7361 | QRhiVulkan::DeferredReleaseEntry e; |
7362 | e.type = QRhiVulkan::DeferredReleaseEntry::ShaderResourceBindings; |
7363 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
7364 | |
7365 | e.shaderResourceBindings.poolIndex = poolIndex; |
7366 | e.shaderResourceBindings.layout = layout; |
7367 | |
7368 | poolIndex = -1; |
7369 | layout = VK_NULL_HANDLE; |
7370 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
7371 | descSets[i] = VK_NULL_HANDLE; |
7372 | |
7373 | QRHI_RES_RHI(QRhiVulkan); |
7374 | if (rhiD) { |
7375 | rhiD->releaseQueue.append(t: e); |
7376 | rhiD->unregisterResource(res: this); |
7377 | } |
7378 | } |
7379 | |
7380 | bool QVkShaderResourceBindings::create() |
7381 | { |
7382 | if (layout) |
7383 | destroy(); |
7384 | |
7385 | QRHI_RES_RHI(QRhiVulkan); |
7386 | if (!rhiD->sanityCheckShaderResourceBindings(srb: this)) |
7387 | return false; |
7388 | |
7389 | rhiD->updateLayoutDesc(srb: this); |
7390 | |
7391 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
7392 | descSets[i] = VK_NULL_HANDLE; |
7393 | |
7394 | sortedBindings.clear(); |
7395 | std::copy(first: m_bindings.cbegin(), last: m_bindings.cend(), result: std::back_inserter(x&: sortedBindings)); |
7396 | std::sort(first: sortedBindings.begin(), last: sortedBindings.end(), comp: QRhiImplementation::sortedBindingLessThan); |
7397 | |
7398 | hasSlottedResource = false; |
7399 | hasDynamicOffset = false; |
7400 | for (const QRhiShaderResourceBinding &binding : std::as_const(t&: sortedBindings)) { |
7401 | const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding); |
7402 | if (b->type == QRhiShaderResourceBinding::UniformBuffer && b->u.ubuf.buf) { |
7403 | if (QRHI_RES(QVkBuffer, b->u.ubuf.buf)->type() == QRhiBuffer::Dynamic) |
7404 | hasSlottedResource = true; |
7405 | if (b->u.ubuf.hasDynamicOffset) |
7406 | hasDynamicOffset = true; |
7407 | } |
7408 | } |
7409 | |
7410 | QVarLengthArray<VkDescriptorSetLayoutBinding, 4> vkbindings; |
7411 | for (const QRhiShaderResourceBinding &binding : std::as_const(t&: sortedBindings)) { |
7412 | const QRhiShaderResourceBinding::Data *b = QRhiImplementation::shaderResourceBindingData(binding); |
7413 | VkDescriptorSetLayoutBinding vkbinding = {}; |
7414 | vkbinding.binding = uint32_t(b->binding); |
7415 | vkbinding.descriptorType = toVkDescriptorType(b); |
7416 | if (b->type == QRhiShaderResourceBinding::SampledTexture || b->type == QRhiShaderResourceBinding::Texture) |
7417 | vkbinding.descriptorCount = b->u.stex.count; |
7418 | else |
7419 | vkbinding.descriptorCount = 1; |
7420 | vkbinding.stageFlags = toVkShaderStageFlags(stage: b->stage); |
7421 | vkbindings.append(t: vkbinding); |
7422 | } |
7423 | |
7424 | VkDescriptorSetLayoutCreateInfo layoutInfo = {}; |
7425 | layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; |
7426 | layoutInfo.bindingCount = uint32_t(vkbindings.size()); |
7427 | layoutInfo.pBindings = vkbindings.constData(); |
7428 | |
7429 | VkResult err = rhiD->df->vkCreateDescriptorSetLayout(rhiD->dev, &layoutInfo, nullptr, &layout); |
7430 | if (err != VK_SUCCESS) { |
7431 | qWarning(msg: "Failed to create descriptor set layout: %d" , err); |
7432 | return false; |
7433 | } |
7434 | |
7435 | VkDescriptorSetAllocateInfo allocInfo = {}; |
7436 | allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; |
7437 | allocInfo.descriptorSetCount = QVK_FRAMES_IN_FLIGHT; |
7438 | VkDescriptorSetLayout layouts[QVK_FRAMES_IN_FLIGHT]; |
7439 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) |
7440 | layouts[i] = layout; |
7441 | allocInfo.pSetLayouts = layouts; |
7442 | if (!rhiD->allocateDescriptorSet(allocInfo: &allocInfo, result: descSets, resultPoolIndex: &poolIndex)) |
7443 | return false; |
7444 | |
7445 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
7446 | boundResourceData[i].resize(sz: sortedBindings.size()); |
7447 | for (BoundResourceData &bd : boundResourceData[i]) |
7448 | memset(s: &bd, c: 0, n: sizeof(BoundResourceData)); |
7449 | } |
7450 | |
7451 | lastActiveFrameSlot = -1; |
7452 | generation += 1; |
7453 | rhiD->registerResource(res: this); |
7454 | return true; |
7455 | } |
7456 | |
7457 | void QVkShaderResourceBindings::updateResources(UpdateFlags flags) |
7458 | { |
7459 | sortedBindings.clear(); |
7460 | std::copy(first: m_bindings.cbegin(), last: m_bindings.cend(), result: std::back_inserter(x&: sortedBindings)); |
7461 | if (!flags.testFlag(flag: BindingsAreSorted)) |
7462 | std::sort(first: sortedBindings.begin(), last: sortedBindings.end(), comp: QRhiImplementation::sortedBindingLessThan); |
7463 | |
7464 | // Reset the state tracking table too - it can deal with assigning a |
7465 | // different QRhiBuffer/Texture/Sampler for a binding point, but it cannot |
7466 | // detect changes in the associated data, such as the buffer offset. And |
7467 | // just like after a create(), a call to updateResources() may lead to now |
7468 | // specifying a different offset for the same QRhiBuffer for a given binding |
7469 | // point. The same applies to other type of associated data that is not part |
7470 | // of the layout, such as the mip level for a StorageImage. Instead of |
7471 | // complicating the checks in setShaderResources(), reset the table here |
7472 | // just like we do in create(). |
7473 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
7474 | Q_ASSERT(boundResourceData[i].size() == sortedBindings.size()); |
7475 | for (BoundResourceData &bd : boundResourceData[i]) |
7476 | memset(s: &bd, c: 0, n: sizeof(BoundResourceData)); |
7477 | } |
7478 | |
7479 | generation += 1; |
7480 | } |
7481 | |
7482 | QVkGraphicsPipeline::QVkGraphicsPipeline(QRhiImplementation *rhi) |
7483 | : QRhiGraphicsPipeline(rhi) |
7484 | { |
7485 | } |
7486 | |
7487 | QVkGraphicsPipeline::~QVkGraphicsPipeline() |
7488 | { |
7489 | destroy(); |
7490 | } |
7491 | |
7492 | void QVkGraphicsPipeline::destroy() |
7493 | { |
7494 | if (!pipeline && !layout) |
7495 | return; |
7496 | |
7497 | QRhiVulkan::DeferredReleaseEntry e; |
7498 | e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline; |
7499 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
7500 | |
7501 | e.pipelineState.pipeline = pipeline; |
7502 | e.pipelineState.layout = layout; |
7503 | |
7504 | pipeline = VK_NULL_HANDLE; |
7505 | layout = VK_NULL_HANDLE; |
7506 | |
7507 | QRHI_RES_RHI(QRhiVulkan); |
7508 | if (rhiD) { |
7509 | rhiD->releaseQueue.append(t: e); |
7510 | rhiD->unregisterResource(res: this); |
7511 | } |
7512 | } |
7513 | |
7514 | bool QVkGraphicsPipeline::create() |
7515 | { |
7516 | if (pipeline) |
7517 | destroy(); |
7518 | |
7519 | QRHI_RES_RHI(QRhiVulkan); |
7520 | rhiD->pipelineCreationStart(); |
7521 | if (!rhiD->sanityCheckGraphicsPipeline(ps: this)) |
7522 | return false; |
7523 | |
7524 | if (!rhiD->ensurePipelineCache()) |
7525 | return false; |
7526 | |
7527 | VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; |
7528 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; |
7529 | pipelineLayoutInfo.setLayoutCount = 1; |
7530 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings); |
7531 | Q_ASSERT(m_shaderResourceBindings && srbD->layout); |
7532 | pipelineLayoutInfo.pSetLayouts = &srbD->layout; |
7533 | VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout); |
7534 | if (err != VK_SUCCESS) { |
7535 | qWarning(msg: "Failed to create pipeline layout: %d" , err); |
7536 | return false; |
7537 | } |
7538 | |
7539 | VkGraphicsPipelineCreateInfo pipelineInfo = {}; |
7540 | pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; |
7541 | |
7542 | QVarLengthArray<VkShaderModule, 4> shaders; |
7543 | QVarLengthArray<VkPipelineShaderStageCreateInfo, 4> shaderStageCreateInfos; |
7544 | for (const QRhiShaderStage &shaderStage : m_shaderStages) { |
7545 | const QShader bakedShader = shaderStage.shader(); |
7546 | const QShaderCode spirv = bakedShader.shader(key: { QShader::SpirvShader, 100, shaderStage.shaderVariant() }); |
7547 | if (spirv.shader().isEmpty()) { |
7548 | qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader; |
7549 | return false; |
7550 | } |
7551 | VkShaderModule shader = rhiD->createShader(spirv: spirv.shader()); |
7552 | if (shader) { |
7553 | shaders.append(t: shader); |
7554 | VkPipelineShaderStageCreateInfo shaderInfo = {}; |
7555 | shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; |
7556 | shaderInfo.stage = toVkShaderStage(type: shaderStage.type()); |
7557 | shaderInfo.module = shader; |
7558 | shaderInfo.pName = spirv.entryPoint().constData(); |
7559 | shaderStageCreateInfos.append(t: shaderInfo); |
7560 | } |
7561 | } |
7562 | pipelineInfo.stageCount = uint32_t(shaderStageCreateInfos.size()); |
7563 | pipelineInfo.pStages = shaderStageCreateInfos.constData(); |
7564 | |
7565 | QVarLengthArray<VkVertexInputBindingDescription, 4> vertexBindings; |
7566 | #ifdef VK_EXT_vertex_attribute_divisor |
7567 | QVarLengthArray<VkVertexInputBindingDivisorDescriptionEXT> nonOneStepRates; |
7568 | #endif |
7569 | int bindingIndex = 0; |
7570 | for (auto it = m_vertexInputLayout.cbeginBindings(), itEnd = m_vertexInputLayout.cendBindings(); |
7571 | it != itEnd; ++it, ++bindingIndex) |
7572 | { |
7573 | VkVertexInputBindingDescription bindingInfo = { |
7574 | .binding: uint32_t(bindingIndex), |
7575 | .stride: it->stride(), |
7576 | .inputRate: it->classification() == QRhiVertexInputBinding::PerVertex |
7577 | ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE |
7578 | }; |
7579 | if (it->classification() == QRhiVertexInputBinding::PerInstance && it->instanceStepRate() != 1) { |
7580 | #ifdef VK_EXT_vertex_attribute_divisor |
7581 | if (rhiD->caps.vertexAttribDivisor) { |
7582 | nonOneStepRates.append(t: { .binding: uint32_t(bindingIndex), .divisor: it->instanceStepRate() }); |
7583 | } else |
7584 | #endif |
7585 | { |
7586 | qWarning(msg: "QRhiVulkan: Instance step rates other than 1 not supported without " |
7587 | "VK_EXT_vertex_attribute_divisor on the device and " |
7588 | "VK_KHR_get_physical_device_properties2 on the instance" ); |
7589 | } |
7590 | } |
7591 | vertexBindings.append(t: bindingInfo); |
7592 | } |
7593 | QVarLengthArray<VkVertexInputAttributeDescription, 4> vertexAttributes; |
7594 | for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes(); |
7595 | it != itEnd; ++it) |
7596 | { |
7597 | VkVertexInputAttributeDescription attributeInfo = { |
7598 | .location: uint32_t(it->location()), |
7599 | .binding: uint32_t(it->binding()), |
7600 | .format: toVkAttributeFormat(format: it->format()), |
7601 | .offset: it->offset() |
7602 | }; |
7603 | vertexAttributes.append(t: attributeInfo); |
7604 | } |
7605 | VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; |
7606 | vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; |
7607 | vertexInputInfo.vertexBindingDescriptionCount = uint32_t(vertexBindings.size()); |
7608 | vertexInputInfo.pVertexBindingDescriptions = vertexBindings.constData(); |
7609 | vertexInputInfo.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.size()); |
7610 | vertexInputInfo.pVertexAttributeDescriptions = vertexAttributes.constData(); |
7611 | #ifdef VK_EXT_vertex_attribute_divisor |
7612 | VkPipelineVertexInputDivisorStateCreateInfoEXT divisorInfo = {}; |
7613 | if (!nonOneStepRates.isEmpty()) { |
7614 | divisorInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT; |
7615 | divisorInfo.vertexBindingDivisorCount = uint32_t(nonOneStepRates.size()); |
7616 | divisorInfo.pVertexBindingDivisors = nonOneStepRates.constData(); |
7617 | vertexInputInfo.pNext = &divisorInfo; |
7618 | } |
7619 | #endif |
7620 | pipelineInfo.pVertexInputState = &vertexInputInfo; |
7621 | |
7622 | QVarLengthArray<VkDynamicState, 8> dynEnable; |
7623 | dynEnable << VK_DYNAMIC_STATE_VIEWPORT; |
7624 | dynEnable << VK_DYNAMIC_STATE_SCISSOR; // ignore UsesScissor - Vulkan requires a scissor for the viewport always |
7625 | if (m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesBlendConstants)) |
7626 | dynEnable << VK_DYNAMIC_STATE_BLEND_CONSTANTS; |
7627 | if (m_flags.testFlag(flag: QRhiGraphicsPipeline::UsesStencilRef)) |
7628 | dynEnable << VK_DYNAMIC_STATE_STENCIL_REFERENCE; |
7629 | |
7630 | VkPipelineDynamicStateCreateInfo dynamicInfo = {}; |
7631 | dynamicInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; |
7632 | dynamicInfo.dynamicStateCount = uint32_t(dynEnable.size()); |
7633 | dynamicInfo.pDynamicStates = dynEnable.constData(); |
7634 | pipelineInfo.pDynamicState = &dynamicInfo; |
7635 | |
7636 | VkPipelineViewportStateCreateInfo viewportInfo = {}; |
7637 | viewportInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; |
7638 | viewportInfo.viewportCount = viewportInfo.scissorCount = 1; |
7639 | pipelineInfo.pViewportState = &viewportInfo; |
7640 | |
7641 | VkPipelineInputAssemblyStateCreateInfo inputAsmInfo = {}; |
7642 | inputAsmInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; |
7643 | inputAsmInfo.topology = toVkTopology(t: m_topology); |
7644 | inputAsmInfo.primitiveRestartEnable = (m_topology == TriangleStrip || m_topology == LineStrip); |
7645 | pipelineInfo.pInputAssemblyState = &inputAsmInfo; |
7646 | |
7647 | VkPipelineTessellationStateCreateInfo tessInfo = {}; |
7648 | #ifdef VK_VERSION_1_1 |
7649 | VkPipelineTessellationDomainOriginStateCreateInfo originInfo = {}; |
7650 | #endif |
7651 | if (m_topology == Patches) { |
7652 | tessInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; |
7653 | tessInfo.patchControlPoints = uint32_t(qMax(a: 1, b: m_patchControlPointCount)); |
7654 | |
7655 | // To be able to use the same tess.evaluation shader with both OpenGL |
7656 | // and Vulkan, flip the tessellation domain origin to be lower left. |
7657 | // This allows declaring the winding order in the shader to be CCW and |
7658 | // still have it working with both APIs. This requires Vulkan 1.1 (or |
7659 | // VK_KHR_maintenance2 but don't bother with that). |
7660 | #ifdef VK_VERSION_1_1 |
7661 | if (rhiD->caps.apiVersion >= QVersionNumber(1, 1)) { |
7662 | originInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_DOMAIN_ORIGIN_STATE_CREATE_INFO; |
7663 | originInfo.domainOrigin = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT; |
7664 | tessInfo.pNext = &originInfo; |
7665 | } else { |
7666 | qWarning(msg: "Proper tessellation support requires Vulkan 1.1 or newer, leaving domain origin unset" ); |
7667 | } |
7668 | #else |
7669 | qWarning("QRhi was built without Vulkan 1.1 headers, this is not sufficient for proper tessellation support" ); |
7670 | #endif |
7671 | |
7672 | pipelineInfo.pTessellationState = &tessInfo; |
7673 | } |
7674 | |
7675 | VkPipelineRasterizationStateCreateInfo rastInfo = {}; |
7676 | rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; |
7677 | rastInfo.cullMode = toVkCullMode(c: m_cullMode); |
7678 | rastInfo.frontFace = toVkFrontFace(f: m_frontFace); |
7679 | if (m_depthBias != 0 || !qFuzzyIsNull(f: m_slopeScaledDepthBias)) { |
7680 | rastInfo.depthBiasEnable = true; |
7681 | rastInfo.depthBiasConstantFactor = float(m_depthBias); |
7682 | rastInfo.depthBiasSlopeFactor = m_slopeScaledDepthBias; |
7683 | } |
7684 | rastInfo.lineWidth = rhiD->caps.wideLines ? m_lineWidth : 1.0f; |
7685 | rastInfo.polygonMode = toVkPolygonMode(mode: m_polygonMode); |
7686 | pipelineInfo.pRasterizationState = &rastInfo; |
7687 | |
7688 | VkPipelineMultisampleStateCreateInfo msInfo = {}; |
7689 | msInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; |
7690 | msInfo.rasterizationSamples = rhiD->effectiveSampleCountBits(sampleCount: m_sampleCount); |
7691 | pipelineInfo.pMultisampleState = &msInfo; |
7692 | |
7693 | VkPipelineDepthStencilStateCreateInfo dsInfo = {}; |
7694 | dsInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; |
7695 | dsInfo.depthTestEnable = m_depthTest; |
7696 | dsInfo.depthWriteEnable = m_depthWrite; |
7697 | dsInfo.depthCompareOp = toVkCompareOp(op: m_depthOp); |
7698 | dsInfo.stencilTestEnable = m_stencilTest; |
7699 | if (m_stencilTest) { |
7700 | fillVkStencilOpState(dst: &dsInfo.front, src: m_stencilFront); |
7701 | dsInfo.front.compareMask = m_stencilReadMask; |
7702 | dsInfo.front.writeMask = m_stencilWriteMask; |
7703 | fillVkStencilOpState(dst: &dsInfo.back, src: m_stencilBack); |
7704 | dsInfo.back.compareMask = m_stencilReadMask; |
7705 | dsInfo.back.writeMask = m_stencilWriteMask; |
7706 | } |
7707 | pipelineInfo.pDepthStencilState = &dsInfo; |
7708 | |
7709 | VkPipelineColorBlendStateCreateInfo blendInfo = {}; |
7710 | blendInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; |
7711 | QVarLengthArray<VkPipelineColorBlendAttachmentState, 4> vktargetBlends; |
7712 | for (const QRhiGraphicsPipeline::TargetBlend &b : std::as_const(t&: m_targetBlends)) { |
7713 | VkPipelineColorBlendAttachmentState blend = {}; |
7714 | blend.blendEnable = b.enable; |
7715 | blend.srcColorBlendFactor = toVkBlendFactor(f: b.srcColor); |
7716 | blend.dstColorBlendFactor = toVkBlendFactor(f: b.dstColor); |
7717 | blend.colorBlendOp = toVkBlendOp(op: b.opColor); |
7718 | blend.srcAlphaBlendFactor = toVkBlendFactor(f: b.srcAlpha); |
7719 | blend.dstAlphaBlendFactor = toVkBlendFactor(f: b.dstAlpha); |
7720 | blend.alphaBlendOp = toVkBlendOp(op: b.opAlpha); |
7721 | blend.colorWriteMask = toVkColorComponents(c: b.colorWrite); |
7722 | vktargetBlends.append(t: blend); |
7723 | } |
7724 | if (vktargetBlends.isEmpty()) { |
7725 | VkPipelineColorBlendAttachmentState blend = {}; |
7726 | blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
7727 | | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; |
7728 | vktargetBlends.append(t: blend); |
7729 | } |
7730 | blendInfo.attachmentCount = uint32_t(vktargetBlends.size()); |
7731 | blendInfo.pAttachments = vktargetBlends.constData(); |
7732 | pipelineInfo.pColorBlendState = &blendInfo; |
7733 | |
7734 | pipelineInfo.layout = layout; |
7735 | |
7736 | Q_ASSERT(m_renderPassDesc && QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp); |
7737 | pipelineInfo.renderPass = QRHI_RES(const QVkRenderPassDescriptor, m_renderPassDesc)->rp; |
7738 | |
7739 | err = rhiD->df->vkCreateGraphicsPipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); |
7740 | |
7741 | for (VkShaderModule shader : shaders) |
7742 | rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr); |
7743 | |
7744 | if (err != VK_SUCCESS) { |
7745 | qWarning(msg: "Failed to create graphics pipeline: %d" , err); |
7746 | return false; |
7747 | } |
7748 | |
7749 | rhiD->pipelineCreationEnd(); |
7750 | lastActiveFrameSlot = -1; |
7751 | generation += 1; |
7752 | rhiD->registerResource(res: this); |
7753 | return true; |
7754 | } |
7755 | |
7756 | QVkComputePipeline::QVkComputePipeline(QRhiImplementation *rhi) |
7757 | : QRhiComputePipeline(rhi) |
7758 | { |
7759 | } |
7760 | |
7761 | QVkComputePipeline::~QVkComputePipeline() |
7762 | { |
7763 | destroy(); |
7764 | } |
7765 | |
7766 | void QVkComputePipeline::destroy() |
7767 | { |
7768 | if (!pipeline && !layout) |
7769 | return; |
7770 | |
7771 | QRhiVulkan::DeferredReleaseEntry e; |
7772 | e.type = QRhiVulkan::DeferredReleaseEntry::Pipeline; |
7773 | e.lastActiveFrameSlot = lastActiveFrameSlot; |
7774 | |
7775 | e.pipelineState.pipeline = pipeline; |
7776 | e.pipelineState.layout = layout; |
7777 | |
7778 | pipeline = VK_NULL_HANDLE; |
7779 | layout = VK_NULL_HANDLE; |
7780 | |
7781 | QRHI_RES_RHI(QRhiVulkan); |
7782 | if (rhiD) { |
7783 | rhiD->releaseQueue.append(t: e); |
7784 | rhiD->unregisterResource(res: this); |
7785 | } |
7786 | } |
7787 | |
7788 | bool QVkComputePipeline::create() |
7789 | { |
7790 | if (pipeline) |
7791 | destroy(); |
7792 | |
7793 | QRHI_RES_RHI(QRhiVulkan); |
7794 | rhiD->pipelineCreationStart(); |
7795 | if (!rhiD->ensurePipelineCache()) |
7796 | return false; |
7797 | |
7798 | VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; |
7799 | pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; |
7800 | pipelineLayoutInfo.setLayoutCount = 1; |
7801 | QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, m_shaderResourceBindings); |
7802 | Q_ASSERT(m_shaderResourceBindings && srbD->layout); |
7803 | pipelineLayoutInfo.pSetLayouts = &srbD->layout; |
7804 | VkResult err = rhiD->df->vkCreatePipelineLayout(rhiD->dev, &pipelineLayoutInfo, nullptr, &layout); |
7805 | if (err != VK_SUCCESS) { |
7806 | qWarning(msg: "Failed to create pipeline layout: %d" , err); |
7807 | return false; |
7808 | } |
7809 | |
7810 | VkComputePipelineCreateInfo pipelineInfo = {}; |
7811 | pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; |
7812 | pipelineInfo.layout = layout; |
7813 | |
7814 | if (m_shaderStage.type() != QRhiShaderStage::Compute) { |
7815 | qWarning(msg: "Compute pipeline requires a compute shader stage" ); |
7816 | return false; |
7817 | } |
7818 | const QShader bakedShader = m_shaderStage.shader(); |
7819 | const QShaderCode spirv = bakedShader.shader(key: { QShader::SpirvShader, 100, m_shaderStage.shaderVariant() }); |
7820 | if (spirv.shader().isEmpty()) { |
7821 | qWarning() << "No SPIR-V 1.0 shader code found in baked shader" << bakedShader; |
7822 | return false; |
7823 | } |
7824 | if (bakedShader.stage() != QShader::ComputeStage) { |
7825 | qWarning() << bakedShader << "is not a compute shader" ; |
7826 | return false; |
7827 | } |
7828 | VkShaderModule shader = rhiD->createShader(spirv: spirv.shader()); |
7829 | VkPipelineShaderStageCreateInfo shaderInfo = {}; |
7830 | shaderInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; |
7831 | shaderInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; |
7832 | shaderInfo.module = shader; |
7833 | shaderInfo.pName = spirv.entryPoint().constData(); |
7834 | pipelineInfo.stage = shaderInfo; |
7835 | |
7836 | err = rhiD->df->vkCreateComputePipelines(rhiD->dev, rhiD->pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); |
7837 | rhiD->df->vkDestroyShaderModule(rhiD->dev, shader, nullptr); |
7838 | if (err != VK_SUCCESS) { |
7839 | qWarning(msg: "Failed to create graphics pipeline: %d" , err); |
7840 | return false; |
7841 | } |
7842 | |
7843 | rhiD->pipelineCreationEnd(); |
7844 | lastActiveFrameSlot = -1; |
7845 | generation += 1; |
7846 | rhiD->registerResource(res: this); |
7847 | return true; |
7848 | } |
7849 | |
7850 | QVkCommandBuffer::QVkCommandBuffer(QRhiImplementation *rhi) |
7851 | : QRhiCommandBuffer(rhi) |
7852 | { |
7853 | resetState(); |
7854 | } |
7855 | |
7856 | QVkCommandBuffer::~QVkCommandBuffer() |
7857 | { |
7858 | destroy(); |
7859 | } |
7860 | |
7861 | void QVkCommandBuffer::destroy() |
7862 | { |
7863 | // nothing to do here, cb is not owned by us |
7864 | } |
7865 | |
7866 | const QRhiNativeHandles *QVkCommandBuffer::nativeHandles() |
7867 | { |
7868 | // Ok this is messy but no other way has been devised yet. Outside |
7869 | // begin(Compute)Pass - end(Compute)Pass it is simple - just return the |
7870 | // primary VkCommandBuffer. Inside, however, we need to provide the current |
7871 | // secondary command buffer (typically the one started by beginExternal(), |
7872 | // in case we are between beginExternal - endExternal inside a pass). |
7873 | |
7874 | if (recordingPass == QVkCommandBuffer::NoPass) { |
7875 | nativeHandlesStruct.commandBuffer = cb; |
7876 | } else { |
7877 | if (passUsesSecondaryCb && !activeSecondaryCbStack.isEmpty()) |
7878 | nativeHandlesStruct.commandBuffer = activeSecondaryCbStack.last(); |
7879 | else |
7880 | nativeHandlesStruct.commandBuffer = cb; |
7881 | } |
7882 | |
7883 | return &nativeHandlesStruct; |
7884 | } |
7885 | |
7886 | QVkSwapChain::QVkSwapChain(QRhiImplementation *rhi) |
7887 | : QRhiSwapChain(rhi), |
7888 | rtWrapper(rhi, this), |
7889 | rtWrapperRight(rhi, this), |
7890 | cbWrapper(rhi) |
7891 | { |
7892 | } |
7893 | |
7894 | QVkSwapChain::~QVkSwapChain() |
7895 | { |
7896 | destroy(); |
7897 | } |
7898 | |
7899 | void QVkSwapChain::destroy() |
7900 | { |
7901 | if (sc == VK_NULL_HANDLE) |
7902 | return; |
7903 | |
7904 | QRHI_RES_RHI(QRhiVulkan); |
7905 | if (rhiD) { |
7906 | rhiD->swapchains.remove(value: this); |
7907 | rhiD->releaseSwapChainResources(swapChain: this); |
7908 | } |
7909 | |
7910 | for (int i = 0; i < QVK_FRAMES_IN_FLIGHT; ++i) { |
7911 | QVkSwapChain::FrameResources &frame(frameRes[i]); |
7912 | frame.cmdBuf = VK_NULL_HANDLE; |
7913 | frame.timestampQueryIndex = -1; |
7914 | } |
7915 | |
7916 | surface = lastConnectedSurface = VK_NULL_HANDLE; |
7917 | |
7918 | if (rhiD) |
7919 | rhiD->unregisterResource(res: this); |
7920 | } |
7921 | |
7922 | QRhiCommandBuffer *QVkSwapChain::currentFrameCommandBuffer() |
7923 | { |
7924 | return &cbWrapper; |
7925 | } |
7926 | |
7927 | QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget() |
7928 | { |
7929 | return &rtWrapper; |
7930 | } |
7931 | |
7932 | QRhiRenderTarget *QVkSwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer) |
7933 | { |
7934 | return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight; |
7935 | } |
7936 | |
7937 | QSize QVkSwapChain::surfacePixelSize() |
7938 | { |
7939 | if (!ensureSurface()) |
7940 | return QSize(); |
7941 | |
7942 | // The size from the QWindow may not exactly match the surface... so if a |
7943 | // size is reported from the surface, use that. |
7944 | VkSurfaceCapabilitiesKHR surfaceCaps = {}; |
7945 | QRHI_RES_RHI(QRhiVulkan); |
7946 | rhiD->vkGetPhysicalDeviceSurfaceCapabilitiesKHR(rhiD->physDev, surface, &surfaceCaps); |
7947 | VkExtent2D bufferSize = surfaceCaps.currentExtent; |
7948 | if (bufferSize.width == uint32_t(-1)) { |
7949 | Q_ASSERT(bufferSize.height == uint32_t(-1)); |
7950 | return m_window->size() * m_window->devicePixelRatio(); |
7951 | } |
7952 | return QSize(int(bufferSize.width), int(bufferSize.height)); |
7953 | } |
7954 | |
7955 | static inline bool hdrFormatMatchesVkSurfaceFormat(QRhiSwapChain::Format f, const VkSurfaceFormatKHR &s) |
7956 | { |
7957 | switch (f) { |
7958 | case QRhiSwapChain::HDRExtendedSrgbLinear: |
7959 | return s.format == VK_FORMAT_R16G16B16A16_SFLOAT |
7960 | && s.colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT; |
7961 | case QRhiSwapChain::HDR10: |
7962 | return (s.format == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || s.format == VK_FORMAT_A2R10G10B10_UNORM_PACK32) |
7963 | && s.colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT; |
7964 | case QRhiSwapChain::HDRExtendedDisplayP3Linear: |
7965 | return s.format == VK_FORMAT_R16G16B16A16_SFLOAT |
7966 | && s.colorSpace == VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT; |
7967 | default: |
7968 | break; |
7969 | } |
7970 | return false; |
7971 | } |
7972 | |
7973 | bool QVkSwapChain::isFormatSupported(Format f) |
7974 | { |
7975 | if (f == SDR) |
7976 | return true; |
7977 | |
7978 | if (!m_window) { |
7979 | qWarning(msg: "Attempted to call isFormatSupported() without a window set" ); |
7980 | return false; |
7981 | } |
7982 | |
7983 | // we may be called before create so query the surface |
7984 | VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(window: m_window); |
7985 | |
7986 | QRHI_RES_RHI(QRhiVulkan); |
7987 | uint32_t formatCount = 0; |
7988 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surf, &formatCount, nullptr); |
7989 | QVarLengthArray<VkSurfaceFormatKHR, 8> formats(formatCount); |
7990 | if (formatCount) { |
7991 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surf, &formatCount, formats.data()); |
7992 | for (uint32_t i = 0; i < formatCount; ++i) { |
7993 | if (hdrFormatMatchesVkSurfaceFormat(f, s: formats[i])) |
7994 | return true; |
7995 | } |
7996 | } |
7997 | |
7998 | return false; |
7999 | } |
8000 | |
8001 | QRhiRenderPassDescriptor *QVkSwapChain::newCompatibleRenderPassDescriptor() |
8002 | { |
8003 | // not yet built so cannot rely on data computed in createOrResize() |
8004 | |
8005 | if (!ensureSurface()) // make sure sampleCount and colorFormat reflect what was requested |
8006 | return nullptr; |
8007 | |
8008 | QRHI_RES_RHI(QRhiVulkan); |
8009 | QVkRenderPassDescriptor *rp = new QVkRenderPassDescriptor(m_rhi); |
8010 | if (!rhiD->createDefaultRenderPass(rpD: rp, |
8011 | hasDepthStencil: m_depthStencil != nullptr, |
8012 | samples, |
8013 | colorFormat)) |
8014 | { |
8015 | delete rp; |
8016 | return nullptr; |
8017 | } |
8018 | |
8019 | rp->ownsRp = true; |
8020 | rp->updateSerializedFormat(); |
8021 | rhiD->registerResource(res: rp); |
8022 | return rp; |
8023 | } |
8024 | |
8025 | static inline bool isSrgbFormat(VkFormat format) |
8026 | { |
8027 | switch (format) { |
8028 | case VK_FORMAT_R8_SRGB: |
8029 | case VK_FORMAT_R8G8_SRGB: |
8030 | case VK_FORMAT_R8G8B8_SRGB: |
8031 | case VK_FORMAT_B8G8R8_SRGB: |
8032 | case VK_FORMAT_R8G8B8A8_SRGB: |
8033 | case VK_FORMAT_B8G8R8A8_SRGB: |
8034 | case VK_FORMAT_A8B8G8R8_SRGB_PACK32: |
8035 | return true; |
8036 | default: |
8037 | return false; |
8038 | } |
8039 | } |
8040 | |
8041 | bool QVkSwapChain::ensureSurface() |
8042 | { |
8043 | // Do nothing when already done, however window may change so check the |
8044 | // surface is still the same. Some of the queries below are very expensive |
8045 | // with some implementations so it is important to do the rest only once |
8046 | // per surface. |
8047 | |
8048 | Q_ASSERT(m_window); |
8049 | VkSurfaceKHR surf = QVulkanInstance::surfaceForWindow(window: m_window); |
8050 | if (!surf) { |
8051 | qWarning(msg: "Failed to get surface for window" ); |
8052 | return false; |
8053 | } |
8054 | if (surface == surf) |
8055 | return true; |
8056 | |
8057 | surface = surf; |
8058 | |
8059 | QRHI_RES_RHI(QRhiVulkan); |
8060 | if (!rhiD->inst->supportsPresent(physicalDevice: rhiD->physDev, queueFamilyIndex: rhiD->gfxQueueFamilyIdx, window: m_window)) { |
8061 | qWarning(msg: "Presenting not supported on this window" ); |
8062 | return false; |
8063 | } |
8064 | |
8065 | quint32 formatCount = 0; |
8066 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, nullptr); |
8067 | QList<VkSurfaceFormatKHR> formats(formatCount); |
8068 | if (formatCount) |
8069 | rhiD->vkGetPhysicalDeviceSurfaceFormatsKHR(rhiD->physDev, surface, &formatCount, formats.data()); |
8070 | |
8071 | // See if there is a better match than the default BGRA8 format. (but if |
8072 | // not, we will stick to the default) |
8073 | const bool srgbRequested = m_flags.testFlag(flag: sRGB); |
8074 | for (int i = 0; i < int(formatCount); ++i) { |
8075 | if (formats[i].format != VK_FORMAT_UNDEFINED) { |
8076 | bool ok = srgbRequested == isSrgbFormat(format: formats[i].format); |
8077 | if (m_format != SDR) |
8078 | ok &= hdrFormatMatchesVkSurfaceFormat(f: m_format, s: formats[i]); |
8079 | if (ok) { |
8080 | colorFormat = formats[i].format; |
8081 | colorSpace = formats[i].colorSpace; |
8082 | break; |
8083 | } |
8084 | } |
8085 | } |
8086 | |
8087 | samples = rhiD->effectiveSampleCountBits(sampleCount: m_sampleCount); |
8088 | |
8089 | quint32 presModeCount = 0; |
8090 | rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, nullptr); |
8091 | supportedPresentationModes.resize(sz: presModeCount); |
8092 | rhiD->vkGetPhysicalDeviceSurfacePresentModesKHR(rhiD->physDev, surface, &presModeCount, |
8093 | supportedPresentationModes.data()); |
8094 | |
8095 | return true; |
8096 | } |
8097 | |
8098 | bool QVkSwapChain::createOrResize() |
8099 | { |
8100 | QRHI_RES_RHI(QRhiVulkan); |
8101 | const bool needsRegistration = !window || window != m_window; |
8102 | |
8103 | // Can be called multiple times due to window resizes - that is not the |
8104 | // same as a simple destroy+create (as with other resources). Thus no |
8105 | // destroy() here. See recreateSwapChain(). |
8106 | |
8107 | // except if the window actually changes |
8108 | if (window && window != m_window) |
8109 | destroy(); |
8110 | |
8111 | window = m_window; |
8112 | m_currentPixelSize = surfacePixelSize(); |
8113 | pixelSize = m_currentPixelSize; |
8114 | |
8115 | if (!rhiD->recreateSwapChain(swapChain: this)) { |
8116 | qWarning(msg: "Failed to create new swapchain" ); |
8117 | return false; |
8118 | } |
8119 | |
8120 | if (needsRegistration) |
8121 | rhiD->swapchains.insert(value: this); |
8122 | |
8123 | if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) { |
8124 | qWarning(msg: "Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems." , |
8125 | m_depthStencil->sampleCount(), m_sampleCount); |
8126 | } |
8127 | if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) { |
8128 | if (m_depthStencil->flags().testFlag(flag: QRhiRenderBuffer::UsedWithSwapChainOnly)) { |
8129 | m_depthStencil->setPixelSize(pixelSize); |
8130 | if (!m_depthStencil->create()) |
8131 | qWarning(msg: "Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d" , |
8132 | pixelSize.width(), pixelSize.height()); |
8133 | } else { |
8134 | qWarning(msg: "Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems." , |
8135 | m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(), |
8136 | pixelSize.width(), pixelSize.height()); |
8137 | } |
8138 | } |
8139 | |
8140 | if (!m_renderPassDesc) |
8141 | qWarning(msg: "QVkSwapChain: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor()." ); |
8142 | |
8143 | rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget |
8144 | rtWrapper.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); |
8145 | Q_ASSERT(rtWrapper.d.rp && rtWrapper.d.rp->rp); |
8146 | |
8147 | rtWrapper.d.pixelSize = pixelSize; |
8148 | rtWrapper.d.dpr = float(window->devicePixelRatio()); |
8149 | rtWrapper.d.sampleCount = samples; |
8150 | rtWrapper.d.colorAttCount = 1; |
8151 | if (m_depthStencil) { |
8152 | rtWrapper.d.dsAttCount = 1; |
8153 | ds = QRHI_RES(QVkRenderBuffer, m_depthStencil); |
8154 | } else { |
8155 | rtWrapper.d.dsAttCount = 0; |
8156 | ds = nullptr; |
8157 | } |
8158 | rtWrapper.d.dsResolveAttCount = 0; |
8159 | if (samples > VK_SAMPLE_COUNT_1_BIT) |
8160 | rtWrapper.d.resolveAttCount = 1; |
8161 | else |
8162 | rtWrapper.d.resolveAttCount = 0; |
8163 | |
8164 | for (int i = 0; i < bufferCount; ++i) { |
8165 | QVkSwapChain::ImageResources &image(imageRes[i]); |
8166 | VkImageView views[3] = { // color, ds, resolve |
8167 | samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView, |
8168 | ds ? ds->imageView : VK_NULL_HANDLE, |
8169 | samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE |
8170 | }; |
8171 | |
8172 | VkFramebufferCreateInfo fbInfo = {}; |
8173 | fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; |
8174 | fbInfo.renderPass = rtWrapper.d.rp->rp; |
8175 | fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount + rtWrapper.d.dsResolveAttCount); |
8176 | fbInfo.pAttachments = views; |
8177 | fbInfo.width = uint32_t(pixelSize.width()); |
8178 | fbInfo.height = uint32_t(pixelSize.height()); |
8179 | fbInfo.layers = 1; |
8180 | |
8181 | VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb); |
8182 | if (err != VK_SUCCESS) { |
8183 | qWarning(msg: "Failed to create framebuffer: %d" , err); |
8184 | return false; |
8185 | } |
8186 | } |
8187 | |
8188 | if (stereo) { |
8189 | rtWrapperRight.setRenderPassDescriptor( |
8190 | m_renderPassDesc); // for the public getter in QRhiRenderTarget |
8191 | rtWrapperRight.d.rp = QRHI_RES(QVkRenderPassDescriptor, m_renderPassDesc); |
8192 | Q_ASSERT(rtWrapperRight.d.rp && rtWrapperRight.d.rp->rp); |
8193 | |
8194 | rtWrapperRight.d.pixelSize = pixelSize; |
8195 | rtWrapperRight.d.dpr = float(window->devicePixelRatio()); |
8196 | rtWrapperRight.d.sampleCount = samples; |
8197 | rtWrapperRight.d.colorAttCount = 1; |
8198 | if (m_depthStencil) { |
8199 | rtWrapperRight.d.dsAttCount = 1; |
8200 | ds = QRHI_RES(QVkRenderBuffer, m_depthStencil); |
8201 | } else { |
8202 | rtWrapperRight.d.dsAttCount = 0; |
8203 | ds = nullptr; |
8204 | } |
8205 | rtWrapperRight.d.dsResolveAttCount = 0; |
8206 | if (samples > VK_SAMPLE_COUNT_1_BIT) |
8207 | rtWrapperRight.d.resolveAttCount = 1; |
8208 | else |
8209 | rtWrapperRight.d.resolveAttCount = 0; |
8210 | |
8211 | for (int i = 0; i < bufferCount; ++i) { |
8212 | QVkSwapChain::ImageResources &image(imageRes[i + bufferCount]); |
8213 | VkImageView views[3] = { |
8214 | // color, ds, resolve |
8215 | samples > VK_SAMPLE_COUNT_1_BIT ? image.msaaImageView : image.imageView, |
8216 | ds ? ds->imageView : VK_NULL_HANDLE, |
8217 | samples > VK_SAMPLE_COUNT_1_BIT ? image.imageView : VK_NULL_HANDLE |
8218 | }; |
8219 | |
8220 | VkFramebufferCreateInfo fbInfo = {}; |
8221 | fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; |
8222 | fbInfo.renderPass = rtWrapperRight.d.rp->rp; |
8223 | fbInfo.attachmentCount = uint32_t(rtWrapperRight.d.colorAttCount + rtWrapperRight.d.dsAttCount |
8224 | + rtWrapperRight.d.resolveAttCount + rtWrapperRight.d.dsResolveAttCount); |
8225 | fbInfo.pAttachments = views; |
8226 | fbInfo.width = uint32_t(pixelSize.width()); |
8227 | fbInfo.height = uint32_t(pixelSize.height()); |
8228 | fbInfo.layers = 1; |
8229 | |
8230 | VkResult err = rhiD->df->vkCreateFramebuffer(rhiD->dev, &fbInfo, nullptr, &image.fb); |
8231 | if (err != VK_SUCCESS) { |
8232 | qWarning(msg: "Failed to create framebuffer: %d" , err); |
8233 | return false; |
8234 | } |
8235 | } |
8236 | } |
8237 | |
8238 | frameCount = 0; |
8239 | |
8240 | if (needsRegistration) |
8241 | rhiD->registerResource(res: this); |
8242 | |
8243 | return true; |
8244 | } |
8245 | |
8246 | QT_END_NAMESPACE |
8247 | |