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