Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qrhid3d12.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
5#include "qrhid3d12_p.h"
6#include "qshader_p.h"
7#include <qmath.h>
8#include <QtCore/private/qsystemerror_p.h>
9#include <QtCore/qcryptographichash.h>
10#include <comdef.h>
12#include "cs_mipmap_p.h"
13#include "cs_mipmap_3d_p.h"
14
15#include <utility>
16
17#if __has_include(<pix.h>)
18#include <pix.h>
19#define QRHI_D3D12_HAS_OLD_PIX
20#endif
21
22#ifdef __ID3D12Device2_INTERFACE_DEFINED__
23
24QT_BEGIN_NAMESPACE
25
26/*
27 Direct 3D 12 backend.
28*/
29
30/*!
31 \class QRhiD3D12InitParams
32 \inmodule QtGuiPrivate
33 \inheaderfile rhi/qrhi.h
34 \brief Direct3D 12 specific initialization parameters.
35
36 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
37 for details.
38
39 A D3D12-based QRhi needs no special parameters for initialization. If
40 desired, enableDebugLayer can be set to \c true to enable the Direct3D
41 debug layer. This can be useful during development, but should be avoided
42 in production builds.
43
44 \badcode
45 QRhiD3D12InitParams params;
46 params.enableDebugLayer = true;
47 rhi = QRhi::create(QRhi::D3D12, &params);
48 \endcode
49
50 \note QRhiSwapChain should only be used in combination with QWindow
51 instances that have their surface type set to QSurface::Direct3DSurface.
52
53 \section2 Working with existing Direct3D 12 devices
54
55 When interoperating with another graphics engine, it may be necessary to
56 get a QRhi instance that uses the same Direct3D device. This can be
57 achieved by passing a pointer to a QRhiD3D12NativeHandles to
58 QRhi::create(). QRhi does not take ownership of any of the external
59 objects.
60
61 Sometimes, for example when using QRhi in combination with OpenXR, one will
62 want to specify which adapter to use, and optionally, which feature level
63 to request on the device, while leaving the device creation to QRhi. This
64 is achieved by leaving the device pointer set to null, while specifying the
65 adapter LUID and feature level.
66
67 Optionally the ID3D12CommandQueue can be specified as well, by setting \c
68 commandQueue to a non-null value.
69 */
70
71/*!
72 \variable QRhiD3D12InitParams::enableDebugLayer
73
74 When set to true, the debug layer is enabled, if installed and available.
75 The default value is false.
76
77 The messages from the debug layer are printed via qDebug, unless the debug
78 layer is too old to support message callbacks, in which case the messages
79 are only sent to the debug output.
80*/
81
82/*!
83 \class QRhiD3D12NativeHandles
84 \inmodule QtGuiPrivate
85 \inheaderfile rhi/qrhi.h
86 \brief Holds the D3D12 device used by the QRhi.
87
88 \note The class uses \c{void *} as the type since including the COM-based
89 \c{d3d12.h} headers is not acceptable here. The actual types are
90 \c{ID3D12Device *} and \c{ID3D12CommandQueue *}.
91
92 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
93 for details.
94 */
95
96/*!
97 \variable QRhiD3D12NativeHandles::dev
98
99 Points to a
100 \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nn-d3d12-id3d12device}{ID3D12Device}
101 or left set to \nullptr if no existing device is to be imported.
102*/
103
104/*!
105 \variable QRhiD3D12NativeHandles::minimumFeatureLevel
106
107 Specifies the \b minimum feature level passed to
108 \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-d3d12createdevice}{D3D12CreateDevice()}.
109 When not set, \c{D3D_FEATURE_LEVEL_11_0} is used. See
110 \l{https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels}{this
111 page} for details.
112
113 Relevant only when QRhi creates the device, ignored when importing a device
114 and device context.
115*/
116
117/*!
118 \variable QRhiD3D12NativeHandles::adapterLuidLow
119
120 The low part of the local identifier (LUID) of the DXGI adapter to use.
121 Relevant only when QRhi creates the device, ignored when importing a device
122 and device context.
123*/
124
125/*!
126 \variable QRhiD3D12NativeHandles::adapterLuidHigh
127
128 The high part of the local identifier (LUID) of the DXGI adapter to use.
129 Relevant only when QRhi creates the device, ignored when importing a device
130 and device context.
131*/
132
133/*!
134 \variable QRhiD3D12NativeHandles::commandQueue
135
136 When set, must point to a
137 \l{https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nn-d3d12-id3d12commandqueue}{ID3D12CommandQueue}.
138 It allows to optionally import a command queue as well, in addition to a
139 device.
140*/
141
142/*!
143 \class QRhiD3D12CommandBufferNativeHandles
144 \inmodule QtGuiPrivate
145 \inheaderfile rhi/qrhi.h
146 \brief Holds the ID3D12GraphicsCommandList1 object that is backing a QRhiCommandBuffer.
147
148 \note The command list object is only guaranteed to be valid, and
149 in recording state, while recording a frame. That is, between a
150 \l{QRhi::beginFrame()}{beginFrame()} - \l{QRhi::endFrame()}{endFrame()} or
151 \l{QRhi::beginOffscreenFrame()}{beginOffscreenFrame()} -
152 \l{QRhi::endOffscreenFrame()}{endOffscreenFrame()} pair.
153
154 \note This is a RHI API with limited compatibility guarantees, see \l QRhi
155 for details.
156 */
157
158/*!
159 \variable QRhiD3D12CommandBufferNativeHandles::commandList
160*/
161
162// https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-feature-levels
163static const D3D_FEATURE_LEVEL MIN_FEATURE_LEVEL = D3D_FEATURE_LEVEL_11_0;
164
165QRhiD3D12::QRhiD3D12(QRhiD3D12InitParams *params, QRhiD3D12NativeHandles *importParams)
166{
167 debugLayer = params->enableDebugLayer;
168 if (importParams) {
169 if (importParams->dev) {
170 ID3D12Device *d3d12Device = reinterpret_cast<ID3D12Device *>(importParams->dev);
171 if (SUCCEEDED(d3d12Device->QueryInterface(__uuidof(ID3D12Device2), reinterpret_cast<void **>(&dev)))) {
172 // get rid of the ref added by QueryInterface
173 d3d12Device->Release();
174 importedDevice = true;
175 } else {
176 qWarning("ID3D12Device2 not supported, cannot import device");
177 }
178 }
179 if (importParams->commandQueue) {
180 cmdQueue = reinterpret_cast<ID3D12CommandQueue *>(importParams->commandQueue);
181 importedCommandQueue = true;
182 }
183 minimumFeatureLevel = D3D_FEATURE_LEVEL(importParams->minimumFeatureLevel);
184 adapterLuid.LowPart = importParams->adapterLuidLow;
185 adapterLuid.HighPart = importParams->adapterLuidHigh;
186 }
187}
188
189template <class Int>
190inline Int aligned(Int v, Int byteAlign)
191{
192 return (v + byteAlign - 1) & ~(byteAlign - 1);
193}
194
195static inline UINT calcSubresource(UINT mipSlice, UINT arraySlice, UINT mipLevels)
196{
197 return mipSlice + arraySlice * mipLevels;
198}
199
200static inline QD3D12RenderTargetData *rtData(QRhiRenderTarget *rt)
201{
202 switch (rt->resourceType()) {
203 case QRhiResource::SwapChainRenderTarget:
204 return &QRHI_RES(QD3D12SwapChainRenderTarget, rt)->d;
205 case QRhiResource::TextureRenderTarget:
206 return &QRHI_RES(QD3D12TextureRenderTarget, rt)->d;
207 break;
208 default:
209 break;
210 }
211 Q_UNREACHABLE_RETURN(nullptr);
212}
213
214#ifdef QRHI_D3D12_INFOQUEUE1_AVAILABLE
215static void __stdcall qd3d12_message_callback(D3D12_MESSAGE_CATEGORY category,
216 D3D12_MESSAGE_SEVERITY severity,
217 D3D12_MESSAGE_ID id,
218 LPCSTR description,
219 void *context)
220{
221 Q_UNUSED(category);
222 Q_UNUSED(context);
223
224 // The callback is registered with IGNORE_FILTERS, so apply the same
225 // filtering the storage filter does.
226 if (severity == D3D12_MESSAGE_SEVERITY_INFO)
227 return;
228 if (id == D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE
229 || id == D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE
230 || id == D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE)
231 {
232 return;
233 }
234
235 // Called on the thread that made the offending call, from within the D3D12
236 // call itself; must not call back into D3D12 from here.
237 qDebug("D3D12: %s", description);
238}
239#endif
240
241bool QRhiD3D12::create(QRhi::Flags flags)
242{
243 rhiFlags = flags;
244
245 UINT factoryFlags = 0;
246 if (debugLayer)
247 factoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
248 HRESULT hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
249 if (FAILED(hr)) {
250 // retry without debug, if it was requested (to match D3D11 backend behavior)
251 if (debugLayer) {
252 qCDebug(QRHI_LOG_INFO, "Debug layer was requested but is not available. "
253 "Attempting to create DXGIFactory2 without it.");
254 factoryFlags &= ~DXGI_CREATE_FACTORY_DEBUG;
255 hr = CreateDXGIFactory2(factoryFlags, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgiFactory));
256 }
257 if (SUCCEEDED(hr)) {
258 debugLayer = false;
259 } else {
260 qWarning("CreateDXGIFactory2() failed to create DXGI factory: %s",
261 qPrintable(QSystemError::windowsComString(hr)));
262 return false;
263 }
264 }
265
266 if (qEnvironmentVariableIsSet("QT_D3D_MAX_FRAME_LATENCY"))
267 maxFrameLatency = UINT(qMax(0, qEnvironmentVariableIntValue("QT_D3D_MAX_FRAME_LATENCY")));
268 if (maxFrameLatency != 0)
269 qCDebug(QRHI_LOG_INFO, "Using frame latency waitable object with max frame latency %u", maxFrameLatency);
270
271 supportsAllowTearing = false;
272 IDXGIFactory5 *factory5 = nullptr;
273 if (SUCCEEDED(dxgiFactory->QueryInterface(__uuidof(IDXGIFactory5), reinterpret_cast<void **>(&factory5)))) {
274 BOOL allowTearing = false;
275 if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))))
276 supportsAllowTearing = allowTearing;
277 factory5->Release();
278 }
279
280 if (debugLayer) {
281 ID3D12Debug1 *debug = nullptr;
282 if (SUCCEEDED(D3D12GetDebugInterface(__uuidof(ID3D12Debug1), reinterpret_cast<void **>(&debug)))) {
283 qCDebug(QRHI_LOG_INFO, "Enabling D3D12 debug layer");
284 debug->EnableDebugLayer();
285 debug->Release();
286 }
287 }
288
289 activeAdapter = nullptr;
290
291 if (!importedDevice) {
292 IDXGIAdapter1 *adapter;
293 int requestedAdapterIndex = -1;
294 if (qEnvironmentVariableIsSet("QT_D3D_ADAPTER_INDEX"))
295 requestedAdapterIndex = qEnvironmentVariableIntValue("QT_D3D_ADAPTER_INDEX");
296
297 if (requestedRhiAdapter)
298 adapterLuid = static_cast<QD3D12Adapter *>(requestedRhiAdapter)->luid;
299
300 // importParams or requestedRhiAdapter may specify an adapter by the luid, use that in the absence of an env.var. override.
301 if (requestedAdapterIndex < 0 && (adapterLuid.LowPart || adapterLuid.HighPart)) {
302 for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
303 DXGI_ADAPTER_DESC1 desc;
304 adapter->GetDesc1(&desc);
305 adapter->Release();
306 if (desc.AdapterLuid.LowPart == adapterLuid.LowPart
307 && desc.AdapterLuid.HighPart == adapterLuid.HighPart)
308 {
309 requestedAdapterIndex = adapterIndex;
310 break;
311 }
312 }
313 }
314
315 if (requestedAdapterIndex < 0 && flags.testFlag(QRhi::PreferSoftwareRenderer)) {
316 for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
317 DXGI_ADAPTER_DESC1 desc;
318 adapter->GetDesc1(&desc);
319 adapter->Release();
320 if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) {
321 requestedAdapterIndex = adapterIndex;
322 break;
323 }
324 }
325 }
326
327 for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
328 DXGI_ADAPTER_DESC1 desc;
329 adapter->GetDesc1(&desc);
330 const QString name = QString::fromUtf16(reinterpret_cast<char16_t *>(desc.Description));
331 qCDebug(QRHI_LOG_INFO, "Adapter %d: '%s' (vendor 0x%X device 0x%X flags 0x%X)",
332 adapterIndex,
333 qPrintable(name),
334 desc.VendorId,
335 desc.DeviceId,
336 desc.Flags);
337 if (!activeAdapter && (requestedAdapterIndex < 0 || requestedAdapterIndex == adapterIndex)) {
338 activeAdapter = adapter;
339 adapterLuid = desc.AdapterLuid;
340 QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
341 qCDebug(QRHI_LOG_INFO, " using this adapter");
342 } else {
343 adapter->Release();
344 }
345 }
346 if (!activeAdapter) {
347 qWarning("No adapter");
348 return false;
349 }
350
351 if (minimumFeatureLevel == 0)
352 minimumFeatureLevel = MIN_FEATURE_LEVEL;
353
354 hr = D3D12CreateDevice(activeAdapter,
355 minimumFeatureLevel,
356 __uuidof(ID3D12Device2),
357 reinterpret_cast<void **>(&dev));
358 if (FAILED(hr)) {
359 qWarning("Failed to create D3D12 device: %s", qPrintable(QSystemError::windowsComString(hr)));
360 return false;
361 }
362 } else {
363 Q_ASSERT(dev);
364 // cannot just get a IDXGIDevice from the ID3D12Device anymore, look up the adapter instead
365 adapterLuid = dev->GetAdapterLuid();
366 IDXGIAdapter1 *adapter;
367 for (int adapterIndex = 0; dxgiFactory->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
368 DXGI_ADAPTER_DESC1 desc;
369 adapter->GetDesc1(&desc);
370 if (desc.AdapterLuid.LowPart == adapterLuid.LowPart
371 && desc.AdapterLuid.HighPart == adapterLuid.HighPart)
372 {
373 activeAdapter = adapter;
374 QRhiD3D::fillDriverInfo(&driverInfoStruct, desc);
375 break;
376 } else {
377 adapter->Release();
378 }
379 }
380 if (!activeAdapter) {
381 qWarning("No adapter");
382 return false;
383 }
384 qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
385 }
386
387 QDxgiVSyncService::instance()->refAdapter(adapterLuid);
388
389 if (debugLayer) {
390 ID3D12InfoQueue *infoQueue;
391 if (SUCCEEDED(dev->QueryInterface(__uuidof(ID3D12InfoQueue), reinterpret_cast<void **>(&infoQueue)))) {
392 if (qEnvironmentVariableIntValue("QT_D3D_DEBUG_BREAK")) {
393 infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, true);
394 infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, true);
395 infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, true);
396 }
397 D3D12_INFO_QUEUE_FILTER filter = {};
398 D3D12_MESSAGE_ID suppressedMessages[3] = {
399 // there is no way of knowing the clear color upfront
400 D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE,
401 // same for the depth-stencil clear values
402 D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE,
403 // we have no control over viewport and scissor rects
404 D3D12_MESSAGE_ID_DRAW_EMPTY_SCISSOR_RECTANGLE
405 };
406 filter.DenyList.NumIDs = 3;
407 filter.DenyList.pIDList = suppressedMessages;
408 // Setting the filter would enable Info messages (e.g. about
409 // resource creation) which we don't need.
410 D3D12_MESSAGE_SEVERITY infoSev = D3D12_MESSAGE_SEVERITY_INFO;
411 filter.DenyList.NumSeverities = 1;
412 filter.DenyList.pSeverityList = &infoSev;
413 infoQueue->PushStorageFilter(&filter);
414#ifdef QRHI_D3D12_INFOQUEUE1_AVAILABLE
415 // Route the messages to qDebug, like QVulkanInstance does with the
416 // Vulkan validation layer messages. Needs a recent enough debug
417 // layer, otherwise this is not possible and the messages will only
418 // show up on the debug output.
419 if (SUCCEEDED(infoQueue->QueryInterface(__uuidof(ID3D12InfoQueue1), reinterpret_cast<void **>(&infoQueue1)))) {
420 // Registering with IGNORE_FILTERS, and so filtering in the
421 // callback instead, is not optional: with FLAG_NONE the
422 // callback stops getting invoked once the debug output is muted
423 // below.
424 if (SUCCEEDED(infoQueue1->RegisterMessageCallback(qd3d12_message_callback,
425 D3D12_MESSAGE_CALLBACK_IGNORE_FILTERS,
426 nullptr,
427 &infoQueueCallbackCookie)))
428 {
429 // Do not want the messages twice, so stop them from going
430 // to the debug output now that we print them ourselves.
431 infoQueue1->SetMuteDebugOutput(true);
432 } else {
433 infoQueue1->Release();
434 infoQueue1 = nullptr;
435 }
436 }
437#endif
438 infoQueue->Release();
439 }
440 }
441
442 if (!importedCommandQueue) {
443 D3D12_COMMAND_QUEUE_DESC queueDesc = {};
444 queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
445 queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
446 hr = dev->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void **>(&cmdQueue));
447 if (FAILED(hr)) {
448 qWarning("Failed to create command queue: %s", qPrintable(QSystemError::windowsComString(hr)));
449 return false;
450 }
451 }
452
453 hr = dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void **>(&fullFence));
454 if (FAILED(hr)) {
455 qWarning("Failed to create fence: %s", qPrintable(QSystemError::windowsComString(hr)));
456 return false;
457 }
458 fullFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
459 fullFenceCounter = 0;
460
461 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
462 hr = dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT,
463 __uuidof(ID3D12CommandAllocator),
464 reinterpret_cast<void **>(&cmdAllocators[i]));
465 if (FAILED(hr)) {
466 qWarning("Failed to create command allocator: %s", qPrintable(QSystemError::windowsComString(hr)));
467 return false;
468 }
469 }
470
471 if (!vma.create(dev, activeAdapter)) {
472 qWarning("Failed to initialize graphics memory suballocator");
473 return false;
474 }
475
476 if (!rtvPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, "main RTV pool")) {
477 qWarning("Could not create RTV pool");
478 return false;
479 }
480
481 if (!dsvPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, "main DSV pool")) {
482 qWarning("Could not create DSV pool");
483 return false;
484 }
485
486 if (!cbvSrvUavPool.create(dev, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, "main CBV-SRV-UAV pool")) {
487 qWarning("Could not create CBV-SRV-UAV pool");
488 return false;
489 }
490
491 resourcePool.create("main resource pool");
492 pipelinePool.create("main pipeline pool");
493 rootSignaturePool.create("main root signature pool");
494 releaseQueue.create(&resourcePool, &pipelinePool, &rootSignaturePool);
495 barrierGen.create(&resourcePool);
496
497 if (!samplerMgr.create(dev)) {
498 qWarning("Could not create sampler pool and shader-visible sampler heap");
499 return false;
500 }
501
502 mipmapGen.create(this);
503 mipmapGen3D.create(this);
504
505 const qint32 smallStagingSize = aligned(SMALL_STAGING_AREA_BYTES_PER_FRAME_START, QD3D12StagingArea::ALIGNMENT);
506 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
507 if (!smallStagingAreas[i].create(this, smallStagingSize, D3D12_HEAP_TYPE_UPLOAD)) {
508 qWarning("Could not create host-visible staging area");
509 return false;
510 }
511 QString decoratedName = QLatin1String("Small staging area buffer/");
512 decoratedName += QString::number(i);
513 smallStagingAreas[i].mem.buffer->SetName(reinterpret_cast<LPCWSTR>(decoratedName.utf16()));
514 }
515
516 if (!shaderVisibleCbvSrvUavHeap.create(dev,
517 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
518 SHADER_VISIBLE_CBV_SRV_UAV_HEAP_PER_FRAME_START_SIZE))
519 {
520 qWarning("Could not create first shader-visible CBV/SRV/UAV heap");
521 return false;
522 }
523
524 if (flags.testFlag(QRhi::EnableTimestamps)) {
525 static bool wantsStablePowerState = qEnvironmentVariableIntValue("QT_D3D_STABLE_POWER_STATE");
526 //
527 // https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-setstablepowerstate
528 //
529 // NB! This is a _global_ setting, affecting other processes (and 3D
530 // APIs such as Vulkan), as long as this application is running. Hence
531 // making it an env.var. for now. Never enable it in production. But
532 // extremely useful for the GPU timings with NVIDIA at least; the
533 // timestamps become stable and smooth, making the number readable and
534 // actually useful e.g. in Quick 3D's DebugView when this is enabled.
535 // (otherwise the number's all over the place)
536 //
537 // See also
538 // https://developer.nvidia.com/blog/advanced-api-performance-setstablepowerstate/
539 // for possible other approaches.
540 //
541 if (wantsStablePowerState)
542 dev->SetStablePowerState(TRUE);
543
544 hr = cmdQueue->GetTimestampFrequency(&timestampTicksPerSecond);
545 if (FAILED(hr)) {
546 qWarning("Failed to query timestamp frequency: %s",
547 qPrintable(QSystemError::windowsComString(hr)));
548 return false;
549 }
550 if (!timestampQueryHeap.create(dev, QD3D12_FRAMES_IN_FLIGHT * 2, D3D12_QUERY_HEAP_TYPE_TIMESTAMP)) {
551 qWarning("Failed to create timestamp query pool");
552 return false;
553 }
554 const quint32 readbackBufSize = QD3D12_FRAMES_IN_FLIGHT * 2 * sizeof(quint64);
555 if (!timestampReadbackArea.create(this, readbackBufSize, D3D12_HEAP_TYPE_READBACK)) {
556 qWarning("Failed to create timestamp readback buffer");
557 return false;
558 }
559 timestampReadbackArea.mem.buffer->SetName(L"Timestamp readback buffer");
560 memset(timestampReadbackArea.mem.p, 0, readbackBufSize);
561 }
562
563 caps = {};
564 D3D12_FEATURE_DATA_D3D12_OPTIONS3 options3 = {};
565 if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &options3, sizeof(options3)))) {
566 caps.multiView = options3.ViewInstancingTier != D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED;
567 // https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html
568 caps.textureViewFormat = options3.CastingFullyTypedFormatSupported;
569 }
570
571#ifdef QRHI_D3D12_CL5_AVAILABLE
572 D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = {};
573 if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &options6, sizeof(options6)))) {
574 caps.vrs = options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED;
575 caps.vrsMap = options6.VariableShadingRateTier == D3D12_VARIABLE_SHADING_RATE_TIER_2;
576 caps.vrsAdditionalRates = options6.AdditionalShadingRatesSupported;
577 shadingRateImageTileSize = options6.ShadingRateImageTileSize;
578 }
579#else
580 caps.vrs = false;
581 caps.vrsMap = false;
582 caps.vrsAdditionalRates = false;
583#endif
584
585 {
586 D3D12_INDIRECT_ARGUMENT_DESC arg = {};
587 arg.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;
588
589 D3D12_COMMAND_SIGNATURE_DESC sigDesc = {};
590 sigDesc.ByteStride = sizeof(D3D12_DRAW_ARGUMENTS);
591 sigDesc.NumArgumentDescs = 1;
592 sigDesc.pArgumentDescs = &arg;
593
594 hr = dev->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&drawCommandSignature));
595 if (FAILED(hr)) {
596 qWarning("Failed to create draw command signature: %s", qPrintable(QSystemError::windowsComString(hr)));
597 return false;
598 }
599 }
600
601 {
602 D3D12_INDIRECT_ARGUMENT_DESC arg = {};
603 arg.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED;
604
605 D3D12_COMMAND_SIGNATURE_DESC sigDesc = {};
606 sigDesc.ByteStride = sizeof(D3D12_DRAW_INDEXED_ARGUMENTS);
607 sigDesc.NumArgumentDescs = 1;
608 sigDesc.pArgumentDescs = &arg;
609
610 hr = dev->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&drawIndexedCommandSignature));
611 if (FAILED(hr)) {
612 qWarning("Failed to create draw indexed command signature: %s", qPrintable(QSystemError::windowsComString(hr)));
613 return false;
614 }
615 }
616
617 {
618 D3D12_INDIRECT_ARGUMENT_DESC arg = {};
619 arg.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH;
620
621 D3D12_COMMAND_SIGNATURE_DESC sigDesc = {};
622 sigDesc.ByteStride = sizeof(D3D12_DISPATCH_ARGUMENTS);
623 sigDesc.NumArgumentDescs = 1;
624 sigDesc.pArgumentDescs = &arg;
625
626 hr = dev->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&dispatchCommandSignature));
627 if (FAILED(hr)) {
628 qWarning("Failed to create dispatch command signature: %s", qPrintable(QSystemError::windowsComString(hr)));
629 return false;
630 }
631 }
632
633 deviceLost = false;
634 offscreenActive = false;
635
636 nativeHandlesStruct.dev = dev;
637 nativeHandlesStruct.minimumFeatureLevel = minimumFeatureLevel;
638 nativeHandlesStruct.adapterLuidLow = adapterLuid.LowPart;
639 nativeHandlesStruct.adapterLuidHigh = adapterLuid.HighPart;
640 nativeHandlesStruct.commandQueue = cmdQueue;
641
642 return true;
643}
644
645void QRhiD3D12::destroy()
646{
647 if (!deviceLost && fullFence && fullFenceEvent)
648 waitGpu();
649
650 releaseQueue.releaseAll();
651
652 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
653 if (offscreenCb[i]) {
654 if (offscreenCb[i]->cmdList)
655 offscreenCb[i]->cmdList->Release();
656 delete offscreenCb[i];
657 offscreenCb[i] = nullptr;
658 }
659 }
660
661 timestampQueryHeap.destroy();
662 timestampReadbackArea.destroy();
663
664 shaderVisibleCbvSrvUavHeap.destroy();
665
666 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i)
667 smallStagingAreas[i].destroy();
668
669 mipmapGen.destroy();
670 mipmapGen3D.destroy();
671 samplerMgr.destroy();
672 resourcePool.destroy();
673 pipelinePool.destroy();
674 rootSignaturePool.destroy();
675 rtvPool.destroy();
676 dsvPool.destroy();
677 cbvSrvUavPool.destroy();
678
679 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
680 if (cmdAllocators[i]) {
681 cmdAllocators[i]->Release();
682 cmdAllocators[i] = nullptr;
683 }
684 }
685
686 if (fullFenceEvent) {
687 CloseHandle(fullFenceEvent);
688 fullFenceEvent = nullptr;
689 }
690
691 if (fullFence) {
692 fullFence->Release();
693 fullFence = nullptr;
694 }
695
696 if (!importedCommandQueue) {
697 if (cmdQueue) {
698 cmdQueue->Release();
699 cmdQueue = nullptr;
700 }
701 }
702
703 vma.destroy();
704
705#ifdef QRHI_D3D12_INFOQUEUE1_AVAILABLE
706 if (infoQueue1) {
707 infoQueue1->UnregisterMessageCallback(infoQueueCallbackCookie);
708 infoQueue1->Release();
709 infoQueue1 = nullptr;
710 infoQueueCallbackCookie = 0;
711 }
712#endif
713
714 if (!importedDevice) {
715 if (dev) {
716 dev->Release();
717 dev = nullptr;
718 }
719 }
720
721 if (dcompDevice) {
722 dcompDevice->Release();
723 dcompDevice = nullptr;
724 }
725
726 if (activeAdapter) {
727 activeAdapter->Release();
728 activeAdapter = nullptr;
729 }
730
731 if (dxgiFactory) {
732 dxgiFactory->Release();
733 dxgiFactory = nullptr;
734 }
735
736 QDxgiVSyncService::instance()->derefAdapter(adapterLuid);
737
738 adapterLuid = {};
739 importedDevice = false;
740 importedCommandQueue = false;
741
742 if (drawCommandSignature) {
743 drawCommandSignature->Release();
744 drawCommandSignature = nullptr;
745 }
746
747 if (drawIndexedCommandSignature) {
748 drawIndexedCommandSignature->Release();
749 drawIndexedCommandSignature = nullptr;
750 }
751
752 if (dispatchCommandSignature) {
753 dispatchCommandSignature->Release();
754 dispatchCommandSignature = nullptr;
755 }
756
757 for (ID3D12CommandSignature *sig : std::as_const(drawCommandSignaturesByStride))
758 sig->Release();
759 drawCommandSignaturesByStride.clear();
760
761 for (ID3D12CommandSignature *sig : std::as_const(drawIndexedCommandSignaturesByStride))
762 sig->Release();
763 drawIndexedCommandSignaturesByStride.clear();
764
765 destroyPipelineLibrary();
766}
767
768QRhi::AdapterList QRhiD3D12::enumerateAdaptersBeforeCreate(QRhiNativeHandles *nativeHandles) const
769{
770 LUID requestedLuid = {};
771 if (nativeHandles) {
772 QRhiD3D12NativeHandles *h = static_cast<QRhiD3D12NativeHandles *>(nativeHandles);
773 const LUID adapterLuid = { h->adapterLuidLow, h->adapterLuidHigh };
774 if (adapterLuid.LowPart || adapterLuid.HighPart)
775 requestedLuid = adapterLuid;
776 }
777
778 IDXGIFactory2 *dxgi = nullptr;
779 if (FAILED(CreateDXGIFactory2(0, __uuidof(IDXGIFactory2), reinterpret_cast<void **>(&dxgi))))
780 return {};
781
782 QRhi::AdapterList list;
783 IDXGIAdapter1 *adapter;
784 for (int adapterIndex = 0; dxgi->EnumAdapters1(UINT(adapterIndex), &adapter) != DXGI_ERROR_NOT_FOUND; ++adapterIndex) {
785 DXGI_ADAPTER_DESC1 desc;
786 adapter->GetDesc1(&desc);
787 adapter->Release();
788 if (requestedLuid.LowPart || requestedLuid.HighPart) {
789 if (desc.AdapterLuid.LowPart != requestedLuid.LowPart
790 || desc.AdapterLuid.HighPart != requestedLuid.HighPart)
791 {
792 continue;
793 }
794 }
795 QD3D12Adapter *a = new QD3D12Adapter;
796 a->luid = desc.AdapterLuid;
797 QRhiD3D::fillDriverInfo(&a->adapterInfo, desc);
798 list.append(a);
799 }
800
801 dxgi->Release();
802 return list;
803}
804
805QRhiDriverInfo QD3D12Adapter::info() const
806{
807 return adapterInfo;
808}
809
810QList<int> QRhiD3D12::supportedSampleCounts() const
811{
812 return { 1, 2, 4, 8 };
813}
814
815QList<QSize> QRhiD3D12::supportedShadingRates(int sampleCount) const
816{
817 QList<QSize> sizes;
818 switch (sampleCount) {
819 case 0:
820 case 1:
821 if (caps.vrsAdditionalRates) {
822 sizes.append(QSize(4, 4));
823 sizes.append(QSize(4, 2));
824 sizes.append(QSize(2, 4));
825 }
826 sizes.append(QSize(2, 2));
827 sizes.append(QSize(2, 1));
828 sizes.append(QSize(1, 2));
829 break;
830 case 2:
831 if (caps.vrsAdditionalRates)
832 sizes.append(QSize(2, 4));
833 sizes.append(QSize(2, 2));
834 sizes.append(QSize(2, 1));
835 sizes.append(QSize(1, 2));
836 break;
837 case 4:
838 sizes.append(QSize(2, 2));
839 sizes.append(QSize(2, 1));
840 sizes.append(QSize(1, 2));
841 break;
842 default:
843 break;
844 }
845 sizes.append(QSize(1, 1));
846 return sizes;
847}
848
849QRhiSwapChain *QRhiD3D12::createSwapChain()
850{
851 return new QD3D12SwapChain(this);
852}
853
854QRhiBuffer *QRhiD3D12::createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size)
855{
856 return new QD3D12Buffer(this, type, usage, size);
857}
858
859int QRhiD3D12::ubufAlignment() const
860{
861 return D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT; // 256
862}
863
864bool QRhiD3D12::isYUpInFramebuffer() const
865{
866 return false;
867}
868
869bool QRhiD3D12::isYUpInNDC() const
870{
871 return true;
872}
873
874bool QRhiD3D12::isClipDepthZeroToOne() const
875{
876 return true;
877}
878
879QMatrix4x4 QRhiD3D12::clipSpaceCorrMatrix() const
880{
881 // Like with Vulkan, but Y is already good.
882
883 // NB the ctor takes row-major
884 static constexpr QMatrix4x4 m(1.0f, 0.0f, 0.0f, 0.0f,
885 0.0f, 1.0f, 0.0f, 0.0f,
886 0.0f, 0.0f, 0.5f, 0.5f,
887 0.0f, 0.0f, 0.0f, 1.0f);
888 return m;
889}
890
891bool QRhiD3D12::isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const
892{
893 Q_UNUSED(flags);
894
895 if (format >= QRhiTexture::ETC2_RGB8 && format <= QRhiTexture::ASTC_12x12)
896 return false;
897
898 return true;
899}
900
901bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
902{
903 switch (feature) {
904 case QRhi::MultisampleTexture:
905 return true;
906 case QRhi::MultisampleRenderBuffer:
907 return true;
908 case QRhi::DebugMarkers:
909#ifdef QRHI_D3D12_HAS_OLD_PIX
910 return true;
911#else
912 return false;
913#endif
914 case QRhi::Timestamps:
915 return true;
916 case QRhi::Instancing:
917 return true;
918 case QRhi::CustomInstanceStepRate:
919 return true;
920 case QRhi::PrimitiveRestart:
921 return true;
922 case QRhi::NonDynamicUniformBuffers:
923 return false;
924 case QRhi::NonFourAlignedEffectiveIndexBufferOffset:
925 return true;
926 case QRhi::NPOTTextureRepeat:
927 return true;
928 case QRhi::RedOrAlpha8IsRed:
929 return true;
930 case QRhi::ElementIndexUint:
931 return true;
932 case QRhi::Compute:
933 return true;
934 case QRhi::WideLines:
935 return false;
936 case QRhi::VertexShaderPointSize:
937 return false;
938 case QRhi::BaseVertex:
939 return true;
940 case QRhi::BaseInstance:
941 return true;
942 case QRhi::TriangleFanTopology:
943 return false;
944 case QRhi::ReadBackNonUniformBuffer:
945 return true;
946 case QRhi::ReadBackNonBaseMipLevel:
947 return true;
948 case QRhi::TexelFetch:
949 return true;
950 case QRhi::RenderToNonBaseMipLevel:
951 return true;
952 case QRhi::IntAttributes:
953 return true;
954 case QRhi::ScreenSpaceDerivatives:
955 return true;
956 case QRhi::ReadBackAnyTextureFormat:
957 return true;
958 case QRhi::PipelineCacheDataLoadSave:
959#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
960 return true;
961#else
962 return false;
963#endif
964 case QRhi::ImageDataStride:
965 return true;
966 case QRhi::RenderBufferImport:
967 return false;
968 case QRhi::ThreeDimensionalTextures:
969 return true;
970 case QRhi::RenderTo3DTextureSlice:
971 return true;
972 case QRhi::TextureArrays:
973 return true;
974 case QRhi::Tessellation:
975 return true;
976 case QRhi::GeometryShader:
977 return true;
978 case QRhi::TextureArrayRange:
979 return true;
980 case QRhi::NonFillPolygonMode:
981 return true;
982 case QRhi::OneDimensionalTextures:
983 return true;
984 case QRhi::OneDimensionalTextureMipmaps:
985 return false; // we generate mipmaps ourselves with compute and this is not implemented
986 case QRhi::HalfAttributes:
987 return true;
988 case QRhi::RenderToOneDimensionalTexture:
989 return true;
990 case QRhi::ThreeDimensionalTextureMipmaps:
991 return true;
992 case QRhi::MultiView:
993 return caps.multiView;
994 case QRhi::TextureViewFormat:
995 return caps.textureViewFormat;
996 case QRhi::ResolveDepthStencil:
997 // there is no Multisample Resolve support for depth/stencil formats
998 // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/hardware-support-for-direct3d-12-1-formats
999 return false;
1000 case QRhi::VariableRateShading:
1001 return caps.vrs;
1002 case QRhi::VariableRateShadingMap:
1003 case QRhi::VariableRateShadingMapWithTexture:
1004 return caps.vrsMap;
1005 case QRhi::PerRenderTargetBlending:
1006 case QRhi::SampleVariables:
1007 return true;
1008 case QRhi::InstanceIndexIncludesBaseInstance:
1009 return false;
1010 case QRhi::DepthClamp:
1011 return true;
1012 case QRhi::DrawIndirect:
1013 return drawCommandSignature != nullptr && drawIndexedCommandSignature != nullptr;
1014 case QRhi::DrawIndirectMulti:
1015 return drawCommandSignature != nullptr && drawIndexedCommandSignature != nullptr;
1016 case QRhi::ShaderDrawParameters:
1017 return false;
1018 case QRhi::DispatchIndirect:
1019 return dispatchCommandSignature != nullptr;
1020 case QRhi::PushConstants:
1021 return true;
1022 case QRhi::DrawIndirectCount:
1023 // ExecuteIndirect natively supports a GPU-supplied count buffer.
1024 return drawCommandSignature != nullptr && drawIndexedCommandSignature != nullptr;
1025 case QRhi::BufferToBufferCopy:
1026 return true;
1027 case QRhi::StaticBuffersOnGpuTimeline:
1028 return true;
1029 }
1030 return false;
1031}
1032
1033int QRhiD3D12::resourceLimit(QRhi::ResourceLimit limit) const
1034{
1035 switch (limit) {
1036 case QRhi::TextureSizeMin:
1037 return 1;
1038 case QRhi::TextureSizeMax:
1039 return 16384;
1040 case QRhi::MaxColorAttachments:
1041 return 8;
1042 case QRhi::FramesInFlight:
1043 return QD3D12_FRAMES_IN_FLIGHT;
1044 case QRhi::MaxAsyncReadbackFrames:
1045 return QD3D12_FRAMES_IN_FLIGHT;
1046 case QRhi::MaxThreadGroupsPerDimension:
1047 return 65535;
1048 case QRhi::MaxThreadsPerThreadGroup:
1049 return 1024;
1050 case QRhi::MaxThreadGroupX:
1051 return 1024;
1052 case QRhi::MaxThreadGroupY:
1053 return 1024;
1054 case QRhi::MaxThreadGroupZ:
1055 return 1024;
1056 case QRhi::TextureArraySizeMax:
1057 return 2048;
1058 case QRhi::MaxUniformBufferRange:
1059 return 65536;
1060 case QRhi::MaxVertexInputs:
1061 return 32;
1062 case QRhi::MaxVertexOutputs:
1063 return 32;
1064 case QRhi::MaxPushConstantsSize:
1065 return 128; // root constants share the root signature budget
1066 case QRhi::MaxVertexStorageBuffers:
1067 case QRhi::MaxFragmentStorageBuffers:
1068 return 128;
1069 case QRhi::ShadingRateImageTileSize:
1070 return shadingRateImageTileSize;
1071 }
1072 return 0;
1073}
1074
1075const QRhiNativeHandles *QRhiD3D12::nativeHandles()
1076{
1077 return &nativeHandlesStruct;
1078}
1079
1080QRhiDriverInfo QRhiD3D12::driverInfo() const
1081{
1082 return driverInfoStruct;
1083}
1084
1085QRhiStats QRhiD3D12::statistics()
1086{
1087 QRhiStats result;
1088 result.totalPipelineCreationTime = totalPipelineCreationTime();
1089
1090 D3D12MA::Budget budgets[2]; // [gpu, system] with discreet GPU or [shared, nothing] with UMA
1091 vma.getBudget(&budgets[0], &budgets[1]);
1092 for (int i = 0; i < 2; ++i) {
1093 const D3D12MA::Statistics &stats(budgets[i].Stats);
1094 result.blockCount += stats.BlockCount;
1095 result.allocCount += stats.AllocationCount;
1096 result.usedBytes += stats.AllocationBytes;
1097 result.unusedBytes += stats.BlockBytes - stats.AllocationBytes;
1098 result.totalUsageBytes += budgets[i].UsageBytes;
1099 }
1100
1101 return result;
1102}
1103
1104bool QRhiD3D12::makeThreadLocalNativeContextCurrent()
1105{
1106 // not applicable
1107 return false;
1108}
1109
1110void QRhiD3D12::setQueueSubmitParams(QRhiNativeHandles *)
1111{
1112 // not applicable
1113}
1114
1115void QRhiD3D12::releaseCachedResources()
1116{
1117 shaderBytecodeCache.data.clear();
1118}
1119
1120// The pipeline library identifies pipelines by name, so we need a key that
1121// covers everything the PSO is built from. Note that the plain-data subobjects
1122// of the pipeline state stream can be hashed as they are: they are all
1123// value-initialized before their fields get set, so the padding is
1124// deterministic. The ones holding pointers cannot, and are fed in separately.
1125
1126static inline void addToKey(QCryptographicHash *h, const void *p, size_t size)
1127{
1128 h->addData(QByteArrayView(static_cast<const char *>(p), qsizetype(size)));
1129}
1130
1131template<typename T>
1132static inline void addToKey(QCryptographicHash *h, const T &v)
1133{
1134 addToKey(h, &v, sizeof(T));
1135}
1136
1137static inline void addToKey(QCryptographicHash *h, const QByteArray &b)
1138{
1139 const quint32 size = quint32(b.size());
1140 addToKey(h, size);
1141 h->addData(b);
1142}
1143
1144static inline void addToKey(QCryptographicHash *h, const QVector<quint32> &v)
1145{
1146 const quint32 count = quint32(v.count());
1147 addToKey(h, count);
1148 addToKey(h, v.constData(), v.count() * sizeof(quint32));
1149}
1150
1151bool QRhiD3D12::createPipelineLibrary(const QByteArray &blob)
1152{
1153#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1154 destroyPipelineLibrary();
1155
1156 pipelineLibraryBlob = blob;
1157 HRESULT hr = dev->CreatePipelineLibrary(pipelineLibraryBlob.isEmpty() ? nullptr
1158 : pipelineLibraryBlob.constData(),
1159 SIZE_T(pipelineLibraryBlob.size()),
1160 __uuidof(ID3D12PipelineLibrary1),
1161 reinterpret_cast<void **>(&pipelineLibrary));
1162 if (FAILED(hr)) {
1163 // E_NOTIMPL with drivers that have no pipeline library support,
1164 // D3D12_ERROR_DRIVER_VERSION_MISMATCH and
1165 // D3D12_ERROR_ADAPTER_NOT_FOUND for a stale blob. None of these are
1166 // fatal, they just mean no pipeline cache.
1167 qCDebug(QRHI_LOG_INFO, "Failed to create pipeline library: %s",
1168 qPrintable(QSystemError::windowsComString(hr)));
1169 pipelineLibrary = nullptr;
1170 pipelineLibraryBlob.clear();
1171 return false;
1172 }
1173 return true;
1174#else
1175 Q_UNUSED(blob);
1176 return false;
1177#endif
1178}
1179
1180bool QRhiD3D12::ensurePipelineLibrary()
1181{
1182#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1183 if (pipelineLibrary)
1184 return true;
1185 if (!rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
1186 return false;
1187 return createPipelineLibrary(QByteArray());
1188#else
1189 return false;
1190#endif
1191}
1192
1193void QRhiD3D12::destroyPipelineLibrary()
1194{
1195#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1196 if (pipelineLibrary) {
1197 pipelineLibrary->Release();
1198 pipelineLibrary = nullptr;
1199 }
1200 pipelineLibraryBlob.clear();
1201 pipelineLibraryNames.clear();
1202#endif
1203}
1204
1205ID3D12PipelineState *QRhiD3D12::loadOrCreatePipelineState(const D3D12_PIPELINE_STATE_STREAM_DESC *streamDesc,
1206 const QByteArray &cacheKey,
1207 const char *what)
1208{
1209 ID3D12PipelineState *pso = nullptr;
1210
1211#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1212 QVarLengthArray<wchar_t, 64> name;
1213 if (ensurePipelineLibrary() && !cacheKey.isEmpty()) {
1214 name.resize(cacheKey.size() + 1);
1215 for (qsizetype i = 0; i < cacheKey.size(); ++i)
1216 name[i] = wchar_t(cacheKey.at(i));
1217 name[cacheKey.size()] = 0;
1218 HRESULT hr = pipelineLibrary->LoadPipeline(name.constData(),
1219 streamDesc,
1220 __uuidof(ID3D12PipelineState),
1221 reinterpret_cast<void **>(&pso));
1222 if (SUCCEEDED(hr))
1223 return pso;
1224 }
1225#endif
1226
1227 HRESULT hr = dev->CreatePipelineState(streamDesc,
1228 __uuidof(ID3D12PipelineState),
1229 reinterpret_cast<void **>(&pso));
1230 if (FAILED(hr)) {
1231 qWarning("Failed to create %s pipeline state: %s",
1232 what, qPrintable(QSystemError::windowsComString(hr)));
1233 return nullptr;
1234 }
1235
1236#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1237 if (pipelineLibrary && !name.isEmpty() && !pipelineLibraryNames.contains(cacheKey)) {
1238 if (SUCCEEDED(pipelineLibrary->StorePipeline(name.constData(), pso)))
1239 pipelineLibraryNames.insert(cacheKey);
1240 }
1241#endif
1242
1243 return pso;
1244}
1245
1246struct QD3D12PipelineCacheDataHeader
1247{
1248 quint32 rhiId;
1249 quint32 arch;
1250 quint32 dataSize;
1251 quint32 vendorId;
1252 quint32 deviceId;
1253 quint32 reserved;
1254 quint64 adapterLuid;
1255};
1256
1257QByteArray QRhiD3D12::pipelineCacheData()
1258{
1259 static_assert(sizeof(QD3D12PipelineCacheDataHeader) == 32);
1260
1261 QByteArray data;
1262#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1263 if (!pipelineLibrary || !rhiFlags.testFlag(QRhi::EnablePipelineCacheDataSave))
1264 return data;
1265
1266 const SIZE_T dataSize = pipelineLibrary->GetSerializedSize();
1267 if (!dataSize)
1268 return data;
1269
1270 const size_t headerSize = sizeof(QD3D12PipelineCacheDataHeader);
1271 data.resize(qsizetype(headerSize + dataSize));
1272 HRESULT hr = pipelineLibrary->Serialize(data.data() + headerSize, dataSize);
1273 if (FAILED(hr)) {
1274 qCDebug(QRHI_LOG_INFO, "Failed to serialize pipeline library: %s",
1275 qPrintable(QSystemError::windowsComString(hr)));
1276 return QByteArray();
1277 }
1278
1279 QD3D12PipelineCacheDataHeader header = {};
1280 header.rhiId = pipelineCacheRhiId();
1281 header.arch = quint32(sizeof(void *));
1282 header.dataSize = quint32(dataSize);
1283 header.vendorId = quint32(driverInfoStruct.vendorId);
1284 header.deviceId = quint32(driverInfoStruct.deviceId);
1285 header.adapterLuid = (quint64(quint32(adapterLuid.HighPart)) << 32) | quint64(adapterLuid.LowPart);
1286 memcpy(data.data(), &header, headerSize);
1287#endif
1288 return data;
1289}
1290
1291void QRhiD3D12::setPipelineCacheData(const QByteArray &data)
1292{
1293#ifdef QRHI_D3D12_PIPELINE_LIBRARY_AVAILABLE
1294 if (data.isEmpty())
1295 return;
1296
1297 const size_t headerSize = sizeof(QD3D12PipelineCacheDataHeader);
1298 if (data.size() < qsizetype(headerSize)) {
1299 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob size");
1300 return;
1301 }
1302 QD3D12PipelineCacheDataHeader header;
1303 memcpy(&header, data.constData(), headerSize);
1304
1305 const quint32 rhiId = pipelineCacheRhiId();
1306 if (header.rhiId != rhiId) {
1307 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: The data is for a different QRhi version or backend (%u, %u)",
1308 rhiId, header.rhiId);
1309 return;
1310 }
1311 const quint32 arch = quint32(sizeof(void *));
1312 if (header.arch != arch) {
1313 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Architecture does not match (%u, %u)",
1314 arch, header.arch);
1315 return;
1316 }
1317 if (header.vendorId != quint32(driverInfoStruct.vendorId)) {
1318 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: vendorId does not match (%u, %u)",
1319 quint32(driverInfoStruct.vendorId), header.vendorId);
1320 return;
1321 }
1322 if (header.deviceId != quint32(driverInfoStruct.deviceId)) {
1323 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: deviceId does not match (%u, %u)",
1324 quint32(driverInfoStruct.deviceId), header.deviceId);
1325 return;
1326 }
1327 const quint64 luid = (quint64(quint32(adapterLuid.HighPart)) << 32) | quint64(adapterLuid.LowPart);
1328 if (header.adapterLuid != luid) {
1329 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: adapter LUID does not match");
1330 return;
1331 }
1332 if (quint64(data.size()) < quint64(headerSize) + header.dataSize) {
1333 qCDebug(QRHI_LOG_INFO, "setPipelineCacheData: Invalid blob, data missing");
1334 return;
1335 }
1336
1337 // The driver version is not in the header: CreatePipelineLibrary() checks
1338 // the blob against the current driver and adapter itself and fails with
1339 // D3D12_ERROR_DRIVER_VERSION_MISMATCH or _ADAPTER_NOT_FOUND when stale.
1340 if (createPipelineLibrary(data.mid(qsizetype(headerSize)))) {
1341 qCDebug(QRHI_LOG_INFO, "Created pipeline library with initial data of %u bytes",
1342 header.dataSize);
1343 }
1344#else
1345 Q_UNUSED(data);
1346#endif
1347}
1348
1349bool QRhiD3D12::isDeviceLost() const
1350{
1351 return deviceLost;
1352}
1353
1354QRhiRenderBuffer *QRhiD3D12::createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize,
1355 int sampleCount, QRhiRenderBuffer::Flags flags,
1356 QRhiTexture::Format backingFormatHint)
1357{
1358 return new QD3D12RenderBuffer(this, type, pixelSize, sampleCount, flags, backingFormatHint);
1359}
1360
1361QRhiTexture *QRhiD3D12::createTexture(QRhiTexture::Format format,
1362 const QSize &pixelSize, int depth, int arraySize,
1363 int sampleCount, QRhiTexture::Flags flags)
1364{
1365 return new QD3D12Texture(this, format, pixelSize, depth, arraySize, sampleCount, flags);
1366}
1367
1368QRhiSampler *QRhiD3D12::createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter,
1369 QRhiSampler::Filter mipmapMode,
1370 QRhiSampler::AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w)
1371{
1372 return new QD3D12Sampler(this, magFilter, minFilter, mipmapMode, u, v, w);
1373}
1374
1375QRhiTextureRenderTarget *QRhiD3D12::createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc,
1376 QRhiTextureRenderTarget::Flags flags)
1377{
1378 return new QD3D12TextureRenderTarget(this, desc, flags);
1379}
1380
1381QRhiShadingRateMap *QRhiD3D12::createShadingRateMap()
1382{
1383 return new QD3D12ShadingRateMap(this);
1384}
1385
1386QRhiGraphicsPipeline *QRhiD3D12::createGraphicsPipeline()
1387{
1388 return new QD3D12GraphicsPipeline(this);
1389}
1390
1391QRhiComputePipeline *QRhiD3D12::createComputePipeline()
1392{
1393 return new QD3D12ComputePipeline(this);
1394}
1395
1396QRhiShaderResourceBindings *QRhiD3D12::createShaderResourceBindings()
1397{
1398 return new QD3D12ShaderResourceBindings(this);
1399}
1400
1401void QRhiD3D12::setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps)
1402{
1403 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
1404 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
1405 QD3D12GraphicsPipeline *psD = QRHI_RES(QD3D12GraphicsPipeline, ps);
1406 const bool pipelineChanged = cbD->currentGraphicsPipeline != psD || cbD->currentPipelineGeneration != psD->generation;
1407
1408 if (pipelineChanged) {
1409 cbD->currentGraphicsPipeline = psD;
1410 cbD->currentComputePipeline = nullptr;
1411 cbD->currentPipelineGeneration = psD->generation;
1412
1413 if (QD3D12Pipeline *pipeline = pipelinePool.lookupRef(psD->handle)) {
1414 Q_ASSERT(pipeline->type == QD3D12Pipeline::Graphics);
1415 cbD->cmdList->SetPipelineState(pipeline->pso);
1416 if (QD3D12RootSignature *rs = rootSignaturePool.lookupRef(psD->rootSigHandle))
1417 cbD->cmdList->SetGraphicsRootSignature(rs->rootSig);
1418 }
1419
1420 cbD->cmdList->IASetPrimitiveTopology(psD->topology);
1421
1422 if (psD->viewInstanceMask)
1423 cbD->cmdList->SetViewInstanceMask(psD->viewInstanceMask);
1424
1425 if (cbD->hasCustomScissorSet && !psD->m_flags.testFlag(QRhiGraphicsPipeline::UsesScissor))
1426 setDefaultScissor(cbD);
1427 }
1428}
1429
1430void QD3D12ShaderResourceBindings::cacheUniformBuffer(QD3D12Stage s,
1431 const QRhiShaderResourceBinding::Data::UniformBufferData &d,
1432 int,
1433 int binding)
1434{
1435 // Which of the buffer's per-frame-slot resources to use, and what dynamic
1436 // offset to add, is only known when binding. Keep the QRhiBuffer so that
1437 // the cache stays valid regardless of the frame slot and the offsets.
1438 bindingCache.cbufs[s].append({ QRHI_RES(QD3D12Buffer, d.buf), d.offset, binding });
1439}
1440
1441void QD3D12ShaderResourceBindings::cacheTextures(QD3D12Stage s,
1442 const QRhiShaderResourceBinding::TextureAndSampler *d,
1443 int count,
1444 int)
1445{
1446 for (int i = 0; i < count; ++i) {
1447 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d[i].tex);
1448 bindingCache.srvs[s].append(texD->srv.cpuHandle);
1449 }
1450}
1451
1452void QD3D12ShaderResourceBindings::cacheSamplers(QD3D12Stage s,
1453 const QRhiShaderResourceBinding::TextureAndSampler *d,
1454 int count,
1455 int)
1456{
1457 if (count == 1) {
1458 QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, d[0].sampler);
1459 bindingCache.samplers[s].append(samplerD->lookupOrCreateShaderVisibleDescriptor().gpuHandle);
1460 return;
1461 }
1462
1463 // An array of samplers must be backed by consecutive descriptors. The
1464 // per-sampler descriptors are scattered in the shader-visible sampler
1465 // heap, so get a dedicated run for this combination of samplers instead.
1466 // (deduplicated by sampler description, so this is not per srb)
1467 QRHI_RES_RHI(QRhiD3D12);
1468 QVarLengthArray<Q_D3D12_SAMPLER_DESC, 8> descs;
1469 for (int i = 0; i < count; ++i)
1470 descs.append({ QRHI_RES(QD3D12Sampler, d[i].sampler)->desc });
1471 bindingCache.samplers[s].append(rhiD->samplerMgr.getShaderVisibleDescriptors(descs).gpuHandle);
1472}
1473
1474void QD3D12ShaderResourceBindings::cacheStorageBuffer(QD3D12Stage s,
1475 const QRhiShaderResourceBinding::Data::StorageBufferData &d,
1476 QD3D12ShaderResourceVisitor::StorageOp,
1477 int)
1478{
1479 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, d.buf);
1480 // SPIRV-Cross generated HLSL uses RWByteAddressBuffer
1481 D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
1482 uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
1483 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER;
1484 uavDesc.Buffer.FirstElement = d.offset / 4;
1485 uavDesc.Buffer.NumElements = aligned(bufD->m_size - d.offset, 4u) / 4;
1486 uavDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW;
1487 bindingCache.uavs[s].append({ bufD->handles[0], uavDesc });
1488}
1489
1490void QD3D12ShaderResourceBindings::cacheStorageImage(QD3D12Stage s,
1491 const QRhiShaderResourceBinding::Data::StorageImageData &d,
1492 QD3D12ShaderResourceVisitor::StorageOp,
1493 int)
1494{
1495 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, d.tex);
1496 const bool isCube = texD->m_flags.testFlag(QRhiTexture::CubeMap);
1497 const bool isArray = texD->m_flags.testFlag(QRhiTexture::TextureArray);
1498 const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
1499 D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
1500 uavDesc.Format = texD->rtFormat;
1501 if (isCube) {
1502 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
1503 uavDesc.Texture2DArray.MipSlice = UINT(d.level);
1504 uavDesc.Texture2DArray.FirstArraySlice = 0;
1505 uavDesc.Texture2DArray.ArraySize = 6;
1506 } else if (isArray) {
1507 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
1508 uavDesc.Texture2DArray.MipSlice = UINT(d.level);
1509 uavDesc.Texture2DArray.FirstArraySlice = 0;
1510 uavDesc.Texture2DArray.ArraySize = UINT(qMax(0, texD->m_arraySize));
1511 } else if (is3D) {
1512 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
1513 uavDesc.Texture3D.MipSlice = UINT(d.level);
1514 uavDesc.Texture3D.WSize = UINT(-1);
1515 } else {
1516 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
1517 uavDesc.Texture2D.MipSlice = UINT(d.level);
1518 }
1519 bindingCache.uavs[s].append({ texD->handle, uavDesc });
1520}
1521
1522void QD3D12ShaderResourceBindings::rebuildBindingCache(const QD3D12ShaderStageData *stageData,
1523 int stageCount)
1524{
1525 bindingCache.reset();
1526
1527 QD3D12ShaderResourceVisitor visitor(this, stageData, stageCount);
1528
1529 using namespace std::placeholders;
1530 visitor.uniformBuffer = std::bind(&QD3D12ShaderResourceBindings::cacheUniformBuffer, this, _1, _2, _3, _4);
1531 visitor.textures = std::bind(&QD3D12ShaderResourceBindings::cacheTextures, this, _1, _2, _3, _4);
1532 visitor.samplers = std::bind(&QD3D12ShaderResourceBindings::cacheSamplers, this, _1, _2, _3, _4);
1533 visitor.storageBuffer = std::bind(&QD3D12ShaderResourceBindings::cacheStorageBuffer, this, _1, _2, _3, _4);
1534 visitor.storageImage = std::bind(&QD3D12ShaderResourceBindings::cacheStorageImage, this, _1, _2, _3, _4);
1535
1536 visitor.visit();
1537
1538 // CBs use root constant buffer views, no need to count them here.
1539 for (int s = 0; s < 6; ++s) {
1540 bindingCache.srvUavCount += bindingCache.srvs[s].count();
1541 bindingCache.srvUavCount += bindingCache.uavs[s].count();
1542 }
1543
1544 bindingCacheValid = true;
1545 bindingCacheGeneration = generation;
1546}
1547
1548void QRhiD3D12::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb,
1549 int dynamicOffsetCount,
1550 const QRhiCommandBuffer::DynamicOffset *dynamicOffsets)
1551{
1552 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
1553 Q_ASSERT(cbD->recordingPass != QD3D12CommandBuffer::NoPass);
1554 QD3D12GraphicsPipeline *gfxPsD = QRHI_RES(QD3D12GraphicsPipeline, cbD->currentGraphicsPipeline);
1555 QD3D12ComputePipeline *compPsD = QRHI_RES(QD3D12ComputePipeline, cbD->currentComputePipeline);
1556
1557 if (!srb) {
1558 if (gfxPsD)
1559 srb = gfxPsD->m_shaderResourceBindings;
1560 else
1561 srb = compPsD->m_shaderResourceBindings;
1562 }
1563
1564 QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, srb);
1565
1566 bool pipelineChanged = false;
1567 if (gfxPsD) {
1568 pipelineChanged = srbD->lastUsedGraphicsPipeline != gfxPsD
1569 || srbD->lastUsedPipelineGeneration != gfxPsD->generation;
1570 srbD->lastUsedGraphicsPipeline = gfxPsD;
1571 srbD->lastUsedComputePipeline = nullptr;
1572 srbD->lastUsedPipelineGeneration = gfxPsD->generation;
1573 } else {
1574 pipelineChanged = srbD->lastUsedComputePipeline != compPsD
1575 || srbD->lastUsedPipelineGeneration != compPsD->generation;
1576 srbD->lastUsedGraphicsPipeline = nullptr;
1577 srbD->lastUsedComputePipeline = compPsD;
1578 srbD->lastUsedPipelineGeneration = compPsD->generation;
1579 }
1580
1581 bool srbUpdate = false;
1582
1583 for (int i = 0, ie = srbD->m_bindings.size(); i != ie; ++i) {
1584 const QRhiShaderResourceBinding::Data *b = shaderResourceBindingData(srbD->m_bindings[i]);
1585 QD3D12ShaderResourceBindings::BoundResourceData &bd(srbD->boundResourceData[i]);
1586 switch (b->type) {
1587 case QRhiShaderResourceBinding::UniformBuffer:
1588 {
1589 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, b->u.ubuf.buf);
1590 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::UniformBuffer));
1591 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
1592 sanityCheckResourceOwnership(bufD);
1593 bufD->executeHostWritesForFrameSlot(currentFrameSlot);
1594 if (bufD->generation != bd.ubuf.generation || bufD->m_id != bd.ubuf.id) {
1595 srbUpdate = true;
1596 bd.ubuf.id = bufD->m_id;
1597 bd.ubuf.generation = bufD->generation;
1598 }
1599 }
1600 break;
1601 case QRhiShaderResourceBinding::SampledTexture:
1602 case QRhiShaderResourceBinding::Texture:
1603 case QRhiShaderResourceBinding::Sampler:
1604 {
1605 const QRhiShaderResourceBinding::Data::TextureAndOrSamplerData *data = &b->u.stex;
1606 if (bd.stex.count != data->count) {
1607 bd.stex.count = data->count;
1608 srbUpdate = true;
1609 }
1610 for (int elem = 0; elem < data->count; ++elem) {
1611 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, data->texSamplers[elem].tex);
1612 QD3D12Sampler *samplerD = QRHI_RES(QD3D12Sampler, data->texSamplers[elem].sampler);
1613 // We use the same code path for both combined and separate
1614 // images and samplers, so tex or sampler (but not both) can be
1615 // null here.
1616 Q_ASSERT(texD || samplerD);
1617 sanityCheckResourceOwnership(texD);
1618 sanityCheckResourceOwnership(samplerD);
1619 const quint64 texId = texD ? texD->m_id : 0;
1620 const uint texGen = texD ? texD->generation : 0;
1621 const quint64 samplerId = samplerD ? samplerD->m_id : 0;
1622 const uint samplerGen = samplerD ? samplerD->generation : 0;
1623 if (texId != bd.stex.d[elem].texId || texGen != bd.stex.d[elem].texGeneration
1624 || samplerId != bd.stex.d[elem].samplerId
1625 || samplerGen != bd.stex.d[elem].samplerGeneration)
1626 {
1627 srbUpdate = true;
1628 bd.stex.d[elem].texId = texId;
1629 bd.stex.d[elem].texGeneration = texGen;
1630 bd.stex.d[elem].samplerId = samplerId;
1631 bd.stex.d[elem].samplerGeneration = samplerGen;
1632 }
1633 if (texD) {
1634 UINT state = 0;
1635 if (b->stage == QRhiShaderResourceBinding::FragmentStage) {
1636 state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
1637 } else if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) {
1638 state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
1639 } else {
1640 state = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
1641 }
1642 barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATES(state));
1643 }
1644 }
1645 }
1646 break;
1647 case QRhiShaderResourceBinding::ImageLoad:
1648 case QRhiShaderResourceBinding::ImageStore:
1649 case QRhiShaderResourceBinding::ImageLoadStore:
1650 {
1651 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, b->u.simage.tex);
1652 sanityCheckResourceOwnership(texD);
1653 if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
1654 srbUpdate = true;
1655 bd.simage.id = texD->m_id;
1656 bd.simage.generation = texD->generation;
1657 }
1658 if (QD3D12Resource *res = resourcePool.lookupRef(texD->handle)) {
1659 if (res->uavUsage) {
1660 if (res->uavUsage & QD3D12Resource::UavUsageWrite) {
1661 // RaW or WaW
1662 barrierGen.enqueueUavBarrier(cbD, texD->handle);
1663 } else {
1664 if (b->type == QRhiShaderResourceBinding::ImageStore
1665 || b->type == QRhiShaderResourceBinding::ImageLoadStore)
1666 {
1667 // WaR or WaW
1668 barrierGen.enqueueUavBarrier(cbD, texD->handle);
1669 }
1670 }
1671 }
1672 res->uavUsage = 0;
1673 if (b->type == QRhiShaderResourceBinding::ImageLoad || b->type == QRhiShaderResourceBinding::ImageLoadStore)
1674 res->uavUsage |= QD3D12Resource::UavUsageRead;
1675 if (b->type == QRhiShaderResourceBinding::ImageStore || b->type == QRhiShaderResourceBinding::ImageLoadStore)
1676 res->uavUsage |= QD3D12Resource::UavUsageWrite;
1677 barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
1678 }
1679 }
1680 break;
1681 case QRhiShaderResourceBinding::BufferLoad:
1682 case QRhiShaderResourceBinding::BufferStore:
1683 case QRhiShaderResourceBinding::BufferLoadStore:
1684 {
1685 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, b->u.sbuf.buf);
1686 sanityCheckResourceOwnership(bufD);
1687 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::StorageBuffer));
1688 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
1689 if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
1690 srbUpdate = true;
1691 bd.sbuf.id = bufD->m_id;
1692 bd.sbuf.generation = bufD->generation;
1693 }
1694 if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) {
1695 if (res->uavUsage) {
1696 if (res->uavUsage & QD3D12Resource::UavUsageWrite) {
1697 // RaW or WaW
1698 barrierGen.enqueueUavBarrier(cbD, bufD->handles[0]);
1699 } else {
1700 if (b->type == QRhiShaderResourceBinding::BufferStore
1701 || b->type == QRhiShaderResourceBinding::BufferLoadStore)
1702 {
1703 // WaR or WaW
1704 barrierGen.enqueueUavBarrier(cbD, bufD->handles[0]);
1705 }
1706 }
1707 }
1708 res->uavUsage = 0;
1709 if (b->type == QRhiShaderResourceBinding::BufferLoad || b->type == QRhiShaderResourceBinding::BufferLoadStore)
1710 res->uavUsage |= QD3D12Resource::UavUsageRead;
1711 if (b->type == QRhiShaderResourceBinding::BufferStore || b->type == QRhiShaderResourceBinding::BufferLoadStore)
1712 res->uavUsage |= QD3D12Resource::UavUsageWrite;
1713 barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
1714 }
1715 }
1716 break;
1717 }
1718 }
1719
1720 const bool srbChanged = gfxPsD ? (cbD->currentGraphicsSrb != srb) : (cbD->currentComputeSrb != srb);
1721 const bool srbRebuilt = cbD->currentSrbGeneration != srbD->generation;
1722
1723 // One ResourceBarrier() for all the transitions, instead of one per
1724 // binding. While the enqueueUavBarrier() calls in the loop above may make
1725 // deferring this to here seem incorrect, that is not a problem: no draw or
1726 // dispatch is recorded in-between, so the whole set takes effect before the
1727 // next one either way, and for a given resource the relative order is still
1728 // preserved.
1729 barrierGen.enqueueBufferedTransitionBarriers(cbD);
1730
1731 // Rebuilding the cache is the expensive part (it runs the visitor over all
1732 // bindings for all stages), so only do it when something it depends on
1733 // actually changed. Note that srbChanged is deliberately not part of this:
1734 // binding a different srb does not invalidate this srb's cache.
1735 if (pipelineChanged || srbUpdate || !srbD->bindingCacheValid
1736 || srbD->bindingCacheGeneration != srbD->generation)
1737 {
1738 const QD3D12ShaderStageData *stageData = gfxPsD ? gfxPsD->stageData.data() : &compPsD->stageData;
1739 srbD->rebuildBindingCache(stageData, gfxPsD ? 5 : 1);
1740 }
1741
1742 if (pipelineChanged || srbChanged || srbRebuilt || srbUpdate || srbD->hasDynamicOffset) {
1743 // The order of root parameters must match
1744 // QD3D12ShaderResourceBindings::createRootSignature(), meaning the
1745 // logic below must mirror that function (uniform buffers first etc.)
1746
1747 const QD3D12ShaderResourceBindings::BindingCache &cache(srbD->bindingCache);
1748
1749 bool gotNewHeap = false;
1750 if (!ensureShaderVisibleDescriptorHeapCapacity(&shaderVisibleCbvSrvUavHeap,
1751 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
1752 currentFrameSlot,
1753 cache.srvUavCount,
1754 &gotNewHeap))
1755 {
1756 return;
1757 }
1758 if (gotNewHeap) {
1759 qCDebug(QRHI_LOG_INFO, "Created new shader-visible CBV/SRV/UAV descriptor heap,"
1760 " per-frame slice size is now %u,"
1761 " if this happens frequently then that's not great.",
1762 shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[0].capacity);
1763 bindShaderVisibleHeaps(cbD);
1764 }
1765
1766 int rootParamIndex = 0;
1767 for (int s = 0; s < 6; ++s) {
1768 for (const QD3D12ShaderResourceBindings::BindingCache::CBuf &cbuf : cache.cbufs[s]) {
1769 if (QD3D12Resource *res = resourcePool.lookupRef(cbuf.buf->handles[currentFrameSlot])) {
1770 quint32 offset = cbuf.offset;
1771 if (srbD->hasDynamicOffset) {
1772 for (int i = 0; i < dynamicOffsetCount; ++i) {
1773 const QRhiCommandBuffer::DynamicOffset &dynOfs(dynamicOffsets[i]);
1774 if (dynOfs.first == cbuf.binding) {
1775 Q_ASSERT(aligned(dynOfs.second, 256u) == dynOfs.second);
1776 offset += dynOfs.second;
1777 }
1778 }
1779 }
1780 const D3D12_GPU_VIRTUAL_ADDRESS gpuAddr = res->resource->GetGPUVirtualAddress() + offset;
1781 if (gfxPsD)
1782 cbD->cmdList->SetGraphicsRootConstantBufferView(rootParamIndex, gpuAddr);
1783 else
1784 cbD->cmdList->SetComputeRootConstantBufferView(rootParamIndex, gpuAddr);
1785 }
1786 rootParamIndex += 1;
1787 }
1788 }
1789 for (int s = 0; s < 6; ++s) {
1790 if (!cache.srvs[s].isEmpty()) {
1791 QD3D12DescriptorHeap &gpuSrvHeap(shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot]);
1792 const UINT count = UINT(cache.srvs[s].count());
1793 const QD3D12Descriptor startDesc = gpuSrvHeap.get(count);
1794 // One destination range of count descriptors, count source
1795 // ranges of one descriptor each (nullptr sizes means all 1).
1796 dev->CopyDescriptors(1, &startDesc.cpuHandle, &count,
1797 count, cache.srvs[s].constData(), nullptr,
1798 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
1799
1800 if (gfxPsD)
1801 cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
1802 else
1803 cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
1804
1805 rootParamIndex += 1;
1806 }
1807 }
1808 for (int s = 0; s < 6; ++s) {
1809 // Samplers are one parameter / descriptor table each, and the
1810 // descriptor is from the shader visible sampler heap already.
1811 for (D3D12_GPU_DESCRIPTOR_HANDLE samplerDescriptor : cache.samplers[s]) {
1812 if (gfxPsD)
1813 cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, samplerDescriptor);
1814 else
1815 cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, samplerDescriptor);
1816
1817 rootParamIndex += 1;
1818 }
1819 }
1820 for (int s = 0; s < 6; ++s) {
1821 if (!cache.uavs[s].isEmpty()) {
1822 QD3D12DescriptorHeap &gpuUavHeap(shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot]);
1823 const int count = cache.uavs[s].count();
1824 const QD3D12Descriptor startDesc = gpuUavHeap.get(count);
1825 for (int i = 0; i < count; ++i) {
1826 const QD3D12ShaderResourceBindings::BindingCache::Uav &uav(cache.uavs[s][i]);
1827 if (QD3D12Resource *res = resourcePool.lookupRef(uav.handle)) {
1828 dev->CreateUnorderedAccessView(res->resource, nullptr, &uav.desc,
1829 gpuUavHeap.incremented(startDesc, i).cpuHandle);
1830 } else {
1831 dev->CreateUnorderedAccessView(nullptr, nullptr, nullptr,
1832 gpuUavHeap.incremented(startDesc, i).cpuHandle);
1833 }
1834 }
1835
1836 if (gfxPsD)
1837 cbD->cmdList->SetGraphicsRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
1838 else
1839 cbD->cmdList->SetComputeRootDescriptorTable(rootParamIndex, startDesc.gpuHandle);
1840
1841 rootParamIndex += 1;
1842 }
1843 }
1844
1845 if (gfxPsD) {
1846 cbD->currentGraphicsSrb = srb;
1847 cbD->currentComputeSrb = nullptr;
1848 } else {
1849 cbD->currentGraphicsSrb = nullptr;
1850 cbD->currentComputeSrb = srb;
1851 }
1852 cbD->currentSrbGeneration = srbD->generation;
1853 }
1854}
1855
1856void QRhiD3D12::setVertexInput(QRhiCommandBuffer *cb,
1857 int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings,
1858 QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat)
1859{
1860 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
1861 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
1862
1863 bool needsBindVBuf = false;
1864 for (int i = 0; i < bindingCount; ++i) {
1865 const int inputSlot = startBinding + i;
1866 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, bindings[i].first);
1867 Q_ASSERT(bufD->m_usage.testFlag(QRhiBuffer::VertexBuffer));
1868 const bool isDynamic = bufD->m_type == QRhiBuffer::Dynamic;
1869 if (isDynamic)
1870 bufD->executeHostWritesForFrameSlot(currentFrameSlot);
1871
1872 if (cbD->currentVertexBuffers[inputSlot] != bufD->handles[isDynamic ? currentFrameSlot : 0]
1873 || cbD->currentVertexOffsets[inputSlot] != bindings[i].second)
1874 {
1875 needsBindVBuf = true;
1876 cbD->currentVertexBuffers[inputSlot] = bufD->handles[isDynamic ? currentFrameSlot : 0];
1877 cbD->currentVertexOffsets[inputSlot] = bindings[i].second;
1878 }
1879 }
1880
1881 if (needsBindVBuf) {
1882 QVarLengthArray<D3D12_VERTEX_BUFFER_VIEW, 4> vbv;
1883 vbv.reserve(bindingCount);
1884
1885 QD3D12GraphicsPipeline *psD = cbD->currentGraphicsPipeline;
1886 const QRhiVertexInputLayout &inputLayout(psD->m_vertexInputLayout);
1887 const int inputBindingCount = inputLayout.cendBindings() - inputLayout.cbeginBindings();
1888
1889 for (int i = 0, ie = qMin(bindingCount, inputBindingCount); i != ie; ++i) {
1890 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, bindings[i].first);
1891 const QD3D12ObjectHandle handle = bufD->handles[bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0];
1892 const quint32 offset = bindings[i].second;
1893 const quint32 stride = inputLayout.bindingAt(i)->stride();
1894
1895 if (bufD->m_type != QRhiBuffer::Dynamic) {
1896 barrierGen.addTransitionBarrier(handle, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
1897 barrierGen.enqueueBufferedTransitionBarriers(cbD);
1898 }
1899
1900 if (QD3D12Resource *res = resourcePool.lookupRef(handle)) {
1901 vbv.append({
1902 res->resource->GetGPUVirtualAddress() + offset,
1903 UINT(res->desc.Width - offset),
1904 stride
1905 });
1906 }
1907 }
1908
1909 cbD->cmdList->IASetVertexBuffers(UINT(startBinding), vbv.count(), vbv.constData());
1910 }
1911
1912 if (indexBuf) {
1913 QD3D12Buffer *ibufD = QRHI_RES(QD3D12Buffer, indexBuf);
1914 Q_ASSERT(ibufD->m_usage.testFlag(QRhiBuffer::IndexBuffer));
1915 const bool isDynamic = ibufD->m_type == QRhiBuffer::Dynamic;
1916 if (isDynamic)
1917 ibufD->executeHostWritesForFrameSlot(currentFrameSlot);
1918
1919 const DXGI_FORMAT dxgiFormat = indexFormat == QRhiCommandBuffer::IndexUInt16 ? DXGI_FORMAT_R16_UINT
1920 : DXGI_FORMAT_R32_UINT;
1921 if (cbD->currentIndexBuffer != ibufD->handles[isDynamic ? currentFrameSlot : 0]
1922 || cbD->currentIndexOffset != indexOffset
1923 || cbD->currentIndexFormat != dxgiFormat)
1924 {
1925 cbD->currentIndexBuffer = ibufD->handles[isDynamic ? currentFrameSlot : 0];
1926 cbD->currentIndexOffset = indexOffset;
1927 cbD->currentIndexFormat = dxgiFormat;
1928
1929 if (ibufD->m_type != QRhiBuffer::Dynamic) {
1930 barrierGen.addTransitionBarrier(cbD->currentIndexBuffer, D3D12_RESOURCE_STATE_INDEX_BUFFER);
1931 barrierGen.enqueueBufferedTransitionBarriers(cbD);
1932 }
1933
1934 if (QD3D12Resource *res = resourcePool.lookupRef(cbD->currentIndexBuffer)) {
1935 const D3D12_INDEX_BUFFER_VIEW ibv = {
1936 res->resource->GetGPUVirtualAddress() + indexOffset,
1937 UINT(res->desc.Width - indexOffset),
1938 dxgiFormat
1939 };
1940 cbD->cmdList->IASetIndexBuffer(&ibv);
1941 }
1942 }
1943 }
1944}
1945
1946void QRhiD3D12::setDefaultScissor(QD3D12CommandBuffer *cbD)
1947{
1948 cbD->hasCustomScissorSet = false;
1949
1950 const QSize outputSize = cbD->currentTarget->pixelSize();
1951 std::array<float, 4> vp = cbD->currentViewport.viewport();
1952 float x = 0, y = 0, w = 0, h = 0;
1953
1954 if (qFuzzyIsNull(vp[2]) && qFuzzyIsNull(vp[3])) {
1955 x = 0;
1956 y = 0;
1957 w = outputSize.width();
1958 h = outputSize.height();
1959 } else {
1960 // x,y is top-left in D3D12_RECT but bottom-left in QRhiScissor
1961 qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, vp, &x, &y, &w, &h);
1962 }
1963
1964 D3D12_RECT r;
1965 r.left = x;
1966 r.top = y;
1967 // right and bottom are exclusive
1968 r.right = x + w;
1969 r.bottom = y + h;
1970 cbD->cmdList->RSSetScissorRects(1, &r);
1971}
1972
1973void QRhiD3D12::setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport)
1974{
1975 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
1976 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
1977 Q_ASSERT(cbD->currentTarget);
1978 const QSize outputSize = cbD->currentTarget->pixelSize();
1979
1980 // D3D expects top-left, QRhiViewport is bottom-left
1981 float x, y, w, h;
1982 if (!qrhi_toTopLeftRenderTargetRect<UnBounded>(outputSize, viewport.viewport(), &x, &y, &w, &h))
1983 return;
1984
1985 D3D12_VIEWPORT v;
1986 v.TopLeftX = x;
1987 v.TopLeftY = y;
1988 v.Width = w;
1989 v.Height = h;
1990 v.MinDepth = viewport.minDepth();
1991 v.MaxDepth = viewport.maxDepth();
1992 cbD->cmdList->RSSetViewports(1, &v);
1993
1994 cbD->currentViewport = viewport;
1995 if (cbD->currentGraphicsPipeline
1996 && !cbD->currentGraphicsPipeline->flags().testFlag(QRhiGraphicsPipeline::UsesScissor))
1997 {
1998 setDefaultScissor(cbD);
1999 }
2000}
2001
2002void QRhiD3D12::setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor)
2003{
2004 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2005 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2006 Q_ASSERT(cbD->currentTarget);
2007 const QSize outputSize = cbD->currentTarget->pixelSize();
2008
2009 // D3D expects top-left, QRhiScissor is bottom-left
2010 int x, y, w, h;
2011 if (!qrhi_toTopLeftRenderTargetRect<Bounded>(outputSize, scissor.scissor(), &x, &y, &w, &h))
2012 return;
2013
2014 D3D12_RECT r;
2015 r.left = x;
2016 r.top = y;
2017 // right and bottom are exclusive
2018 r.right = x + w;
2019 r.bottom = y + h;
2020 cbD->cmdList->RSSetScissorRects(1, &r);
2021
2022 cbD->hasCustomScissorSet = true;
2023}
2024
2025void QRhiD3D12::setBlendConstants(QRhiCommandBuffer *cb, const QColor &c)
2026{
2027 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2028 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2029 float v[4] = { c.redF(), c.greenF(), c.blueF(), c.alphaF() };
2030 cbD->cmdList->OMSetBlendFactor(v);
2031}
2032
2033void QRhiD3D12::setStencilRef(QRhiCommandBuffer *cb, quint32 refValue)
2034{
2035 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2036 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2037 cbD->cmdList->OMSetStencilRef(refValue);
2038}
2039
2040void QRhiD3D12::setPushConstants(QRhiCommandBuffer *cb, quint32 offset, quint32 size, const void *data)
2041{
2042 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2043 Q_ASSERT(cbD->recordingPass != QD3D12CommandBuffer::NoPass);
2044
2045 const bool compute = cbD->recordingPass == QD3D12CommandBuffer::ComputePass;
2046 const int index = compute ? (cbD->currentComputePipeline ? cbD->currentComputePipeline->pushConstantRootParamIndex : -1)
2047 : (cbD->currentGraphicsPipeline ? cbD->currentGraphicsPipeline->pushConstantRootParamIndex : -1);
2048 if (index < 0) {
2049 qWarning("No pipeline with a push constant block is active; setPushConstants ignored");
2050 return;
2051 }
2052
2053 if (compute)
2054 cbD->cmdList->SetComputeRoot32BitConstants(UINT(index), size / 4, data, offset / 4);
2055 else
2056 cbD->cmdList->SetGraphicsRoot32BitConstants(UINT(index), size / 4, data, offset / 4);
2057}
2058
2059static inline D3D12_SHADING_RATE toD3DShadingRate(const QSize &coarsePixelSize)
2060{
2061 if (coarsePixelSize == QSize(1, 2))
2062 return D3D12_SHADING_RATE_1X2;
2063 if (coarsePixelSize == QSize(2, 1))
2064 return D3D12_SHADING_RATE_2X1;
2065 if (coarsePixelSize == QSize(2, 2))
2066 return D3D12_SHADING_RATE_2X2;
2067 if (coarsePixelSize == QSize(2, 4))
2068 return D3D12_SHADING_RATE_2X4;
2069 if (coarsePixelSize == QSize(4, 2))
2070 return D3D12_SHADING_RATE_4X2;
2071 if (coarsePixelSize == QSize(4, 4))
2072 return D3D12_SHADING_RATE_4X4;
2073 return D3D12_SHADING_RATE_1X1;
2074}
2075
2076void QRhiD3D12::setShadingRate(QRhiCommandBuffer *cb, const QSize &coarsePixelSize)
2077{
2078 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2079 cbD->hasShadingRateSet = false;
2080
2081#ifdef QRHI_D3D12_CL5_AVAILABLE
2082 if (!caps.vrs)
2083 return;
2084
2085 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2086 const D3D12_SHADING_RATE_COMBINER combiners[] = { D3D12_SHADING_RATE_COMBINER_MAX, D3D12_SHADING_RATE_COMBINER_MAX };
2087 cbD->cmdList->RSSetShadingRate(toD3DShadingRate(coarsePixelSize), combiners);
2088 if (coarsePixelSize.width() != 1 || coarsePixelSize.height() != 1)
2089 cbD->hasShadingRateSet = true;
2090#else
2091 Q_UNUSED(cb);
2092 Q_UNUSED(coarsePixelSize);
2093 qWarning("Attempted to set ShadingRate without building Qt against a sufficiently new Windows SDK and d3d12.h. This cannot work.");
2094#endif
2095}
2096
2097void QRhiD3D12::draw(QRhiCommandBuffer *cb, quint32 vertexCount,
2098 quint32 instanceCount, quint32 firstVertex, quint32 firstInstance)
2099{
2100 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2101 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2102 cbD->cmdList->DrawInstanced(vertexCount, instanceCount, firstVertex, firstInstance);
2103}
2104
2105void QRhiD3D12::drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount,
2106 quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance)
2107{
2108 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2109 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2110 cbD->cmdList->DrawIndexedInstanced(indexCount, instanceCount,
2111 firstIndex, vertexOffset,
2112 firstInstance);
2113}
2114
2115void QRhiD3D12::drawIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
2116 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
2117{
2118 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2119 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2120
2121 QD3D12Buffer *indirectBufferD = QRHI_RES(QD3D12Buffer, indirectBuffer);
2122 const bool isDynamic = indirectBufferD->m_type == QRhiBuffer::Dynamic;
2123 const QD3D12ObjectHandle indirectBufferHandle = indirectBufferD->handles[isDynamic ? currentFrameSlot : 0];
2124 if (isDynamic) {
2125 indirectBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
2126 } else {
2127 barrierGen.addTransitionBarrier(indirectBufferHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
2128 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2129 }
2130 QD3D12Resource *indirectRes = resourcePool.lookupRef(indirectBufferHandle);
2131 if (!indirectRes)
2132 return;
2133 ID3D12Resource *indirectBufferRes = indirectRes->resource;
2134
2135 const bool canUseMulti = (stride == sizeof(QRhiIndirectDrawCommand) && drawCommandSignature);
2136
2137 if (canUseMulti && drawCount > 1) {
2138 cbD->cmdList->ExecuteIndirect(drawCommandSignature, drawCount,
2139 indirectBufferRes, indirectBufferOffset,
2140 nullptr, 0);
2141 } else {
2142 UINT offset = indirectBufferOffset;
2143 for (quint32 i = 0; i < drawCount; ++i) {
2144 cbD->cmdList->ExecuteIndirect(drawCommandSignature, 1,
2145 indirectBufferRes, offset,
2146 nullptr, 0);
2147 offset += stride;
2148 }
2149 }
2150}
2151
2152void QRhiD3D12::drawIndexedIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
2153 quint32 indirectBufferOffset, quint32 drawCount, quint32 stride)
2154{
2155 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2156 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2157
2158 QD3D12Buffer *indirectBufferD = QRHI_RES(QD3D12Buffer, indirectBuffer);
2159 const bool isDynamic = indirectBufferD->m_type == QRhiBuffer::Dynamic;
2160 const QD3D12ObjectHandle indirectBufferHandle = indirectBufferD->handles[isDynamic ? currentFrameSlot : 0];
2161 if (isDynamic) {
2162 indirectBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
2163 } else {
2164 barrierGen.addTransitionBarrier(indirectBufferHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
2165 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2166 }
2167 QD3D12Resource *indirectRes = resourcePool.lookupRef(indirectBufferHandle);
2168 if (!indirectRes)
2169 return;
2170 ID3D12Resource *indirectBufferRes = indirectRes->resource;
2171
2172 const bool canUseMulti = (stride == sizeof(QRhiIndexedIndirectDrawCommand) && drawIndexedCommandSignature);
2173
2174 if (canUseMulti && drawCount > 1) {
2175 cbD->cmdList->ExecuteIndirect(drawIndexedCommandSignature, drawCount,
2176 indirectBufferRes, indirectBufferOffset,
2177 nullptr, 0);
2178 } else {
2179 UINT offset = indirectBufferOffset;
2180 for (quint32 i = 0; i < drawCount; ++i) {
2181 cbD->cmdList->ExecuteIndirect(drawIndexedCommandSignature, 1,
2182 indirectBufferRes, offset,
2183 nullptr, 0);
2184 offset += stride;
2185 }
2186 }
2187}
2188
2189void QRhiD3D12::debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name)
2190{
2191 if (!debugMarkers)
2192 return;
2193
2194 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2195#ifdef QRHI_D3D12_HAS_OLD_PIX
2196 PIXBeginEvent(cbD->cmdList, PIX_COLOR_DEFAULT, reinterpret_cast<LPCWSTR>(QString::fromLatin1(name).utf16()));
2197#else
2198 Q_UNUSED(cbD);
2199 Q_UNUSED(name);
2200#endif
2201}
2202
2203void QRhiD3D12::debugMarkEnd(QRhiCommandBuffer *cb)
2204{
2205 if (!debugMarkers)
2206 return;
2207
2208 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2209#ifdef QRHI_D3D12_HAS_OLD_PIX
2210 PIXEndEvent(cbD->cmdList);
2211#else
2212 Q_UNUSED(cbD);
2213#endif
2214}
2215
2216void QRhiD3D12::debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg)
2217{
2218 if (!debugMarkers)
2219 return;
2220
2221 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2222#ifdef QRHI_D3D12_HAS_OLD_PIX
2223 PIXSetMarker(cbD->cmdList, PIX_COLOR_DEFAULT, reinterpret_cast<LPCWSTR>(QString::fromLatin1(msg).utf16()));
2224#else
2225 Q_UNUSED(cbD);
2226 Q_UNUSED(msg);
2227#endif
2228}
2229
2230const QRhiNativeHandles *QRhiD3D12::nativeHandles(QRhiCommandBuffer *cb)
2231{
2232 return QRHI_RES(QD3D12CommandBuffer, cb)->nativeHandles();
2233}
2234
2235void QRhiD3D12::beginExternal(QRhiCommandBuffer *cb)
2236{
2237 Q_UNUSED(cb);
2238}
2239
2240void QRhiD3D12::endExternal(QRhiCommandBuffer *cb)
2241{
2242 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2243 cbD->resetPerPassState();
2244 bindShaderVisibleHeaps(cbD);
2245 if (cbD->currentTarget) { // could be compute, no rendertarget then
2246 QD3D12RenderTargetData *rtD = rtData(cbD->currentTarget);
2247 cbD->cmdList->OMSetRenderTargets(UINT(rtD->colorAttCount),
2248 rtD->rtv,
2249 FALSE,
2250 rtD->dsAttCount ? &rtD->dsv : nullptr);
2251 }
2252}
2253
2254double QRhiD3D12::lastCompletedGpuTime(QRhiCommandBuffer *cb)
2255{
2256 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2257 return cbD->lastGpuTime;
2258}
2259
2260static void calculateGpuTime(QD3D12CommandBuffer *cbD,
2261 int timestampPairStartIndex,
2262 const quint8 *readbackBufPtr,
2263 quint64 timestampTicksPerSecond)
2264{
2265 const size_t byteOffset = timestampPairStartIndex * sizeof(quint64);
2266 const quint64 *p = reinterpret_cast<const quint64 *>(readbackBufPtr + byteOffset);
2267 const quint64 startTime = *p++;
2268 const quint64 endTime = *p;
2269 if (startTime < endTime) {
2270 const quint64 ticks = endTime - startTime;
2271 const double timeSec = ticks / double(timestampTicksPerSecond);
2272 cbD->lastGpuTime = timeSec;
2273 }
2274}
2275
2276QRhi::FrameOpResult QRhiD3D12::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags)
2277{
2278 Q_UNUSED(flags);
2279
2280 // Report a device loss that was detected earlier (in createOrResize()'s
2281 // ResizeBuffers(), during swapchain creation, or by the previous frame's
2282 // Present()). Without this the caller has no way to find out - it would
2283 // carry on and render into resources that could not be recreated. The
2284 // Vulkan and OpenGL backends already report the loss from beginFrame().
2285 if (deviceLost)
2286 return QRhi::FrameOpDeviceLost;
2287
2288 QD3D12SwapChain *swapChainD = QRHI_RES(QD3D12SwapChain, swapChain);
2289 currentSwapChain = swapChainD;
2290 currentFrameSlot = swapChainD->currentFrameSlot;
2291 QD3D12SwapChain::FrameResources &fr(swapChainD->frameRes[currentFrameSlot]);
2292
2293 // We could do smarter things but mirror the Vulkan backend for now: Make
2294 // sure the previous commands for this same frame slot have finished. Do
2295 // this also for any other swapchain's commands with the same frame slot.
2296 // While this reduces concurrency in render-to-swapchain-A,
2297 // render-to-swapchain-B, repeat kind of scenarios, it keeps resource usage
2298 // safe: swapchain A starting its frame 0, followed by swapchain B starting
2299 // its own frame 0 will make B wait for A's frame 0 commands. If a resource
2300 // is written in B's frame or when B checks for pending resource releases,
2301 // that won't mess up A's in-flight commands (as they are guaranteed not to
2302 // be in flight anymore). With Qt Quick this situation cannot happen anyway
2303 // by design (one QRhi per window).
2304 for (QD3D12SwapChain *sc : std::as_const(swapchains))
2305 sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: swapChainD->currentFrameSlot, not sc's
2306
2307 if (swapChainD->frameLatencyWaitableObject) {
2308 // only wait when endFrame() called Present(), otherwise this would become a 1 sec timeout
2309 if (swapChainD->lastFrameLatencyWaitSlot != currentFrameSlot) {
2310 WaitForSingleObjectEx(swapChainD->frameLatencyWaitableObject, 1000, true);
2311 swapChainD->lastFrameLatencyWaitSlot = currentFrameSlot;
2312 }
2313 }
2314
2315 HRESULT hr = cmdAllocators[currentFrameSlot]->Reset();
2316 if (FAILED(hr)) {
2317 qWarning("Failed to reset command allocator: %s",
2318 qPrintable(QSystemError::windowsComString(hr)));
2319 return QRhi::FrameOpError;
2320 }
2321
2322 if (!startCommandListForCurrentFrameSlot(&fr.cmdList))
2323 return QRhi::FrameOpError;
2324
2325 QD3D12CommandBuffer *cbD = &swapChainD->cbWrapper;
2326 cbD->cmdList = fr.cmdList;
2327
2328 swapChainD->rtWrapper.d.rtv[0] = swapChainD->sampleDesc.Count > 1
2329 ? swapChainD->msaaRtvs[currentFrameSlot].cpuHandle
2330 : swapChainD->rtvs[swapChainD->currentBackBufferIndex].cpuHandle;
2331
2332 swapChainD->rtWrapper.d.dsv = swapChainD->ds ? swapChainD->ds->dsv.cpuHandle
2333 : D3D12_CPU_DESCRIPTOR_HANDLE { 0 };
2334
2335 if (swapChainD->stereo) {
2336 swapChainD->rtWrapperRight.d.rtv[0] = swapChainD->sampleDesc.Count > 1
2337 ? swapChainD->msaaRtvs[currentFrameSlot].cpuHandle
2338 : swapChainD->rtvsRight[swapChainD->currentBackBufferIndex].cpuHandle;
2339
2340 swapChainD->rtWrapperRight.d.dsv =
2341 swapChainD->ds ? swapChainD->ds->dsv.cpuHandle : D3D12_CPU_DESCRIPTOR_HANDLE{ 0 };
2342 }
2343
2344
2345 // Time to release things that are marked for currentFrameSlot since due to
2346 // the wait above we know that the previous commands on the GPU for this
2347 // slot must have finished already.
2348 releaseQueue.executeDeferredReleases(currentFrameSlot);
2349
2350 // Full reset of the command buffer data.
2351 cbD->resetState();
2352
2353 // Move the head back to zero for the per-frame shader-visible descriptor heap work areas.
2354 shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0;
2355 // Same for the small staging area, resizing it first if what this slot was
2356 // asked for the last time round says it should be bigger or smaller.
2357 resetAndResizeSmallStagingArea(currentFrameSlot);
2358
2359 bindShaderVisibleHeaps(cbD);
2360
2361 finishActiveReadbacks(); // last, in case the readback-completed callback issues rhi calls
2362
2363 if (timestampQueryHeap.isValid() && timestampTicksPerSecond) {
2364 // Read the timestamps for the previous frame for this slot. (the
2365 // ResolveQuery() should have completed by now due to the wait above)
2366 const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
2367 calculateGpuTime(cbD,
2368 timestampPairStartIndex,
2369 timestampReadbackArea.mem.p,
2370 timestampTicksPerSecond);
2371 // Write the start timestamp for this frame for this slot.
2372 cbD->cmdList->EndQuery(timestampQueryHeap.heap,
2373 D3D12_QUERY_TYPE_TIMESTAMP,
2374 timestampPairStartIndex);
2375 }
2376
2377 QDxgiVSyncService::instance()->beginFrame(adapterLuid);
2378
2379 return QRhi::FrameOpSuccess;
2380}
2381
2382QRhi::FrameOpResult QRhiD3D12::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags)
2383{
2384 QD3D12SwapChain *swapChainD = QRHI_RES(QD3D12SwapChain, swapChain);
2385 Q_ASSERT(currentSwapChain == swapChainD);
2386 QD3D12CommandBuffer *cbD = &swapChainD->cbWrapper;
2387
2388 QD3D12ObjectHandle backBufferResourceHandle = swapChainD->colorBuffers[swapChainD->currentBackBufferIndex];
2389 if (swapChainD->sampleDesc.Count > 1) {
2390 QD3D12ObjectHandle msaaBackBufferResourceHandle = swapChainD->msaaBuffers[currentFrameSlot];
2391 barrierGen.addTransitionBarrier(msaaBackBufferResourceHandle, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
2392 barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_RESOLVE_DEST);
2393 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2394 const QD3D12Resource *src = resourcePool.lookupRef(msaaBackBufferResourceHandle);
2395 const QD3D12Resource *dst = resourcePool.lookupRef(backBufferResourceHandle);
2396 if (src && dst)
2397 cbD->cmdList->ResolveSubresource(dst->resource, 0, src->resource, 0, swapChainD->colorFormat);
2398 }
2399
2400 barrierGen.addTransitionBarrier(backBufferResourceHandle, D3D12_RESOURCE_STATE_PRESENT);
2401 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2402
2403 if (timestampQueryHeap.isValid()) {
2404 const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
2405 cbD->cmdList->EndQuery(timestampQueryHeap.heap,
2406 D3D12_QUERY_TYPE_TIMESTAMP,
2407 timestampPairStartIndex + 1);
2408 cbD->cmdList->ResolveQueryData(timestampQueryHeap.heap,
2409 D3D12_QUERY_TYPE_TIMESTAMP,
2410 timestampPairStartIndex,
2411 2,
2412 timestampReadbackArea.mem.buffer,
2413 timestampPairStartIndex * sizeof(quint64));
2414 }
2415
2416 D3D12GraphicsCommandList *cmdList = cbD->cmdList;
2417 HRESULT hr = cmdList->Close();
2418 if (FAILED(hr)) {
2419 qWarning("Failed to close command list: %s",
2420 qPrintable(QSystemError::windowsComString(hr)));
2421 return QRhi::FrameOpError;
2422 }
2423
2424 ID3D12CommandList *execList[] = { cmdList };
2425 cmdQueue->ExecuteCommandLists(1, execList);
2426
2427 if (!flags.testFlag(QRhi::SkipPresent)) {
2428 UINT presentFlags = 0;
2429 if (swapChainD->swapInterval == 0
2430 && (swapChainD->swapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING))
2431 {
2432 presentFlags |= DXGI_PRESENT_ALLOW_TEARING;
2433 }
2434 if (!swapChainD->swapChain) {
2435 qWarning("Failed to present, no swapchain");
2436 return QRhi::FrameOpError;
2437 }
2438 HRESULT hr = swapChainD->swapChain->Present(swapChainD->swapInterval, presentFlags);
2439 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
2440 qWarning("Device loss detected in Present()");
2441 deviceLost = true;
2442 return QRhi::FrameOpDeviceLost;
2443 } else if (FAILED(hr)) {
2444 qWarning("Failed to present: %s", qPrintable(QSystemError::windowsComString(hr)));
2445 return QRhi::FrameOpError;
2446 }
2447
2448 if (dcompDevice && swapChainD->dcompTarget && swapChainD->dcompVisual)
2449 dcompDevice->Commit();
2450 }
2451
2452 swapChainD->addCommandCompletionSignalForCurrentFrameSlot();
2453
2454 // NB! The deferred-release mechanism here differs from the older QRhi
2455 // backends. There is no lastActiveFrameSlot tracking. Instead,
2456 // currentFrameSlot is written to the registered entries now, and so the
2457 // resources will get released in the frames_in_flight'th beginFrame()
2458 // counting starting from now.
2459 releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot);
2460
2461 if (!flags.testFlag(QRhi::SkipPresent)) {
2462 // Only move to the next slot if we presented. Otherwise will block and
2463 // wait for completion in the next beginFrame already, but SkipPresent
2464 // should be infrequent anyway.
2465 swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QD3D12_FRAMES_IN_FLIGHT;
2466 swapChainD->currentBackBufferIndex = swapChainD->swapChain->GetCurrentBackBufferIndex();
2467 }
2468
2469 currentSwapChain = nullptr;
2470 return QRhi::FrameOpSuccess;
2471}
2472
2473QRhi::FrameOpResult QRhiD3D12::beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags)
2474{
2475 Q_UNUSED(flags);
2476
2477 // Switch to the next slot manually. Swapchains do not know about this
2478 // which is good. So for example an onscreen, onscreen, offscreen,
2479 // onscreen, onscreen, onscreen sequence of frames leads to 0, 1, 0, 0, 1,
2480 // 0. (no strict alternation anymore) But this is not different from what
2481 // happens when multiple swapchains are involved. Offscreen frames are
2482 // synchronous anyway in the sense that they wait for execution to complete
2483 // in endOffscreenFrame, so no resources used in that frame are busy
2484 // anymore in the next frame.
2485
2486 currentFrameSlot = (currentFrameSlot + 1) % QD3D12_FRAMES_IN_FLIGHT;
2487
2488 for (QD3D12SwapChain *sc : std::as_const(swapchains))
2489 sc->waitCommandCompletionForFrameSlot(currentFrameSlot); // note: not sc's currentFrameSlot
2490
2491 HRESULT hr = cmdAllocators[currentFrameSlot]->Reset();
2492 if (FAILED(hr)) {
2493 qWarning("Failed to reset command allocator: %s",
2494 qPrintable(QSystemError::windowsComString(hr)));
2495 return QRhi::FrameOpError;
2496 }
2497
2498 if (!offscreenCb[currentFrameSlot])
2499 offscreenCb[currentFrameSlot] = new QD3D12CommandBuffer(this);
2500 QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot];
2501 if (!startCommandListForCurrentFrameSlot(&cbD->cmdList))
2502 return QRhi::FrameOpError;
2503
2504 releaseQueue.executeDeferredReleases(currentFrameSlot);
2505 cbD->resetState();
2506 shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0;
2507 resetAndResizeSmallStagingArea(currentFrameSlot);
2508
2509 bindShaderVisibleHeaps(cbD);
2510
2511 if (timestampQueryHeap.isValid() && timestampTicksPerSecond) {
2512 cbD->cmdList->EndQuery(timestampQueryHeap.heap,
2513 D3D12_QUERY_TYPE_TIMESTAMP,
2514 currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT);
2515 }
2516
2517 offscreenActive = true;
2518 *cb = cbD;
2519
2520 return QRhi::FrameOpSuccess;
2521}
2522
2523QRhi::FrameOpResult QRhiD3D12::endOffscreenFrame(QRhi::EndFrameFlags flags)
2524{
2525 Q_UNUSED(flags);
2526 Q_ASSERT(offscreenActive);
2527 offscreenActive = false;
2528
2529 QD3D12CommandBuffer *cbD = offscreenCb[currentFrameSlot];
2530 if (timestampQueryHeap.isValid()) {
2531 const int timestampPairStartIndex = currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT;
2532 cbD->cmdList->EndQuery(timestampQueryHeap.heap,
2533 D3D12_QUERY_TYPE_TIMESTAMP,
2534 timestampPairStartIndex + 1);
2535 cbD->cmdList->ResolveQueryData(timestampQueryHeap.heap,
2536 D3D12_QUERY_TYPE_TIMESTAMP,
2537 timestampPairStartIndex,
2538 2,
2539 timestampReadbackArea.mem.buffer,
2540 timestampPairStartIndex * sizeof(quint64));
2541 }
2542
2543 D3D12GraphicsCommandList *cmdList = cbD->cmdList;
2544 HRESULT hr = cmdList->Close();
2545 if (FAILED(hr)) {
2546 qWarning("Failed to close command list: %s",
2547 qPrintable(QSystemError::windowsComString(hr)));
2548 return QRhi::FrameOpError;
2549 }
2550
2551 ID3D12CommandList *execList[] = { cmdList };
2552 cmdQueue->ExecuteCommandLists(1, execList);
2553
2554 releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot);
2555
2556 // wait for completion
2557 waitGpu();
2558
2559 // Here we know that executing the host-side reads for this (or any
2560 // previous) frame is safe since we waited for completion above.
2561 finishActiveReadbacks(true);
2562
2563 // the timestamp query results should be available too, given the wait
2564 if (timestampQueryHeap.isValid()) {
2565 calculateGpuTime(cbD,
2566 currentFrameSlot * QD3D12_FRAMES_IN_FLIGHT,
2567 timestampReadbackArea.mem.p,
2568 timestampTicksPerSecond);
2569 }
2570
2571 return QRhi::FrameOpSuccess;
2572}
2573
2574QRhi::FrameOpResult QRhiD3D12::finish()
2575{
2576 QD3D12CommandBuffer *cbD = nullptr;
2577 if (inFrame) {
2578 if (offscreenActive) {
2579 Q_ASSERT(!currentSwapChain);
2580 cbD = offscreenCb[currentFrameSlot];
2581 } else {
2582 Q_ASSERT(currentSwapChain);
2583 cbD = &currentSwapChain->cbWrapper;
2584 }
2585 if (!cbD)
2586 return QRhi::FrameOpError;
2587
2588 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
2589
2590 D3D12GraphicsCommandList *cmdList = cbD->cmdList;
2591 HRESULT hr = cmdList->Close();
2592 if (FAILED(hr)) {
2593 qWarning("Failed to close command list: %s",
2594 qPrintable(QSystemError::windowsComString(hr)));
2595 return QRhi::FrameOpError;
2596 }
2597
2598 ID3D12CommandList *execList[] = { cmdList };
2599 cmdQueue->ExecuteCommandLists(1, execList);
2600
2601 releaseQueue.activatePendingDeferredReleaseRequests(currentFrameSlot);
2602 }
2603
2604 // full blocking wait for everything, frame slots do not matter now
2605 waitGpu();
2606
2607 if (inFrame) {
2608 HRESULT hr = cmdAllocators[currentFrameSlot]->Reset();
2609 if (FAILED(hr)) {
2610 qWarning("Failed to reset command allocator: %s",
2611 qPrintable(QSystemError::windowsComString(hr)));
2612 return QRhi::FrameOpError;
2613 }
2614
2615 if (!startCommandListForCurrentFrameSlot(&cbD->cmdList))
2616 return QRhi::FrameOpError;
2617
2618 cbD->resetState();
2619
2620 shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[currentFrameSlot].head = 0;
2621 smallStagingAreas[currentFrameSlot].head = 0;
2622
2623 bindShaderVisibleHeaps(cbD);
2624 }
2625
2626 releaseQueue.releaseAll();
2627 finishActiveReadbacks(true);
2628
2629 return QRhi::FrameOpSuccess;
2630}
2631
2632void QRhiD3D12::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2633{
2634 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2635 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
2636 enqueueResourceUpdates(cbD, resourceUpdates);
2637}
2638
2639void QRhiD3D12::beginPass(QRhiCommandBuffer *cb,
2640 QRhiRenderTarget *rt,
2641 const QColor &colorClearValue,
2642 const QRhiDepthStencilClearValue &depthStencilClearValue,
2643 QRhiResourceUpdateBatch *resourceUpdates,
2644 QRhiCommandBuffer::BeginPassFlags)
2645{
2646 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2647 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
2648
2649 if (resourceUpdates)
2650 enqueueResourceUpdates(cbD, resourceUpdates);
2651
2652 QD3D12RenderTargetData *rtD = rtData(rt);
2653 bool wantsColorClear = true;
2654 bool wantsDsClear = true;
2655 if (rt->resourceType() == QRhiRenderTarget::TextureRenderTarget) {
2656 QD3D12TextureRenderTarget *rtTex = QRHI_RES(QD3D12TextureRenderTarget, rt);
2657 wantsColorClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents);
2658 wantsDsClear = !rtTex->m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
2659 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D12Texture, QD3D12RenderBuffer>(rtTex->description(), rtD->currentResIdList))
2660 rtTex->create();
2661
2662 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments(); it != itEnd; ++it) {
2663 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture());
2664 QD3D12Texture *resolveTexD = QRHI_RES(QD3D12Texture, it->resolveTexture());
2665 QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer());
2666 if (texD)
2667 barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
2668 else if (rbD)
2669 barrierGen.addTransitionBarrier(rbD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
2670 if (resolveTexD)
2671 barrierGen.addTransitionBarrier(resolveTexD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
2672 }
2673 if (rtTex->m_desc.depthStencilBuffer()) {
2674 QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, rtTex->m_desc.depthStencilBuffer());
2675 Q_ASSERT(rbD->m_type == QRhiRenderBuffer::DepthStencil);
2676 barrierGen.addTransitionBarrier(rbD->handle, D3D12_RESOURCE_STATE_DEPTH_WRITE);
2677 } else if (rtTex->m_desc.depthTexture()) {
2678 QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, rtTex->m_desc.depthTexture());
2679 barrierGen.addTransitionBarrier(depthTexD->handle, D3D12_RESOURCE_STATE_DEPTH_WRITE);
2680 }
2681 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2682 } else {
2683 Q_ASSERT(currentSwapChain);
2684 barrierGen.addTransitionBarrier(currentSwapChain->sampleDesc.Count > 1
2685 ? currentSwapChain->msaaBuffers[currentFrameSlot]
2686 : currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex],
2687 D3D12_RESOURCE_STATE_RENDER_TARGET);
2688 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2689 }
2690
2691 cbD->cmdList->OMSetRenderTargets(UINT(rtD->colorAttCount),
2692 rtD->rtv,
2693 FALSE,
2694 rtD->dsAttCount ? &rtD->dsv : nullptr);
2695
2696 if (rtD->colorAttCount && wantsColorClear) {
2697 float clearColor[4] = {
2698 colorClearValue.redF(),
2699 colorClearValue.greenF(),
2700 colorClearValue.blueF(),
2701 colorClearValue.alphaF()
2702 };
2703 for (int i = 0; i < rtD->colorAttCount; ++i)
2704 cbD->cmdList->ClearRenderTargetView(rtD->rtv[i], clearColor, 0, nullptr);
2705 }
2706 if (rtD->dsAttCount && wantsDsClear) {
2707 cbD->cmdList->ClearDepthStencilView(rtD->dsv,
2708 D3D12_CLEAR_FLAGS(D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL),
2709 depthStencilClearValue.depthClearValue(),
2710 UINT8(depthStencilClearValue.stencilClearValue()),
2711 0,
2712 nullptr);
2713 }
2714
2715 cbD->recordingPass = QD3D12CommandBuffer::RenderPass;
2716 cbD->currentTarget = rt;
2717
2718 bool hasShadingRateMapSet = false;
2719#ifdef QRHI_D3D12_CL5_AVAILABLE
2720 if (rtD->rp->hasShadingRateMap) {
2721 cbD->setShadingRate(QSize(1, 1));
2722 QD3D12ShadingRateMap *rateMapD = rt->resourceType() == QRhiRenderTarget::TextureRenderTarget
2723 ? QRHI_RES(QD3D12ShadingRateMap, QRHI_RES(QD3D12TextureRenderTarget, rt)->m_desc.shadingRateMap())
2724 : QRHI_RES(QD3D12ShadingRateMap, QRHI_RES(QD3D12SwapChainRenderTarget, rt)->swapChain()->shadingRateMap());
2725 if (QD3D12Resource *res = resourcePool.lookupRef(rateMapD->handle)) {
2726 barrierGen.addTransitionBarrier(rateMapD->handle, D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE);
2727 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2728 cbD->cmdList->RSSetShadingRateImage(res->resource);
2729 hasShadingRateMapSet = true;
2730 }
2731 } else if (cbD->hasShadingRateMapSet) {
2732 cbD->cmdList->RSSetShadingRateImage(nullptr);
2733 cbD->setShadingRate(QSize(1, 1));
2734 } else if (cbD->hasShadingRateSet) {
2735 cbD->setShadingRate(QSize(1, 1));
2736 }
2737#endif
2738
2739 cbD->resetPerPassState();
2740
2741 // shading rate tracking is reset in resetPerPassState(), sync what we did just above
2742 cbD->hasShadingRateMapSet = hasShadingRateMapSet;
2743}
2744
2745void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2746{
2747 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2748 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2749
2750 if (cbD->currentTarget->resourceType() == QRhiResource::TextureRenderTarget) {
2751 QD3D12TextureRenderTarget *rtTex = QRHI_RES(QD3D12TextureRenderTarget, cbD->currentTarget);
2752 for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
2753 it != itEnd; ++it)
2754 {
2755 const QRhiColorAttachment &colorAtt(*it);
2756 if (!colorAtt.resolveTexture())
2757 continue;
2758
2759 QD3D12Texture *dstTexD = QRHI_RES(QD3D12Texture, colorAtt.resolveTexture());
2760 QD3D12Resource *dstRes = resourcePool.lookupRef(dstTexD->handle);
2761 if (!dstRes)
2762 continue;
2763
2764 QD3D12Texture *srcTexD = QRHI_RES(QD3D12Texture, colorAtt.texture());
2765 QD3D12RenderBuffer *srcRbD = QRHI_RES(QD3D12RenderBuffer, colorAtt.renderBuffer());
2766 Q_ASSERT(srcTexD || srcRbD);
2767 QD3D12Resource *srcRes = resourcePool.lookupRef(srcTexD ? srcTexD->handle : srcRbD->handle);
2768 if (!srcRes)
2769 continue;
2770
2771 if (srcTexD) {
2772 if (srcTexD->dxgiFormat != dstTexD->dxgiFormat) {
2773 qWarning("Resolve source (%d) and destination (%d) formats do not match",
2774 int(srcTexD->dxgiFormat), int(dstTexD->dxgiFormat));
2775 continue;
2776 }
2777 if (srcTexD->sampleDesc.Count <= 1) {
2778 qWarning("Cannot resolve a non-multisample texture");
2779 continue;
2780 }
2781 if (srcTexD->m_pixelSize != dstTexD->m_pixelSize) {
2782 qWarning("Resolve source and destination sizes do not match");
2783 continue;
2784 }
2785 } else {
2786 if (srcRbD->dxgiFormat != dstTexD->dxgiFormat) {
2787 qWarning("Resolve source (%d) and destination (%d) formats do not match",
2788 int(srcRbD->dxgiFormat), int(dstTexD->dxgiFormat));
2789 continue;
2790 }
2791 if (srcRbD->m_pixelSize != dstTexD->m_pixelSize) {
2792 qWarning("Resolve source and destination sizes do not match");
2793 continue;
2794 }
2795 }
2796
2797 // The subresources of a placed resource must be initialized with a
2798 // Discard, Clear, or Copy before they can be used for anything else.
2799 // ResolveSubresource is not one of those, so discard the destination
2800 // subresources that were never written to, otherwise the debug layer
2801 // complains. Only the subresources that are about to be overwritten
2802 // by the resolve are discarded, and only once each.
2803 const UINT resolveCount = colorAtt.multiViewCount() >= 2 ? colorAtt.multiViewCount() : 1;
2804 QBitArray &initialized(dstTexD->subresourceInitialized);
2805 QVarLengthArray<UINT, 4> dstSubresources;
2806 QVarLengthArray<UINT, 4> subresourcesToDiscard;
2807 for (UINT resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
2808 const UINT dstSubresource = calcSubresource(UINT(colorAtt.resolveLevel()),
2809 UINT(colorAtt.resolveLayer()) + resolveIdx,
2810 dstTexD->mipLevelCount);
2811 dstSubresources.append(dstSubresource);
2812 if (int(dstSubresource) >= initialized.size())
2813 initialized.resize(int(dstSubresource) + 1);
2814 if (!initialized.testBit(int(dstSubresource))) {
2815 initialized.setBit(int(dstSubresource));
2816 subresourcesToDiscard.append(dstSubresource);
2817 }
2818 }
2819
2820 barrierGen.addTransitionBarrier(srcTexD ? srcTexD->handle : srcRbD->handle, D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
2821
2822 // Discarding needs the RENDER_TARGET state, unlike the resolve itself.
2823 if (!subresourcesToDiscard.isEmpty()) {
2824 barrierGen.addTransitionBarrier(dstTexD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
2825 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2826 for (UINT dstSubresource : subresourcesToDiscard) {
2827 D3D12_DISCARD_REGION region = {};
2828 region.FirstSubresource = dstSubresource;
2829 region.NumSubresources = 1;
2830 cbD->cmdList->DiscardResource(dstRes->resource, &region);
2831 }
2832 }
2833
2834 barrierGen.addTransitionBarrier(dstTexD->handle, D3D12_RESOURCE_STATE_RESOLVE_DEST);
2835 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2836
2837 for (UINT resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
2838 const UINT srcSubresource = calcSubresource(0, UINT(colorAtt.layer()) + resolveIdx, 1);
2839 cbD->cmdList->ResolveSubresource(dstRes->resource, dstSubresources[resolveIdx],
2840 srcRes->resource, srcSubresource,
2841 dstTexD->dxgiFormat);
2842 }
2843 }
2844 if (rtTex->m_desc.depthResolveTexture())
2845 qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
2846 }
2847
2848 cbD->recordingPass = QD3D12CommandBuffer::NoPass;
2849 cbD->currentTarget = nullptr;
2850
2851 if (resourceUpdates)
2852 enqueueResourceUpdates(cbD, resourceUpdates);
2853}
2854
2855void QRhiD3D12::beginComputePass(QRhiCommandBuffer *cb,
2856 QRhiResourceUpdateBatch *resourceUpdates,
2857 QRhiCommandBuffer::BeginPassFlags)
2858{
2859 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2860 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::NoPass);
2861
2862 if (resourceUpdates)
2863 enqueueResourceUpdates(cbD, resourceUpdates);
2864
2865 cbD->recordingPass = QD3D12CommandBuffer::ComputePass;
2866
2867 cbD->resetPerPassState();
2868}
2869
2870void QRhiD3D12::endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
2871{
2872 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2873 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
2874
2875 cbD->recordingPass = QD3D12CommandBuffer::NoPass;
2876
2877 if (resourceUpdates)
2878 enqueueResourceUpdates(cbD, resourceUpdates);
2879}
2880
2881void QRhiD3D12::setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps)
2882{
2883 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2884 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
2885 QD3D12ComputePipeline *psD = QRHI_RES(QD3D12ComputePipeline, ps);
2886 const bool pipelineChanged = cbD->currentComputePipeline != psD || cbD->currentPipelineGeneration != psD->generation;
2887
2888 if (pipelineChanged) {
2889 cbD->currentGraphicsPipeline = nullptr;
2890 cbD->currentComputePipeline = psD;
2891 cbD->currentPipelineGeneration = psD->generation;
2892
2893 if (QD3D12Pipeline *pipeline = pipelinePool.lookupRef(psD->handle)) {
2894 Q_ASSERT(pipeline->type == QD3D12Pipeline::Compute);
2895 cbD->cmdList->SetPipelineState(pipeline->pso);
2896 if (QD3D12RootSignature *rs = rootSignaturePool.lookupRef(psD->rootSigHandle))
2897 cbD->cmdList->SetComputeRootSignature(rs->rootSig);
2898 }
2899 }
2900}
2901
2902void QRhiD3D12::dispatch(QRhiCommandBuffer *cb, int x, int y, int z)
2903{
2904 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2905 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
2906 cbD->cmdList->Dispatch(UINT(x), UINT(y), UINT(z));
2907}
2908
2909// ByteStride is a property of the command signature, so a non-canonical stride
2910// needs its own. Cannot loop with MaxCommandCount 1 like drawIndirect() does,
2911// because ExecuteIndirect executes min(MaxCommandCount, *countBuffer) commands.
2912ID3D12CommandSignature *QRhiD3D12::indirectDrawCommandSignature(bool indexed, quint32 stride)
2913{
2914 const quint32 canonicalStride = indexed ? sizeof(QRhiIndexedIndirectDrawCommand)
2915 : sizeof(QRhiIndirectDrawCommand);
2916 if (stride == canonicalStride)
2917 return indexed ? drawIndexedCommandSignature : drawCommandSignature;
2918
2919 QHash<quint32, ID3D12CommandSignature *> &cache(indexed ? drawIndexedCommandSignaturesByStride
2920 : drawCommandSignaturesByStride);
2921 auto it = cache.constFind(stride);
2922 if (it != cache.constEnd())
2923 return *it;
2924
2925 D3D12_INDIRECT_ARGUMENT_DESC arg = {};
2926 arg.Type = indexed ? D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED : D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;
2927
2928 D3D12_COMMAND_SIGNATURE_DESC sigDesc = {};
2929 sigDesc.ByteStride = stride;
2930 sigDesc.NumArgumentDescs = 1;
2931 sigDesc.pArgumentDescs = &arg;
2932
2933 ID3D12CommandSignature *sig = nullptr;
2934 HRESULT hr = dev->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&sig));
2935 if (FAILED(hr)) {
2936 qWarning("Failed to create indirect draw command signature with stride %u: %s",
2937 stride, qPrintable(QSystemError::windowsComString(hr)));
2938 return nullptr;
2939 }
2940
2941 cache.insert(stride, sig);
2942 return sig;
2943}
2944
2945void QRhiD3D12::drawIndirectCount(QRhiCommandBuffer *cb,
2946 QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset,
2947 QRhiBuffer *countBuffer, quint32 countBufferOffset,
2948 quint32 maxDrawCount, quint32 stride)
2949{
2950 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2951 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2952
2953 ID3D12CommandSignature *sig = indirectDrawCommandSignature(false, stride);
2954 if (!sig)
2955 return;
2956
2957 QD3D12Buffer *indirectBufferD = QRHI_RES(QD3D12Buffer, indirectBuffer);
2958 const bool indirectIsDynamic = indirectBufferD->m_type == QRhiBuffer::Dynamic;
2959 const QD3D12ObjectHandle indirectHandle = indirectBufferD->handles[indirectIsDynamic ? currentFrameSlot : 0];
2960 if (indirectIsDynamic)
2961 indirectBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
2962 else
2963 barrierGen.addTransitionBarrier(indirectHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
2964
2965 QD3D12Buffer *countBufferD = QRHI_RES(QD3D12Buffer, countBuffer);
2966 const bool countIsDynamic = countBufferD->m_type == QRhiBuffer::Dynamic;
2967 const QD3D12ObjectHandle countHandle = countBufferD->handles[countIsDynamic ? currentFrameSlot : 0];
2968 if (countIsDynamic)
2969 countBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
2970 else
2971 barrierGen.addTransitionBarrier(countHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
2972
2973 if (!indirectIsDynamic || !countIsDynamic)
2974 barrierGen.enqueueBufferedTransitionBarriers(cbD);
2975
2976 QD3D12Resource *indirectRes = resourcePool.lookupRef(indirectHandle);
2977 QD3D12Resource *countRes = resourcePool.lookupRef(countHandle);
2978 if (!indirectRes || !countRes)
2979 return;
2980
2981 cbD->cmdList->ExecuteIndirect(sig, maxDrawCount,
2982 indirectRes->resource, indirectBufferOffset,
2983 countRes->resource, countBufferOffset);
2984}
2985
2986void QRhiD3D12::drawIndexedIndirectCount(QRhiCommandBuffer *cb,
2987 QRhiBuffer *indirectBuffer, quint32 indirectBufferOffset,
2988 QRhiBuffer *countBuffer, quint32 countBufferOffset,
2989 quint32 maxDrawCount, quint32 stride)
2990{
2991 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
2992 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::RenderPass);
2993
2994 ID3D12CommandSignature *sig = indirectDrawCommandSignature(true, stride);
2995 if (!sig)
2996 return;
2997
2998 QD3D12Buffer *indirectBufferD = QRHI_RES(QD3D12Buffer, indirectBuffer);
2999 const bool indirectIsDynamic = indirectBufferD->m_type == QRhiBuffer::Dynamic;
3000 const QD3D12ObjectHandle indirectHandle = indirectBufferD->handles[indirectIsDynamic ? currentFrameSlot : 0];
3001 if (indirectIsDynamic)
3002 indirectBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
3003 else
3004 barrierGen.addTransitionBarrier(indirectHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
3005
3006 QD3D12Buffer *countBufferD = QRHI_RES(QD3D12Buffer, countBuffer);
3007 const bool countIsDynamic = countBufferD->m_type == QRhiBuffer::Dynamic;
3008 const QD3D12ObjectHandle countHandle = countBufferD->handles[countIsDynamic ? currentFrameSlot : 0];
3009 if (countIsDynamic)
3010 countBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
3011 else
3012 barrierGen.addTransitionBarrier(countHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
3013
3014 if (!indirectIsDynamic || !countIsDynamic)
3015 barrierGen.enqueueBufferedTransitionBarriers(cbD);
3016
3017 QD3D12Resource *indirectRes = resourcePool.lookupRef(indirectHandle);
3018 QD3D12Resource *countRes = resourcePool.lookupRef(countHandle);
3019 if (!indirectRes || !countRes)
3020 return;
3021
3022 cbD->cmdList->ExecuteIndirect(sig, maxDrawCount,
3023 indirectRes->resource, indirectBufferOffset,
3024 countRes->resource, countBufferOffset);
3025}
3026
3027void QRhiD3D12::dispatchIndirect(QRhiCommandBuffer *cb, QRhiBuffer *indirectBuffer,
3028 quint32 indirectBufferOffset)
3029{
3030 QD3D12CommandBuffer *cbD = QRHI_RES(QD3D12CommandBuffer, cb);
3031 Q_ASSERT(cbD->recordingPass == QD3D12CommandBuffer::ComputePass);
3032
3033 QD3D12Buffer *indirectBufferD = QRHI_RES(QD3D12Buffer, indirectBuffer);
3034 const bool isDynamic = indirectBufferD->m_type == QRhiBuffer::Dynamic;
3035 const QD3D12ObjectHandle indirectBufferHandle = indirectBufferD->handles[isDynamic ? currentFrameSlot : 0];
3036 if (isDynamic) {
3037 indirectBufferD->executeHostWritesForFrameSlot(currentFrameSlot);
3038 } else {
3039 // For Static/Immutable buffers, transition to INDIRECT_ARGUMENT state
3040 // so any earlier UAV writes (e.g. by a compute shader that produced
3041 // the dispatch arguments) are flushed before ExecuteIndirect reads.
3042 barrierGen.addTransitionBarrier(indirectBufferHandle, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT);
3043 barrierGen.enqueueBufferedTransitionBarriers(cbD);
3044 }
3045 QD3D12Resource *indirectRes = resourcePool.lookupRef(indirectBufferHandle);
3046 if (!indirectRes)
3047 return;
3048
3049 cbD->cmdList->ExecuteIndirect(dispatchCommandSignature, 1,
3050 indirectRes->resource, indirectBufferOffset,
3051 nullptr, 0);
3052}
3053
3054bool QD3D12DescriptorHeap::create(ID3D12Device *device,
3055 quint32 descriptorCount,
3056 D3D12_DESCRIPTOR_HEAP_TYPE heapType,
3057 D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags)
3058{
3059 head = 0;
3060 capacity = descriptorCount;
3061 this->heapType = heapType;
3062 this->heapFlags = heapFlags;
3063
3064 D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
3065 heapDesc.Type = heapType;
3066 heapDesc.NumDescriptors = capacity;
3067 heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAGS(heapFlags);
3068
3069 HRESULT hr = device->CreateDescriptorHeap(&heapDesc, __uuidof(ID3D12DescriptorHeap), reinterpret_cast<void **>(&heap));
3070 if (FAILED(hr)) {
3071 qWarning("Failed to create descriptor heap: %s", qPrintable(QSystemError::windowsComString(hr)));
3072 heap = nullptr;
3073 capacity = descriptorByteSize = 0;
3074 return false;
3075 }
3076
3077 descriptorByteSize = device->GetDescriptorHandleIncrementSize(heapType);
3078 heapStart.cpuHandle = heap->GetCPUDescriptorHandleForHeapStart();
3079 if (heapFlags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)
3080 heapStart.gpuHandle = heap->GetGPUDescriptorHandleForHeapStart();
3081
3082 return true;
3083}
3084
3085void QD3D12DescriptorHeap::createWithExisting(const QD3D12DescriptorHeap &other,
3086 quint32 offsetInDescriptors,
3087 quint32 descriptorCount)
3088{
3089 heap = nullptr;
3090 head = 0;
3091 capacity = descriptorCount;
3092 heapType = other.heapType;
3093 heapFlags = other.heapFlags;
3094 descriptorByteSize = other.descriptorByteSize;
3095 heapStart = incremented(other.heapStart, offsetInDescriptors);
3096}
3097
3098void QD3D12DescriptorHeap::destroy()
3099{
3100 if (heap) {
3101 heap->Release();
3102 heap = nullptr;
3103 }
3104 capacity = 0;
3105}
3106
3107void QD3D12DescriptorHeap::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue)
3108{
3109 if (heap) {
3110 releaseQueue->deferredReleaseDescriptorHeap(heap);
3111 heap = nullptr;
3112 }
3113 capacity = 0;
3114}
3115
3116QD3D12Descriptor QD3D12DescriptorHeap::get(quint32 count)
3117{
3118 Q_ASSERT(count > 0);
3119 if (head + count > capacity) {
3120 qWarning("Cannot get %u descriptors as that would exceed capacity %u", count, capacity);
3121 return {};
3122 }
3123 head += count;
3124 return at(head - count);
3125}
3126
3127QD3D12Descriptor QD3D12DescriptorHeap::at(quint32 index) const
3128{
3129 const quint32 startOffset = index * descriptorByteSize;
3130 QD3D12Descriptor result;
3131 result.cpuHandle.ptr = heapStart.cpuHandle.ptr + startOffset;
3132 if (heapStart.gpuHandle.ptr != 0)
3133 result.gpuHandle.ptr = heapStart.gpuHandle.ptr + startOffset;
3134 return result;
3135}
3136
3137bool QD3D12CpuDescriptorPool::create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE heapType, const char *debugName)
3138{
3139 QD3D12DescriptorHeap firstHeap;
3140 if (!firstHeap.create(device, DESCRIPTORS_PER_HEAP, heapType, D3D12_DESCRIPTOR_HEAP_FLAG_NONE))
3141 return false;
3142 heaps.append(HeapWithMap::init(firstHeap, DESCRIPTORS_PER_HEAP));
3143 descriptorByteSize = heaps[0].heap.descriptorByteSize;
3144 this->device = device;
3145 this->debugName = debugName;
3146 return true;
3147}
3148
3149void QD3D12CpuDescriptorPool::destroy()
3150{
3151#ifndef QT_NO_DEBUG
3152 // debug builds: just do it always
3153 static bool leakCheck = true;
3154#else
3155 // release builds: opt-in
3156 static bool leakCheck = qEnvironmentVariableIntValue("QT_RHI_LEAK_CHECK");
3157#endif
3158 if (leakCheck) {
3159 for (const HeapWithMap &heap : std::as_const(heaps)) {
3160 const int leakedDescriptorCount = heap.map.count(true);
3161 if (leakedDescriptorCount > 0) {
3162 qWarning("QD3D12CpuDescriptorPool::destroy(): "
3163 "Heap %p for descriptor pool %p '%s' has %d unreleased descriptors",
3164 &heap.heap, this, debugName, leakedDescriptorCount);
3165 }
3166 }
3167 }
3168 for (HeapWithMap &heap : heaps)
3169 heap.heap.destroy();
3170 heaps.clear();
3171}
3172
3173QD3D12Descriptor QD3D12CpuDescriptorPool::allocate(quint32 count)
3174{
3175 Q_ASSERT(count > 0 && count <= DESCRIPTORS_PER_HEAP);
3176
3177 HeapWithMap &last(heaps.last());
3178 if (last.heap.head + count <= last.heap.capacity) {
3179 quint32 firstIndex = last.heap.head;
3180 for (quint32 i = 0; i < count; ++i)
3181 last.map.setBit(firstIndex + i);
3182 return last.heap.get(count);
3183 }
3184
3185 for (HeapWithMap &heap : heaps) {
3186 quint32 freeCount = 0;
3187 for (quint32 i = 0; i < DESCRIPTORS_PER_HEAP; ++i) {
3188 if (heap.map.testBit(i)) {
3189 freeCount = 0;
3190 } else {
3191 freeCount += 1;
3192 if (freeCount == count) {
3193 const quint32 firstIndex = i - (freeCount - 1);
3194 for (quint32 j = 0; j < count; ++j)
3195 heap.map.setBit(firstIndex + j);
3196 // May need to bump heap.head to ensure the heap.get() on
3197 // the other code path above does not accidentally alias the
3198 // same entries when count > 1.
3199 if (firstIndex + count > heap.heap.head)
3200 heap.heap.head = firstIndex + count;
3201 return heap.heap.at(firstIndex);
3202 }
3203 }
3204 }
3205 }
3206
3207 QD3D12DescriptorHeap newHeap;
3208 if (!newHeap.create(device, DESCRIPTORS_PER_HEAP, last.heap.heapType, last.heap.heapFlags))
3209 return {};
3210
3211 heaps.append(HeapWithMap::init(newHeap, DESCRIPTORS_PER_HEAP));
3212
3213 for (quint32 i = 0; i < count; ++i)
3214 heaps.last().map.setBit(i);
3215
3216 return heaps.last().heap.get(count);
3217}
3218
3219void QD3D12CpuDescriptorPool::release(const QD3D12Descriptor &descriptor, quint32 count)
3220{
3221 Q_ASSERT(count > 0 && count <= DESCRIPTORS_PER_HEAP);
3222 if (!descriptor.isValid())
3223 return;
3224
3225 const SIZE_T addr = descriptor.cpuHandle.ptr;
3226 for (HeapWithMap &heap : heaps) {
3227 const SIZE_T begin = heap.heap.heapStart.cpuHandle.ptr;
3228 const SIZE_T end = begin + heap.heap.descriptorByteSize * heap.heap.capacity;
3229 if (addr >= begin && addr < end) {
3230 quint32 firstIndex = (addr - begin) / heap.heap.descriptorByteSize;
3231 for (quint32 i = 0; i < count; ++i)
3232 heap.map.setBit(firstIndex + i, false);
3233 return;
3234 }
3235 }
3236
3237 qWarning("QD3D12CpuDescriptorPool::release: Descriptor with address %llu is not in any heap",
3238 quint64(descriptor.cpuHandle.ptr));
3239}
3240
3241bool QD3D12QueryHeap::create(ID3D12Device *device,
3242 quint32 queryCount,
3243 D3D12_QUERY_HEAP_TYPE heapType)
3244{
3245 capacity = queryCount;
3246
3247 D3D12_QUERY_HEAP_DESC heapDesc = {};
3248 heapDesc.Type = heapType;
3249 heapDesc.Count = capacity;
3250
3251 HRESULT hr = device->CreateQueryHeap(&heapDesc, __uuidof(ID3D12QueryHeap), reinterpret_cast<void **>(&heap));
3252 if (FAILED(hr)) {
3253 qWarning("Failed to create query heap: %s", qPrintable(QSystemError::windowsComString(hr)));
3254 heap = nullptr;
3255 capacity = 0;
3256 return false;
3257 }
3258
3259 return true;
3260}
3261
3262void QD3D12QueryHeap::destroy()
3263{
3264 if (heap) {
3265 heap->Release();
3266 heap = nullptr;
3267 }
3268 capacity = 0;
3269}
3270
3271bool QD3D12StagingArea::create(QRhiD3D12 *rhi, quint32 capacity, D3D12_HEAP_TYPE heapType)
3272{
3273 Q_ASSERT(heapType == D3D12_HEAP_TYPE_UPLOAD || heapType == D3D12_HEAP_TYPE_READBACK);
3274 D3D12_RESOURCE_DESC resourceDesc = {};
3275 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
3276 resourceDesc.Width = capacity;
3277 resourceDesc.Height = 1;
3278 resourceDesc.DepthOrArraySize = 1;
3279 resourceDesc.MipLevels = 1;
3280 resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
3281 resourceDesc.SampleDesc = { 1, 0 };
3282 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
3283 resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
3284 UINT state = heapType == D3D12_HEAP_TYPE_UPLOAD ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST;
3285 HRESULT hr = rhi->vma.createResource(heapType,
3286 &resourceDesc,
3287 D3D12_RESOURCE_STATES(state),
3288 nullptr,
3289 &allocation,
3290 __uuidof(ID3D12Resource),
3291 reinterpret_cast<void **>(&resource));
3292 if (FAILED(hr)) {
3293 qWarning("Failed to create buffer for staging area: %s",
3294 qPrintable(QSystemError::windowsComString(hr)));
3295 return false;
3296 }
3297 void *p = nullptr;
3298 hr = resource->Map(0, nullptr, &p);
3299 if (FAILED(hr)) {
3300 qWarning("Failed to map buffer for staging area: %s",
3301 qPrintable(QSystemError::windowsComString(hr)));
3302 destroy();
3303 return false;
3304 }
3305
3306 mem.p = static_cast<quint8 *>(p);
3307 mem.gpuAddr = resource->GetGPUVirtualAddress();
3308 mem.buffer = resource;
3309 mem.bufferOffset = 0;
3310
3311 this->capacity = capacity;
3312 head = 0;
3313
3314 return true;
3315}
3316
3317void QD3D12StagingArea::destroy()
3318{
3319 if (resource) {
3320 resource->Release();
3321 resource = nullptr;
3322 }
3323 if (allocation) {
3324 allocation->Release();
3325 allocation = nullptr;
3326 }
3327 mem = {};
3328}
3329
3330void QD3D12StagingArea::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue)
3331{
3332 if (resource)
3333 releaseQueue->deferredReleaseResourceAndAllocation(resource, allocation);
3334 mem = {};
3335}
3336
3337QD3D12StagingArea::Allocation QD3D12StagingArea::get(quint32 byteSize)
3338{
3339 const quint32 allocSize = aligned(byteSize, ALIGNMENT);
3340 if (head + allocSize > capacity) {
3341 qWarning("Failed to allocate %u (%u) bytes from staging area of size %u with %u bytes left",
3342 allocSize, byteSize, capacity, remainingCapacity());
3343 return {};
3344 }
3345 const quint32 offset = head;
3346 head += allocSize;
3347 return {
3348 mem.p + offset,
3349 mem.gpuAddr + offset,
3350 mem.buffer,
3351 offset
3352 };
3353}
3354
3355// Can be called inside and outside of begin-endFrame. Removes from the pool
3356// and releases the underlying native resource only in the frames_in_flight'th
3357// beginFrame() counted starting from the next endFrame().
3358void QD3D12ReleaseQueue::deferredReleaseResource(const QD3D12ObjectHandle &handle)
3359{
3360 DeferredReleaseEntry e;
3361 e.handle = handle;
3362 queue.append(e);
3363}
3364
3365void QD3D12ReleaseQueue::deferredReleaseResourceWithViews(const QD3D12ObjectHandle &handle,
3366 QD3D12CpuDescriptorPool *pool,
3367 const QD3D12Descriptor &viewsStart,
3368 int viewCount)
3369{
3370 DeferredReleaseEntry e;
3371 e.type = DeferredReleaseEntry::Resource;
3372 e.handle = handle;
3373 e.poolForViews = pool;
3374 e.viewsStart = viewsStart;
3375 e.viewCount = viewCount;
3376 queue.append(e);
3377}
3378
3379void QD3D12ReleaseQueue::deferredReleasePipeline(const QD3D12ObjectHandle &handle)
3380{
3381 DeferredReleaseEntry e;
3382 e.type = DeferredReleaseEntry::Pipeline;
3383 e.handle = handle;
3384 queue.append(e);
3385}
3386
3387void QD3D12ReleaseQueue::deferredReleaseRootSignature(const QD3D12ObjectHandle &handle)
3388{
3389 DeferredReleaseEntry e;
3390 e.type = DeferredReleaseEntry::RootSignature;
3391 e.handle = handle;
3392 queue.append(e);
3393}
3394
3395void QD3D12ReleaseQueue::deferredReleaseCallback(std::function<void(void*)> callback, void *userData)
3396{
3397 DeferredReleaseEntry e;
3398 e.type = DeferredReleaseEntry::Callback;
3399 e.callback = callback;
3400 e.callbackUserData = userData;
3401 queue.append(e);
3402}
3403
3404void QD3D12ReleaseQueue::deferredReleaseResourceAndAllocation(ID3D12Resource *resource,
3405 D3D12MA::Allocation *allocation)
3406{
3407 DeferredReleaseEntry e;
3408 e.type = DeferredReleaseEntry::ResourceAndAllocation;
3409 e.resourceAndAllocation = { resource, allocation };
3410 queue.append(e);
3411}
3412
3413void QD3D12ReleaseQueue::deferredReleaseDescriptorHeap(ID3D12DescriptorHeap *heap)
3414{
3415 DeferredReleaseEntry e;
3416 e.type = DeferredReleaseEntry::DescriptorHeap;
3417 e.descriptorHeap = heap;
3418 queue.append(e);
3419}
3420
3421void QD3D12ReleaseQueue::deferredReleaseViews(QD3D12CpuDescriptorPool *pool,
3422 const QD3D12Descriptor &viewsStart,
3423 int viewCount)
3424{
3425 DeferredReleaseEntry e;
3426 e.type = DeferredReleaseEntry::Views;
3427 e.poolForViews = pool;
3428 e.viewsStart = viewsStart;
3429 e.viewCount = viewCount;
3430 queue.append(e);
3431}
3432
3433void QD3D12ReleaseQueue::activatePendingDeferredReleaseRequests(int frameSlot)
3434{
3435 for (DeferredReleaseEntry &e : queue) {
3436 if (!e.frameSlotToBeReleasedIn.has_value())
3437 e.frameSlotToBeReleasedIn = frameSlot;
3438 }
3439}
3440
3441void QD3D12ReleaseQueue::executeDeferredReleases(int frameSlot, bool forced)
3442{
3443 qsizetype keepBegin = queue.size();
3444 for (qsizetype i = keepBegin - 1; i >= 0; --i) {
3445 const DeferredReleaseEntry &e(queue[i]);
3446 if (forced || (e.frameSlotToBeReleasedIn.has_value() && e.frameSlotToBeReleasedIn.value() == frameSlot)) {
3447 switch (e.type) {
3448 case DeferredReleaseEntry::Resource:
3449 resourcePool->remove(e.handle);
3450 if (e.poolForViews && e.viewsStart.isValid() && e.viewCount > 0)
3451 e.poolForViews->release(e.viewsStart, e.viewCount);
3452 break;
3453 case DeferredReleaseEntry::Pipeline:
3454 pipelinePool->remove(e.handle);
3455 break;
3456 case DeferredReleaseEntry::RootSignature:
3457 rootSignaturePool->remove(e.handle);
3458 break;
3459 case DeferredReleaseEntry::Callback:
3460 e.callback(e.callbackUserData);
3461 break;
3462 case DeferredReleaseEntry::ResourceAndAllocation:
3463 // order matters: resource first, then the allocation (which
3464 // may be null)
3465 e.resourceAndAllocation.first->Release();
3466 if (e.resourceAndAllocation.second)
3467 e.resourceAndAllocation.second->Release();
3468 break;
3469 case DeferredReleaseEntry::DescriptorHeap:
3470 e.descriptorHeap->Release();
3471 break;
3472 case DeferredReleaseEntry::Views:
3473 e.poolForViews->release(e.viewsStart, e.viewCount);
3474 break;
3475 }
3476 } else if (--keepBegin != i) {
3477 queue[keepBegin] = std::move(queue[i]);
3478 }
3479 }
3480 if (keepBegin)
3481 queue.remove(0, keepBegin);
3482}
3483
3484void QD3D12ReleaseQueue::releaseAll()
3485{
3486 executeDeferredReleases(0, true);
3487}
3488
3489void QD3D12ResourceBarrierGenerator::addTransitionBarrier(const QD3D12ObjectHandle &resourceHandle,
3490 D3D12_RESOURCE_STATES stateAfter)
3491{
3492 if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) {
3493 if (stateAfter != res->state) {
3494 transitionResourceBarriers.append({ resourceHandle, res->state, stateAfter });
3495 res->state = stateAfter;
3496 }
3497 }
3498}
3499
3500void QD3D12ResourceBarrierGenerator::enqueueBufferedTransitionBarriers(QD3D12CommandBuffer *cbD)
3501{
3502 QVarLengthArray<D3D12_RESOURCE_BARRIER, PREALLOC> barriers;
3503 for (const TransitionResourceBarrier &trb : transitionResourceBarriers) {
3504 if (QD3D12Resource *res = resourcePool->lookupRef(trb.resourceHandle)) {
3505 D3D12_RESOURCE_BARRIER barrier = {};
3506 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
3507 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
3508 barrier.Transition.pResource = res->resource;
3509 barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
3510 barrier.Transition.StateBefore = trb.stateBefore;
3511 barrier.Transition.StateAfter = trb.stateAfter;
3512 barriers.append(barrier);
3513 }
3514 }
3515 transitionResourceBarriers.clear();
3516 if (!barriers.isEmpty())
3517 cbD->cmdList->ResourceBarrier(barriers.count(), barriers.constData());
3518}
3519
3520void QD3D12ResourceBarrierGenerator::enqueueSubresourceTransitionBarrier(QD3D12CommandBuffer *cbD,
3521 const QD3D12ObjectHandle &resourceHandle,
3522 UINT subresource,
3523 D3D12_RESOURCE_STATES stateBefore,
3524 D3D12_RESOURCE_STATES stateAfter)
3525{
3526 if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) {
3527 D3D12_RESOURCE_BARRIER barrier = {};
3528 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
3529 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
3530 barrier.Transition.pResource = res->resource;
3531 barrier.Transition.Subresource = subresource;
3532 barrier.Transition.StateBefore = stateBefore;
3533 barrier.Transition.StateAfter = stateAfter;
3534 cbD->cmdList->ResourceBarrier(1, &barrier);
3535 }
3536}
3537
3538void QD3D12ResourceBarrierGenerator::enqueueUavBarrier(QD3D12CommandBuffer *cbD,
3539 const QD3D12ObjectHandle &resourceHandle)
3540{
3541 if (QD3D12Resource *res = resourcePool->lookupRef(resourceHandle)) {
3542 D3D12_RESOURCE_BARRIER barrier = {};
3543 barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
3544 barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
3545 barrier.UAV.pResource = res->resource;
3546 cbD->cmdList->ResourceBarrier(1, &barrier);
3547 }
3548}
3549
3550void QD3D12ShaderBytecodeCache::insertWithCapacityLimit(const QRhiShaderStage &key, const Shader &s)
3551{
3552 if (data.count() >= QRhiD3D12::MAX_SHADER_CACHE_ENTRIES)
3553 data.clear();
3554 data.insert(key, s);
3555}
3556
3557bool QD3D12ShaderVisibleDescriptorHeap::create(ID3D12Device *device,
3558 D3D12_DESCRIPTOR_HEAP_TYPE type,
3559 quint32 perFrameDescriptorCount)
3560{
3561 Q_ASSERT(type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
3562
3563 quint32 size = perFrameDescriptorCount * QD3D12_FRAMES_IN_FLIGHT;
3564
3565 // https://learn.microsoft.com/en-us/windows/win32/direct3d12/hardware-support
3566 const quint32 CBV_SRV_UAV_MAX = 1000000;
3567 const quint32 SAMPLER_MAX = 2048;
3568 if (type == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)
3569 size = qMin(size, CBV_SRV_UAV_MAX);
3570 else if (type == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER)
3571 size = qMin(size, SAMPLER_MAX);
3572
3573 if (!heap.create(device, size, type, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE)) {
3574 qWarning("Failed to create shader-visible descriptor heap of size %u", size);
3575 return false;
3576 }
3577
3578 perFrameDescriptorCount = size / QD3D12_FRAMES_IN_FLIGHT;
3579 quint32 currentOffsetInDescriptors = 0;
3580 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
3581 perFrameHeapSlice[i].createWithExisting(heap, currentOffsetInDescriptors, perFrameDescriptorCount);
3582 currentOffsetInDescriptors += perFrameDescriptorCount;
3583 }
3584
3585 return true;
3586}
3587
3588void QD3D12ShaderVisibleDescriptorHeap::destroy()
3589{
3590 heap.destroy();
3591}
3592
3593void QD3D12ShaderVisibleDescriptorHeap::destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue)
3594{
3595 heap.destroyWithDeferredRelease(releaseQueue);
3596}
3597
3598static inline std::pair<int, int> mapBinding(int binding, const QD3D12ShaderStageData *sd)
3599{
3600 const QShader::NativeResourceBindingMap &map(sd->nativeResourceBindingMap);
3601 if (map.isEmpty()) {
3602 // An empty map normally means an old qsb that did not generate one,
3603 // hence the 1:1 fallback. But the push constant register is only
3604 // known from qsb versions that always generate the map, so for a
3605 // stage with a push constant block an empty map really means the
3606 // shader has no other resources. Falling back would put a uniform
3607 // buffer at binding 0 on the register reserved for the push constant
3608 // block, and the root signature would fail to build.
3609 if (sd->pushConstantRegister >= 0)
3610 return { -1, -1 };
3611 return { binding, binding }; // assume 1:1 mapping
3612 }
3613
3614 auto it = map.constFind(binding);
3615 if (it != map.cend())
3616 return *it;
3617
3618 // Hitting this path is normal too. It is not given that the resource is
3619 // present in the shaders for all the stages specified by the visibility
3620 // mask in the QRhiShaderResourceBinding.
3621 return { -1, -1 };
3622}
3623
3624void QD3D12ShaderResourceVisitor::visit()
3625{
3626 for (int bindingIdx = 0, bindingCount = srb->m_bindings.count(); bindingIdx != bindingCount; ++bindingIdx) {
3627 const QRhiShaderResourceBinding &b(srb->m_bindings[bindingIdx]);
3628 const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
3629
3630 for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) {
3631 const QD3D12ShaderStageData *sd = &stageData[stageIdx];
3632 if (!sd->valid)
3633 continue;
3634
3635 if (!bd->stage.testFlag(qd3d12_stageToSrb(sd->stage)))
3636 continue;
3637
3638 switch (bd->type) {
3639 case QRhiShaderResourceBinding::UniformBuffer:
3640 {
3641 const int shaderRegister = mapBinding(bd->binding, sd).first;
3642 if (shaderRegister >= 0 && uniformBuffer)
3643 uniformBuffer(sd->stage, bd->u.ubuf, shaderRegister, bd->binding);
3644 }
3645 break;
3646 case QRhiShaderResourceBinding::SampledTexture:
3647 {
3648 Q_ASSERT(bd->u.stex.count > 0);
3649 const int textureBaseShaderRegister = mapBinding(bd->binding, sd).first;
3650 const int samplerBaseShaderRegister = mapBinding(bd->binding, sd).second;
3651 if (textureBaseShaderRegister >= 0 && textures)
3652 textures(sd->stage, bd->u.stex.texSamplers, bd->u.stex.count, textureBaseShaderRegister);
3653 if (samplerBaseShaderRegister >= 0 && samplers)
3654 samplers(sd->stage, bd->u.stex.texSamplers, bd->u.stex.count, samplerBaseShaderRegister);
3655 }
3656 break;
3657 case QRhiShaderResourceBinding::Texture:
3658 {
3659 Q_ASSERT(bd->u.stex.count > 0);
3660 const int baseShaderRegister = mapBinding(bd->binding, sd).first;
3661 if (baseShaderRegister >= 0 && textures)
3662 textures(sd->stage, bd->u.stex.texSamplers, bd->u.stex.count, baseShaderRegister);
3663 }
3664 break;
3665 case QRhiShaderResourceBinding::Sampler:
3666 {
3667 Q_ASSERT(bd->u.stex.count > 0);
3668 const int baseShaderRegister = mapBinding(bd->binding, sd).first;
3669 if (baseShaderRegister >= 0 && samplers)
3670 samplers(sd->stage, bd->u.stex.texSamplers, bd->u.stex.count, baseShaderRegister);
3671 }
3672 break;
3673 case QRhiShaderResourceBinding::ImageLoad:
3674 {
3675 const int shaderRegister = mapBinding(bd->binding, sd).first;
3676 if (shaderRegister >= 0 && storageImage)
3677 storageImage(sd->stage, bd->u.simage, Load, shaderRegister);
3678 }
3679 break;
3680 case QRhiShaderResourceBinding::ImageStore:
3681 {
3682 const int shaderRegister = mapBinding(bd->binding, sd).first;
3683 if (shaderRegister >= 0 && storageImage)
3684 storageImage(sd->stage, bd->u.simage, Store, shaderRegister);
3685 }
3686 break;
3687 case QRhiShaderResourceBinding::ImageLoadStore:
3688 {
3689 const int shaderRegister = mapBinding(bd->binding, sd).first;
3690 if (shaderRegister >= 0 && storageImage)
3691 storageImage(sd->stage, bd->u.simage, LoadStore, shaderRegister);
3692 }
3693 break;
3694 case QRhiShaderResourceBinding::BufferLoad:
3695 {
3696 const int shaderRegister = mapBinding(bd->binding, sd).first;
3697 if (shaderRegister >= 0 && storageBuffer)
3698 storageBuffer(sd->stage, bd->u.sbuf, Load, shaderRegister);
3699 }
3700 break;
3701 case QRhiShaderResourceBinding::BufferStore:
3702 {
3703 const int shaderRegister = mapBinding(bd->binding, sd).first;
3704 if (shaderRegister >= 0 && storageBuffer)
3705 storageBuffer(sd->stage, bd->u.sbuf, Store, shaderRegister);
3706 }
3707 break;
3708 case QRhiShaderResourceBinding::BufferLoadStore:
3709 {
3710 const int shaderRegister = mapBinding(bd->binding, sd).first;
3711 if (shaderRegister >= 0 && storageBuffer)
3712 storageBuffer(sd->stage, bd->u.sbuf, LoadStore, shaderRegister);
3713 }
3714 break;
3715 }
3716 }
3717 }
3718}
3719
3720bool QD3D12SamplerManager::create(ID3D12Device *device)
3721{
3722 // This does not need to be per-frame slot, just grab space for MAX_SAMPLERS samplers.
3723 if (!shaderVisibleSamplerHeap.create(device,
3724 D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
3725 MAX_SAMPLERS / QD3D12_FRAMES_IN_FLIGHT))
3726 {
3727 qWarning("Could not create shader-visible SAMPLER heap");
3728 return false;
3729 }
3730
3731 this->device = device;
3732 return true;
3733}
3734
3735void QD3D12SamplerManager::destroy()
3736{
3737 if (device) {
3738 shaderVisibleSamplerHeap.destroy();
3739 device = nullptr;
3740 }
3741}
3742
3743QD3D12Descriptor QD3D12SamplerManager::getShaderVisibleDescriptor(const D3D12_SAMPLER_DESC &desc)
3744{
3745 auto it = gpuMap.constFind({desc});
3746 if (it != gpuMap.cend())
3747 return *it;
3748
3749 QD3D12Descriptor descriptor = shaderVisibleSamplerHeap.heap.get(1);
3750 if (descriptor.isValid()) {
3751 device->CreateSampler(&desc, descriptor.cpuHandle);
3752 gpuMap.insert({desc}, descriptor);
3753 } else {
3754 qWarning("Out of shader-visible SAMPLER descriptor heap space,"
3755 " this should not happen, maximum number of unique samplers is %u",
3756 shaderVisibleSamplerHeap.heap.capacity);
3757 }
3758
3759 return descriptor;
3760}
3761
3762QD3D12Descriptor QD3D12SamplerManager::getShaderVisibleDescriptors(const QVarLengthArray<Q_D3D12_SAMPLER_DESC, 8> &descs)
3763{
3764 // Deliberately keyed on the sampler descriptions, not on the QRhiSampler
3765 // objects: an array of samplers with the same combination of filtering and
3766 // addressing modes can share one descriptor run, however many
3767 // QRhiShaderResourceBindings reference it.
3768 auto it = gpuArrayMap.constFind({ descs });
3769 if (it != gpuArrayMap.cend())
3770 return *it;
3771
3772 const quint32 count = quint32(descs.count());
3773 QD3D12Descriptor startDescriptor = shaderVisibleSamplerHeap.heap.get(count);
3774 if (startDescriptor.isValid()) {
3775 for (quint32 i = 0; i < count; ++i) {
3776 device->CreateSampler(&descs[int(i)].desc,
3777 shaderVisibleSamplerHeap.heap.incremented(startDescriptor, i).cpuHandle);
3778 }
3779 gpuArrayMap.insert({ descs }, startDescriptor);
3780 } else {
3781 // Not inserting anything into the map on failure, so this is retried
3782 // (and warns again) on every setShaderResources() with this binding.
3783 // Same as the single sampler case above, and preferable to caching the
3784 // invalid descriptor, which would keep binding a null descriptor table
3785 // without saying anything about it.
3786 qWarning("Out of shader-visible SAMPLER descriptor heap space when reserving"
3787 " %u consecutive descriptors for a sampler array, maximum number of"
3788 " sampler descriptors is %u",
3789 count, shaderVisibleSamplerHeap.heap.capacity);
3790 }
3791
3792 return startDescriptor;
3793}
3794
3795void QD3D12MipmapGenerator::create(QRhiD3D12 *rhiD)
3796{
3797 this->rhiD = rhiD;
3798}
3799
3800bool QD3D12MipmapGenerator::ensureCreated()
3801{
3802 if (!pipelineHandle.isNull())
3803 return true;
3804
3805 if (createFailed)
3806 return false;
3807
3808 if (!buildPipeline()) {
3809 createFailed = true;
3810 return false;
3811 }
3812
3813 return true;
3814}
3815
3816bool QD3D12MipmapGenerator::buildPipeline()
3817{
3818 qCDebug(QRHI_LOG_INFO, "Building mipmap generator compute pipeline on first use");
3819
3820 D3D12_ROOT_PARAMETER1 rootParams[3] = {};
3821 D3D12_DESCRIPTOR_RANGE1 descriptorRanges[2] = {};
3822
3823 // b0
3824 rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
3825 rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
3826 rootParams[0].Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
3827
3828 // t0
3829 descriptorRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
3830 descriptorRanges[0].NumDescriptors = 1;
3831 descriptorRanges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
3832 rootParams[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
3833 rootParams[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
3834 rootParams[1].DescriptorTable.NumDescriptorRanges = 1;
3835 rootParams[1].DescriptorTable.pDescriptorRanges = &descriptorRanges[0];
3836
3837 // u0..3
3838 descriptorRanges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
3839 descriptorRanges[1].NumDescriptors = 4;
3840 rootParams[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
3841 rootParams[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
3842 rootParams[2].DescriptorTable.NumDescriptorRanges = 1;
3843 rootParams[2].DescriptorTable.pDescriptorRanges = &descriptorRanges[1];
3844
3845 // s0
3846 D3D12_STATIC_SAMPLER_DESC samplerDesc = {};
3847 samplerDesc.Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
3848 samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
3849 samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
3850 samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
3851 samplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
3852
3853 D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {};
3854 rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
3855 rsDesc.Desc_1_1.NumParameters = 3;
3856 rsDesc.Desc_1_1.pParameters = rootParams;
3857 rsDesc.Desc_1_1.NumStaticSamplers = 1;
3858 rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc;
3859
3860 ID3DBlob *signature = nullptr;
3861 HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
3862 if (FAILED(hr)) {
3863 qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
3864 return false;
3865 }
3866 ID3D12RootSignature *rootSig = nullptr;
3867 hr = rhiD->dev->CreateRootSignature(0,
3868 signature->GetBufferPointer(),
3869 signature->GetBufferSize(),
3870 __uuidof(ID3D12RootSignature),
3871 reinterpret_cast<void **>(&rootSig));
3872 signature->Release();
3873 if (FAILED(hr)) {
3874 qWarning("Failed to create root signature: %s",
3875 qPrintable(QSystemError::windowsComString(hr)));
3876 return false;
3877 }
3878
3879 rootSigHandle = QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig);
3880
3881 D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
3882 psoDesc.pRootSignature = rootSig;
3883 psoDesc.CS.pShaderBytecode = g_csMipmap;
3884 psoDesc.CS.BytecodeLength = sizeof(g_csMipmap);
3885 ID3D12PipelineState *pso = nullptr;
3886 hr = rhiD->dev->CreateComputePipelineState(&psoDesc,
3887 __uuidof(ID3D12PipelineState),
3888 reinterpret_cast<void **>(&pso));
3889 if (FAILED(hr)) {
3890 qWarning("Failed to create compute pipeline state: %s",
3891 qPrintable(QSystemError::windowsComString(hr)));
3892 rhiD->rootSignaturePool.remove(rootSigHandle);
3893 rootSigHandle = {};
3894 return false;
3895 }
3896
3897 pipelineHandle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso);
3898
3899 return true;
3900}
3901
3902void QD3D12MipmapGenerator::destroy()
3903{
3904 // Be safe even if create() was never called due to other failures in the
3905 // init sequence.
3906 if (!rhiD)
3907 return;
3908
3909 rhiD->pipelinePool.remove(pipelineHandle);
3910 pipelineHandle = {};
3911 rhiD->rootSignaturePool.remove(rootSigHandle);
3912 rootSigHandle = {};
3913 createFailed = false;
3914}
3915
3916void QD3D12MipmapGenerator::generate(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &textureHandle)
3917{
3918 if (!ensureCreated())
3919 return;
3920
3921 QD3D12Pipeline *pipeline = rhiD->pipelinePool.lookupRef(pipelineHandle);
3922 if (!pipeline)
3923 return;
3924 QD3D12RootSignature *rootSig = rhiD->rootSignaturePool.lookupRef(rootSigHandle);
3925 if (!rootSig)
3926 return;
3927 QD3D12Resource *res = rhiD->resourcePool.lookupRef(textureHandle);
3928 if (!res)
3929 return;
3930
3931 const quint32 mipLevelCount = res->desc.MipLevels;
3932 if (mipLevelCount < 2)
3933 return;
3934
3935 if (res->desc.SampleDesc.Count > 1) {
3936 qWarning("Cannot generate mipmaps for MSAA texture");
3937 return;
3938 }
3939
3940 const bool is1D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE1D;
3941 if (is1D) {
3942 qWarning("Cannot generate mipmaps for 1D texture");
3943 return;
3944 }
3945
3946 const bool is3D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D;
3947 const bool isCubeOrArray = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D
3948 && res->desc.DepthOrArraySize > 1;
3949 const quint32 layerCount = isCubeOrArray ? res->desc.DepthOrArraySize : 1;
3950
3951 if (is3D) {
3952 qWarning("2D mipmap generator invoked for 3D texture, this should not happen");
3953 return;
3954 }
3955
3956 rhiD->barrierGen.addTransitionBarrier(textureHandle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
3957 rhiD->barrierGen.enqueueBufferedTransitionBarriers(cbD);
3958
3959 cbD->cmdList->SetPipelineState(pipeline->pso);
3960 cbD->cmdList->SetComputeRootSignature(rootSig->rootSig);
3961
3962 const quint32 descriptorByteSize = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].descriptorByteSize;
3963
3964 struct CBufData {
3965 quint32 srcMipLevel;
3966 quint32 numMipLevels;
3967 float texelWidth;
3968 float texelHeight;
3969 };
3970
3971 const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(sizeof(CBufData), mipLevelCount * layerCount);
3972 std::optional<QD3D12StagingArea> ownStagingArea;
3973 rhiD->recordSmallStagingAreaDemand(allocSize);
3974 if (rhiD->smallStagingAreas[rhiD->currentFrameSlot].remainingCapacity() < allocSize) {
3975 ownStagingArea = QD3D12StagingArea();
3976 if (!ownStagingArea->create(rhiD, allocSize, D3D12_HEAP_TYPE_UPLOAD)) {
3977 qWarning("Could not create staging area for mipmap generation");
3978 return;
3979 }
3980 }
3981 QD3D12StagingArea *workArea = ownStagingArea.has_value()
3982 ? &ownStagingArea.value()
3983 : &rhiD->smallStagingAreas[rhiD->currentFrameSlot];
3984
3985 bool gotNewHeap = false;
3986 if (!rhiD->ensureShaderVisibleDescriptorHeapCapacity(&rhiD->shaderVisibleCbvSrvUavHeap,
3987 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
3988 rhiD->currentFrameSlot,
3989 (1 + 4) * mipLevelCount * layerCount,
3990 &gotNewHeap))
3991 {
3992 qWarning("Could not ensure enough space in descriptor heap for mipmap generation");
3993 return;
3994 }
3995 if (gotNewHeap)
3996 rhiD->bindShaderVisibleHeaps(cbD);
3997
3998 for (quint32 layer = 0; layer < layerCount; ++layer) {
3999 for (quint32 level = 0; level < mipLevelCount ;) {
4000 UINT subresource = calcSubresource(level, layer, res->desc.MipLevels);
4001 rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource,
4002 D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
4003 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
4004
4005 quint32 levelPlusOneMipWidth = res->desc.Width >> (level + 1);
4006 quint32 levelPlusOneMipHeight = res->desc.Height >> (level + 1);
4007 const quint32 dw = levelPlusOneMipWidth == 1 ? levelPlusOneMipHeight : levelPlusOneMipWidth;
4008 const quint32 dh = levelPlusOneMipHeight == 1 ? levelPlusOneMipWidth : levelPlusOneMipHeight;
4009 // number of times the size can be halved while still resulting in an even dimension
4010 const quint32 additionalMips = qCountTrailingZeroBits(dw | dh);
4011 const quint32 numGenMips = qMin(1u + qMin(3u, additionalMips), res->desc.MipLevels - level);
4012 levelPlusOneMipWidth = qMax(1u, levelPlusOneMipWidth);
4013 levelPlusOneMipHeight = qMax(1u, levelPlusOneMipHeight);
4014
4015 CBufData cbufData = {
4016 level,
4017 numGenMips,
4018 1.0f / float(levelPlusOneMipWidth),
4019 1.0f / float(levelPlusOneMipHeight)
4020 };
4021
4022 QD3D12StagingArea::Allocation cbuf = workArea->get(sizeof(cbufData));
4023 memcpy(cbuf.p, &cbufData, sizeof(cbufData));
4024 cbD->cmdList->SetComputeRootConstantBufferView(0, cbuf.gpuAddr);
4025
4026 QD3D12Descriptor srv = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(1);
4027 D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
4028 srvDesc.Format = res->desc.Format;
4029 srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
4030 if (isCubeOrArray) {
4031 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
4032 srvDesc.Texture2DArray.MipLevels = res->desc.MipLevels;
4033 srvDesc.Texture2DArray.FirstArraySlice = layer;
4034 srvDesc.Texture2DArray.ArraySize = 1;
4035 } else {
4036 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
4037 srvDesc.Texture2D.MipLevels = res->desc.MipLevels;
4038 }
4039 rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle);
4040 cbD->cmdList->SetComputeRootDescriptorTable(1, srv.gpuHandle);
4041
4042 QD3D12Descriptor uavStart = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(4);
4043 D3D12_CPU_DESCRIPTOR_HANDLE uavCpuHandle = uavStart.cpuHandle;
4044 // if level is N, then need UAVs for levels N+1, ..., N+4
4045 for (quint32 uavIdx = 0; uavIdx < 4; ++uavIdx) {
4046 const quint32 uavMipLevel = qMin(level + 1u + uavIdx, res->desc.MipLevels - 1u);
4047 D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
4048 uavDesc.Format = res->desc.Format;
4049 if (isCubeOrArray) {
4050 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY;
4051 uavDesc.Texture2DArray.MipSlice = uavMipLevel;
4052 uavDesc.Texture2DArray.FirstArraySlice = layer;
4053 uavDesc.Texture2DArray.ArraySize = 1;
4054 } else {
4055 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
4056 uavDesc.Texture2D.MipSlice = uavMipLevel;
4057 }
4058 rhiD->dev->CreateUnorderedAccessView(res->resource, nullptr, &uavDesc, uavCpuHandle);
4059 uavCpuHandle.ptr += descriptorByteSize;
4060 }
4061 cbD->cmdList->SetComputeRootDescriptorTable(2, uavStart.gpuHandle);
4062
4063 cbD->cmdList->Dispatch(levelPlusOneMipWidth, levelPlusOneMipHeight, 1);
4064
4065 rhiD->barrierGen.enqueueUavBarrier(cbD, textureHandle);
4066 rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource,
4067 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
4068 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
4069
4070 level += numGenMips;
4071 }
4072 }
4073
4074 if (ownStagingArea.has_value())
4075 ownStagingArea->destroyWithDeferredRelease(&rhiD->releaseQueue);
4076}
4077
4078void QD3D12MipmapGenerator3D::create(QRhiD3D12 *rhiD)
4079{
4080 this->rhiD = rhiD;
4081}
4082
4083bool QD3D12MipmapGenerator3D::ensureCreated()
4084{
4085 if (!pipelineHandle.isNull())
4086 return true;
4087
4088 if (createFailed)
4089 return false;
4090
4091 if (!buildPipeline()) {
4092 createFailed = true;
4093 return false;
4094 }
4095
4096 return true;
4097}
4098
4099bool QD3D12MipmapGenerator3D::buildPipeline()
4100{
4101 qCDebug(QRHI_LOG_INFO, "Building 3D texture mipmap generator compute pipeline on first use");
4102
4103 D3D12_ROOT_PARAMETER1 rootParams[3] = {};
4104 D3D12_DESCRIPTOR_RANGE1 descriptorRanges[2] = {};
4105
4106 // b0
4107 rootParams[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
4108 rootParams[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
4109 rootParams[0].Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
4110
4111 // t0
4112 descriptorRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
4113 descriptorRanges[0].NumDescriptors = 1;
4114 descriptorRanges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE;
4115 rootParams[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
4116 rootParams[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
4117 rootParams[1].DescriptorTable.NumDescriptorRanges = 1;
4118 rootParams[1].DescriptorTable.pDescriptorRanges = &descriptorRanges[0];
4119
4120 // u0
4121 descriptorRanges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
4122 descriptorRanges[1].NumDescriptors = 1;
4123 rootParams[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
4124 rootParams[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
4125 rootParams[2].DescriptorTable.NumDescriptorRanges = 1;
4126 rootParams[2].DescriptorTable.pDescriptorRanges = &descriptorRanges[1];
4127
4128 // s0
4129 D3D12_STATIC_SAMPLER_DESC samplerDesc = {};
4130 samplerDesc.Filter = D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
4131 samplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
4132 samplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
4133 samplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
4134 samplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
4135
4136 D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {};
4137 rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
4138 rsDesc.Desc_1_1.NumParameters = 3;
4139 rsDesc.Desc_1_1.pParameters = rootParams;
4140 rsDesc.Desc_1_1.NumStaticSamplers = 1;
4141 rsDesc.Desc_1_1.pStaticSamplers = &samplerDesc;
4142
4143 ID3DBlob *signature = nullptr;
4144 HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
4145 if (FAILED(hr)) {
4146 qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
4147 return false;
4148 }
4149 ID3D12RootSignature *rootSig = nullptr;
4150 hr = rhiD->dev->CreateRootSignature(0,
4151 signature->GetBufferPointer(),
4152 signature->GetBufferSize(),
4153 __uuidof(ID3D12RootSignature),
4154 reinterpret_cast<void **>(&rootSig));
4155 signature->Release();
4156 if (FAILED(hr)) {
4157 qWarning("Failed to create root signature: %s",
4158 qPrintable(QSystemError::windowsComString(hr)));
4159 return false;
4160 }
4161
4162 rootSigHandle = QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig);
4163
4164 D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {};
4165 psoDesc.pRootSignature = rootSig;
4166 psoDesc.CS.pShaderBytecode = g_csMipmap3D;
4167 psoDesc.CS.BytecodeLength = sizeof(g_csMipmap3D);
4168 ID3D12PipelineState *pso = nullptr;
4169 hr = rhiD->dev->CreateComputePipelineState(&psoDesc,
4170 __uuidof(ID3D12PipelineState),
4171 reinterpret_cast<void **>(&pso));
4172 if (FAILED(hr)) {
4173 qWarning("Failed to create compute pipeline state: %s",
4174 qPrintable(QSystemError::windowsComString(hr)));
4175 rhiD->rootSignaturePool.remove(rootSigHandle);
4176 rootSigHandle = {};
4177 return false;
4178 }
4179
4180 pipelineHandle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso);
4181
4182 return true;
4183}
4184
4185void QD3D12MipmapGenerator3D::destroy()
4186{
4187 if (!rhiD)
4188 return;
4189
4190 rhiD->pipelinePool.remove(pipelineHandle);
4191 pipelineHandle = {};
4192 rhiD->rootSignaturePool.remove(rootSigHandle);
4193 rootSigHandle = {};
4194 createFailed = false;
4195}
4196
4197void QD3D12MipmapGenerator3D::generate(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &textureHandle)
4198{
4199 if (!ensureCreated())
4200 return;
4201
4202 QD3D12Pipeline *pipeline = rhiD->pipelinePool.lookupRef(pipelineHandle);
4203 if (!pipeline)
4204 return;
4205 QD3D12RootSignature *rootSig = rhiD->rootSignaturePool.lookupRef(rootSigHandle);
4206 if (!rootSig)
4207 return;
4208 QD3D12Resource *res = rhiD->resourcePool.lookupRef(textureHandle);
4209 if (!res)
4210 return;
4211
4212 const quint32 mipLevelCount = res->desc.MipLevels;
4213 if (mipLevelCount < 2)
4214 return;
4215
4216 const bool is3D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D;
4217 if (!is3D) {
4218 qWarning("3D mipmap generator invoked for non-3D texture, this should not happen");
4219 return;
4220 }
4221
4222 rhiD->barrierGen.addTransitionBarrier(textureHandle, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
4223 rhiD->barrierGen.enqueueBufferedTransitionBarriers(cbD);
4224
4225 cbD->cmdList->SetPipelineState(pipeline->pso);
4226 cbD->cmdList->SetComputeRootSignature(rootSig->rootSig);
4227
4228 const quint32 descriptorByteSize = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].descriptorByteSize;
4229
4230 struct CBufData {
4231 float texelWidth;
4232 float texelHeight;
4233 float texelDepth;
4234 quint32 srcMipLevel;
4235 };
4236
4237 const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(sizeof(CBufData), mipLevelCount);
4238 std::optional<QD3D12StagingArea> ownStagingArea;
4239 rhiD->recordSmallStagingAreaDemand(allocSize);
4240 if (rhiD->smallStagingAreas[rhiD->currentFrameSlot].remainingCapacity() < allocSize) {
4241 ownStagingArea = QD3D12StagingArea();
4242 if (!ownStagingArea->create(rhiD, allocSize, D3D12_HEAP_TYPE_UPLOAD)) {
4243 qWarning("Could not create staging area for mipmap generation");
4244 return;
4245 }
4246 }
4247 QD3D12StagingArea *workArea = ownStagingArea.has_value()
4248 ? &ownStagingArea.value()
4249 : &rhiD->smallStagingAreas[rhiD->currentFrameSlot];
4250
4251 bool gotNewHeap = false;
4252 if (!rhiD->ensureShaderVisibleDescriptorHeapCapacity(&rhiD->shaderVisibleCbvSrvUavHeap,
4253 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
4254 rhiD->currentFrameSlot,
4255 (1 + 1) * mipLevelCount, // 1 SRV + 1 UAV
4256 &gotNewHeap))
4257 {
4258 qWarning("Could not ensure enough space in descriptor heap for mipmap generation");
4259 return;
4260 }
4261 if (gotNewHeap)
4262 rhiD->bindShaderVisibleHeaps(cbD);
4263
4264 for (quint32 level = 0; level < mipLevelCount; ++level) {
4265 UINT subresource = calcSubresource(level, 0u, res->desc.MipLevels);
4266 rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource,
4267 D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
4268 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
4269
4270 quint32 levelPlusOneMipWidth = qMax<quint32>(1, res->desc.Width >> (level + 1));
4271 quint32 levelPlusOneMipHeight = qMax<quint32>(1, res->desc.Height >> (level + 1));
4272 quint32 levelPlusOneMipDepth = qMax<quint32>(1, res->desc.DepthOrArraySize >> (level + 1));
4273
4274 CBufData cbufData = {
4275 1.0f / float(levelPlusOneMipWidth),
4276 1.0f / float(levelPlusOneMipHeight),
4277 1.0f / float(levelPlusOneMipDepth),
4278 quint32(level)
4279 };
4280
4281 QD3D12StagingArea::Allocation cbuf = workArea->get(sizeof(cbufData));
4282 memcpy(cbuf.p, &cbufData, sizeof(cbufData));
4283 cbD->cmdList->SetComputeRootConstantBufferView(0, cbuf.gpuAddr);
4284
4285 QD3D12Descriptor srv = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(1);
4286 D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
4287 srvDesc.Format = res->desc.Format;
4288 srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
4289 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
4290 srvDesc.Texture3D.MipLevels = res->desc.MipLevels;
4291
4292 rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle);
4293 cbD->cmdList->SetComputeRootDescriptorTable(1, srv.gpuHandle);
4294
4295 QD3D12Descriptor uavStart = rhiD->shaderVisibleCbvSrvUavHeap.perFrameHeapSlice[rhiD->currentFrameSlot].get(1);
4296 D3D12_CPU_DESCRIPTOR_HANDLE uavCpuHandle = uavStart.cpuHandle;
4297 const quint32 uavMipLevel = qMin(level + 1u, res->desc.MipLevels - 1u);
4298 D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
4299 uavDesc.Format = res->desc.Format;
4300 uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D;
4301 uavDesc.Texture3D.MipSlice = uavMipLevel;
4302 uavDesc.Texture3D.WSize = UINT(-1);
4303 rhiD->dev->CreateUnorderedAccessView(res->resource, nullptr, &uavDesc, uavCpuHandle);
4304 uavCpuHandle.ptr += descriptorByteSize;
4305 cbD->cmdList->SetComputeRootDescriptorTable(2, uavStart.gpuHandle);
4306
4307 cbD->cmdList->Dispatch(levelPlusOneMipWidth, levelPlusOneMipHeight, levelPlusOneMipDepth);
4308
4309 rhiD->barrierGen.enqueueUavBarrier(cbD, textureHandle);
4310 rhiD->barrierGen.enqueueSubresourceTransitionBarrier(cbD, textureHandle, subresource,
4311 D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
4312 D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
4313 }
4314
4315 if (ownStagingArea.has_value())
4316 ownStagingArea->destroyWithDeferredRelease(&rhiD->releaseQueue);
4317}
4318
4319bool QD3D12MemoryAllocator::create(ID3D12Device *device, IDXGIAdapter1 *adapter)
4320{
4321 this->device = device;
4322
4323 // We can function with and without D3D12MA: CreateCommittedResource is
4324 // just fine for our purposes and not any complicated API-wise; the memory
4325 // allocator is interesting for efficiency mainly since it can suballocate
4326 // instead of making everything a committed resource allocation.
4327
4328 static bool disableMA = qEnvironmentVariableIntValue("QT_D3D_NO_SUBALLOC");
4329 if (disableMA)
4330 return true;
4331
4332 DXGI_ADAPTER_DESC1 desc;
4333 adapter->GetDesc1(&desc);
4334 if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
4335 return true;
4336
4337 D3D12MA::ALLOCATOR_DESC allocatorDesc = {};
4338 allocatorDesc.pDevice = device;
4339 allocatorDesc.pAdapter = adapter;
4340 // A QRhi is supposed to be used from one single thread only. Disable
4341 // the allocator's own mutexes. This may give a performance boost.
4342 allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_SINGLETHREADED;
4343 HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator);
4344 if (FAILED(hr)) {
4345 qWarning("Failed to initialize D3D12 Memory Allocator: %s",
4346 qPrintable(QSystemError::windowsComString(hr)));
4347 return false;
4348 }
4349 return true;
4350}
4351
4352void QD3D12MemoryAllocator::destroy()
4353{
4354 if (allocator) {
4355 allocator->Release();
4356 allocator = nullptr;
4357 }
4358}
4359
4360HRESULT QD3D12MemoryAllocator::createResource(D3D12_HEAP_TYPE heapType,
4361 const D3D12_RESOURCE_DESC *resourceDesc,
4362 D3D12_RESOURCE_STATES initialState,
4363 const D3D12_CLEAR_VALUE *optimizedClearValue,
4364 D3D12MA::Allocation **maybeAllocation,
4365 REFIID riidResource,
4366 void **ppvResource)
4367{
4368 if (allocator) {
4369 D3D12MA::ALLOCATION_DESC allocDesc = {};
4370 allocDesc.HeapType = heapType;
4371 return allocator->CreateResource(&allocDesc,
4372 resourceDesc,
4373 initialState,
4374 optimizedClearValue,
4375 maybeAllocation,
4376 riidResource,
4377 ppvResource);
4378 } else {
4379 *maybeAllocation = nullptr;
4380 D3D12_HEAP_PROPERTIES heapProps = {};
4381 heapProps.Type = heapType;
4382 return device->CreateCommittedResource(&heapProps,
4383 D3D12_HEAP_FLAG_NONE,
4384 resourceDesc,
4385 initialState,
4386 optimizedClearValue,
4387 riidResource,
4388 ppvResource);
4389 }
4390}
4391
4392void QD3D12MemoryAllocator::getBudget(D3D12MA::Budget *localBudget, D3D12MA::Budget *nonLocalBudget)
4393{
4394 if (allocator) {
4395 allocator->GetBudget(localBudget, nonLocalBudget);
4396 } else {
4397 *localBudget = {};
4398 *nonLocalBudget = {};
4399 }
4400}
4401
4402void QRhiD3D12::waitGpu()
4403{
4404 fullFenceCounter += 1u;
4405 if (SUCCEEDED(cmdQueue->Signal(fullFence, fullFenceCounter))) {
4406 if (SUCCEEDED(fullFence->SetEventOnCompletion(fullFenceCounter, fullFenceEvent)))
4407 WaitForSingleObject(fullFenceEvent, INFINITE);
4408 }
4409}
4410
4411DXGI_SAMPLE_DESC QRhiD3D12::effectiveSampleDesc(int sampleCount, DXGI_FORMAT format) const
4412{
4413 DXGI_SAMPLE_DESC desc;
4414 desc.Count = 1;
4415 desc.Quality = 0;
4416
4417 const int s = effectiveSampleCount(sampleCount);
4418
4419 if (s > 1) {
4420 D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaInfo = {};
4421 msaaInfo.Format = format;
4422 msaaInfo.SampleCount = UINT(s);
4423 if (SUCCEEDED(dev->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msaaInfo, sizeof(msaaInfo)))) {
4424 if (msaaInfo.NumQualityLevels > 0) {
4425 desc.Count = UINT(s);
4426 desc.Quality = msaaInfo.NumQualityLevels - 1;
4427 } else {
4428 qWarning("No quality levels for multisampling with sample count %d", s);
4429 }
4430 }
4431 }
4432
4433 return desc;
4434}
4435
4436bool QRhiD3D12::startCommandListForCurrentFrameSlot(D3D12GraphicsCommandList **cmdList)
4437{
4438 ID3D12CommandAllocator *cmdAlloc = cmdAllocators[currentFrameSlot];
4439 if (!*cmdList) {
4440 HRESULT hr = dev->CreateCommandList(0,
4441 D3D12_COMMAND_LIST_TYPE_DIRECT,
4442 cmdAlloc,
4443 nullptr,
4444 __uuidof(D3D12GraphicsCommandList),
4445 reinterpret_cast<void **>(cmdList));
4446 if (FAILED(hr)) {
4447 qWarning("Failed to create command list: %s", qPrintable(QSystemError::windowsComString(hr)));
4448 return false;
4449 }
4450 } else {
4451 HRESULT hr = (*cmdList)->Reset(cmdAlloc, nullptr);
4452 if (FAILED(hr)) {
4453 qWarning("Failed to reset command list: %s", qPrintable(QSystemError::windowsComString(hr)));
4454 return false;
4455 }
4456 }
4457 return true;
4458}
4459
4460static inline QRhiTexture::Format swapchainReadbackTextureFormat(DXGI_FORMAT format, QRhiTexture::Flags *flags)
4461{
4462 switch (format) {
4463 case DXGI_FORMAT_R8G8B8A8_UNORM:
4464 return QRhiTexture::RGBA8;
4465 case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
4466 if (flags)
4467 (*flags) |= QRhiTexture::sRGB;
4468 return QRhiTexture::RGBA8;
4469 case DXGI_FORMAT_B8G8R8A8_UNORM:
4470 return QRhiTexture::BGRA8;
4471 case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
4472 if (flags)
4473 (*flags) |= QRhiTexture::sRGB;
4474 return QRhiTexture::BGRA8;
4475 case DXGI_FORMAT_R16G16B16A16_FLOAT:
4476 return QRhiTexture::RGBA16F;
4477 case DXGI_FORMAT_R32G32B32A32_FLOAT:
4478 return QRhiTexture::RGBA32F;
4479 case DXGI_FORMAT_R10G10B10A2_UNORM:
4480 return QRhiTexture::RGB10A2;
4481 default:
4482 qWarning("DXGI_FORMAT %d cannot be read back", format);
4483 break;
4484 }
4485 return QRhiTexture::UnknownFormat;
4486}
4487
4488void QRhiD3D12::enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates)
4489{
4490 QRhiResourceUpdateBatchPrivate *ud = QRhiResourceUpdateBatchPrivate::get(resourceUpdates);
4491
4492 for (int opIdx = 0; opIdx < ud->activeBufferOpCount; ++opIdx) {
4493 const QRhiResourceUpdateBatchPrivate::BufferOp &u(ud->bufferOps[opIdx]);
4494 if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::DynamicUpdate) {
4495 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf);
4496 Q_ASSERT(bufD->m_type == QRhiBuffer::Dynamic);
4497 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
4498 if (u.offset == 0 && u.data.size() == bufD->m_size)
4499 bufD->pendingHostWrites[i].clear();
4500 bufD->pendingHostWrites[i].append({ u.offset, u.data });
4501 }
4502 } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::StaticUpload) {
4503 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf);
4504 Q_ASSERT(bufD->m_type != QRhiBuffer::Dynamic);
4505 Q_ASSERT(u.offset + u.data.size() <= bufD->m_size);
4506
4507 // The general approach to staging upload data is to first try
4508 // using the per-frame "small" staging area, which is a very simple
4509 // linear allocator; if that's not big enough then create a
4510 // dedicated StagingArea and then deferred-release it to make sure
4511 // if stays alive while the frame is possibly still in flight.
4512
4513 QD3D12StagingArea::Allocation stagingAlloc;
4514 const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(bufD->m_size, 1);
4515 recordSmallStagingAreaDemand(allocSize);
4516 if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize)
4517 stagingAlloc = smallStagingAreas[currentFrameSlot].get(bufD->m_size);
4518
4519 std::optional<QD3D12StagingArea> ownStagingArea;
4520 if (!stagingAlloc.isValid()) {
4521 ownStagingArea = QD3D12StagingArea();
4522 if (!ownStagingArea->create(this, allocSize, D3D12_HEAP_TYPE_UPLOAD))
4523 continue;
4524 stagingAlloc = ownStagingArea->get(allocSize);
4525 if (!stagingAlloc.isValid()) {
4526 ownStagingArea->destroy();
4527 continue;
4528 }
4529 }
4530
4531 memcpy(stagingAlloc.p + u.offset, u.data.constData(), u.data.size());
4532
4533 barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_COPY_DEST);
4534 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4535
4536 if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) {
4537 cbD->cmdList->CopyBufferRegion(res->resource,
4538 u.offset,
4539 stagingAlloc.buffer,
4540 stagingAlloc.bufferOffset + u.offset,
4541 u.data.size());
4542 }
4543
4544 if (ownStagingArea.has_value())
4545 ownStagingArea->destroyWithDeferredRelease(&releaseQueue);
4546 } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Read) {
4547 QD3D12Buffer *bufD = QRHI_RES(QD3D12Buffer, u.buf);
4548 if (bufD->m_type == QRhiBuffer::Dynamic) {
4549 bufD->executeHostWritesForFrameSlot(currentFrameSlot);
4550 if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[currentFrameSlot])) {
4551 Q_ASSERT(res->cpuMapPtr);
4552 u.result->data.resize(u.readSize);
4553 memcpy(u.result->data.data(), reinterpret_cast<char *>(res->cpuMapPtr) + u.offset, u.readSize);
4554 }
4555 if (u.result->completed)
4556 u.result->completed();
4557 } else {
4558 QD3D12Readback readback;
4559 readback.frameSlot = currentFrameSlot;
4560 readback.result = u.result;
4561 readback.byteSize = u.readSize;
4562 const quint32 allocSize = aligned(u.readSize, QD3D12StagingArea::ALIGNMENT);
4563 if (!readback.staging.create(this, allocSize, D3D12_HEAP_TYPE_READBACK)) {
4564 if (u.result->completed)
4565 u.result->completed();
4566 continue;
4567 }
4568 QD3D12StagingArea::Allocation stagingAlloc = readback.staging.get(u.readSize);
4569 if (!stagingAlloc.isValid()) {
4570 readback.staging.destroy();
4571 if (u.result->completed)
4572 u.result->completed();
4573 continue;
4574 }
4575 Q_ASSERT(stagingAlloc.bufferOffset == 0);
4576 barrierGen.addTransitionBarrier(bufD->handles[0], D3D12_RESOURCE_STATE_COPY_SOURCE);
4577 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4578 if (QD3D12Resource *res = resourcePool.lookupRef(bufD->handles[0])) {
4579 cbD->cmdList->CopyBufferRegion(stagingAlloc.buffer, 0, res->resource, u.offset, u.readSize);
4580 activeReadbacks.append(readback);
4581 } else {
4582 readback.staging.destroy();
4583 if (u.result->completed)
4584 u.result->completed();
4585 }
4586 }
4587 } else if (u.type == QRhiResourceUpdateBatchPrivate::BufferOp::Copy) {
4588 QD3D12Buffer *dstD = QRHI_RES(QD3D12Buffer, u.buf);
4589 QD3D12Buffer *srcD = QRHI_RES(QD3D12Buffer, u.src);
4590 Q_ASSERT(dstD->m_type != QRhiBuffer::Dynamic && srcD->m_type != QRhiBuffer::Dynamic);
4591
4592 QD3D12Resource *dstRes = resourcePool.lookupRef(dstD->handles[0]);
4593 QD3D12Resource *srcRes = resourcePool.lookupRef(srcD->handles[0]);
4594 if (!dstRes || !srcRes)
4595 continue;
4596
4597 barrierGen.addTransitionBarrier(srcD->handles[0], D3D12_RESOURCE_STATE_COPY_SOURCE);
4598 barrierGen.addTransitionBarrier(dstD->handles[0], D3D12_RESOURCE_STATE_COPY_DEST);
4599 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4600
4601 cbD->cmdList->CopyBufferRegion(dstRes->resource, u.offset,
4602 srcRes->resource, u.srcOffset,
4603 u.readSize);
4604 }
4605 }
4606
4607 for (int opIdx = 0; opIdx < ud->activeTextureOpCount; ++opIdx) {
4608 const QRhiResourceUpdateBatchPrivate::TextureOp &u(ud->textureOps[opIdx]);
4609 if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Upload) {
4610 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst);
4611 const bool is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4612 QD3D12Resource *res = resourcePool.lookupRef(texD->handle);
4613 if (!res)
4614 continue;
4615 barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_COPY_DEST);
4616 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4617 for (int layer = 0, maxLayer = u.subresDesc.size(); layer < maxLayer; ++layer) {
4618 for (int level = 0; level < QRhi::MAX_MIP_LEVELS; ++level) {
4619 for (const QRhiTextureSubresourceUploadDescription &subresDesc : std::as_const(u.subresDesc[layer][level])) {
4620 D3D12_SUBRESOURCE_FOOTPRINT footprint = {};
4621 footprint.Format = res->desc.Format;
4622 footprint.Depth = 1;
4623 quint32 totalBytes = 0;
4624
4625 QSize subresSize = subresDesc.sourceSize().isEmpty() ? q->sizeForMipLevel(level, texD->m_pixelSize)
4626 : subresDesc.sourceSize();
4627 const QPoint srcPos = subresDesc.sourceTopLeft();
4628 QPoint dstPos = subresDesc.destinationTopLeft();
4629
4630 if (subresDesc.image().isNull()
4631 && !subresDesc.data().isEmpty()
4632 && !isCompressedFormat(texD->m_format))
4633 {
4634 subresSize = clampedSubResourceUploadSize(subresSize, dstPos, level, texD->m_pixelSize);
4635 quint32 bytesPerPixel = 0;
4636 textureFormatInfo(texD->m_format, subresSize, nullptr, nullptr, &bytesPerPixel);
4637 subresSize = clampedSubResourceUploadSizeForSourceData(subresSize,
4638 subresDesc.dataStride(),
4639 bytesPerPixel,
4640 subresDesc.data().size());
4641 if (subresSize.isEmpty())
4642 continue;
4643 }
4644
4645 if (!subresDesc.image().isNull()) {
4646 const QImage img = subresDesc.image();
4647 const int bpl = img.bytesPerLine();
4648 footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
4649 totalBytes = footprint.RowPitch * img.height();
4650 } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
4651 QSize blockDim;
4652 quint32 bpl = 0;
4653 compressedFormatInfo(texD->m_format, subresSize, &bpl, nullptr, &blockDim);
4654 footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
4655 const int rowCount = aligned(subresSize.height(), blockDim.height()) / blockDim.height();
4656 totalBytes = footprint.RowPitch * rowCount;
4657 } else if (!subresDesc.data().isEmpty()) {
4658 quint32 bpl = 0;
4659 if (subresDesc.dataStride())
4660 bpl = subresDesc.dataStride();
4661 else
4662 textureFormatInfo(texD->m_format, subresSize, &bpl, nullptr, nullptr);
4663 footprint.RowPitch = aligned<UINT>(bpl, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
4664 totalBytes = footprint.RowPitch * subresSize.height();
4665 } else {
4666 qWarning("Invalid texture upload for %p layer=%d mip=%d", texD, layer, level);
4667 continue;
4668 }
4669
4670 const quint32 allocSize = QD3D12StagingArea::allocSizeForArray(totalBytes, 1);
4671 QD3D12StagingArea::Allocation stagingAlloc;
4672 recordSmallStagingAreaDemand(allocSize);
4673 if (smallStagingAreas[currentFrameSlot].remainingCapacity() >= allocSize)
4674 stagingAlloc = smallStagingAreas[currentFrameSlot].get(allocSize);
4675
4676 std::optional<QD3D12StagingArea> ownStagingArea;
4677 if (!stagingAlloc.isValid()) {
4678 ownStagingArea = QD3D12StagingArea();
4679 if (!ownStagingArea->create(this, allocSize, D3D12_HEAP_TYPE_UPLOAD))
4680 continue;
4681 stagingAlloc = ownStagingArea->get(allocSize);
4682 if (!stagingAlloc.isValid()) {
4683 ownStagingArea->destroy();
4684 continue;
4685 }
4686 }
4687
4688 D3D12_TEXTURE_COPY_LOCATION dst;
4689 dst.pResource = res->resource;
4690 dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
4691 dst.SubresourceIndex = calcSubresource(UINT(level), is3D ? 0u : UINT(layer), texD->mipLevelCount);
4692 D3D12_TEXTURE_COPY_LOCATION src;
4693 src.pResource = stagingAlloc.buffer;
4694 src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
4695 src.PlacedFootprint.Offset = stagingAlloc.bufferOffset;
4696
4697 D3D12_BOX srcBox; // back, right, bottom are exclusive
4698
4699 if (!subresDesc.image().isNull()) {
4700 const QImage img = subresDesc.image();
4701 const int bpc = qMax(1, img.depth() / 8);
4702 const int bpl = img.bytesPerLine();
4703
4704 QSize size = subresDesc.sourceSize().isEmpty() ? img.size() : subresDesc.sourceSize();
4705 size.setWidth(qMin(size.width(), img.width() - srcPos.x()));
4706 size.setHeight(qMin(size.height(), img.height() - srcPos.y()));
4707 size = clampedSubResourceUploadSize(size, dstPos, level, texD->m_pixelSize);
4708
4709 footprint.Width = size.width();
4710 footprint.Height = size.height();
4711
4712 srcBox.left = 0;
4713 srcBox.top = 0;
4714 srcBox.right = UINT(size.width());
4715 srcBox.bottom = UINT(size.height());
4716 srcBox.front = 0;
4717 srcBox.back = 1;
4718
4719 const uchar *imgPtr = img.constBits();
4720 const quint32 lineBytes = size.width() * bpc;
4721 for (int y = 0, h = size.height(); y < h; ++y) {
4722 memcpy(stagingAlloc.p + y * footprint.RowPitch,
4723 imgPtr + srcPos.x() * bpc + (y + srcPos.y()) * bpl,
4724 lineBytes);
4725 }
4726 } else if (!subresDesc.data().isEmpty() && isCompressedFormat(texD->m_format)) {
4727 QSize blockDim;
4728 quint32 bpl = 0;
4729 compressedFormatInfo(texD->m_format, subresSize, &bpl, nullptr, &blockDim);
4730 // x and y must be multiples of the block width and height
4731 dstPos.setX(aligned(dstPos.x(), blockDim.width()));
4732 dstPos.setY(aligned(dstPos.y(), blockDim.height()));
4733
4734 srcBox.left = 0;
4735 srcBox.top = 0;
4736 // width and height must be multiples of the block width and height
4737 srcBox.right = aligned(subresSize.width(), blockDim.width());
4738 srcBox.bottom = aligned(subresSize.height(), blockDim.height());
4739
4740 srcBox.front = 0;
4741 srcBox.back = 1;
4742
4743 footprint.Width = aligned(subresSize.width(), blockDim.width());
4744 footprint.Height = aligned(subresSize.height(), blockDim.height());
4745
4746 const quint32 copyBytes = qMin(bpl, footprint.RowPitch);
4747 const QByteArray imgData = subresDesc.data();
4748 const char *imgPtr = imgData.constData();
4749 const int rowCount = aligned(subresSize.height(), blockDim.height()) / blockDim.height();
4750 for (int y = 0; y < rowCount; ++y) {
4751 const quint64 srcOffset = quint64(y) * bpl;
4752 if (srcOffset >= quint64(imgData.size()))
4753 break;
4754 const quint32 n = quint32(qMin(quint64(copyBytes),
4755 quint64(imgData.size()) - srcOffset));
4756 memcpy(stagingAlloc.p + y * footprint.RowPitch, imgPtr + srcOffset, n);
4757 }
4758 } else if (!subresDesc.data().isEmpty()) {
4759 srcBox.left = 0;
4760 srcBox.top = 0;
4761 srcBox.right = subresSize.width();
4762 srcBox.bottom = subresSize.height();
4763 srcBox.front = 0;
4764 srcBox.back = 1;
4765
4766 footprint.Width = subresSize.width();
4767 footprint.Height = subresSize.height();
4768
4769 quint32 bpl = 0;
4770 if (subresDesc.dataStride())
4771 bpl = subresDesc.dataStride();
4772 else
4773 textureFormatInfo(texD->m_format, subresSize, &bpl, nullptr, nullptr);
4774
4775 const quint32 copyBytes = qMin(bpl, footprint.RowPitch);
4776 const QByteArray data = subresDesc.data();
4777 const char *imgPtr = data.constData();
4778 for (int y = 0, h = subresSize.height(); y < h; ++y) {
4779 // subresSize is bounded above, but copyBytes is the full bpl
4780 // for a padded dataStride(), so the last row would still read
4781 // past the data: a stride pads only *between* rows, and the
4782 // caller is not required to supply trailing padding.
4783 const quint64 srcOffset = quint64(y) * bpl;
4784 if (srcOffset >= quint64(data.size()))
4785 break;
4786 const quint32 n = quint32(qMin(quint64(copyBytes),
4787 quint64(data.size()) - srcOffset));
4788 memcpy(stagingAlloc.p + y * footprint.RowPitch, imgPtr + srcOffset, n);
4789 }
4790 }
4791
4792 src.PlacedFootprint.Footprint = footprint;
4793
4794 cbD->cmdList->CopyTextureRegion(&dst,
4795 UINT(dstPos.x()),
4796 UINT(dstPos.y()),
4797 is3D ? UINT(layer) : 0u,
4798 &src,
4799 &srcBox);
4800
4801 if (ownStagingArea.has_value())
4802 ownStagingArea->destroyWithDeferredRelease(&releaseQueue);
4803 }
4804 }
4805 }
4806 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Copy) {
4807 Q_ASSERT(u.src && u.dst);
4808 QD3D12Texture *srcD = QRHI_RES(QD3D12Texture, u.src);
4809 QD3D12Texture *dstD = QRHI_RES(QD3D12Texture, u.dst);
4810 const bool srcIs3D = srcD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4811 const bool dstIs3D = dstD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4812 QD3D12Resource *srcRes = resourcePool.lookupRef(srcD->handle);
4813 QD3D12Resource *dstRes = resourcePool.lookupRef(dstD->handle);
4814 if (!srcRes || !dstRes)
4815 continue;
4816
4817 barrierGen.addTransitionBarrier(srcD->handle, D3D12_RESOURCE_STATE_COPY_SOURCE);
4818 barrierGen.addTransitionBarrier(dstD->handle, D3D12_RESOURCE_STATE_COPY_DEST);
4819 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4820
4821 const UINT srcSubresource = calcSubresource(UINT(u.desc.sourceLevel()),
4822 srcIs3D ? 0u : UINT(u.desc.sourceLayer()),
4823 srcD->mipLevelCount);
4824 const UINT dstSubresource = calcSubresource(UINT(u.desc.destinationLevel()),
4825 dstIs3D ? 0u : UINT(u.desc.destinationLayer()),
4826 dstD->mipLevelCount);
4827 const QPoint dp = u.desc.destinationTopLeft();
4828 const QSize mipSize = q->sizeForMipLevel(u.desc.sourceLevel(), srcD->m_pixelSize);
4829 const QSize copySize = u.desc.pixelSize().isEmpty() ? mipSize : u.desc.pixelSize();
4830 const QPoint sp = u.desc.sourceTopLeft();
4831
4832 D3D12_BOX srcBox;
4833 srcBox.left = UINT(sp.x());
4834 srcBox.top = UINT(sp.y());
4835 srcBox.front = srcIs3D ? UINT(u.desc.sourceLayer()) : 0u;
4836 // back, right, bottom are exclusive
4837 srcBox.right = srcBox.left + UINT(copySize.width());
4838 srcBox.bottom = srcBox.top + UINT(copySize.height());
4839 srcBox.back = srcBox.front + 1;
4840
4841 D3D12_TEXTURE_COPY_LOCATION src;
4842 src.pResource = srcRes->resource;
4843 src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
4844 src.SubresourceIndex = srcSubresource;
4845 D3D12_TEXTURE_COPY_LOCATION dst;
4846 dst.pResource = dstRes->resource;
4847 dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
4848 dst.SubresourceIndex = dstSubresource;
4849
4850 cbD->cmdList->CopyTextureRegion(&dst,
4851 UINT(dp.x()),
4852 UINT(dp.y()),
4853 dstIs3D ? UINT(u.desc.destinationLayer()) : 0u,
4854 &src,
4855 &srcBox);
4856 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::Read) {
4857 QD3D12Readback readback;
4858 readback.frameSlot = currentFrameSlot;
4859 readback.result = u.result;
4860
4861 QD3D12ObjectHandle srcHandle;
4862 QRect rect;
4863 bool is3D = false;
4864 if (u.rb.texture()) {
4865 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.rb.texture());
4866 if (texD->sampleDesc.Count > 1) {
4867 qWarning("Multisample texture cannot be read back");
4868 continue;
4869 }
4870 is3D = texD->m_flags.testFlag(QRhiTexture::ThreeDimensional);
4871 if (u.rb.rect().isValid())
4872 rect = u.rb.rect();
4873 else
4874 rect = QRect({0, 0}, q->sizeForMipLevel(u.rb.level(), texD->m_pixelSize));
4875 readback.format = texD->m_format;
4876 srcHandle = texD->handle;
4877 } else {
4878 Q_ASSERT(currentSwapChain);
4879 if (u.rb.rect().isValid())
4880 rect = u.rb.rect();
4881 else
4882 rect = QRect({0, 0}, currentSwapChain->pixelSize);
4883 readback.format = swapchainReadbackTextureFormat(currentSwapChain->colorFormat, nullptr);
4884 if (readback.format == QRhiTexture::UnknownFormat)
4885 continue;
4886 srcHandle = currentSwapChain->colorBuffers[currentSwapChain->currentBackBufferIndex];
4887 }
4888 readback.pixelSize = rect.size();
4889
4890 textureFormatInfo(readback.format,
4891 readback.pixelSize,
4892 &readback.bytesPerLine,
4893 &readback.byteSize,
4894 nullptr);
4895
4896 QD3D12Resource *srcRes = resourcePool.lookupRef(srcHandle);
4897 if (!srcRes)
4898 continue;
4899
4900 const UINT subresource = calcSubresource(UINT(u.rb.level()),
4901 is3D ? 0u : UINT(u.rb.layer()),
4902 srcRes->desc.MipLevels);
4903 D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout;
4904 // totalBytes is what we get from D3D, with the 256 aligned stride,
4905 // readback.byteSize is the final result that's not relevant here yet
4906 UINT64 totalBytes = 0;
4907 dev->GetCopyableFootprints(&srcRes->desc, subresource, 1, 0,
4908 &layout, nullptr, nullptr, &totalBytes);
4909 readback.stagingRowPitch = layout.Footprint.RowPitch;
4910
4911 const quint32 allocSize = aligned<quint32>(totalBytes, QD3D12StagingArea::ALIGNMENT);
4912 if (!readback.staging.create(this, allocSize, D3D12_HEAP_TYPE_READBACK)) {
4913 if (u.result->completed)
4914 u.result->completed();
4915 continue;
4916 }
4917 QD3D12StagingArea::Allocation stagingAlloc = readback.staging.get(totalBytes);
4918 if (!stagingAlloc.isValid()) {
4919 readback.staging.destroy();
4920 if (u.result->completed)
4921 u.result->completed();
4922 continue;
4923 }
4924 Q_ASSERT(stagingAlloc.bufferOffset == 0);
4925
4926 barrierGen.addTransitionBarrier(srcHandle, D3D12_RESOURCE_STATE_COPY_SOURCE);
4927 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4928
4929 D3D12_TEXTURE_COPY_LOCATION dst;
4930 dst.pResource = stagingAlloc.buffer;
4931 dst.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
4932 dst.PlacedFootprint.Offset = 0;
4933 dst.PlacedFootprint.Footprint = layout.Footprint;
4934
4935 D3D12_TEXTURE_COPY_LOCATION src;
4936 src.pResource = srcRes->resource;
4937 src.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
4938 src.SubresourceIndex = subresource;
4939
4940 D3D12_BOX srcBox = {};
4941 srcBox.left = UINT(rect.left());
4942 srcBox.top = UINT(rect.top());
4943 srcBox.front = is3D ? UINT(u.rb.layer()) : 0u;
4944 // back, right, bottom are exclusive
4945 srcBox.right = srcBox.left + UINT(rect.width());
4946 srcBox.bottom = srcBox.top + UINT(rect.height());
4947 srcBox.back = srcBox.front + 1;
4948
4949 cbD->cmdList->CopyTextureRegion(&dst, 0, 0, 0, &src, &srcBox);
4950 activeReadbacks.append(readback);
4951 } else if (u.type == QRhiResourceUpdateBatchPrivate::TextureOp::GenMips) {
4952 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, u.dst);
4953 Q_ASSERT(texD->flags().testFlag(QRhiTexture::UsedWithGenerateMips));
4954
4955 // Writing the levels above 0 with unordered access views does not
4956 // initialize the subresources of a placed resource, so discard
4957 // those levels upfront, otherwise the debug layer complains once
4958 // they get sampled. Level 0 must be left alone, it has content.
4959 QD3D12Resource *res = resourcePool.lookupRef(texD->handle);
4960 if (res && texD->mipLevelCount >= 2
4961 && (res->desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET))
4962 {
4963 const bool is3D = res->desc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D;
4964 const UINT layerCount = is3D ? 1u : UINT(res->desc.DepthOrArraySize);
4965 QBitArray &initialized(texD->subresourceInitialized);
4966 QVarLengthArray<UINT, 16> subresourcesToDiscard;
4967 for (UINT layer = 0; layer < layerCount; ++layer) {
4968 for (UINT level = 1; level < texD->mipLevelCount; ++level) {
4969 const UINT subresource = calcSubresource(level, layer, texD->mipLevelCount);
4970 if (int(subresource) >= initialized.size())
4971 initialized.resize(int(subresource) + 1);
4972 if (!initialized.testBit(int(subresource))) {
4973 initialized.setBit(int(subresource));
4974 subresourcesToDiscard.append(subresource);
4975 }
4976 }
4977 }
4978 if (!subresourcesToDiscard.isEmpty()) {
4979 // Discarding needs the RENDER_TARGET state.
4980 barrierGen.addTransitionBarrier(texD->handle, D3D12_RESOURCE_STATE_RENDER_TARGET);
4981 barrierGen.enqueueBufferedTransitionBarriers(cbD);
4982 for (UINT subresource : subresourcesToDiscard) {
4983 D3D12_DISCARD_REGION region = {};
4984 region.FirstSubresource = subresource;
4985 region.NumSubresources = 1;
4986 cbD->cmdList->DiscardResource(res->resource, &region);
4987 }
4988 }
4989 }
4990
4991 if (texD->flags().testFlag(QRhiTexture::ThreeDimensional))
4992 mipmapGen3D.generate(cbD, texD->handle);
4993 else
4994 mipmapGen.generate(cbD, texD->handle);
4995 }
4996 }
4997
4998 ud->free();
4999}
5000
5001void QRhiD3D12::finishActiveReadbacks(bool forced)
5002{
5003 QVarLengthArray<std::function<void()>, 4> completedCallbacks;
5004
5005 for (int i = activeReadbacks.size() - 1; i >= 0; --i) {
5006 QD3D12Readback &readback(activeReadbacks[i]);
5007 if (forced || currentFrameSlot == readback.frameSlot || readback.frameSlot < 0) {
5008 readback.result->format = readback.format;
5009 readback.result->pixelSize = readback.pixelSize;
5010 readback.result->data.resize(int(readback.byteSize));
5011
5012 if (readback.format != QRhiTexture::UnknownFormat) {
5013 quint8 *dstPtr = reinterpret_cast<quint8 *>(readback.result->data.data());
5014 const quint8 *srcPtr = readback.staging.mem.p;
5015 const quint32 lineSize = qMin(readback.bytesPerLine, readback.stagingRowPitch);
5016 for (int y = 0, h = readback.pixelSize.height(); y < h; ++y)
5017 memcpy(dstPtr + y * readback.bytesPerLine, srcPtr + y * readback.stagingRowPitch, lineSize);
5018 } else {
5019 memcpy(readback.result->data.data(), readback.staging.mem.p, readback.byteSize);
5020 }
5021
5022 readback.staging.destroy();
5023
5024 if (readback.result->completed)
5025 completedCallbacks.append(readback.result->completed);
5026
5027 activeReadbacks.remove(i);
5028 }
5029 }
5030
5031 for (auto f : completedCallbacks)
5032 f();
5033}
5034
5035bool QRhiD3D12::ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h,
5036 D3D12_DESCRIPTOR_HEAP_TYPE type,
5037 int frameSlot,
5038 quint32 neededDescriptorCount,
5039 bool *gotNew)
5040{
5041 // Gets a new heap if needed. Note that the capacity we get is clamped
5042 // automatically (e.g. to 1 million, or 2048 for samplers), so * 2 does not
5043 // mean we can grow indefinitely, then again even using the same size would
5044 // work (because we what we are after here is a new heap for the rest of
5045 // the commands, not affecting what's already recorded).
5046 if (h->perFrameHeapSlice[frameSlot].remainingCapacity() < neededDescriptorCount) {
5047 const quint32 newPerFrameSize = qMax(h->perFrameHeapSlice[frameSlot].capacity * 2,
5048 neededDescriptorCount);
5049 QD3D12ShaderVisibleDescriptorHeap newHeap;
5050 if (!newHeap.create(dev, type, newPerFrameSize)) {
5051 qWarning("Could not create new shader-visible descriptor heap");
5052 return false;
5053 }
5054 h->destroyWithDeferredRelease(&releaseQueue);
5055 *h = newHeap;
5056 *gotNew = true;
5057 }
5058 return true;
5059}
5060
5061void QRhiD3D12::resetAndResizeSmallStagingArea(int frameSlot)
5062{
5063 QD3D12StagingArea &area(smallStagingAreas[frameSlot]);
5064
5065 // What this slot was asked for the last time it was used. Note that a
5066 // finish() in the middle of a frame rewinds the area without clearing the
5067 // counter, so with that this is an overestimate - which only means the
5068 // area may end up somewhat larger than strictly necessary.
5069 const quint32 needed = smallStagingAreaBytesNeeded[frameSlot];
5070 smallStagingAreaBytesNeeded[frameSlot] = 0;
5071
5072 quint32 newCapacity = 0;
5073 if (needed > area.capacity) {
5074 // Fell back to dedicated staging areas last time. Grow so that it
5075 // does not have to happen again.
5076 smallStagingAreaLowDemandFrames[frameSlot] = 0;
5077 newCapacity = qMin(qNextPowerOfTwo(needed), SMALL_STAGING_AREA_BYTES_PER_FRAME_MAX);
5078 } else if (needed <= area.capacity / 4 && area.capacity > SMALL_STAGING_AREA_BYTES_PER_FRAME_START) {
5079 if (++smallStagingAreaLowDemandFrames[frameSlot] >= SMALL_STAGING_AREA_LOW_DEMAND_FRAMES) {
5080 smallStagingAreaLowDemandFrames[frameSlot] = 0;
5081 newCapacity = qMax(area.capacity / 2, SMALL_STAGING_AREA_BYTES_PER_FRAME_START);
5082 }
5083 } else {
5084 smallStagingAreaLowDemandFrames[frameSlot] = 0;
5085 }
5086
5087 if (newCapacity && newCapacity != area.capacity) {
5088 QD3D12StagingArea newArea;
5089 if (newArea.create(this, aligned(newCapacity, QD3D12StagingArea::ALIGNMENT), D3D12_HEAP_TYPE_UPLOAD)) {
5090 area.destroyWithDeferredRelease(&releaseQueue);
5091 area = newArea;
5092 QString decoratedName = QLatin1String("Small staging area buffer/");
5093 decoratedName += QString::number(frameSlot);
5094 area.mem.buffer->SetName(reinterpret_cast<LPCWSTR>(decoratedName.utf16()));
5095 }
5096 // If it failed, just carry on with the existing area; the dedicated
5097 // staging area fallback keeps things working.
5098 }
5099
5100 area.head = 0;
5101}
5102
5103void QRhiD3D12::bindShaderVisibleHeaps(QD3D12CommandBuffer *cbD)
5104{
5105 ID3D12DescriptorHeap *heaps[] = {
5106 shaderVisibleCbvSrvUavHeap.heap.heap,
5107 samplerMgr.shaderVisibleSamplerHeap.heap.heap
5108 };
5109 cbD->cmdList->SetDescriptorHeaps(2, heaps);
5110}
5111
5112QD3D12Buffer::QD3D12Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size)
5113 : QRhiBuffer(rhi, type, usage, size)
5114{
5115}
5116
5117QD3D12Buffer::~QD3D12Buffer()
5118{
5119 destroy();
5120}
5121
5122void QD3D12Buffer::destroy()
5123{
5124 if (handles[0].isNull())
5125 return;
5126
5127 QRHI_RES_RHI(QRhiD3D12);
5128
5129 // destroy() implementations, unlike other functions, are expected to test
5130 // for m_rhi (rhiD) being null, to allow surviving in case one attempts to
5131 // destroy a (leaked) resource after the QRhi.
5132 //
5133 // If there is no QRhi anymore, we do not deferred-release but that's fine
5134 // since the QRhi already released everything that was in the resourcePool.
5135
5136 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
5137 if (rhiD)
5138 rhiD->releaseQueue.deferredReleaseResource(handles[i]);
5139 handles[i] = {};
5140 pendingHostWrites[i].clear();
5141 }
5142
5143 if (rhiD)
5144 rhiD->unregisterResource(this);
5145}
5146
5147bool QD3D12Buffer::create()
5148{
5149 if (!handles[0].isNull())
5150 destroy();
5151
5152 if (m_usage.testFlag(QRhiBuffer::UniformBuffer) && m_type != Dynamic) {
5153 qWarning("UniformBuffer must always be Dynamic");
5154 return false;
5155 }
5156
5157 if (m_usage.testFlag(QRhiBuffer::StorageBuffer) && m_type == Dynamic) {
5158 qWarning("StorageBuffer cannot be combined with Dynamic");
5159 return false;
5160 }
5161
5162 const quint32 nonZeroSize = m_size <= 0 ? 256 : m_size;
5163 const quint32 roundedSize = aligned(nonZeroSize, m_usage.testFlag(QRhiBuffer::UniformBuffer) ? 256u : 4u);
5164
5165 UINT resourceFlags = D3D12_RESOURCE_FLAG_NONE;
5166 if (m_usage.testFlag(QRhiBuffer::StorageBuffer))
5167 resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
5168
5169 QRHI_RES_RHI(QRhiD3D12);
5170 HRESULT hr = 0;
5171 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
5172 if (i == 0 || m_type == Dynamic) {
5173 D3D12_RESOURCE_DESC resourceDesc = {};
5174 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
5175 resourceDesc.Width = roundedSize;
5176 resourceDesc.Height = 1;
5177 resourceDesc.DepthOrArraySize = 1;
5178 resourceDesc.MipLevels = 1;
5179 resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
5180 resourceDesc.SampleDesc = { 1, 0 };
5181 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
5182 resourceDesc.Flags = D3D12_RESOURCE_FLAGS(resourceFlags);
5183 ID3D12Resource *resource = nullptr;
5184 D3D12MA::Allocation *allocation = nullptr;
5185 // Dynamic == host (CPU) visible
5186 D3D12_HEAP_TYPE heapType = m_type == Dynamic
5187 ? D3D12_HEAP_TYPE_UPLOAD
5188 : D3D12_HEAP_TYPE_DEFAULT;
5189 D3D12_RESOURCE_STATES resourceState = m_type == Dynamic
5190 ? D3D12_RESOURCE_STATE_GENERIC_READ
5191 : D3D12_RESOURCE_STATE_COMMON;
5192 hr = rhiD->vma.createResource(heapType,
5193 &resourceDesc,
5194 resourceState,
5195 nullptr,
5196 &allocation,
5197 __uuidof(resource),
5198 reinterpret_cast<void **>(&resource));
5199 if (FAILED(hr))
5200 break;
5201 if (!m_objectName.isEmpty()) {
5202 QString decoratedName = QString::fromUtf8(m_objectName);
5203 if (m_type == Dynamic) {
5204 decoratedName += QLatin1Char('/');
5205 decoratedName += QString::number(i);
5206 }
5207 resource->SetName(reinterpret_cast<LPCWSTR>(decoratedName.utf16()));
5208 }
5209 void *cpuMemPtr = nullptr;
5210 if (m_type == Dynamic) {
5211 // will be mapped for ever on the CPU, this makes future host write operations very simple
5212 hr = resource->Map(0, nullptr, &cpuMemPtr);
5213 if (FAILED(hr)) {
5214 qWarning("Map() failed to dynamic buffer");
5215 resource->Release();
5216 if (allocation)
5217 allocation->Release();
5218 break;
5219 }
5220 }
5221 handles[i] = QD3D12Resource::addToPool(&rhiD->resourcePool,
5222 resource,
5223 resourceState,
5224 allocation,
5225 cpuMemPtr);
5226 }
5227 }
5228 if (FAILED(hr)) {
5229 qWarning("Failed to create buffer: '%s' Type was %d, size was %u, using D3D12MA was %d.",
5230 qPrintable(QSystemError::windowsComString(hr)),
5231 int(m_type),
5232 roundedSize,
5233 int(rhiD->vma.isUsingD3D12MA()));
5234 return false;
5235 }
5236
5237 generation += 1;
5238 rhiD->registerResource(this);
5239 return true;
5240}
5241
5242QRhiBuffer::NativeBuffer QD3D12Buffer::nativeBuffer()
5243{
5244 NativeBuffer b;
5245 Q_ASSERT(sizeof(b.objects) / sizeof(b.objects[0]) >= size_t(QD3D12_FRAMES_IN_FLIGHT));
5246 QRHI_RES_RHI(QRhiD3D12);
5247 if (m_type == Dynamic) {
5248 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
5249 executeHostWritesForFrameSlot(i);
5250 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[i]))
5251 b.objects[i] = res->resource;
5252 else
5253 b.objects[i] = nullptr;
5254 }
5255 b.slotCount = QD3D12_FRAMES_IN_FLIGHT;
5256 return b;
5257 }
5258 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[0]))
5259 b.objects[0] = res->resource;
5260 else
5261 b.objects[0] = nullptr;
5262 b.slotCount = 1;
5263 return b;
5264}
5265
5266char *QD3D12Buffer::beginFullDynamicBufferUpdateForCurrentFrame()
5267{
5268 // Shortcut the entire buffer update mechanism and allow the client to do
5269 // the host writes directly to the buffer. This will lead to unexpected
5270 // results when combined with QRhiResourceUpdateBatch-based updates for the
5271 // buffer, but provides a fast path for dynamic buffers that have all their
5272 // content changed in every frame.
5273
5274 Q_ASSERT(m_type == Dynamic);
5275 QRHI_RES_RHI(QRhiD3D12);
5276 Q_ASSERT(rhiD->inFrame);
5277 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[rhiD->currentFrameSlot]))
5278 return static_cast<char *>(res->cpuMapPtr);
5279
5280 return nullptr;
5281}
5282
5283void QD3D12Buffer::endFullDynamicBufferUpdateForCurrentFrame()
5284{
5285 // nothing to do here
5286}
5287
5288void QD3D12Buffer::executeHostWritesForFrameSlot(int frameSlot)
5289{
5290 if (pendingHostWrites[frameSlot].isEmpty())
5291 return;
5292
5293 Q_ASSERT(m_type == QRhiBuffer::Dynamic);
5294 QRHI_RES_RHI(QRhiD3D12);
5295 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handles[frameSlot])) {
5296 Q_ASSERT(res->cpuMapPtr);
5297 for (const QD3D12Buffer::HostWrite &u : std::as_const(pendingHostWrites[frameSlot]))
5298 memcpy(static_cast<char *>(res->cpuMapPtr) + u.offset, u.data.constData(), u.data.size());
5299 }
5300 pendingHostWrites[frameSlot].clear();
5301}
5302
5303static inline DXGI_FORMAT toD3DTextureFormat(QRhiTexture::Format format, QRhiTexture::Flags flags)
5304{
5305 const bool srgb = flags.testFlag(QRhiTexture::sRGB);
5306 switch (format) {
5307 case QRhiTexture::RGBA8:
5308 return srgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
5309 case QRhiTexture::BGRA8:
5310 return srgb ? DXGI_FORMAT_B8G8R8A8_UNORM_SRGB : DXGI_FORMAT_B8G8R8A8_UNORM;
5311 case QRhiTexture::R8:
5312 return DXGI_FORMAT_R8_UNORM;
5313 case QRhiTexture::R8SI:
5314 return DXGI_FORMAT_R8_SINT;
5315 case QRhiTexture::R8UI:
5316 return DXGI_FORMAT_R8_UINT;
5317 case QRhiTexture::RG8:
5318 return DXGI_FORMAT_R8G8_UNORM;
5319 case QRhiTexture::R16:
5320 return DXGI_FORMAT_R16_UNORM;
5321 case QRhiTexture::RG16:
5322 return DXGI_FORMAT_R16G16_UNORM;
5323 case QRhiTexture::RED_OR_ALPHA8:
5324 return DXGI_FORMAT_R8_UNORM;
5325
5326 case QRhiTexture::RGBA16F:
5327 return DXGI_FORMAT_R16G16B16A16_FLOAT;
5328 case QRhiTexture::RGBA32F:
5329 return DXGI_FORMAT_R32G32B32A32_FLOAT;
5330 case QRhiTexture::R16F:
5331 return DXGI_FORMAT_R16_FLOAT;
5332 case QRhiTexture::R32F:
5333 return DXGI_FORMAT_R32_FLOAT;
5334
5335 case QRhiTexture::RGB10A2:
5336 return DXGI_FORMAT_R10G10B10A2_UNORM;
5337
5338 case QRhiTexture::R32SI:
5339 return DXGI_FORMAT_R32_SINT;
5340 case QRhiTexture::R32UI:
5341 return DXGI_FORMAT_R32_UINT;
5342 case QRhiTexture::RG32SI:
5343 return DXGI_FORMAT_R32G32_SINT;
5344 case QRhiTexture::RG32UI:
5345 return DXGI_FORMAT_R32G32_UINT;
5346 case QRhiTexture::RGBA32SI:
5347 return DXGI_FORMAT_R32G32B32A32_SINT;
5348 case QRhiTexture::RGBA32UI:
5349 return DXGI_FORMAT_R32G32B32A32_UINT;
5350
5351 case QRhiTexture::D16:
5352 return DXGI_FORMAT_R16_TYPELESS;
5353 case QRhiTexture::D24:
5354 return DXGI_FORMAT_R24G8_TYPELESS;
5355 case QRhiTexture::D24S8:
5356 return DXGI_FORMAT_R24G8_TYPELESS;
5357 case QRhiTexture::D32F:
5358 return DXGI_FORMAT_R32_TYPELESS;
5359 case QRhiTexture::Format::D32FS8:
5360 return DXGI_FORMAT_R32G8X24_TYPELESS;
5361
5362 case QRhiTexture::BC1:
5363 return srgb ? DXGI_FORMAT_BC1_UNORM_SRGB : DXGI_FORMAT_BC1_UNORM;
5364 case QRhiTexture::BC2:
5365 return srgb ? DXGI_FORMAT_BC2_UNORM_SRGB : DXGI_FORMAT_BC2_UNORM;
5366 case QRhiTexture::BC3:
5367 return srgb ? DXGI_FORMAT_BC3_UNORM_SRGB : DXGI_FORMAT_BC3_UNORM;
5368 case QRhiTexture::BC4:
5369 return DXGI_FORMAT_BC4_UNORM;
5370 case QRhiTexture::BC5:
5371 return DXGI_FORMAT_BC5_UNORM;
5372 case QRhiTexture::BC6H:
5373 return DXGI_FORMAT_BC6H_UF16;
5374 case QRhiTexture::BC7:
5375 return srgb ? DXGI_FORMAT_BC7_UNORM_SRGB : DXGI_FORMAT_BC7_UNORM;
5376
5377 case QRhiTexture::ETC2_RGB8:
5378 case QRhiTexture::ETC2_RGB8A1:
5379 case QRhiTexture::ETC2_RGBA8:
5380 qWarning("QRhiD3D12 does not support ETC2 textures");
5381 return DXGI_FORMAT_R8G8B8A8_UNORM;
5382
5383 case QRhiTexture::ASTC_4x4:
5384 case QRhiTexture::ASTC_5x4:
5385 case QRhiTexture::ASTC_5x5:
5386 case QRhiTexture::ASTC_6x5:
5387 case QRhiTexture::ASTC_6x6:
5388 case QRhiTexture::ASTC_8x5:
5389 case QRhiTexture::ASTC_8x6:
5390 case QRhiTexture::ASTC_8x8:
5391 case QRhiTexture::ASTC_10x5:
5392 case QRhiTexture::ASTC_10x6:
5393 case QRhiTexture::ASTC_10x8:
5394 case QRhiTexture::ASTC_10x10:
5395 case QRhiTexture::ASTC_12x10:
5396 case QRhiTexture::ASTC_12x12:
5397 qWarning("QRhiD3D12 does not support ASTC textures");
5398 return DXGI_FORMAT_R8G8B8A8_UNORM;
5399
5400 default:
5401 break;
5402 }
5403 return DXGI_FORMAT_R8G8B8A8_UNORM;
5404}
5405
5406QD3D12RenderBuffer::QD3D12RenderBuffer(QRhiImplementation *rhi,
5407 Type type,
5408 const QSize &pixelSize,
5409 int sampleCount,
5410 Flags flags,
5411 QRhiTexture::Format backingFormatHint)
5412 : QRhiRenderBuffer(rhi, type, pixelSize, sampleCount, flags, backingFormatHint)
5413{
5414}
5415
5416QD3D12RenderBuffer::~QD3D12RenderBuffer()
5417{
5418 destroy();
5419}
5420
5421void QD3D12RenderBuffer::destroy()
5422{
5423 if (handle.isNull())
5424 return;
5425
5426 QRHI_RES_RHI(QRhiD3D12);
5427 if (rhiD) {
5428 if (rtv.isValid())
5429 rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->rtvPool, rtv, 1);
5430 else if (dsv.isValid())
5431 rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->dsvPool, dsv, 1);
5432 }
5433
5434 handle = {};
5435 rtv = {};
5436 dsv = {};
5437
5438 if (rhiD)
5439 rhiD->unregisterResource(this);
5440}
5441
5442bool QD3D12RenderBuffer::create()
5443{
5444 if (!handle.isNull())
5445 destroy();
5446
5447 if (m_pixelSize.isEmpty())
5448 return false;
5449
5450 QRHI_RES_RHI(QRhiD3D12);
5451
5452 switch (m_type) {
5453 case QRhiRenderBuffer::Color:
5454 {
5455 dxgiFormat = toD3DTextureFormat(backingFormat(), {});
5456 sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
5457 D3D12_RESOURCE_DESC resourceDesc = {};
5458 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
5459 resourceDesc.Width = UINT64(m_pixelSize.width());
5460 resourceDesc.Height = UINT(m_pixelSize.height());
5461 resourceDesc.DepthOrArraySize = 1;
5462 resourceDesc.MipLevels = 1;
5463 resourceDesc.Format = dxgiFormat;
5464 resourceDesc.SampleDesc = sampleDesc;
5465 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
5466 resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
5467 D3D12_CLEAR_VALUE clearValue = {};
5468 clearValue.Format = dxgiFormat;
5469 // have a separate allocation and resource object (meaning both will need its own Release())
5470 ID3D12Resource *resource = nullptr;
5471 D3D12MA::Allocation *allocation = nullptr;
5472 HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
5473 &resourceDesc,
5474 D3D12_RESOURCE_STATE_RENDER_TARGET,
5475 &clearValue,
5476 &allocation,
5477 __uuidof(ID3D12Resource),
5478 reinterpret_cast<void **>(&resource));
5479 if (FAILED(hr)) {
5480 qWarning("Failed to create color buffer: %s", qPrintable(QSystemError::windowsComString(hr)));
5481 return false;
5482 }
5483 handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_RENDER_TARGET, allocation);
5484 rtv = rhiD->rtvPool.allocate(1);
5485 if (!rtv.isValid())
5486 return false;
5487 D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
5488 rtvDesc.Format = dxgiFormat;
5489 rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS
5490 : D3D12_RTV_DIMENSION_TEXTURE2D;
5491 rhiD->dev->CreateRenderTargetView(resource, &rtvDesc, rtv.cpuHandle);
5492 }
5493 break;
5494 case QRhiRenderBuffer::DepthStencil:
5495 {
5496 dxgiFormat = DS_FORMAT;
5497 sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
5498 D3D12_RESOURCE_DESC resourceDesc = {};
5499 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
5500 resourceDesc.Width = UINT64(m_pixelSize.width());
5501 resourceDesc.Height = UINT(m_pixelSize.height());
5502 resourceDesc.DepthOrArraySize = 1;
5503 resourceDesc.MipLevels = 1;
5504 resourceDesc.Format = dxgiFormat;
5505 resourceDesc.SampleDesc = sampleDesc;
5506 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
5507 resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
5508 if (m_flags.testFlag(UsedWithSwapChainOnly))
5509 resourceDesc.Flags |= D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;
5510 D3D12_CLEAR_VALUE clearValue = {};
5511 clearValue.Format = dxgiFormat;
5512 clearValue.DepthStencil.Depth = 1.0f;
5513 clearValue.DepthStencil.Stencil = 0;
5514 ID3D12Resource *resource = nullptr;
5515 D3D12MA::Allocation *allocation = nullptr;
5516 HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
5517 &resourceDesc,
5518 D3D12_RESOURCE_STATE_DEPTH_WRITE,
5519 &clearValue,
5520 &allocation,
5521 __uuidof(ID3D12Resource),
5522 reinterpret_cast<void **>(&resource));
5523 if (FAILED(hr)) {
5524 qWarning("Failed to create depth-stencil buffer: %s", qPrintable(QSystemError::windowsComString(hr)));
5525 return false;
5526 }
5527 handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_DEPTH_WRITE, allocation);
5528 dsv = rhiD->dsvPool.allocate(1);
5529 if (!dsv.isValid())
5530 return false;
5531 D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
5532 dsvDesc.Format = dxgiFormat;
5533 dsvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
5534 : D3D12_DSV_DIMENSION_TEXTURE2D;
5535 rhiD->dev->CreateDepthStencilView(resource, &dsvDesc, dsv.cpuHandle);
5536 }
5537 break;
5538 }
5539
5540 if (!m_objectName.isEmpty()) {
5541 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) {
5542 const QString name = QString::fromUtf8(m_objectName);
5543 res->resource->SetName(reinterpret_cast<LPCWSTR>(name.utf16()));
5544 }
5545 }
5546
5547 generation += 1;
5548 rhiD->registerResource(this);
5549 return true;
5550}
5551
5552QRhiTexture::Format QD3D12RenderBuffer::backingFormat() const
5553{
5554 if (m_backingFormatHint != QRhiTexture::UnknownFormat)
5555 return m_backingFormatHint;
5556 else
5557 return m_type == Color ? QRhiTexture::RGBA8 : QRhiTexture::UnknownFormat;
5558}
5559
5560QD3D12Texture::QD3D12Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth,
5561 int arraySize, int sampleCount, Flags flags)
5562 : QRhiTexture(rhi, format, pixelSize, depth, arraySize, sampleCount, flags)
5563{
5564}
5565
5566QD3D12Texture::~QD3D12Texture()
5567{
5568 destroy();
5569}
5570
5571void QD3D12Texture::destroy()
5572{
5573 if (handle.isNull())
5574 return;
5575
5576 QRHI_RES_RHI(QRhiD3D12);
5577 if (rhiD)
5578 rhiD->releaseQueue.deferredReleaseResourceWithViews(handle, &rhiD->cbvSrvUavPool, srv, 1);
5579
5580 handle = {};
5581 srv = {};
5582 subresourceInitialized.clear();
5583
5584 if (rhiD)
5585 rhiD->unregisterResource(this);
5586}
5587
5588static inline DXGI_FORMAT toD3DDepthTextureSRVFormat(QRhiTexture::Format format)
5589{
5590 switch (format) {
5591 case QRhiTexture::Format::D16:
5592 return DXGI_FORMAT_R16_FLOAT;
5593 case QRhiTexture::Format::D24:
5594 return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
5595 case QRhiTexture::Format::D24S8:
5596 return DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
5597 case QRhiTexture::Format::D32F:
5598 return DXGI_FORMAT_R32_FLOAT;
5599 case QRhiTexture::Format::D32FS8:
5600 return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
5601 default:
5602 break;
5603 }
5604 Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32_FLOAT);
5605}
5606
5607static inline DXGI_FORMAT toD3DDepthTextureDSVFormat(QRhiTexture::Format format)
5608{
5609 // here the result cannot be typeless
5610 switch (format) {
5611 case QRhiTexture::Format::D16:
5612 return DXGI_FORMAT_D16_UNORM;
5613 case QRhiTexture::Format::D24:
5614 return DXGI_FORMAT_D24_UNORM_S8_UINT;
5615 case QRhiTexture::Format::D24S8:
5616 return DXGI_FORMAT_D24_UNORM_S8_UINT;
5617 case QRhiTexture::Format::D32F:
5618 return DXGI_FORMAT_D32_FLOAT;
5619 case QRhiTexture::Format::D32FS8:
5620 return DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
5621 default:
5622 break;
5623 }
5624 Q_UNREACHABLE_RETURN(DXGI_FORMAT_D32_FLOAT);
5625}
5626
5627static inline bool isDepthTextureFormat(QRhiTexture::Format format)
5628{
5629 switch (format) {
5630 case QRhiTexture::Format::D16:
5631 case QRhiTexture::Format::D24:
5632 case QRhiTexture::Format::D24S8:
5633 case QRhiTexture::Format::D32F:
5634 case QRhiTexture::Format::D32FS8:
5635 return true;
5636 default:
5637 return false;
5638 }
5639}
5640
5641bool QD3D12Texture::prepareCreate(QSize *adjustedSize)
5642{
5643 if (!handle.isNull())
5644 destroy();
5645
5646 QRHI_RES_RHI(QRhiD3D12);
5647 if (!rhiD->isTextureFormatSupported(m_format, m_flags))
5648 return false;
5649
5650 const bool isDepth = isDepthTextureFormat(m_format);
5651 const bool isCube = m_flags.testFlag(CubeMap);
5652 const bool is3D = m_flags.testFlag(ThreeDimensional);
5653 const bool isArray = m_flags.testFlag(TextureArray);
5654 const bool hasMipMaps = m_flags.testFlag(MipMapped);
5655 const bool is1D = m_flags.testFlag(OneDimensional);
5656
5657 const QSize size = is1D ? QSize(qMax(1, m_pixelSize.width()), 1)
5658 : (m_pixelSize.isEmpty() ? QSize(1, 1) : m_pixelSize);
5659
5660 dxgiFormat = toD3DTextureFormat(m_format, m_flags);
5661 if (isDepth) {
5662 srvFormat = toD3DDepthTextureSRVFormat(m_format);
5663 rtFormat = toD3DDepthTextureDSVFormat(m_format);
5664 } else {
5665 srvFormat = dxgiFormat;
5666 rtFormat = dxgiFormat;
5667 }
5668 if (m_writeViewFormat.format != UnknownFormat) {
5669 if (isDepth)
5670 rtFormat = toD3DDepthTextureDSVFormat(m_writeViewFormat.format);
5671 else
5672 rtFormat = toD3DTextureFormat(m_writeViewFormat.format, m_writeViewFormat.srgb ? sRGB : Flags());
5673 }
5674 if (m_readViewFormat.format != UnknownFormat) {
5675 if (isDepth)
5676 srvFormat = toD3DDepthTextureSRVFormat(m_readViewFormat.format);
5677 else
5678 srvFormat = toD3DTextureFormat(m_readViewFormat.format, m_readViewFormat.srgb ? sRGB : Flags());
5679 }
5680
5681 mipLevelCount = uint(hasMipMaps ? rhiD->q->mipLevelsForSize(size) : 1);
5682 sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, dxgiFormat);
5683 if (sampleDesc.Count > 1) {
5684 if (isCube) {
5685 qWarning("Cubemap texture cannot be multisample");
5686 return false;
5687 }
5688 if (is3D) {
5689 qWarning("3D texture cannot be multisample");
5690 return false;
5691 }
5692 if (hasMipMaps) {
5693 qWarning("Multisample texture cannot have mipmaps");
5694 return false;
5695 }
5696 }
5697 if (isDepth && hasMipMaps) {
5698 qWarning("Depth texture cannot have mipmaps");
5699 return false;
5700 }
5701 if (isCube && is3D) {
5702 qWarning("Texture cannot be both cube and 3D");
5703 return false;
5704 }
5705 if (isArray && is3D) {
5706 qWarning("Texture cannot be both array and 3D");
5707 return false;
5708 }
5709 if (isCube && is1D) {
5710 qWarning("Texture cannot be both cube and 1D");
5711 return false;
5712 }
5713 if (is1D && is3D) {
5714 qWarning("Texture cannot be both 1D and 3D");
5715 return false;
5716 }
5717 if (m_depth > 1 && !is3D) {
5718 qWarning("Texture cannot have a depth of %d when it is not 3D", m_depth);
5719 return false;
5720 }
5721 if (m_arraySize > 0 && !isArray) {
5722 qWarning("Texture cannot have an array size of %d when it is not an array", m_arraySize);
5723 return false;
5724 }
5725 if (m_arraySize < 1 && isArray) {
5726 qWarning("Texture is an array but array size is %d", m_arraySize);
5727 return false;
5728 }
5729
5730 if (!rhiD->textureFormatInfo(m_format, size, nullptr, nullptr, nullptr))
5731 return false;
5732
5733 if (adjustedSize)
5734 *adjustedSize = size;
5735
5736 return true;
5737}
5738
5739bool QD3D12Texture::finishCreate()
5740{
5741 QRHI_RES_RHI(QRhiD3D12);
5742 const bool isCube = m_flags.testFlag(CubeMap);
5743 const bool is3D = m_flags.testFlag(ThreeDimensional);
5744 const bool isArray = m_flags.testFlag(TextureArray);
5745 const bool is1D = m_flags.testFlag(OneDimensional);
5746
5747 D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
5748 srvDesc.Format = srvFormat;
5749 srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
5750
5751 if (isCube) {
5752 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
5753 srvDesc.TextureCube.MipLevels = mipLevelCount;
5754 } else {
5755 if (is1D) {
5756 if (isArray) {
5757 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY;
5758 srvDesc.Texture1DArray.MipLevels = mipLevelCount;
5759 if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
5760 srvDesc.Texture1DArray.FirstArraySlice = UINT(m_arrayRangeStart);
5761 srvDesc.Texture1DArray.ArraySize = UINT(m_arrayRangeLength);
5762 } else {
5763 srvDesc.Texture1DArray.FirstArraySlice = 0;
5764 srvDesc.Texture1DArray.ArraySize = UINT(qMax(0, m_arraySize));
5765 }
5766 } else {
5767 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D;
5768 srvDesc.Texture1D.MipLevels = mipLevelCount;
5769 }
5770 } else if (isArray) {
5771 if (sampleDesc.Count > 1) {
5772 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY;
5773 if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
5774 srvDesc.Texture2DMSArray.FirstArraySlice = UINT(m_arrayRangeStart);
5775 srvDesc.Texture2DMSArray.ArraySize = UINT(m_arrayRangeLength);
5776 } else {
5777 srvDesc.Texture2DMSArray.FirstArraySlice = 0;
5778 srvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, m_arraySize));
5779 }
5780 } else {
5781 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
5782 srvDesc.Texture2DArray.MipLevels = mipLevelCount;
5783 if (m_arrayRangeStart >= 0 && m_arrayRangeLength >= 0) {
5784 srvDesc.Texture2DArray.FirstArraySlice = UINT(m_arrayRangeStart);
5785 srvDesc.Texture2DArray.ArraySize = UINT(m_arrayRangeLength);
5786 } else {
5787 srvDesc.Texture2DArray.FirstArraySlice = 0;
5788 srvDesc.Texture2DArray.ArraySize = UINT(qMax(0, m_arraySize));
5789 }
5790 }
5791 } else {
5792 if (sampleDesc.Count > 1) {
5793 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
5794 } else if (is3D) {
5795 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
5796 srvDesc.Texture3D.MipLevels = mipLevelCount;
5797 } else {
5798 srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
5799 srvDesc.Texture2D.MipLevels = mipLevelCount;
5800 }
5801 }
5802 }
5803
5804 srv = rhiD->cbvSrvUavPool.allocate(1);
5805 if (!srv.isValid())
5806 return false;
5807
5808 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle)) {
5809 rhiD->dev->CreateShaderResourceView(res->resource, &srvDesc, srv.cpuHandle);
5810 if (!m_objectName.isEmpty()) {
5811 const QString name = QString::fromUtf8(m_objectName);
5812 res->resource->SetName(reinterpret_cast<LPCWSTR>(name.utf16()));
5813 }
5814 } else {
5815 return false;
5816 }
5817
5818 generation += 1;
5819 return true;
5820}
5821
5822bool QD3D12Texture::create()
5823{
5824 QSize size;
5825 if (!prepareCreate(&size))
5826 return false;
5827
5828 const bool isDepth = isDepthTextureFormat(m_format);
5829 const bool isCube = m_flags.testFlag(CubeMap);
5830 const bool is3D = m_flags.testFlag(ThreeDimensional);
5831 const bool isArray = m_flags.testFlag(TextureArray);
5832 const bool is1D = m_flags.testFlag(OneDimensional);
5833
5834 QRHI_RES_RHI(QRhiD3D12);
5835
5836 bool needsOptimizedClearValueSpecified = false;
5837 UINT resourceFlags = 0;
5838 if (m_flags.testFlag(RenderTarget) || sampleDesc.Count > 1) {
5839 if (isDepth)
5840 resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
5841 else
5842 resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
5843 needsOptimizedClearValueSpecified = true;
5844 }
5845 if (m_flags.testFlag(UsedWithGenerateMips)) {
5846 if (isDepth) {
5847 qWarning("Depth texture cannot have mipmaps generated");
5848 return false;
5849 }
5850 resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
5851 }
5852 if (m_flags.testFlag(UsedWithLoadStore))
5853 resourceFlags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
5854
5855 D3D12_RESOURCE_DESC resourceDesc = {};
5856 resourceDesc.Dimension = is1D ? D3D12_RESOURCE_DIMENSION_TEXTURE1D
5857 : (is3D ? D3D12_RESOURCE_DIMENSION_TEXTURE3D
5858 : D3D12_RESOURCE_DIMENSION_TEXTURE2D);
5859 resourceDesc.Width = UINT64(size.width());
5860 resourceDesc.Height = UINT(size.height());
5861 resourceDesc.DepthOrArraySize = isCube ? 6
5862 : (isArray ? UINT(qMax(0, m_arraySize))
5863 : (is3D ? qMax(1, m_depth)
5864 : 1));
5865 resourceDesc.MipLevels = mipLevelCount;
5866 resourceDesc.Format = dxgiFormat;
5867 resourceDesc.SampleDesc = sampleDesc;
5868 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
5869 resourceDesc.Flags = D3D12_RESOURCE_FLAGS(resourceFlags);
5870 D3D12_CLEAR_VALUE clearValue = {};
5871 clearValue.Format = dxgiFormat;
5872 if (isDepth) {
5873 clearValue.Format = toD3DDepthTextureDSVFormat(m_format);
5874 clearValue.DepthStencil.Depth = 1.0f;
5875 clearValue.DepthStencil.Stencil = 0;
5876 }
5877 ID3D12Resource *resource = nullptr;
5878 D3D12MA::Allocation *allocation = nullptr;
5879 HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
5880 &resourceDesc,
5881 D3D12_RESOURCE_STATE_COMMON,
5882 needsOptimizedClearValueSpecified ? &clearValue : nullptr,
5883 &allocation,
5884 __uuidof(ID3D12Resource),
5885 reinterpret_cast<void **>(&resource));
5886 if (FAILED(hr)) {
5887 qWarning("Failed to create texture: '%s'"
5888 " Dim was %d Size was %ux%u Depth/ArraySize was %u MipLevels was %u Format was %d Sample count was %d",
5889 qPrintable(QSystemError::windowsComString(hr)),
5890 int(resourceDesc.Dimension),
5891 uint(resourceDesc.Width),
5892 uint(resourceDesc.Height),
5893 uint(resourceDesc.DepthOrArraySize),
5894 uint(resourceDesc.MipLevels),
5895 int(resourceDesc.Format),
5896 int(resourceDesc.SampleDesc.Count));
5897 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
5898 rhiD->deviceLost = true;
5899 return false;
5900 }
5901
5902 handle = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_COMMON, allocation);
5903
5904 if (!finishCreate())
5905 return false;
5906
5907 rhiD->registerResource(this);
5908 return true;
5909}
5910
5911bool QD3D12Texture::createFrom(QRhiTexture::NativeTexture src)
5912{
5913 if (!src.object)
5914 return false;
5915
5916 if (!prepareCreate())
5917 return false;
5918
5919 ID3D12Resource *resource = reinterpret_cast<ID3D12Resource *>(src.object);
5920 D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATES(src.layout);
5921
5922 QRHI_RES_RHI(QRhiD3D12);
5923 handle = QD3D12Resource::addNonOwningToPool(&rhiD->resourcePool, resource, state);
5924
5925 if (!finishCreate())
5926 return false;
5927
5928 rhiD->registerResource(this);
5929 return true;
5930}
5931
5932QRhiTexture::NativeTexture QD3D12Texture::nativeTexture()
5933{
5934 QRHI_RES_RHI(QRhiD3D12);
5935 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle))
5936 return { quint64(res->resource), int(res->state) };
5937
5938 return {};
5939}
5940
5941void QD3D12Texture::setNativeLayout(int layout)
5942{
5943 QRHI_RES_RHI(QRhiD3D12);
5944 if (QD3D12Resource *res = rhiD->resourcePool.lookupRef(handle))
5945 res->state = D3D12_RESOURCE_STATES(layout);
5946}
5947
5948QD3D12Sampler::QD3D12Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode,
5949 AddressMode u, AddressMode v, AddressMode w)
5950 : QRhiSampler(rhi, magFilter, minFilter, mipmapMode, u, v, w)
5951{
5952}
5953
5954QD3D12Sampler::~QD3D12Sampler()
5955{
5956 destroy();
5957}
5958
5959void QD3D12Sampler::destroy()
5960{
5961 shaderVisibleDescriptor = {};
5962
5963 QRHI_RES_RHI(QRhiD3D12);
5964 if (rhiD)
5965 rhiD->unregisterResource(this);
5966}
5967
5968static inline D3D12_FILTER toD3DFilter(QRhiSampler::Filter minFilter, QRhiSampler::Filter magFilter, QRhiSampler::Filter mipFilter)
5969{
5970 if (minFilter == QRhiSampler::Nearest) {
5971 if (magFilter == QRhiSampler::Nearest) {
5972 if (mipFilter == QRhiSampler::Linear)
5973 return D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR;
5974 else
5975 return D3D12_FILTER_MIN_MAG_MIP_POINT;
5976 } else {
5977 if (mipFilter == QRhiSampler::Linear)
5978 return D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR;
5979 else
5980 return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT;
5981 }
5982 } else {
5983 if (magFilter == QRhiSampler::Nearest) {
5984 if (mipFilter == QRhiSampler::Linear)
5985 return D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR;
5986 else
5987 return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT;
5988 } else {
5989 if (mipFilter == QRhiSampler::Linear)
5990 return D3D12_FILTER_MIN_MAG_MIP_LINEAR;
5991 else
5992 return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT;
5993 }
5994 }
5995 Q_UNREACHABLE_RETURN(D3D12_FILTER_MIN_MAG_MIP_LINEAR);
5996}
5997
5998static inline D3D12_TEXTURE_ADDRESS_MODE toD3DAddressMode(QRhiSampler::AddressMode m)
5999{
6000 switch (m) {
6001 case QRhiSampler::Repeat:
6002 return D3D12_TEXTURE_ADDRESS_MODE_WRAP;
6003 case QRhiSampler::ClampToEdge:
6004 return D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
6005 case QRhiSampler::Mirror:
6006 return D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
6007 }
6008 Q_UNREACHABLE_RETURN(D3D12_TEXTURE_ADDRESS_MODE_CLAMP);
6009}
6010
6011static inline D3D12_COMPARISON_FUNC toD3DTextureComparisonFunc(QRhiSampler::CompareOp op)
6012{
6013 switch (op) {
6014 case QRhiSampler::Never:
6015 return D3D12_COMPARISON_FUNC_NEVER;
6016 case QRhiSampler::Less:
6017 return D3D12_COMPARISON_FUNC_LESS;
6018 case QRhiSampler::Equal:
6019 return D3D12_COMPARISON_FUNC_EQUAL;
6020 case QRhiSampler::LessOrEqual:
6021 return D3D12_COMPARISON_FUNC_LESS_EQUAL;
6022 case QRhiSampler::Greater:
6023 return D3D12_COMPARISON_FUNC_GREATER;
6024 case QRhiSampler::NotEqual:
6025 return D3D12_COMPARISON_FUNC_NOT_EQUAL;
6026 case QRhiSampler::GreaterOrEqual:
6027 return D3D12_COMPARISON_FUNC_GREATER_EQUAL;
6028 case QRhiSampler::Always:
6029 return D3D12_COMPARISON_FUNC_ALWAYS;
6030 }
6031 Q_UNREACHABLE_RETURN(D3D12_COMPARISON_FUNC_NEVER);
6032}
6033
6034bool QD3D12Sampler::create()
6035{
6036 desc = {};
6037 desc.Filter = toD3DFilter(m_minFilter, m_magFilter, m_mipmapMode);
6038 if (m_compareOp != Never)
6039 desc.Filter = D3D12_FILTER(desc.Filter | 0x80);
6040 desc.AddressU = toD3DAddressMode(m_addressU);
6041 desc.AddressV = toD3DAddressMode(m_addressV);
6042 desc.AddressW = toD3DAddressMode(m_addressW);
6043 desc.MaxAnisotropy = 1.0f;
6044 desc.ComparisonFunc = toD3DTextureComparisonFunc(m_compareOp);
6045 desc.MaxLOD = m_mipmapMode == None ? 0.0f : 10000.0f;
6046
6047 // The shader-visible descriptor is looked up lazily and depends on desc, so
6048 // drop the old one.
6049 shaderVisibleDescriptor = {};
6050
6051 generation += 1;
6052
6053 QRHI_RES_RHI(QRhiD3D12);
6054 rhiD->registerResource(this, false);
6055 return true;
6056}
6057
6058QD3D12Descriptor QD3D12Sampler::lookupOrCreateShaderVisibleDescriptor()
6059{
6060 if (!shaderVisibleDescriptor.isValid()) {
6061 QRHI_RES_RHI(QRhiD3D12);
6062 shaderVisibleDescriptor = rhiD->samplerMgr.getShaderVisibleDescriptor(desc);
6063 }
6064 return shaderVisibleDescriptor;
6065}
6066
6067QD3D12ShadingRateMap::QD3D12ShadingRateMap(QRhiImplementation *rhi)
6068 : QRhiShadingRateMap(rhi)
6069{
6070}
6071
6072QD3D12ShadingRateMap::~QD3D12ShadingRateMap()
6073{
6074 destroy();
6075}
6076
6077void QD3D12ShadingRateMap::destroy()
6078{
6079 if (handle.isNull())
6080 return;
6081
6082 handle = {};
6083}
6084
6085bool QD3D12ShadingRateMap::createFrom(QRhiTexture *src)
6086{
6087 if (!handle.isNull())
6088 destroy();
6089
6090 handle = QRHI_RES(QD3D12Texture, src)->handle;
6091
6092 return true;
6093}
6094
6095QD3D12TextureRenderTarget::QD3D12TextureRenderTarget(QRhiImplementation *rhi,
6096 const QRhiTextureRenderTargetDescription &desc,
6097 Flags flags)
6098 : QRhiTextureRenderTarget(rhi, desc, flags),
6099 d(rhi)
6100{
6101}
6102
6103QD3D12TextureRenderTarget::~QD3D12TextureRenderTarget()
6104{
6105 destroy();
6106}
6107
6108void QD3D12TextureRenderTarget::destroy()
6109{
6110 if (!rtv[0].isValid() && !dsv.isValid())
6111 return;
6112
6113 QRHI_RES_RHI(QRhiD3D12);
6114 if (dsv.isValid()) {
6115 if (ownsDsv && rhiD)
6116 rhiD->releaseQueue.deferredReleaseViews(&rhiD->dsvPool, dsv, 1);
6117 dsv = {};
6118 }
6119
6120 for (int i = 0; i < QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i) {
6121 if (rtv[i].isValid()) {
6122 if (ownsRtv[i] && rhiD)
6123 rhiD->releaseQueue.deferredReleaseViews(&rhiD->rtvPool, rtv[i], 1);
6124 rtv[i] = {};
6125 }
6126 }
6127
6128 if (rhiD)
6129 rhiD->unregisterResource(this);
6130}
6131
6132QRhiRenderPassDescriptor *QD3D12TextureRenderTarget::newCompatibleRenderPassDescriptor()
6133{
6134 // not yet built so cannot rely on data computed in create()
6135
6136 QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi);
6137
6138 rpD->colorAttachmentCount = 0;
6139 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it) {
6140 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, it->texture());
6141 QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, it->renderBuffer());
6142 if (texD)
6143 rpD->colorFormat[rpD->colorAttachmentCount] = texD->rtFormat;
6144 else if (rbD)
6145 rpD->colorFormat[rpD->colorAttachmentCount] = rbD->dxgiFormat;
6146 rpD->colorAttachmentCount += 1;
6147 }
6148
6149 rpD->hasDepthStencil = false;
6150 if (m_desc.depthStencilBuffer()) {
6151 rpD->hasDepthStencil = true;
6152 rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT;
6153 } else if (m_desc.depthTexture()) {
6154 QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, m_desc.depthTexture());
6155 rpD->hasDepthStencil = true;
6156 rpD->dsFormat = toD3DDepthTextureDSVFormat(depthTexD->format()); // cannot be a typeless format
6157 }
6158
6159 rpD->hasShadingRateMap = m_desc.shadingRateMap() != nullptr;
6160
6161 rpD->updateSerializedFormat();
6162
6163 QRHI_RES_RHI(QRhiD3D12);
6164 rhiD->registerResource(rpD);
6165 return rpD;
6166}
6167
6168bool QD3D12TextureRenderTarget::create()
6169{
6170 if (rtv[0].isValid() || dsv.isValid())
6171 destroy();
6172
6173 QRHI_RES_RHI(QRhiD3D12);
6174 Q_ASSERT(m_desc.colorAttachmentCount() > 0 || m_desc.depthTexture());
6175 Q_ASSERT(!m_desc.depthStencilBuffer() || !m_desc.depthTexture());
6176 const bool hasDepthStencil = m_desc.depthStencilBuffer() || m_desc.depthTexture();
6177 d.colorAttCount = 0;
6178 int attIndex = 0;
6179
6180 for (auto it = m_desc.cbeginColorAttachments(), itEnd = m_desc.cendColorAttachments(); it != itEnd; ++it, ++attIndex) {
6181 d.colorAttCount += 1;
6182 const QRhiColorAttachment &colorAtt(*it);
6183 QRhiTexture *texture = colorAtt.texture();
6184 QRhiRenderBuffer *rb = colorAtt.renderBuffer();
6185 Q_ASSERT(texture || rb);
6186 if (texture) {
6187 QD3D12Texture *texD = QRHI_RES(QD3D12Texture, texture);
6188 QD3D12Resource *res = rhiD->resourcePool.lookupRef(texD->handle);
6189 if (!res) {
6190 qWarning("Could not look up texture handle for render target");
6191 return false;
6192 }
6193 const bool isMultiView = it->multiViewCount() >= 2;
6194 UINT layerCount = isMultiView ? UINT(it->multiViewCount()) : 1;
6195 D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
6196 rtvDesc.Format = texD->rtFormat;
6197 if (texD->flags().testFlag(QRhiTexture::CubeMap)) {
6198 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
6199 rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
6200 rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
6201 rtvDesc.Texture2DArray.ArraySize = layerCount;
6202 } else if (texD->flags().testFlag(QRhiTexture::OneDimensional)) {
6203 if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
6204 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1DARRAY;
6205 rtvDesc.Texture1DArray.MipSlice = UINT(colorAtt.level());
6206 rtvDesc.Texture1DArray.FirstArraySlice = UINT(colorAtt.layer());
6207 rtvDesc.Texture1DArray.ArraySize = layerCount;
6208 } else {
6209 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1D;
6210 rtvDesc.Texture1D.MipSlice = UINT(colorAtt.level());
6211 }
6212 } else if (texD->flags().testFlag(QRhiTexture::TextureArray)) {
6213 if (texD->sampleDesc.Count > 1) {
6214 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY;
6215 rtvDesc.Texture2DMSArray.FirstArraySlice = UINT(colorAtt.layer());
6216 rtvDesc.Texture2DMSArray.ArraySize = layerCount;
6217 } else {
6218 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
6219 rtvDesc.Texture2DArray.MipSlice = UINT(colorAtt.level());
6220 rtvDesc.Texture2DArray.FirstArraySlice = UINT(colorAtt.layer());
6221 rtvDesc.Texture2DArray.ArraySize = layerCount;
6222 }
6223 } else if (texD->flags().testFlag(QRhiTexture::ThreeDimensional)) {
6224 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D;
6225 rtvDesc.Texture3D.MipSlice = UINT(colorAtt.level());
6226 rtvDesc.Texture3D.FirstWSlice = UINT(colorAtt.layer());
6227 rtvDesc.Texture3D.WSize = layerCount;
6228 } else {
6229 if (texD->sampleDesc.Count > 1) {
6230 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS;
6231 } else {
6232 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
6233 rtvDesc.Texture2D.MipSlice = UINT(colorAtt.level());
6234 }
6235 }
6236 rtv[attIndex] = rhiD->rtvPool.allocate(1);
6237 if (!rtv[attIndex].isValid()) {
6238 qWarning("Failed to allocate RTV for texture render target");
6239 return false;
6240 }
6241 rhiD->dev->CreateRenderTargetView(res->resource, &rtvDesc, rtv[attIndex].cpuHandle);
6242 ownsRtv[attIndex] = true;
6243 if (attIndex == 0) {
6244 d.pixelSize = rhiD->q->sizeForMipLevel(colorAtt.level(), texD->pixelSize());
6245 d.sampleCount = int(texD->sampleDesc.Count);
6246 }
6247 } else if (rb) {
6248 QD3D12RenderBuffer *rbD = QRHI_RES(QD3D12RenderBuffer, rb);
6249 ownsRtv[attIndex] = false;
6250 rtv[attIndex] = rbD->rtv;
6251 if (attIndex == 0) {
6252 d.pixelSize = rbD->pixelSize();
6253 d.sampleCount = int(rbD->sampleDesc.Count);
6254 }
6255 }
6256 }
6257
6258 d.dpr = 1;
6259
6260 if (hasDepthStencil) {
6261 if (m_desc.depthTexture()) {
6262 ownsDsv = true;
6263 QD3D12Texture *depthTexD = QRHI_RES(QD3D12Texture, m_desc.depthTexture());
6264 QD3D12Resource *res = rhiD->resourcePool.lookupRef(depthTexD->handle);
6265 if (!res) {
6266 qWarning("Could not look up depth texture handle");
6267 return false;
6268 }
6269 D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc = {};
6270 dsvDesc.Format = depthTexD->rtFormat;
6271 const bool isMultisample = depthTexD->sampleDesc.Count > 1;
6272 if (depthTexD->flags().testFlag(QRhiTexture::TextureArray)) {
6273 if (isMultisample) {
6274 dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY;
6275 if (m_desc.depthLayer() >= 0) {
6276 dsvDesc.Texture2DMSArray.FirstArraySlice = UINT(m_desc.depthLayer());
6277 dsvDesc.Texture2DMSArray.ArraySize = 1;
6278 } else if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
6279 dsvDesc.Texture2DMSArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
6280 dsvDesc.Texture2DMSArray.ArraySize = UINT(depthTexD->arrayRangeLength());
6281 } else {
6282 dsvDesc.Texture2DMSArray.FirstArraySlice = 0;
6283 dsvDesc.Texture2DMSArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
6284 }
6285 } else {
6286 dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY;
6287 if (m_desc.depthLayer() >= 0) {
6288 dsvDesc.Texture2DArray.FirstArraySlice = UINT(m_desc.depthLayer());
6289 dsvDesc.Texture2DArray.ArraySize = 1;
6290 } else if (depthTexD->arrayRangeStart() >= 0 && depthTexD->arrayRangeLength() >= 0) {
6291 dsvDesc.Texture2DArray.FirstArraySlice = UINT(depthTexD->arrayRangeStart());
6292 dsvDesc.Texture2DArray.ArraySize = UINT(depthTexD->arrayRangeLength());
6293 } else {
6294 dsvDesc.Texture2DArray.FirstArraySlice = 0;
6295 dsvDesc.Texture2DArray.ArraySize = UINT(qMax(0, depthTexD->arraySize()));
6296 }
6297 }
6298 }
6299 else {
6300 dsvDesc.ViewDimension = isMultisample ? D3D12_DSV_DIMENSION_TEXTURE2DMS
6301 : D3D12_DSV_DIMENSION_TEXTURE2D;
6302 }
6303 dsv = rhiD->dsvPool.allocate(1);
6304 if (!dsv.isValid()) {
6305 qWarning("Failed to allocate DSV for texture render target");
6306 return false;
6307 }
6308 rhiD->dev->CreateDepthStencilView(res->resource, &dsvDesc, dsv.cpuHandle);
6309 if (d.colorAttCount == 0) {
6310 d.pixelSize = depthTexD->pixelSize();
6311 d.sampleCount = int(depthTexD->sampleDesc.Count);
6312 }
6313 } else {
6314 ownsDsv = false;
6315 QD3D12RenderBuffer *depthRbD = QRHI_RES(QD3D12RenderBuffer, m_desc.depthStencilBuffer());
6316 dsv = depthRbD->dsv;
6317 if (d.colorAttCount == 0) {
6318 d.pixelSize = m_desc.depthStencilBuffer()->pixelSize();
6319 d.sampleCount = int(depthRbD->sampleDesc.Count);
6320 }
6321 }
6322 d.dsAttCount = 1;
6323 } else {
6324 d.dsAttCount = 0;
6325 }
6326
6327 D3D12_CPU_DESCRIPTOR_HANDLE nullDescHandle = { 0 };
6328 for (int i = 0; i < QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS; ++i)
6329 d.rtv[i] = i < d.colorAttCount ? rtv[i].cpuHandle : nullDescHandle;
6330 d.dsv = dsv.cpuHandle;
6331 d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
6332
6333 QRhiRenderTargetAttachmentTracker::updateResIdList<QD3D12Texture, QD3D12RenderBuffer>(m_desc, &d.currentResIdList);
6334
6335 rhiD->registerResource(this);
6336 return true;
6337}
6338
6339QSize QD3D12TextureRenderTarget::pixelSize() const
6340{
6341 if (!QRhiRenderTargetAttachmentTracker::isUpToDate<QD3D12Texture, QD3D12RenderBuffer>(m_desc, d.currentResIdList))
6342 const_cast<QD3D12TextureRenderTarget *>(this)->create();
6343
6344 return d.pixelSize;
6345}
6346
6347float QD3D12TextureRenderTarget::devicePixelRatio() const
6348{
6349 return d.dpr;
6350}
6351
6352int QD3D12TextureRenderTarget::sampleCount() const
6353{
6354 return d.sampleCount;
6355}
6356
6357QD3D12ShaderResourceBindings::QD3D12ShaderResourceBindings(QRhiImplementation *rhi)
6358 : QRhiShaderResourceBindings(rhi)
6359{
6360}
6361
6362QD3D12ShaderResourceBindings::~QD3D12ShaderResourceBindings()
6363{
6364 destroy();
6365}
6366
6367void QD3D12ShaderResourceBindings::destroy()
6368{
6369 bindingCache.reset();
6370 bindingCacheValid = false;
6371 boundResourceData.clear();
6372
6373 QRHI_RES_RHI(QRhiD3D12);
6374 if (rhiD)
6375 rhiD->unregisterResource(this);
6376}
6377
6378bool QD3D12ShaderResourceBindings::create()
6379{
6380 QRHI_RES_RHI(QRhiD3D12);
6381 if (!rhiD->sanityCheckShaderResourceBindings(this))
6382 return false;
6383
6384 rhiD->updateLayoutDesc(this);
6385
6386 boundResourceData.resize(m_bindings.count());
6387 for (BoundResourceData &bd : boundResourceData)
6388 memset(&bd, 0, sizeof(BoundResourceData));
6389
6390 bindingCache.reset();
6391 bindingCacheValid = false;
6392
6393 hasDynamicOffset = false;
6394 for (const QRhiShaderResourceBinding &b : std::as_const(m_bindings)) {
6395 const QRhiShaderResourceBinding::Data *bd = QRhiImplementation::shaderResourceBindingData(b);
6396 if (bd->type == QRhiShaderResourceBinding::UniformBuffer && bd->u.ubuf.hasDynamicOffset) {
6397 hasDynamicOffset = true;
6398 break;
6399 }
6400 }
6401
6402 // The root signature is not part of the srb. Unintuitive, but the shader
6403 // translation pipeline ties our hands: as long as the per-shader (so per
6404 // stage!) nativeResourceBindingMap exist, meaning f.ex. that a SPIR-V
6405 // combined image sampler binding X passed in here may map to the tY and sY
6406 // HLSL registers, where Y is known only once the mapping table from the
6407 // shader is looked up. Creating a root parameters at this stage is
6408 // therefore impossible.
6409
6410 generation += 1;
6411 rhiD->registerResource(this, false);
6412 return true;
6413}
6414
6415void QD3D12ShaderResourceBindings::updateResources(UpdateFlags flags)
6416{
6417 Q_UNUSED(flags);
6418
6419 Q_ASSERT(boundResourceData.count() == m_bindings.count());
6420 for (BoundResourceData &bd : boundResourceData)
6421 memset(&bd, 0, sizeof(BoundResourceData));
6422
6423 bindingCache.reset();
6424 bindingCacheValid = false;
6425
6426 generation += 1;
6427}
6428
6429// Accessing the QRhiBuffer/Texture/Sampler resources must be avoided in the
6430// callbacks; that would only be possible if the srb had those specified, and
6431// that's not required at the time of srb and pipeline create() time, and
6432// createRootSignature is called from the pipeline create().
6433
6434void QD3D12ShaderResourceBindings::visitUniformBuffer(QD3D12Stage s,
6435 const QRhiShaderResourceBinding::Data::UniformBufferData &,
6436 int shaderRegister,
6437 int)
6438{
6439 D3D12_ROOT_PARAMETER1 rootParam = {};
6440 rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
6441 rootParam.ShaderVisibility = qd3d12_stageToVisibility(s);
6442 rootParam.Descriptor.ShaderRegister = shaderRegister;
6443 rootParam.Descriptor.Flags = D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC;
6444 visitorData.cbParams[s].append(rootParam);
6445}
6446
6447void QD3D12ShaderResourceBindings::visitTextures(QD3D12Stage s,
6448 const QRhiShaderResourceBinding::TextureAndSampler *,
6449 int count,
6450 int baseShaderRegister)
6451{
6452 // An array of textures is one range of count descriptors, not count ranges
6453 // of one: the shader declares the array as a single range and D3D12
6454 // rejects the pipeline unless the range is bound in full.
6455 D3D12_DESCRIPTOR_RANGE1 range = {};
6456 range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
6457 range.NumDescriptors = UINT(count);
6458 range.BaseShaderRegister = baseShaderRegister;
6459 range.OffsetInDescriptorsFromTableStart = visitorData.currentSrvRangeOffset[s];
6460 visitorData.currentSrvRangeOffset[s] += UINT(count);
6461 visitorData.srvRanges[s].append(range);
6462 if (visitorData.srvRanges[s].count() == 1) {
6463 visitorData.srvTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
6464 visitorData.srvTables[s].ShaderVisibility = qd3d12_stageToVisibility(s);
6465 }
6466}
6467
6468void QD3D12ShaderResourceBindings::visitSamplers(QD3D12Stage s,
6469 const QRhiShaderResourceBinding::TextureAndSampler *,
6470 int count,
6471 int baseShaderRegister)
6472{
6473 // Unlike SRVs and UAVs, samplers are handled so that each binding becomes
6474 // a root parameter with its own descriptor table. An array of samplers is
6475 // one range of count descriptors within that table: shaders declare such
6476 // an array as a single range and D3D12 rejects the pipeline unless the
6477 // range is bound in full.
6478
6479 int &rangeStoreIdx(visitorData.samplerRangeHeads[s]);
6480 if (rangeStoreIdx == 16) {
6481 qWarning("Sampler binding count in QD3D12Stage %d exceeds the limit of 16, this is disallowed by QRhi", s);
6482 return;
6483 }
6484 D3D12_DESCRIPTOR_RANGE1 range = {};
6485 range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
6486 range.NumDescriptors = UINT(count);
6487 range.BaseShaderRegister = baseShaderRegister;
6488 visitorData.samplerRanges[s][rangeStoreIdx] = range;
6489 D3D12_ROOT_PARAMETER1 param = {};
6490 param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
6491 param.ShaderVisibility = qd3d12_stageToVisibility(s);
6492 param.DescriptorTable.NumDescriptorRanges = 1;
6493 param.DescriptorTable.pDescriptorRanges = &visitorData.samplerRanges[s][rangeStoreIdx];
6494 rangeStoreIdx += 1;
6495 visitorData.samplerTables[s].append(param);
6496}
6497
6498void QD3D12ShaderResourceBindings::visitStorageBuffer(QD3D12Stage s,
6499 const QRhiShaderResourceBinding::Data::StorageBufferData &,
6500 QD3D12ShaderResourceVisitor::StorageOp,
6501 int shaderRegister)
6502{
6503 D3D12_DESCRIPTOR_RANGE1 range = {};
6504 range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
6505 range.NumDescriptors = 1;
6506 range.BaseShaderRegister = shaderRegister;
6507 range.OffsetInDescriptorsFromTableStart = visitorData.currentUavRangeOffset[s];
6508 visitorData.currentUavRangeOffset[s] += 1;
6509 visitorData.uavRanges[s].append(range);
6510 if (visitorData.uavRanges[s].count() == 1) {
6511 visitorData.uavTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
6512 visitorData.uavTables[s].ShaderVisibility = qd3d12_stageToVisibility(s);
6513 }
6514}
6515
6516void QD3D12ShaderResourceBindings::visitStorageImage(QD3D12Stage s,
6517 const QRhiShaderResourceBinding::Data::StorageImageData &,
6518 QD3D12ShaderResourceVisitor::StorageOp,
6519 int shaderRegister)
6520{
6521 D3D12_DESCRIPTOR_RANGE1 range = {};
6522 range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
6523 range.NumDescriptors = 1;
6524 range.BaseShaderRegister = shaderRegister;
6525 range.OffsetInDescriptorsFromTableStart = visitorData.currentUavRangeOffset[s];
6526 visitorData.currentUavRangeOffset[s] += 1;
6527 visitorData.uavRanges[s].append(range);
6528 if (visitorData.uavRanges[s].count() == 1) {
6529 visitorData.uavTables[s].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
6530 visitorData.uavTables[s].ShaderVisibility = qd3d12_stageToVisibility(s);
6531 }
6532}
6533
6534static void qd3d12_setPushConstantInfo(QD3D12ShaderStageData *sd, const QShader &shader, const QShaderKey &key)
6535{
6536 const QList<QShaderDescription::PushConstantBlock> blocks = shader.description().pushConstantBlocks();
6537 if (blocks.isEmpty())
6538 return;
6539 sd->pushConstantRegister = shader.nativeShaderInfo(key).extraBufferBindings.value(
6540 QShaderPrivate::HlslPushConstantBufferBinding, -1);
6541 sd->pushConstantSize = quint32((blocks.first().size + 3) & ~3);
6542}
6543
6544QD3D12ObjectHandle QD3D12ShaderResourceBindings::createRootSignature(const QD3D12ShaderStageData *stageData,
6545 int stageCount)
6546{
6547 QRHI_RES_RHI(QRhiD3D12);
6548
6549 // It's not just that the root signature has to be tied to the pipeline
6550 // (cannot just freely create it like e.g. with Vulkan where one just
6551 // creates a descriptor layout 1:1 with the QRhiShaderResourceBindings'
6552 // data), due to not knowing the shader-specific resource binding mapping
6553 // tables at the point of srb creation, but each shader stage may have a
6554 // different mapping table. (ugh!)
6555 //
6556 // Hence we set up everything per-stage, even if it means the root
6557 // signature gets unnecessarily big. (note that the magic is in the
6558 // ShaderVisibility: even though the register range is the same in the
6559 // descriptor tables, the visibility is different)
6560
6561 QD3D12ShaderResourceVisitor visitor(this, stageData, stageCount);
6562
6563 visitorData = {};
6564
6565 using namespace std::placeholders;
6566 visitor.uniformBuffer = std::bind(&QD3D12ShaderResourceBindings::visitUniformBuffer, this, _1, _2, _3, _4);
6567 visitor.textures = std::bind(&QD3D12ShaderResourceBindings::visitTextures, this, _1, _2, _3, _4);
6568 visitor.samplers = std::bind(&QD3D12ShaderResourceBindings::visitSamplers, this, _1, _2, _3, _4);
6569 visitor.storageBuffer = std::bind(&QD3D12ShaderResourceBindings::visitStorageBuffer, this, _1, _2, _3, _4);
6570 visitor.storageImage = std::bind(&QD3D12ShaderResourceBindings::visitStorageImage, this, _1, _2, _3, _4);
6571
6572 visitor.visit();
6573
6574 // The maximum size of a root signature is 256 bytes, where a descriptor
6575 // table is 4, a root descriptor (e.g. CBV) is 8. We have 5 stages at most
6576 // (or 1 with compute) and a separate descriptor table for SRVs (->
6577 // textures) and UAVs (-> storage buffers and images) per stage, plus each
6578 // uniform buffer counts as a CBV in the stages it is visible.
6579 //
6580 // Due to the limited maximum size of a shader-visible sampler heap (2048)
6581 // and the potential costly switching of descriptor heaps, each sampler is
6582 // declared as a separate root parameter / descriptor table (meaning that
6583 // two samplers in the same stage are two parameters and two tables, not
6584 // just one). QRhi documents a hard limit of 16 on texture/sampler bindings
6585 // in a shader (matching D3D11), so we can hopefully get away with this.
6586 //
6587 // This means that e.g. a vertex+fragment shader with a uniform buffer
6588 // visible in both and one texture+sampler in the fragment shader would
6589 // consume 2*8 + 4 + 4 = 24 bytes. This also implies that clients
6590 // specifying the minimal stage bit mask for each entry in
6591 // QRhiShaderResourceBindings are ideal for this backend since it helps
6592 // reducing the chance of hitting the size limit.
6593
6594 QVarLengthArray<D3D12_ROOT_PARAMETER1, 4> rootParams;
6595 for (int s = 0; s < 6; ++s) {
6596 if (!visitorData.cbParams[s].isEmpty())
6597 rootParams.append(visitorData.cbParams[s].constData(), visitorData.cbParams[s].count());
6598 }
6599 for (int s = 0; s < 6; ++s) {
6600 if (!visitorData.srvRanges[s].isEmpty()) {
6601 visitorData.srvTables[s].DescriptorTable.NumDescriptorRanges = visitorData.srvRanges[s].count();
6602 visitorData.srvTables[s].DescriptorTable.pDescriptorRanges = visitorData.srvRanges[s].constData();
6603 rootParams.append(visitorData.srvTables[s]);
6604 }
6605 }
6606 for (int s = 0; s < 6; ++s) {
6607 if (!visitorData.samplerTables[s].isEmpty())
6608 rootParams.append(visitorData.samplerTables[s].constData(), visitorData.samplerTables[s].count());
6609 }
6610 for (int s = 0; s < 6; ++s) {
6611 if (!visitorData.uavRanges[s].isEmpty()) {
6612 visitorData.uavTables[s].DescriptorTable.NumDescriptorRanges = visitorData.uavRanges[s].count();
6613 visitorData.uavTables[s].DescriptorTable.pDescriptorRanges = visitorData.uavRanges[s].constData();
6614 rootParams.append(visitorData.uavTables[s]);
6615 }
6616 }
6617
6618 // Root constants go last so that the indices of everything above, which
6619 // the draw time code recomputes by walking the same order, are unaffected.
6620 pushConstantRootParamIndex = -1;
6621 UINT pushConstantReg = 0;
6622 quint32 pushConstantSize = 0;
6623 int pushConstantStages = 0;
6624 D3D12_SHADER_VISIBILITY pushConstantVis = D3D12_SHADER_VISIBILITY_ALL;
6625 for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) {
6626 const QD3D12ShaderStageData &sd(stageData[stageIdx]);
6627 if (!sd.valid || !sd.pushConstantSize || sd.pushConstantRegister < 0)
6628 continue;
6629 pushConstantReg = UINT(sd.pushConstantRegister);
6630 pushConstantSize = qMax(pushConstantSize, sd.pushConstantSize);
6631 pushConstantVis = qd3d12_stageToVisibility(sd.stage);
6632 ++pushConstantStages;
6633 }
6634 if (pushConstantSize) {
6635 // Assumes nothing else uses the push constant register (b0 as
6636 // reserved by qsb); a stage without a block would get its uniform
6637 // block there and root signature creation would fail.
6638 D3D12_ROOT_PARAMETER1 param = {};
6639 param.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
6640 param.ShaderVisibility = pushConstantStages > 1 ? D3D12_SHADER_VISIBILITY_ALL : pushConstantVis;
6641 param.Constants.ShaderRegister = pushConstantReg;
6642 param.Constants.Num32BitValues = pushConstantSize / 4;
6643 pushConstantRootParamIndex = rootParams.count();
6644 rootParams.append(param);
6645 }
6646
6647 D3D12_VERSIONED_ROOT_SIGNATURE_DESC rsDesc = {};
6648 rsDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
6649 if (!rootParams.isEmpty()) {
6650 rsDesc.Desc_1_1.NumParameters = rootParams.count();
6651 rsDesc.Desc_1_1.pParameters = rootParams.constData();
6652 }
6653
6654 UINT rsFlags = 0;
6655 for (int stageIdx = 0; stageIdx < stageCount; ++stageIdx) {
6656 if (stageData[stageIdx].valid && stageData[stageIdx].stage == VS)
6657 rsFlags |= D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
6658 }
6659 rsDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAGS(rsFlags);
6660
6661 ID3DBlob *signature = nullptr;
6662 HRESULT hr = D3D12SerializeVersionedRootSignature(&rsDesc, &signature, nullptr);
6663 if (FAILED(hr)) {
6664 qWarning("Failed to serialize root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
6665 return {};
6666 }
6667 ID3D12RootSignature *rootSig = nullptr;
6668 hr = rhiD->dev->CreateRootSignature(0,
6669 signature->GetBufferPointer(),
6670 signature->GetBufferSize(),
6671 __uuidof(ID3D12RootSignature),
6672 reinterpret_cast<void **>(&rootSig));
6673 signature->Release();
6674 if (FAILED(hr)) {
6675 qWarning("Failed to create root signature: %s", qPrintable(QSystemError::windowsComString(hr)));
6676 return {};
6677 }
6678
6679 return QD3D12RootSignature::addToPool(&rhiD->rootSignaturePool, rootSig);
6680}
6681
6682// For shader model < 6.0 we do the same as the D3D11 backend: use the old
6683// compiler (D3DCompile) to generate DXBC, just as qsb does (when -c is passed)
6684// by invoking fxc, not dxc. For SM >= 6.0 we have to use the new compiler and
6685// work with DXIL. And that involves IDxcCompiler and needs the presence of
6686// dxcompiler.dll and dxil.dll at runtime. Plus there's a chance we have
6687// ancient SDK headers when not using MSVC. So this is heavily optional,
6688// meaning support for dxc can be disabled both at build time (no dxcapi.h) and
6689// at run time (no DLLs).
6690
6691static inline void makeHlslTargetString(char target[7], const char stage[3], int version)
6692{
6693 const int smMajor = version / 10;
6694 const int smMinor = version % 10;
6695 target[0] = stage[0];
6696 target[1] = stage[1];
6697 target[2] = '_';
6698 target[3] = '0' + smMajor;
6699 target[4] = '_';
6700 target[5] = '0' + smMinor;
6701 target[6] = '\0';
6702}
6703
6704enum class HlslCompileFlag
6705{
6706 WithDebugInfo = 0x01
6707};
6708
6709static QByteArray legacyCompile(const QShaderCode &hlslSource, const char *target, int flags, QString *error)
6710{
6711 static const pD3DCompile d3dCompile = QRhiD3D::resolveD3DCompile();
6712 if (!d3dCompile) {
6713 qWarning("Unable to resolve function D3DCompile()");
6714 return QByteArray();
6715 }
6716
6717 ID3DBlob *bytecode = nullptr;
6718 ID3DBlob *errors = nullptr;
6719 UINT d3dCompileFlags = 0;
6720 if (flags & int(HlslCompileFlag::WithDebugInfo))
6721 d3dCompileFlags |= D3DCOMPILE_DEBUG;
6722
6723 HRESULT hr = d3dCompile(hlslSource.shader().constData(), SIZE_T(hlslSource.shader().size()),
6724 nullptr, nullptr, nullptr,
6725 hlslSource.entryPoint().constData(), target, d3dCompileFlags, 0, &bytecode, &errors);
6726 if (FAILED(hr) || !bytecode) {
6727 qWarning("HLSL shader compilation failed: 0x%x", uint(hr));
6728 if (errors) {
6729 *error = QString::fromUtf8(static_cast<const char *>(errors->GetBufferPointer()),
6730 int(errors->GetBufferSize()));
6731 errors->Release();
6732 }
6733 return QByteArray();
6734 }
6735
6736 QByteArray result;
6737 result.resize(int(bytecode->GetBufferSize()));
6738 memcpy(result.data(), bytecode->GetBufferPointer(), size_t(result.size()));
6739 bytecode->Release();
6740 return result;
6741}
6742
6743#ifdef QRHI_D3D12_HAS_DXC
6744
6745#ifndef DXC_CP_UTF8
6746#define DXC_CP_UTF8 65001
6747#endif
6748
6749#ifndef DXC_ARG_DEBUG
6750#define DXC_ARG_DEBUG L"-Zi"
6751#endif
6752
6753static QByteArray dxcCompile(const QShaderCode &hlslSource, const char *target, int flags, QString *error)
6754{
6755 static std::pair<IDxcCompiler *, IDxcLibrary *> dxc = QRhiD3D::createDxcCompiler();
6756 IDxcCompiler *compiler = dxc.first;
6757 if (!compiler) {
6758 qWarning("Unable to instantiate IDxcCompiler. Likely no dxcompiler.dll and dxil.dll present. "
6759 "Use windeployqt or try https://github.com/microsoft/DirectXShaderCompiler/releases");
6760 return QByteArray();
6761 }
6762 IDxcLibrary *library = dxc.second;
6763 if (!library)
6764 return QByteArray();
6765
6766 IDxcBlobEncoding *sourceBlob = nullptr;
6767 HRESULT hr = library->CreateBlobWithEncodingOnHeapCopy(hlslSource.shader().constData(),
6768 UINT32(hlslSource.shader().size()),
6769 DXC_CP_UTF8,
6770 &sourceBlob);
6771 if (FAILED(hr)) {
6772 qWarning("Failed to create source blob for dxc: 0x%x (%s)",
6773 uint(hr),
6774 qPrintable(QSystemError::windowsComString(hr)));
6775 return QByteArray();
6776 }
6777
6778 const QString entryPointStr = QString::fromLatin1(hlslSource.entryPoint());
6779 const QString targetStr = QString::fromLatin1(target);
6780
6781 QVarLengthArray<LPCWSTR, 4> argPtrs;
6782 QString debugArg;
6783 if (flags & int(HlslCompileFlag::WithDebugInfo)) {
6784 debugArg = QString::fromUtf16(reinterpret_cast<const char16_t *>(DXC_ARG_DEBUG));
6785 argPtrs.append(reinterpret_cast<LPCWSTR>(debugArg.utf16()));
6786 }
6787
6788 IDxcOperationResult *result = nullptr;
6789 hr = compiler->Compile(sourceBlob,
6790 nullptr,
6791 reinterpret_cast<LPCWSTR>(entryPointStr.utf16()),
6792 reinterpret_cast<LPCWSTR>(targetStr.utf16()),
6793 argPtrs.data(), argPtrs.count(),
6794 nullptr, 0,
6795 nullptr,
6796 &result);
6797 sourceBlob->Release();
6798 if (SUCCEEDED(hr))
6799 result->GetStatus(&hr);
6800 if (FAILED(hr)) {
6801 qWarning("HLSL shader compilation failed: 0x%x (%s)",
6802 uint(hr),
6803 qPrintable(QSystemError::windowsComString(hr)));
6804 if (result) {
6805 IDxcBlobEncoding *errorsBlob = nullptr;
6806 if (SUCCEEDED(result->GetErrorBuffer(&errorsBlob))) {
6807 if (errorsBlob) {
6808 *error = QString::fromUtf8(static_cast<const char *>(errorsBlob->GetBufferPointer()),
6809 int(errorsBlob->GetBufferSize()));
6810 errorsBlob->Release();
6811 }
6812 }
6813 }
6814 return QByteArray();
6815 }
6816
6817 IDxcBlob *bytecode = nullptr;
6818 if FAILED(result->GetResult(&bytecode)) {
6819 qWarning("No result from IDxcCompiler: 0x%x (%s)",
6820 uint(hr),
6821 qPrintable(QSystemError::windowsComString(hr)));
6822 return QByteArray();
6823 }
6824
6825 QByteArray ba;
6826 ba.resize(int(bytecode->GetBufferSize()));
6827 memcpy(ba.data(), bytecode->GetBufferPointer(), size_t(ba.size()));
6828 bytecode->Release();
6829 return ba;
6830}
6831
6832#endif // QRHI_D3D12_HAS_DXC
6833
6834static QByteArray compileHlslShaderSource(const QShader &shader,
6835 QShader::Variant shaderVariant,
6836 int flags,
6837 QString *error,
6838 QShaderKey *usedShaderKey)
6839{
6840 // look for SM 6.7, 6.6, .., 5.0
6841 const int shaderModelMax = 67;
6842 for (int sm = shaderModelMax; sm >= 50; --sm) {
6843 for (QShader::Source type : { QShader::DxilShader, QShader::DxbcShader }) {
6844 QShaderKey key = { type, sm, shaderVariant };
6845 QShaderCode intermediateBytecodeShader = shader.shader(key);
6846 if (!intermediateBytecodeShader.shader().isEmpty()) {
6847 if (usedShaderKey)
6848 *usedShaderKey = key;
6849 return intermediateBytecodeShader.shader();
6850 }
6851 }
6852 }
6853
6854 QShaderCode hlslSource;
6855 QShaderKey key;
6856 for (int sm = shaderModelMax; sm >= 50; --sm) {
6857 key = { QShader::HlslShader, sm, shaderVariant };
6858 hlslSource = shader.shader(key);
6859 if (!hlslSource.shader().isEmpty())
6860 break;
6861 }
6862
6863 if (hlslSource.shader().isEmpty()) {
6864 qWarning() << "No HLSL (shader model 6.7..5.0) code found in baked shader" << shader;
6865 return QByteArray();
6866 }
6867
6868 if (usedShaderKey)
6869 *usedShaderKey = key;
6870
6871 char target[7];
6872 switch (shader.stage()) {
6873 case QShader::VertexStage:
6874 makeHlslTargetString(target, "vs", key.sourceVersion().version());
6875 break;
6876 case QShader::TessellationControlStage:
6877 makeHlslTargetString(target, "hs", key.sourceVersion().version());
6878 break;
6879 case QShader::TessellationEvaluationStage:
6880 makeHlslTargetString(target, "ds", key.sourceVersion().version());
6881 break;
6882 case QShader::GeometryStage:
6883 makeHlslTargetString(target, "gs", key.sourceVersion().version());
6884 break;
6885 case QShader::FragmentStage:
6886 makeHlslTargetString(target, "ps", key.sourceVersion().version());
6887 break;
6888 case QShader::ComputeStage:
6889 makeHlslTargetString(target, "cs", key.sourceVersion().version());
6890 break;
6891 default:
6892 qWarning("compileHlslShaderSource: Unknown stage (%d)", int(shader.stage()));
6893 return QByteArray();
6894 }
6895
6896 if (key.sourceVersion().version() >= 60) {
6897#ifdef QRHI_D3D12_HAS_DXC
6898 return dxcCompile(hlslSource, target, flags, error);
6899#else
6900 qWarning("Attempted to runtime-compile HLSL source code for shader model >= 6.0 "
6901 "but the Qt build has no support for DXC. "
6902 "Rebuild Qt with a recent Windows SDK or switch to an MSVC build.");
6903#endif
6904 }
6905
6906 return legacyCompile(hlslSource, target, flags, error);
6907}
6908
6909static inline UINT8 toD3DColorWriteMask(QRhiGraphicsPipeline::ColorMask c)
6910{
6911 UINT8 f = 0;
6912 if (c.testFlag(QRhiGraphicsPipeline::R))
6913 f |= D3D12_COLOR_WRITE_ENABLE_RED;
6914 if (c.testFlag(QRhiGraphicsPipeline::G))
6915 f |= D3D12_COLOR_WRITE_ENABLE_GREEN;
6916 if (c.testFlag(QRhiGraphicsPipeline::B))
6917 f |= D3D12_COLOR_WRITE_ENABLE_BLUE;
6918 if (c.testFlag(QRhiGraphicsPipeline::A))
6919 f |= D3D12_COLOR_WRITE_ENABLE_ALPHA;
6920 return f;
6921}
6922
6923static inline D3D12_BLEND toD3DBlendFactor(QRhiGraphicsPipeline::BlendFactor f, bool rgb)
6924{
6925 // SrcBlendAlpha and DstBlendAlpha do not accept *_COLOR. With other APIs
6926 // this is handled internally (so that e.g. VK_BLEND_FACTOR_SRC_COLOR is
6927 // accepted and is in effect equivalent to VK_BLEND_FACTOR_SRC_ALPHA when
6928 // set as an alpha src/dest factor), but for D3D we have to take care of it
6929 // ourselves. Hence the rgb argument.
6930
6931 switch (f) {
6932 case QRhiGraphicsPipeline::Zero:
6933 return D3D12_BLEND_ZERO;
6934 case QRhiGraphicsPipeline::One:
6935 return D3D12_BLEND_ONE;
6936 case QRhiGraphicsPipeline::SrcColor:
6937 return rgb ? D3D12_BLEND_SRC_COLOR : D3D12_BLEND_SRC_ALPHA;
6938 case QRhiGraphicsPipeline::OneMinusSrcColor:
6939 return rgb ? D3D12_BLEND_INV_SRC_COLOR : D3D12_BLEND_INV_SRC_ALPHA;
6940 case QRhiGraphicsPipeline::DstColor:
6941 return rgb ? D3D12_BLEND_DEST_COLOR : D3D12_BLEND_DEST_ALPHA;
6942 case QRhiGraphicsPipeline::OneMinusDstColor:
6943 return rgb ? D3D12_BLEND_INV_DEST_COLOR : D3D12_BLEND_INV_DEST_ALPHA;
6944 case QRhiGraphicsPipeline::SrcAlpha:
6945 return D3D12_BLEND_SRC_ALPHA;
6946 case QRhiGraphicsPipeline::OneMinusSrcAlpha:
6947 return D3D12_BLEND_INV_SRC_ALPHA;
6948 case QRhiGraphicsPipeline::DstAlpha:
6949 return D3D12_BLEND_DEST_ALPHA;
6950 case QRhiGraphicsPipeline::OneMinusDstAlpha:
6951 return D3D12_BLEND_INV_DEST_ALPHA;
6952 case QRhiGraphicsPipeline::ConstantColor:
6953 case QRhiGraphicsPipeline::ConstantAlpha:
6954 return D3D12_BLEND_BLEND_FACTOR;
6955 case QRhiGraphicsPipeline::OneMinusConstantColor:
6956 case QRhiGraphicsPipeline::OneMinusConstantAlpha:
6957 return D3D12_BLEND_INV_BLEND_FACTOR;
6958 case QRhiGraphicsPipeline::SrcAlphaSaturate:
6959 return D3D12_BLEND_SRC_ALPHA_SAT;
6960 case QRhiGraphicsPipeline::Src1Color:
6961 return rgb ? D3D12_BLEND_SRC1_COLOR : D3D12_BLEND_SRC1_ALPHA;
6962 case QRhiGraphicsPipeline::OneMinusSrc1Color:
6963 return rgb ? D3D12_BLEND_INV_SRC1_COLOR : D3D12_BLEND_INV_SRC1_ALPHA;
6964 case QRhiGraphicsPipeline::Src1Alpha:
6965 return D3D12_BLEND_SRC1_ALPHA;
6966 case QRhiGraphicsPipeline::OneMinusSrc1Alpha:
6967 return D3D12_BLEND_INV_SRC1_ALPHA;
6968 }
6969 Q_UNREACHABLE_RETURN(D3D12_BLEND_ZERO);
6970}
6971
6972static inline D3D12_BLEND_OP toD3DBlendOp(QRhiGraphicsPipeline::BlendOp op)
6973{
6974 switch (op) {
6975 case QRhiGraphicsPipeline::Add:
6976 return D3D12_BLEND_OP_ADD;
6977 case QRhiGraphicsPipeline::Subtract:
6978 return D3D12_BLEND_OP_SUBTRACT;
6979 case QRhiGraphicsPipeline::ReverseSubtract:
6980 return D3D12_BLEND_OP_REV_SUBTRACT;
6981 case QRhiGraphicsPipeline::Min:
6982 return D3D12_BLEND_OP_MIN;
6983 case QRhiGraphicsPipeline::Max:
6984 return D3D12_BLEND_OP_MAX;
6985 }
6986 Q_UNREACHABLE_RETURN(D3D12_BLEND_OP_ADD);
6987}
6988
6989static inline D3D12_CULL_MODE toD3DCullMode(QRhiGraphicsPipeline::CullMode c)
6990{
6991 switch (c) {
6992 case QRhiGraphicsPipeline::None:
6993 return D3D12_CULL_MODE_NONE;
6994 case QRhiGraphicsPipeline::Front:
6995 return D3D12_CULL_MODE_FRONT;
6996 case QRhiGraphicsPipeline::Back:
6997 return D3D12_CULL_MODE_BACK;
6998 }
6999 Q_UNREACHABLE_RETURN(D3D12_CULL_MODE_NONE);
7000}
7001
7002static inline D3D12_FILL_MODE toD3DFillMode(QRhiGraphicsPipeline::PolygonMode mode)
7003{
7004 switch (mode) {
7005 case QRhiGraphicsPipeline::Fill:
7006 return D3D12_FILL_MODE_SOLID;
7007 case QRhiGraphicsPipeline::Line:
7008 return D3D12_FILL_MODE_WIREFRAME;
7009 }
7010 Q_UNREACHABLE_RETURN(D3D12_FILL_MODE_SOLID);
7011}
7012
7013static inline D3D12_COMPARISON_FUNC toD3DCompareOp(QRhiGraphicsPipeline::CompareOp op)
7014{
7015 switch (op) {
7016 case QRhiGraphicsPipeline::Never:
7017 return D3D12_COMPARISON_FUNC_NEVER;
7018 case QRhiGraphicsPipeline::Less:
7019 return D3D12_COMPARISON_FUNC_LESS;
7020 case QRhiGraphicsPipeline::Equal:
7021 return D3D12_COMPARISON_FUNC_EQUAL;
7022 case QRhiGraphicsPipeline::LessOrEqual:
7023 return D3D12_COMPARISON_FUNC_LESS_EQUAL;
7024 case QRhiGraphicsPipeline::Greater:
7025 return D3D12_COMPARISON_FUNC_GREATER;
7026 case QRhiGraphicsPipeline::NotEqual:
7027 return D3D12_COMPARISON_FUNC_NOT_EQUAL;
7028 case QRhiGraphicsPipeline::GreaterOrEqual:
7029 return D3D12_COMPARISON_FUNC_GREATER_EQUAL;
7030 case QRhiGraphicsPipeline::Always:
7031 return D3D12_COMPARISON_FUNC_ALWAYS;
7032 }
7033 Q_UNREACHABLE_RETURN(D3D12_COMPARISON_FUNC_ALWAYS);
7034}
7035
7036static inline D3D12_STENCIL_OP toD3DStencilOp(QRhiGraphicsPipeline::StencilOp op)
7037{
7038 switch (op) {
7039 case QRhiGraphicsPipeline::StencilZero:
7040 return D3D12_STENCIL_OP_ZERO;
7041 case QRhiGraphicsPipeline::Keep:
7042 return D3D12_STENCIL_OP_KEEP;
7043 case QRhiGraphicsPipeline::Replace:
7044 return D3D12_STENCIL_OP_REPLACE;
7045 case QRhiGraphicsPipeline::IncrementAndClamp:
7046 return D3D12_STENCIL_OP_INCR_SAT;
7047 case QRhiGraphicsPipeline::DecrementAndClamp:
7048 return D3D12_STENCIL_OP_DECR_SAT;
7049 case QRhiGraphicsPipeline::Invert:
7050 return D3D12_STENCIL_OP_INVERT;
7051 case QRhiGraphicsPipeline::IncrementAndWrap:
7052 return D3D12_STENCIL_OP_INCR;
7053 case QRhiGraphicsPipeline::DecrementAndWrap:
7054 return D3D12_STENCIL_OP_DECR;
7055 }
7056 Q_UNREACHABLE_RETURN(D3D12_STENCIL_OP_KEEP);
7057}
7058
7059static inline D3D12_PRIMITIVE_TOPOLOGY toD3DTopology(QRhiGraphicsPipeline::Topology t, int patchControlPointCount)
7060{
7061 switch (t) {
7062 case QRhiGraphicsPipeline::Triangles:
7063 return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
7064 case QRhiGraphicsPipeline::TriangleStrip:
7065 return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
7066 case QRhiGraphicsPipeline::TriangleFan:
7067 qWarning("Triangle fans are not supported with D3D");
7068 return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
7069 case QRhiGraphicsPipeline::Lines:
7070 return D3D_PRIMITIVE_TOPOLOGY_LINELIST;
7071 case QRhiGraphicsPipeline::LineStrip:
7072 return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
7073 case QRhiGraphicsPipeline::Points:
7074 return D3D_PRIMITIVE_TOPOLOGY_POINTLIST;
7075 case QRhiGraphicsPipeline::Patches:
7076 Q_ASSERT(patchControlPointCount >= 1 && patchControlPointCount <= 32);
7077 return D3D_PRIMITIVE_TOPOLOGY(D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST + (patchControlPointCount - 1));
7078 }
7079 Q_UNREACHABLE_RETURN(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
7080}
7081
7082static inline D3D12_PRIMITIVE_TOPOLOGY_TYPE toD3DTopologyType(QRhiGraphicsPipeline::Topology t)
7083{
7084 switch (t) {
7085 case QRhiGraphicsPipeline::Triangles:
7086 case QRhiGraphicsPipeline::TriangleStrip:
7087 case QRhiGraphicsPipeline::TriangleFan:
7088 return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
7089 case QRhiGraphicsPipeline::Lines:
7090 case QRhiGraphicsPipeline::LineStrip:
7091 return D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
7092 case QRhiGraphicsPipeline::Points:
7093 return D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT;
7094 case QRhiGraphicsPipeline::Patches:
7095 return D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
7096 }
7097 Q_UNREACHABLE_RETURN(D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE);
7098}
7099
7100static inline DXGI_FORMAT toD3DAttributeFormat(QRhiVertexInputAttribute::Format format)
7101{
7102 switch (format) {
7103 case QRhiVertexInputAttribute::Float4:
7104 return DXGI_FORMAT_R32G32B32A32_FLOAT;
7105 case QRhiVertexInputAttribute::Float3:
7106 return DXGI_FORMAT_R32G32B32_FLOAT;
7107 case QRhiVertexInputAttribute::Float2:
7108 return DXGI_FORMAT_R32G32_FLOAT;
7109 case QRhiVertexInputAttribute::Float:
7110 return DXGI_FORMAT_R32_FLOAT;
7111 case QRhiVertexInputAttribute::UNormByte4:
7112 return DXGI_FORMAT_R8G8B8A8_UNORM;
7113 case QRhiVertexInputAttribute::UNormByte2:
7114 return DXGI_FORMAT_R8G8_UNORM;
7115 case QRhiVertexInputAttribute::UNormByte:
7116 return DXGI_FORMAT_R8_UNORM;
7117 case QRhiVertexInputAttribute::UInt4:
7118 return DXGI_FORMAT_R32G32B32A32_UINT;
7119 case QRhiVertexInputAttribute::UInt3:
7120 return DXGI_FORMAT_R32G32B32_UINT;
7121 case QRhiVertexInputAttribute::UInt2:
7122 return DXGI_FORMAT_R32G32_UINT;
7123 case QRhiVertexInputAttribute::UInt:
7124 return DXGI_FORMAT_R32_UINT;
7125 case QRhiVertexInputAttribute::SInt4:
7126 return DXGI_FORMAT_R32G32B32A32_SINT;
7127 case QRhiVertexInputAttribute::SInt3:
7128 return DXGI_FORMAT_R32G32B32_SINT;
7129 case QRhiVertexInputAttribute::SInt2:
7130 return DXGI_FORMAT_R32G32_SINT;
7131 case QRhiVertexInputAttribute::SInt:
7132 return DXGI_FORMAT_R32_SINT;
7133 case QRhiVertexInputAttribute::Half4:
7134 // Note: D3D does not support half3. Pass through half3 as half4.
7135 case QRhiVertexInputAttribute::Half3:
7136 return DXGI_FORMAT_R16G16B16A16_FLOAT;
7137 case QRhiVertexInputAttribute::Half2:
7138 return DXGI_FORMAT_R16G16_FLOAT;
7139 case QRhiVertexInputAttribute::Half:
7140 return DXGI_FORMAT_R16_FLOAT;
7141 case QRhiVertexInputAttribute::UShort4:
7142 // Note: D3D does not support UShort3. Pass through UShort3 as UShort4.
7143 case QRhiVertexInputAttribute::UShort3:
7144 return DXGI_FORMAT_R16G16B16A16_UINT;
7145 case QRhiVertexInputAttribute::UShort2:
7146 return DXGI_FORMAT_R16G16_UINT;
7147 case QRhiVertexInputAttribute::UShort:
7148 return DXGI_FORMAT_R16_UINT;
7149 case QRhiVertexInputAttribute::SShort4:
7150 // Note: D3D does not support SShort3. Pass through SShort3 as SShort4.
7151 case QRhiVertexInputAttribute::SShort3:
7152 return DXGI_FORMAT_R16G16B16A16_SINT;
7153 case QRhiVertexInputAttribute::SShort2:
7154 return DXGI_FORMAT_R16G16_SINT;
7155 case QRhiVertexInputAttribute::SShort:
7156 return DXGI_FORMAT_R16_SINT;
7157 }
7158 Q_UNREACHABLE_RETURN(DXGI_FORMAT_R32G32B32A32_FLOAT);
7159}
7160
7161QD3D12GraphicsPipeline::QD3D12GraphicsPipeline(QRhiImplementation *rhi)
7162 : QRhiGraphicsPipeline(rhi)
7163{
7164}
7165
7166QD3D12GraphicsPipeline::~QD3D12GraphicsPipeline()
7167{
7168 destroy();
7169}
7170
7171void QD3D12GraphicsPipeline::destroy()
7172{
7173 if (handle.isNull())
7174 return;
7175
7176 QRHI_RES_RHI(QRhiD3D12);
7177 if (rhiD) {
7178 rhiD->releaseQueue.deferredReleasePipeline(handle);
7179 rhiD->releaseQueue.deferredReleaseRootSignature(rootSigHandle);
7180 }
7181
7182 handle = {};
7183 stageData = {};
7184
7185 if (rhiD)
7186 rhiD->unregisterResource(this);
7187}
7188
7189bool QD3D12GraphicsPipeline::create()
7190{
7191 if (!handle.isNull())
7192 destroy();
7193
7194 QRHI_RES_RHI(QRhiD3D12);
7195 if (!rhiD->sanityCheckGraphicsPipeline(this))
7196 return false;
7197
7198 rhiD->pipelineCreationStart();
7199
7200 QByteArray shaderBytecode[5];
7201 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
7202 const QD3D12Stage d3dStage = qd3d12_stage(shaderStage.type());
7203 stageData[d3dStage].valid = true;
7204 stageData[d3dStage].stage = d3dStage;
7205 auto cacheIt = rhiD->shaderBytecodeCache.data.constFind(shaderStage);
7206 if (cacheIt != rhiD->shaderBytecodeCache.data.constEnd()) {
7207 shaderBytecode[d3dStage] = cacheIt->bytecode;
7208 stageData[d3dStage].nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
7209 stageData[d3dStage].pushConstantRegister = cacheIt->pushConstantRegister;
7210 stageData[d3dStage].pushConstantSize = cacheIt->pushConstantSize;
7211 } else {
7212 QString error;
7213 QShaderKey shaderKey;
7214 int compileFlags = 0;
7215 if (m_flags.testFlag(CompileShadersWithDebugInfo))
7216 compileFlags |= int(HlslCompileFlag::WithDebugInfo);
7217 const QByteArray bytecode = compileHlslShaderSource(shaderStage.shader(),
7218 shaderStage.shaderVariant(),
7219 compileFlags,
7220 &error,
7221 &shaderKey);
7222 if (bytecode.isEmpty()) {
7223 qWarning("HLSL graphics shader compilation failed: %s", qPrintable(error));
7224 return false;
7225 }
7226
7227 shaderBytecode[d3dStage] = bytecode;
7228 stageData[d3dStage].nativeResourceBindingMap = shaderStage.shader().nativeResourceBindingMap(shaderKey);
7229 qd3d12_setPushConstantInfo(&stageData[d3dStage], shaderStage.shader(), shaderKey);
7230 rhiD->shaderBytecodeCache.insertWithCapacityLimit(shaderStage,
7231 { bytecode, stageData[d3dStage].nativeResourceBindingMap,
7232 stageData[d3dStage].pushConstantRegister,
7233 stageData[d3dStage].pushConstantSize });
7234 }
7235 }
7236
7237 QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, m_shaderResourceBindings);
7238 if (srbD) {
7239 rootSigHandle = srbD->createRootSignature(stageData.data(), 5);
7240 pushConstantRootParamIndex = srbD->pushConstantRootParamIndex;
7241 if (rootSigHandle.isNull()) {
7242 qWarning("Failed to create root signature");
7243 return false;
7244 }
7245 }
7246 ID3D12RootSignature *rootSig = nullptr;
7247 if (QD3D12RootSignature *rs = rhiD->rootSignaturePool.lookupRef(rootSigHandle))
7248 rootSig = rs->rootSig;
7249 if (!rootSig) {
7250 qWarning("Cannot create graphics pipeline state without root signature");
7251 return false;
7252 }
7253
7254 QD3D12RenderPassDescriptor *rpD = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
7255 DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
7256 if (rpD->colorAttachmentCount > 0) {
7257 format = DXGI_FORMAT(rpD->colorFormat[0]);
7258 } else if (rpD->hasDepthStencil) {
7259 format = DXGI_FORMAT(rpD->dsFormat);
7260 } else {
7261 qWarning("Cannot create graphics pipeline state without color or depthStencil format");
7262 return false;
7263 }
7264 const DXGI_SAMPLE_DESC sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, format);
7265
7266 struct {
7267 QD3D12PipelineStateSubObject<ID3D12RootSignature *, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE> rootSig;
7268 QD3D12PipelineStateSubObject<D3D12_INPUT_LAYOUT_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_INPUT_LAYOUT> inputLayout;
7269 QD3D12PipelineStateSubObject<D3D12_INDEX_BUFFER_STRIP_CUT_VALUE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_IB_STRIP_CUT_VALUE> primitiveRestartValue;
7270 QD3D12PipelineStateSubObject<D3D12_PRIMITIVE_TOPOLOGY_TYPE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PRIMITIVE_TOPOLOGY> primitiveTopology;
7271 QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS> VS;
7272 QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS> HS;
7273 QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS> DS;
7274 QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS> GS;
7275 QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS> PS;
7276 QD3D12PipelineStateSubObject<D3D12_RASTERIZER_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER> rasterizerState;
7277 QD3D12PipelineStateSubObject<D3D12_DEPTH_STENCIL_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL> depthStencilState;
7278 QD3D12PipelineStateSubObject<D3D12_BLEND_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND> blendState;
7279 QD3D12PipelineStateSubObject<D3D12_RT_FORMAT_ARRAY, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS> rtFormats;
7280 QD3D12PipelineStateSubObject<DXGI_FORMAT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT> dsFormat;
7281 QD3D12PipelineStateSubObject<DXGI_SAMPLE_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC> sampleDesc;
7282 QD3D12PipelineStateSubObject<UINT, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK> sampleMask;
7283 QD3D12PipelineStateSubObject<D3D12_VIEW_INSTANCING_DESC, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING> viewInstancingDesc;
7284 } stream;
7285
7286 stream.rootSig.object = rootSig;
7287
7288 QVarLengthArray<D3D12_INPUT_ELEMENT_DESC, 4> inputDescs;
7289 QByteArrayList matrixSliceSemantics;
7290 if (!shaderBytecode[VS].isEmpty()) {
7291 for (auto it = m_vertexInputLayout.cbeginAttributes(), itEnd = m_vertexInputLayout.cendAttributes();
7292 it != itEnd; ++it)
7293 {
7294 D3D12_INPUT_ELEMENT_DESC desc = {};
7295 // The output from SPIRV-Cross uses TEXCOORD<location> as the
7296 // semantic, except for matrices that are unrolled into consecutive
7297 // vec2/3/4s attributes and need TEXCOORD<location>_ as
7298 // SemanticName and row/column index as SemanticIndex.
7299 const int matrixSlice = it->matrixSlice();
7300 if (matrixSlice < 0) {
7301 desc.SemanticName = "TEXCOORD";
7302 desc.SemanticIndex = UINT(it->location());
7303 } else {
7304 QByteArray sem;
7305 sem.resize(16);
7306 std::snprintf(sem.data(), sem.size(), "TEXCOORD%d_", it->location() - matrixSlice);
7307 matrixSliceSemantics.append(sem);
7308 desc.SemanticName = matrixSliceSemantics.last().constData();
7309 desc.SemanticIndex = UINT(matrixSlice);
7310 }
7311 desc.Format = toD3DAttributeFormat(it->format());
7312 desc.InputSlot = UINT(it->binding());
7313 desc.AlignedByteOffset = it->offset();
7314 const QRhiVertexInputBinding *inputBinding = m_vertexInputLayout.bindingAt(it->binding());
7315 if (inputBinding->classification() == QRhiVertexInputBinding::PerInstance) {
7316 desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
7317 desc.InstanceDataStepRate = inputBinding->instanceStepRate();
7318 } else {
7319 desc.InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
7320 }
7321 inputDescs.append(desc);
7322 }
7323 }
7324
7325 stream.inputLayout.object.NumElements = inputDescs.count();
7326 stream.inputLayout.object.pInputElementDescs = inputDescs.isEmpty() ? nullptr : inputDescs.constData();
7327
7328 stream.primitiveRestartValue.object = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF;
7329
7330 stream.primitiveTopology.object = toD3DTopologyType(m_topology);
7331 topology = toD3DTopology(m_topology, m_patchControlPointCount);
7332
7333 for (const QRhiShaderStage &shaderStage : std::as_const(m_shaderStages)) {
7334 const int d3dStage = qd3d12_stage(shaderStage.type());
7335 switch (d3dStage) {
7336 case VS:
7337 stream.VS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
7338 stream.VS.object.BytecodeLength = shaderBytecode[d3dStage].size();
7339 break;
7340 case HS:
7341 stream.HS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
7342 stream.HS.object.BytecodeLength = shaderBytecode[d3dStage].size();
7343 break;
7344 case DS:
7345 stream.DS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
7346 stream.DS.object.BytecodeLength = shaderBytecode[d3dStage].size();
7347 break;
7348 case GS:
7349 stream.GS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
7350 stream.GS.object.BytecodeLength = shaderBytecode[d3dStage].size();
7351 break;
7352 case PS:
7353 stream.PS.object.pShaderBytecode = shaderBytecode[d3dStage].constData();
7354 stream.PS.object.BytecodeLength = shaderBytecode[d3dStage].size();
7355 break;
7356 default:
7357 Q_UNREACHABLE();
7358 break;
7359 }
7360 }
7361
7362 stream.rasterizerState.object.FillMode = toD3DFillMode(m_polygonMode);
7363 stream.rasterizerState.object.CullMode = toD3DCullMode(m_cullMode);
7364 stream.rasterizerState.object.FrontCounterClockwise = m_frontFace == CCW;
7365 stream.rasterizerState.object.DepthBias = m_depthBias;
7366 stream.rasterizerState.object.SlopeScaledDepthBias = m_slopeScaledDepthBias;
7367 stream.rasterizerState.object.DepthClipEnable = m_depthClamp ? FALSE : TRUE;
7368 stream.rasterizerState.object.MultisampleEnable = sampleDesc.Count > 1;
7369
7370 stream.depthStencilState.object.DepthEnable = m_depthTest;
7371 stream.depthStencilState.object.DepthWriteMask = m_depthWrite ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO;
7372 stream.depthStencilState.object.DepthFunc = toD3DCompareOp(m_depthOp);
7373 stream.depthStencilState.object.StencilEnable = m_stencilTest;
7374 if (m_stencilTest) {
7375 stream.depthStencilState.object.StencilReadMask = UINT8(m_stencilReadMask);
7376 stream.depthStencilState.object.StencilWriteMask = UINT8(m_stencilWriteMask);
7377 stream.depthStencilState.object.FrontFace.StencilFailOp = toD3DStencilOp(m_stencilFront.failOp);
7378 stream.depthStencilState.object.FrontFace.StencilDepthFailOp = toD3DStencilOp(m_stencilFront.depthFailOp);
7379 stream.depthStencilState.object.FrontFace.StencilPassOp = toD3DStencilOp(m_stencilFront.passOp);
7380 stream.depthStencilState.object.FrontFace.StencilFunc = toD3DCompareOp(m_stencilFront.compareOp);
7381 stream.depthStencilState.object.BackFace.StencilFailOp = toD3DStencilOp(m_stencilBack.failOp);
7382 stream.depthStencilState.object.BackFace.StencilDepthFailOp = toD3DStencilOp(m_stencilBack.depthFailOp);
7383 stream.depthStencilState.object.BackFace.StencilPassOp = toD3DStencilOp(m_stencilBack.passOp);
7384 stream.depthStencilState.object.BackFace.StencilFunc = toD3DCompareOp(m_stencilBack.compareOp);
7385 }
7386
7387 stream.blendState.object.IndependentBlendEnable = m_targetBlends.count() > 1;
7388 for (int i = 0, ie = m_targetBlends.count(); i != ie; ++i) {
7389 const QRhiGraphicsPipeline::TargetBlend &b(m_targetBlends[i]);
7390 D3D12_RENDER_TARGET_BLEND_DESC blend = {};
7391 blend.BlendEnable = b.enable;
7392 blend.SrcBlend = toD3DBlendFactor(b.srcColor, true);
7393 blend.DestBlend = toD3DBlendFactor(b.dstColor, true);
7394 blend.BlendOp = toD3DBlendOp(b.opColor);
7395 blend.SrcBlendAlpha = toD3DBlendFactor(b.srcAlpha, false);
7396 blend.DestBlendAlpha = toD3DBlendFactor(b.dstAlpha, false);
7397 blend.BlendOpAlpha = toD3DBlendOp(b.opAlpha);
7398 blend.RenderTargetWriteMask = toD3DColorWriteMask(b.colorWrite);
7399 stream.blendState.object.RenderTarget[i] = blend;
7400 }
7401 if (m_targetBlends.isEmpty()) {
7402 D3D12_RENDER_TARGET_BLEND_DESC blend = {};
7403 blend.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
7404 stream.blendState.object.RenderTarget[0] = blend;
7405 }
7406
7407 stream.rtFormats.object.NumRenderTargets = rpD->colorAttachmentCount;
7408 for (int i = 0; i < rpD->colorAttachmentCount; ++i)
7409 stream.rtFormats.object.RTFormats[i] = DXGI_FORMAT(rpD->colorFormat[i]);
7410
7411 stream.dsFormat.object = rpD->hasDepthStencil ? DXGI_FORMAT(rpD->dsFormat) : DXGI_FORMAT_UNKNOWN;
7412
7413 stream.sampleDesc.object = sampleDesc;
7414
7415 stream.sampleMask.object = 0xFFFFFFFF;
7416
7417 viewInstanceMask = 0;
7418 const bool isMultiView = m_multiViewCount >= 2;
7419 stream.viewInstancingDesc.object.ViewInstanceCount = isMultiView ? m_multiViewCount : 0;
7420 QVarLengthArray<D3D12_VIEW_INSTANCE_LOCATION, 4> viewInstanceLocations;
7421 if (isMultiView) {
7422 for (int i = 0; i < m_multiViewCount; ++i) {
7423 viewInstanceMask |= (1 << i);
7424 viewInstanceLocations.append({ 0, UINT(i) });
7425 }
7426 stream.viewInstancingDesc.object.pViewInstanceLocations = viewInstanceLocations.constData();
7427 }
7428
7429 const D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = { sizeof(stream), &stream };
7430
7431 QCryptographicHash keyHash(QCryptographicHash::Sha1);
7432 for (const QByteArray &bytecode : shaderBytecode)
7433 addToKey(&keyHash, bytecode);
7434 for (const D3D12_INPUT_ELEMENT_DESC &desc : inputDescs) {
7435 keyHash.addData(QByteArrayView(desc.SemanticName));
7436 addToKey(&keyHash, desc.SemanticIndex);
7437 addToKey(&keyHash, desc.Format);
7438 addToKey(&keyHash, desc.InputSlot);
7439 addToKey(&keyHash, desc.AlignedByteOffset);
7440 addToKey(&keyHash, desc.InputSlotClass);
7441 addToKey(&keyHash, desc.InstanceDataStepRate);
7442 }
7443 addToKey(&keyHash, stream.primitiveRestartValue.object);
7444 addToKey(&keyHash, stream.primitiveTopology.object);
7445 addToKey(&keyHash, stream.rasterizerState.object);
7446 addToKey(&keyHash, stream.depthStencilState.object);
7447 addToKey(&keyHash, stream.blendState.object);
7448 addToKey(&keyHash, stream.rtFormats.object);
7449 addToKey(&keyHash, stream.dsFormat.object);
7450 addToKey(&keyHash, stream.sampleDesc.object);
7451 addToKey(&keyHash, stream.sampleMask.object);
7452 addToKey(&keyHash, stream.viewInstancingDesc.object.ViewInstanceCount);
7453 addToKey(&keyHash, stream.viewInstancingDesc.object.Flags);
7454 addToKey(&keyHash, viewInstanceLocations.constData(),
7455 viewInstanceLocations.count() * sizeof(D3D12_VIEW_INSTANCE_LOCATION));
7456 // Stands in for the root signature, which is built from this plus the
7457 // shaders' native resource binding maps.
7458 addToKey(&keyHash, srbD->serializedLayoutDescription());
7459
7460 ID3D12PipelineState *pso = rhiD->loadOrCreatePipelineState(&streamDesc, keyHash.result().toHex(), "graphics");
7461 if (!pso) {
7462 rhiD->rootSignaturePool.remove(rootSigHandle);
7463 rootSigHandle = {};
7464 return false;
7465 }
7466
7467 handle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Graphics, pso);
7468
7469 rhiD->pipelineCreationEnd();
7470 generation += 1;
7471 rhiD->registerResource(this);
7472 return true;
7473}
7474
7475QD3D12ComputePipeline::QD3D12ComputePipeline(QRhiImplementation *rhi)
7476 : QRhiComputePipeline(rhi)
7477{
7478}
7479
7480QD3D12ComputePipeline::~QD3D12ComputePipeline()
7481{
7482 destroy();
7483}
7484
7485void QD3D12ComputePipeline::destroy()
7486{
7487 if (handle.isNull())
7488 return;
7489
7490 QRHI_RES_RHI(QRhiD3D12);
7491 if (rhiD) {
7492 rhiD->releaseQueue.deferredReleasePipeline(handle);
7493 rhiD->releaseQueue.deferredReleaseRootSignature(rootSigHandle);
7494 }
7495
7496 handle = {};
7497 stageData = {};
7498
7499 if (rhiD)
7500 rhiD->unregisterResource(this);
7501}
7502
7503bool QD3D12ComputePipeline::create()
7504{
7505 if (!handle.isNull())
7506 destroy();
7507
7508 QRHI_RES_RHI(QRhiD3D12);
7509 rhiD->pipelineCreationStart();
7510
7511 stageData.valid = true;
7512 stageData.stage = CS;
7513
7514 QByteArray shaderBytecode;
7515 auto cacheIt = rhiD->shaderBytecodeCache.data.constFind(m_shaderStage);
7516 if (cacheIt != rhiD->shaderBytecodeCache.data.constEnd()) {
7517 shaderBytecode = cacheIt->bytecode;
7518 stageData.nativeResourceBindingMap = cacheIt->nativeResourceBindingMap;
7519 stageData.pushConstantRegister = cacheIt->pushConstantRegister;
7520 stageData.pushConstantSize = cacheIt->pushConstantSize;
7521 } else {
7522 QString error;
7523 QShaderKey shaderKey;
7524 int compileFlags = 0;
7525 if (m_flags.testFlag(CompileShadersWithDebugInfo))
7526 compileFlags |= int(HlslCompileFlag::WithDebugInfo);
7527 const QByteArray bytecode = compileHlslShaderSource(m_shaderStage.shader(),
7528 m_shaderStage.shaderVariant(),
7529 compileFlags,
7530 &error,
7531 &shaderKey);
7532 if (bytecode.isEmpty()) {
7533 qWarning("HLSL compute shader compilation failed: %s", qPrintable(error));
7534 return false;
7535 }
7536
7537 shaderBytecode = bytecode;
7538 stageData.nativeResourceBindingMap = m_shaderStage.shader().nativeResourceBindingMap(shaderKey);
7539 qd3d12_setPushConstantInfo(&stageData, m_shaderStage.shader(), shaderKey);
7540 rhiD->shaderBytecodeCache.insertWithCapacityLimit(m_shaderStage, { bytecode,
7541 stageData.nativeResourceBindingMap,
7542 stageData.pushConstantRegister,
7543 stageData.pushConstantSize });
7544 }
7545
7546 QD3D12ShaderResourceBindings *srbD = QRHI_RES(QD3D12ShaderResourceBindings, m_shaderResourceBindings);
7547 if (srbD) {
7548 rootSigHandle = srbD->createRootSignature(&stageData, 1);
7549 pushConstantRootParamIndex = srbD->pushConstantRootParamIndex;
7550 if (rootSigHandle.isNull()) {
7551 qWarning("Failed to create root signature");
7552 return false;
7553 }
7554 }
7555 ID3D12RootSignature *rootSig = nullptr;
7556 if (QD3D12RootSignature *rs = rhiD->rootSignaturePool.lookupRef(rootSigHandle))
7557 rootSig = rs->rootSig;
7558 if (!rootSig) {
7559 qWarning("Cannot create compute pipeline state without root signature");
7560 return false;
7561 }
7562
7563 struct {
7564 QD3D12PipelineStateSubObject<ID3D12RootSignature *, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE> rootSig;
7565 QD3D12PipelineStateSubObject<D3D12_SHADER_BYTECODE, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_CS> CS;
7566 } stream;
7567 stream.rootSig.object = rootSig;
7568 stream.CS.object.pShaderBytecode = shaderBytecode.constData();
7569 stream.CS.object.BytecodeLength = shaderBytecode.size();
7570 const D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = { sizeof(stream), &stream };
7571
7572 QCryptographicHash keyHash(QCryptographicHash::Sha1);
7573 addToKey(&keyHash, shaderBytecode);
7574 addToKey(&keyHash, srbD->serializedLayoutDescription());
7575
7576 ID3D12PipelineState *pso = rhiD->loadOrCreatePipelineState(&streamDesc, keyHash.result().toHex(), "compute");
7577 if (!pso) {
7578 rhiD->rootSignaturePool.remove(rootSigHandle);
7579 rootSigHandle = {};
7580 return false;
7581 }
7582
7583 handle = QD3D12Pipeline::addToPool(&rhiD->pipelinePool, QD3D12Pipeline::Compute, pso);
7584
7585 rhiD->pipelineCreationEnd();
7586 generation += 1;
7587 rhiD->registerResource(this);
7588 return true;
7589}
7590
7591// This is a lot like in the Metal backend: we need to now the rtv and dsv
7592// formats to create a graphics pipeline, and that's exactly what our
7593// "renderpass descriptor" is going to hold.
7594QD3D12RenderPassDescriptor::QD3D12RenderPassDescriptor(QRhiImplementation *rhi)
7595 : QRhiRenderPassDescriptor(rhi)
7596{
7597 serializedFormatData.reserve(16);
7598}
7599
7600QD3D12RenderPassDescriptor::~QD3D12RenderPassDescriptor()
7601{
7602 destroy();
7603}
7604
7605void QD3D12RenderPassDescriptor::destroy()
7606{
7607 QRHI_RES_RHI(QRhiD3D12);
7608 if (rhiD)
7609 rhiD->unregisterResource(this);
7610}
7611
7612bool QD3D12RenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other) const
7613{
7614 if (!other)
7615 return false;
7616
7617 const QD3D12RenderPassDescriptor *o = QRHI_RES(const QD3D12RenderPassDescriptor, other);
7618
7619 if (colorAttachmentCount != o->colorAttachmentCount)
7620 return false;
7621
7622 if (hasDepthStencil != o->hasDepthStencil)
7623 return false;
7624
7625 for (int i = 0; i < colorAttachmentCount; ++i) {
7626 if (colorFormat[i] != o->colorFormat[i])
7627 return false;
7628 }
7629
7630 if (hasDepthStencil) {
7631 if (dsFormat != o->dsFormat)
7632 return false;
7633 }
7634
7635 if (hasShadingRateMap != o->hasShadingRateMap)
7636 return false;
7637
7638 return true;
7639}
7640
7641void QD3D12RenderPassDescriptor::updateSerializedFormat()
7642{
7643 serializedFormatData.clear();
7644 auto p = std::back_inserter(serializedFormatData);
7645
7646 *p++ = colorAttachmentCount;
7647 *p++ = hasDepthStencil;
7648 for (int i = 0; i < colorAttachmentCount; ++i)
7649 *p++ = colorFormat[i];
7650 *p++ = hasDepthStencil ? dsFormat : 0;
7651}
7652
7653QRhiRenderPassDescriptor *QD3D12RenderPassDescriptor::newCompatibleRenderPassDescriptor() const
7654{
7655 QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi);
7656 rpD->colorAttachmentCount = colorAttachmentCount;
7657 rpD->hasDepthStencil = hasDepthStencil;
7658 memcpy(rpD->colorFormat, colorFormat, sizeof(colorFormat));
7659 rpD->dsFormat = dsFormat;
7660 rpD->hasShadingRateMap = hasShadingRateMap;
7661
7662 rpD->updateSerializedFormat();
7663
7664 QRHI_RES_RHI(QRhiD3D12);
7665 rhiD->registerResource(rpD);
7666 return rpD;
7667}
7668
7669QVector<quint32> QD3D12RenderPassDescriptor::serializedFormat() const
7670{
7671 return serializedFormatData;
7672}
7673
7674QD3D12CommandBuffer::QD3D12CommandBuffer(QRhiImplementation *rhi)
7675 : QRhiCommandBuffer(rhi)
7676{
7677 resetState();
7678}
7679
7680QD3D12CommandBuffer::~QD3D12CommandBuffer()
7681{
7682 destroy();
7683}
7684
7685void QD3D12CommandBuffer::destroy()
7686{
7687 // nothing to do here, the command list is not owned by us
7688}
7689
7690const QRhiNativeHandles *QD3D12CommandBuffer::nativeHandles()
7691{
7692 nativeHandlesStruct.commandList = cmdList;
7693 return &nativeHandlesStruct;
7694}
7695
7696QD3D12SwapChainRenderTarget::QD3D12SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain)
7697 : QRhiSwapChainRenderTarget(rhi, swapchain),
7698 d(rhi)
7699{
7700}
7701
7702QD3D12SwapChainRenderTarget::~QD3D12SwapChainRenderTarget()
7703{
7704 destroy();
7705}
7706
7707void QD3D12SwapChainRenderTarget::destroy()
7708{
7709 // nothing to do here
7710}
7711
7712QSize QD3D12SwapChainRenderTarget::pixelSize() const
7713{
7714 return d.pixelSize;
7715}
7716
7717float QD3D12SwapChainRenderTarget::devicePixelRatio() const
7718{
7719 return d.dpr;
7720}
7721
7722int QD3D12SwapChainRenderTarget::sampleCount() const
7723{
7724 return d.sampleCount;
7725}
7726
7727QD3D12SwapChain::QD3D12SwapChain(QRhiImplementation *rhi)
7728 : QRhiSwapChain(rhi),
7729 rtWrapper(rhi, this),
7730 rtWrapperRight(rhi, this),
7731 cbWrapper(rhi)
7732{
7733}
7734
7735QD3D12SwapChain::~QD3D12SwapChain()
7736{
7737 destroy();
7738}
7739
7740void QD3D12SwapChain::destroy()
7741{
7742 if (!swapChain)
7743 return;
7744
7745 releaseBuffers();
7746
7747 swapChain->Release();
7748 swapChain = nullptr;
7749 sourceSwapChain1->Release();
7750 sourceSwapChain1 = nullptr;
7751
7752 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
7753 FrameResources &fr(frameRes[i]);
7754 if (fr.fence)
7755 fr.fence->Release();
7756 if (fr.fenceEvent)
7757 CloseHandle(fr.fenceEvent);
7758 if (fr.cmdList)
7759 fr.cmdList->Release();
7760 fr = {};
7761 }
7762
7763 if (dcompVisual) {
7764 dcompVisual->Release();
7765 dcompVisual = nullptr;
7766 }
7767
7768 if (dcompTarget) {
7769 dcompTarget->Release();
7770 dcompTarget = nullptr;
7771 }
7772
7773 if (frameLatencyWaitableObject) {
7774 CloseHandle(frameLatencyWaitableObject);
7775 frameLatencyWaitableObject = nullptr;
7776 }
7777
7778 QDxgiVSyncService::instance()->unregisterWindow(window);
7779
7780 QRHI_RES_RHI(QRhiD3D12);
7781 if (rhiD) {
7782 rhiD->swapchains.remove(this);
7783 rhiD->unregisterResource(this);
7784 }
7785}
7786
7787void QD3D12SwapChain::releaseBuffers()
7788{
7789 QRHI_RES_RHI(QRhiD3D12);
7790 rhiD->waitGpu();
7791 for (UINT i = 0; i < BUFFER_COUNT; ++i) {
7792 rhiD->resourcePool.remove(colorBuffers[i]);
7793 rhiD->rtvPool.release(rtvs[i], 1);
7794 if (stereo)
7795 rhiD->rtvPool.release(rtvsRight[i], 1);
7796 }
7797 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
7798 if (!msaaBuffers[i].isNull())
7799 rhiD->resourcePool.remove(msaaBuffers[i]);
7800 if (msaaRtvs[i].isValid())
7801 rhiD->rtvPool.release(msaaRtvs[i], 1);
7802 }
7803}
7804
7805void QD3D12SwapChain::waitCommandCompletionForFrameSlot(int frameSlot)
7806{
7807 FrameResources &fr(frameRes[frameSlot]);
7808 if (fr.fence->GetCompletedValue() < fr.fenceCounter) {
7809 fr.fence->SetEventOnCompletion(fr.fenceCounter, fr.fenceEvent);
7810 WaitForSingleObject(fr.fenceEvent, INFINITE);
7811 }
7812}
7813
7814void QD3D12SwapChain::addCommandCompletionSignalForCurrentFrameSlot()
7815{
7816 QRHI_RES_RHI(QRhiD3D12);
7817 FrameResources &fr(frameRes[currentFrameSlot]);
7818 fr.fenceCounter += 1u;
7819 rhiD->cmdQueue->Signal(fr.fence, fr.fenceCounter);
7820}
7821
7822QRhiCommandBuffer *QD3D12SwapChain::currentFrameCommandBuffer()
7823{
7824 return &cbWrapper;
7825}
7826
7827QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget()
7828{
7829 return &rtWrapper;
7830}
7831
7832QRhiRenderTarget *QD3D12SwapChain::currentFrameRenderTarget(StereoTargetBuffer targetBuffer)
7833{
7834 return !stereo || targetBuffer == StereoTargetBuffer::LeftBuffer ? &rtWrapper : &rtWrapperRight;
7835}
7836
7837QSize QD3D12SwapChain::surfacePixelSize()
7838{
7839 Q_ASSERT(m_window);
7840 return m_window->size() * m_window->devicePixelRatio();
7841}
7842
7843bool QD3D12SwapChain::isFormatSupported(Format f)
7844{
7845 if (f == SDR)
7846 return true;
7847
7848 if (!m_window) {
7849 qWarning("Attempted to call isFormatSupported() without a window set");
7850 return false;
7851 }
7852
7853 QRHI_RES_RHI(QRhiD3D12);
7854 if (QDxgiHdrInfo(rhiD->activeAdapter).isHdrCapable(m_window))
7855 return f == QRhiSwapChain::HDRExtendedSrgbLinear || f == QRhiSwapChain::HDR10;
7856
7857 return false;
7858}
7859
7860QRhiSwapChainHdrInfo QD3D12SwapChain::hdrInfo()
7861{
7862 QRhiSwapChainHdrInfo info = QRhiSwapChain::hdrInfo();
7863 // Must use m_window, not window, given this may be called before createOrResize().
7864 if (m_window) {
7865 QRHI_RES_RHI(QRhiD3D12);
7866 info = QDxgiHdrInfo(rhiD->activeAdapter).queryHdrInfo(m_window);
7867 }
7868 return info;
7869}
7870
7871QRhiRenderPassDescriptor *QD3D12SwapChain::newCompatibleRenderPassDescriptor()
7872{
7873 // not yet built so cannot rely on data computed in createOrResize()
7874 chooseFormats();
7875
7876 QD3D12RenderPassDescriptor *rpD = new QD3D12RenderPassDescriptor(m_rhi);
7877 rpD->colorAttachmentCount = 1;
7878 rpD->hasDepthStencil = m_depthStencil != nullptr;
7879 rpD->colorFormat[0] = int(srgbAdjustedColorFormat);
7880 rpD->dsFormat = QD3D12RenderBuffer::DS_FORMAT;
7881
7882 rpD->hasShadingRateMap = m_shadingRateMap != nullptr;
7883
7884 rpD->updateSerializedFormat();
7885
7886 QRHI_RES_RHI(QRhiD3D12);
7887 rhiD->registerResource(rpD);
7888 return rpD;
7889}
7890
7891bool QRhiD3D12::ensureDirectCompositionDevice()
7892{
7893 if (dcompDevice)
7894 return true;
7895
7896 qCDebug(QRHI_LOG_INFO, "Creating Direct Composition device (needed for semi-transparent windows)");
7897 dcompDevice = QRhiD3D::createDirectCompositionDevice();
7898 return dcompDevice ? true : false;
7899}
7900
7901static const DXGI_FORMAT DEFAULT_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
7902static const DXGI_FORMAT DEFAULT_SRGB_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
7903
7904void QD3D12SwapChain::chooseFormats()
7905{
7906 colorFormat = DEFAULT_FORMAT;
7907 srgbAdjustedColorFormat = m_flags.testFlag(sRGB) ? DEFAULT_SRGB_FORMAT : DEFAULT_FORMAT;
7908 hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709; // SDR
7909 QRHI_RES_RHI(QRhiD3D12);
7910 if (m_format != SDR) {
7911 if (QDxgiHdrInfo(rhiD->activeAdapter).isHdrCapable(m_window)) {
7912 // https://docs.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range
7913 switch (m_format) {
7914 case HDRExtendedSrgbLinear:
7915 colorFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
7916 hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709;
7917 srgbAdjustedColorFormat = colorFormat;
7918 break;
7919 case HDR10:
7920 colorFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
7921 hdrColorSpace = DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020;
7922 srgbAdjustedColorFormat = colorFormat;
7923 break;
7924 default:
7925 break;
7926 }
7927 } else {
7928 // This happens also when Use HDR is set to Off in the Windows
7929 // Display settings. Show a helpful warning, but continue with the
7930 // default non-HDR format.
7931 qWarning("The output associated with the window is not HDR capable "
7932 "(or Use HDR is Off in the Display Settings), ignoring HDR format request");
7933 }
7934 }
7935 sampleDesc = rhiD->effectiveSampleDesc(m_sampleCount, colorFormat);
7936}
7937
7938bool QD3D12SwapChain::createOrResize()
7939{
7940 // Can be called multiple times due to window resizes - that is not the
7941 // same as a simple destroy+create (as with other resources). Just need to
7942 // resize the buffers then.
7943
7944 const bool needsRegistration = !window || window != m_window;
7945
7946 // except if the window actually changes
7947 if (window && window != m_window)
7948 destroy();
7949
7950 window = m_window;
7951 m_currentPixelSize = surfacePixelSize();
7952 pixelSize = m_currentPixelSize;
7953
7954 if (pixelSize.isEmpty())
7955 return false;
7956
7957 HWND hwnd = reinterpret_cast<HWND>(window->winId());
7958 HRESULT hr;
7959 QRHI_RES_RHI(QRhiD3D12);
7960 stereo = m_window->format().stereo() && rhiD->dxgiFactory->IsWindowedStereoEnabled();
7961
7962 if (m_flags.testFlag(SurfaceHasPreMulAlpha) || m_flags.testFlag(SurfaceHasNonPreMulAlpha)) {
7963 if (rhiD->ensureDirectCompositionDevice()) {
7964 if (!dcompTarget) {
7965 hr = rhiD->dcompDevice->CreateTargetForHwnd(hwnd, false, &dcompTarget);
7966 if (FAILED(hr)) {
7967 qWarning("Failed to create Direct Composition target for the window: %s",
7968 qPrintable(QSystemError::windowsComString(hr)));
7969 }
7970 }
7971 if (dcompTarget && !dcompVisual) {
7972 hr = rhiD->dcompDevice->CreateVisual(&dcompVisual);
7973 if (FAILED(hr)) {
7974 qWarning("Failed to create DirectComposition visual: %s",
7975 qPrintable(QSystemError::windowsComString(hr)));
7976 }
7977 }
7978 }
7979 // simple consistency check
7980 if (window->requestedFormat().alphaBufferSize() <= 0)
7981 qWarning("Swapchain says surface has alpha but the window has no alphaBufferSize set. "
7982 "This may lead to problems.");
7983 }
7984
7985 swapInterval = m_flags.testFlag(QRhiSwapChain::NoVSync) ? 0 : 1;
7986 swapChainFlags = 0;
7987 if (swapInterval == 0 && rhiD->supportsAllowTearing)
7988 swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
7989
7990 // maxFrameLatency 0 means no waitable object usage.
7991 // Ignore it also when NoVSync is on, and when using WARP.
7992 const bool useFrameLatencyWaitableObject = rhiD->maxFrameLatency != 0
7993 && swapInterval != 0
7994 && rhiD->driverInfoStruct.deviceType != QRhiDriverInfo::CpuDevice;
7995 if (useFrameLatencyWaitableObject)
7996 swapChainFlags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
7997
7998 if (!swapChain) {
7999 chooseFormats();
8000
8001 DXGI_SWAP_CHAIN_DESC1 desc = {};
8002 desc.Width = UINT(pixelSize.width());
8003 desc.Height = UINT(pixelSize.height());
8004 desc.Format = colorFormat;
8005 desc.SampleDesc.Count = 1;
8006 desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
8007 desc.BufferCount = BUFFER_COUNT;
8008 desc.Flags = swapChainFlags;
8009 desc.Scaling = DXGI_SCALING_NONE;
8010 desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
8011 desc.Stereo = stereo;
8012
8013 if (dcompVisual) {
8014 // With DirectComposition setting AlphaMode to STRAIGHT fails the
8015 // swapchain creation, whereas the result seems to be identical
8016 // with any of the other values, including IGNORE. (?)
8017 desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
8018
8019 // DirectComposition has its own limitations, cannot use
8020 // SCALING_NONE. So with semi-transparency requested we are forced
8021 // to SCALING_STRETCH.
8022 desc.Scaling = DXGI_SCALING_STRETCH;
8023 }
8024
8025 if (dcompVisual)
8026 hr = rhiD->dxgiFactory->CreateSwapChainForComposition(rhiD->cmdQueue, &desc, nullptr, &sourceSwapChain1);
8027 else
8028 hr = rhiD->dxgiFactory->CreateSwapChainForHwnd(rhiD->cmdQueue, hwnd, &desc, nullptr, nullptr, &sourceSwapChain1);
8029
8030 // If failed and we tried a HDR format, then try with SDR. This
8031 // matches other backends, such as Vulkan where if the format is
8032 // not supported, the default one is used instead.
8033 if (FAILED(hr) && m_format != SDR) {
8034 colorFormat = DEFAULT_FORMAT;
8035 desc.Format = DEFAULT_FORMAT;
8036 if (dcompVisual)
8037 hr = rhiD->dxgiFactory->CreateSwapChainForComposition(rhiD->cmdQueue, &desc, nullptr, &sourceSwapChain1);
8038 else
8039 hr = rhiD->dxgiFactory->CreateSwapChainForHwnd(rhiD->cmdQueue, hwnd, &desc, nullptr, nullptr, &sourceSwapChain1);
8040 }
8041
8042 if (SUCCEEDED(hr)) {
8043 if (FAILED(sourceSwapChain1->QueryInterface(__uuidof(IDXGISwapChain3), reinterpret_cast<void **>(&swapChain)))) {
8044 qWarning("IDXGISwapChain3 not available");
8045 return false;
8046 }
8047 if (m_format != SDR) {
8048 hr = swapChain->SetColorSpace1(hdrColorSpace);
8049 if (FAILED(hr)) {
8050 qWarning("Failed to set color space on swapchain: %s",
8051 qPrintable(QSystemError::windowsComString(hr)));
8052 }
8053 }
8054 if (useFrameLatencyWaitableObject) {
8055 swapChain->SetMaximumFrameLatency(rhiD->maxFrameLatency);
8056 frameLatencyWaitableObject = swapChain->GetFrameLatencyWaitableObject();
8057 }
8058 if (dcompVisual) {
8059 hr = dcompVisual->SetContent(swapChain);
8060 if (SUCCEEDED(hr)) {
8061 hr = dcompTarget->SetRoot(dcompVisual);
8062 if (FAILED(hr)) {
8063 qWarning("Failed to associate Direct Composition visual with the target: %s",
8064 qPrintable(QSystemError::windowsComString(hr)));
8065 }
8066 } else {
8067 qWarning("Failed to set content for Direct Composition visual: %s",
8068 qPrintable(QSystemError::windowsComString(hr)));
8069 }
8070 } else {
8071 // disable Alt+Enter; not relevant when using DirectComposition
8072 rhiD->dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
8073 }
8074 }
8075 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
8076 qWarning("Device loss detected during swapchain creation");
8077 rhiD->deviceLost = true;
8078 return false;
8079 } else if (FAILED(hr)) {
8080 qWarning("Failed to create D3D12 swapchain: %s"
8081 " (Width=%u Height=%u Format=%u SampleCount=%u BufferCount=%u Scaling=%u SwapEffect=%u Stereo=%u)",
8082 qPrintable(QSystemError::windowsComString(hr)),
8083 desc.Width, desc.Height, UINT(desc.Format), desc.SampleDesc.Count,
8084 desc.BufferCount, UINT(desc.Scaling), UINT(desc.SwapEffect), UINT(desc.Stereo));
8085 return false;
8086 }
8087
8088 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
8089 hr = rhiD->dev->CreateFence(0,
8090 D3D12_FENCE_FLAG_NONE,
8091 __uuidof(ID3D12Fence),
8092 reinterpret_cast<void **>(&frameRes[i].fence));
8093 if (FAILED(hr)) {
8094 qWarning("Failed to create fence for swapchain: %s",
8095 qPrintable(QSystemError::windowsComString(hr)));
8096 return false;
8097 }
8098 frameRes[i].fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
8099
8100 frameRes[i].fenceCounter = 0;
8101 }
8102 } else {
8103 releaseBuffers();
8104 hr = swapChain->ResizeBuffers(BUFFER_COUNT,
8105 UINT(pixelSize.width()),
8106 UINT(pixelSize.height()),
8107 colorFormat,
8108 swapChainFlags);
8109 if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
8110 qWarning("Device loss detected in ResizeBuffers()");
8111 rhiD->deviceLost = true;
8112 return false;
8113 } else if (FAILED(hr)) {
8114 qWarning("Failed to resize D3D12 swapchain: %s", qPrintable(QSystemError::windowsComString(hr)));
8115 return false;
8116 }
8117 }
8118
8119 for (UINT i = 0; i < BUFFER_COUNT; ++i) {
8120 ID3D12Resource *colorBuffer;
8121 hr = swapChain->GetBuffer(i, __uuidof(ID3D12Resource), reinterpret_cast<void **>(&colorBuffer));
8122 if (FAILED(hr)) {
8123 qWarning("Failed to get buffer %u for D3D12 swapchain: %s",
8124 i, qPrintable(QSystemError::windowsComString(hr)));
8125 return false;
8126 }
8127 colorBuffers[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, colorBuffer, D3D12_RESOURCE_STATE_PRESENT);
8128 rtvs[i] = rhiD->rtvPool.allocate(1);
8129 D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
8130 rtvDesc.Format = srgbAdjustedColorFormat;
8131 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
8132 rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvs[i].cpuHandle);
8133
8134 if (stereo) {
8135 rtvsRight[i] = rhiD->rtvPool.allocate(1);
8136 D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
8137 rtvDesc.Format = srgbAdjustedColorFormat;
8138 rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
8139 rtvDesc.Texture2DArray.ArraySize = 1;
8140 rtvDesc.Texture2DArray.FirstArraySlice = 1;
8141 rhiD->dev->CreateRenderTargetView(colorBuffer, &rtvDesc, rtvsRight[i].cpuHandle);
8142 }
8143 }
8144
8145 if (m_depthStencil && m_depthStencil->sampleCount() != m_sampleCount) {
8146 qWarning("Depth-stencil buffer's sampleCount (%d) does not match color buffers' sample count (%d). Expect problems.",
8147 m_depthStencil->sampleCount(), m_sampleCount);
8148 }
8149 if (m_depthStencil && m_depthStencil->pixelSize() != pixelSize) {
8150 if (m_depthStencil->flags().testFlag(QRhiRenderBuffer::UsedWithSwapChainOnly)) {
8151 m_depthStencil->setPixelSize(pixelSize);
8152 if (!m_depthStencil->create())
8153 qWarning("Failed to rebuild swapchain's associated depth-stencil buffer for size %dx%d",
8154 pixelSize.width(), pixelSize.height());
8155 } else {
8156 qWarning("Depth-stencil buffer's size (%dx%d) does not match the surface size (%dx%d). Expect problems.",
8157 m_depthStencil->pixelSize().width(), m_depthStencil->pixelSize().height(),
8158 pixelSize.width(), pixelSize.height());
8159 }
8160 }
8161
8162 ds = m_depthStencil ? QRHI_RES(QD3D12RenderBuffer, m_depthStencil) : nullptr;
8163
8164 if (sampleDesc.Count > 1) {
8165 for (int i = 0; i < QD3D12_FRAMES_IN_FLIGHT; ++i) {
8166 D3D12_RESOURCE_DESC resourceDesc = {};
8167 resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
8168 resourceDesc.Width = UINT64(pixelSize.width());
8169 resourceDesc.Height = UINT(pixelSize.height());
8170 resourceDesc.DepthOrArraySize = 1;
8171 resourceDesc.MipLevels = 1;
8172 resourceDesc.Format = srgbAdjustedColorFormat;
8173 resourceDesc.SampleDesc = sampleDesc;
8174 resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
8175 resourceDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
8176 D3D12_CLEAR_VALUE clearValue = {};
8177 clearValue.Format = colorFormat;
8178 ID3D12Resource *resource = nullptr;
8179 D3D12MA::Allocation *allocation = nullptr;
8180 HRESULT hr = rhiD->vma.createResource(D3D12_HEAP_TYPE_DEFAULT,
8181 &resourceDesc,
8182 D3D12_RESOURCE_STATE_RENDER_TARGET,
8183 &clearValue,
8184 &allocation,
8185 __uuidof(ID3D12Resource),
8186 reinterpret_cast<void **>(&resource));
8187 if (FAILED(hr)) {
8188 qWarning("Failed to create MSAA color buffer: %s", qPrintable(QSystemError::windowsComString(hr)));
8189 return false;
8190 }
8191 msaaBuffers[i] = QD3D12Resource::addToPool(&rhiD->resourcePool, resource, D3D12_RESOURCE_STATE_RENDER_TARGET, allocation);
8192 msaaRtvs[i] = rhiD->rtvPool.allocate(1);
8193 if (!msaaRtvs[i].isValid())
8194 return false;
8195 D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {};
8196 rtvDesc.Format = srgbAdjustedColorFormat;
8197 rtvDesc.ViewDimension = sampleDesc.Count > 1 ? D3D12_RTV_DIMENSION_TEXTURE2DMS
8198 : D3D12_RTV_DIMENSION_TEXTURE2D;
8199 rhiD->dev->CreateRenderTargetView(resource, &rtvDesc, msaaRtvs[i].cpuHandle);
8200 }
8201 }
8202
8203 currentBackBufferIndex = swapChain->GetCurrentBackBufferIndex();
8204 currentFrameSlot = 0;
8205 lastFrameLatencyWaitSlot = -1; // wait already in the first frame, as instructed in the dxgi docs
8206
8207 rtWrapper.setRenderPassDescriptor(m_renderPassDesc); // for the public getter in QRhiRenderTarget
8208 QD3D12SwapChainRenderTarget *rtD = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapper);
8209 rtD->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
8210 rtD->d.pixelSize = pixelSize;
8211 rtD->d.dpr = float(window->devicePixelRatio());
8212 rtD->d.sampleCount = int(sampleDesc.Count);
8213 rtD->d.colorAttCount = 1;
8214 rtD->d.dsAttCount = m_depthStencil ? 1 : 0;
8215
8216 rtWrapperRight.setRenderPassDescriptor(m_renderPassDesc);
8217 QD3D12SwapChainRenderTarget *rtDr = QRHI_RES(QD3D12SwapChainRenderTarget, &rtWrapperRight);
8218 rtDr->d.rp = QRHI_RES(QD3D12RenderPassDescriptor, m_renderPassDesc);
8219 rtDr->d.pixelSize = pixelSize;
8220 rtDr->d.dpr = float(window->devicePixelRatio());
8221 rtDr->d.sampleCount = int(sampleDesc.Count);
8222 rtDr->d.colorAttCount = 1;
8223 rtDr->d.dsAttCount = m_depthStencil ? 1 : 0;
8224
8225 QDxgiVSyncService::instance()->registerWindow(window);
8226
8227 if (needsRegistration || !rhiD->swapchains.contains(this))
8228 rhiD->swapchains.insert(this);
8229
8230 rhiD->registerResource(this);
8231
8232 return true;
8233}
8234
8235QT_END_NAMESPACE
8236
8237#endif // __ID3D12Device2_INTERFACE_DEFINED__
#define __has_include(x)