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