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