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