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