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