// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QRHID3D12_P_H #define QRHID3D12_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qrhi_p.h" #include #include #include #include #include #include #include #include #include "D3D12MemAlloc.h" // ID3D12Device2 and ID3D12GraphicsCommandList1 and types and enums introduced // with those are hard requirements now. These should be declared in any // moderately recent d3d12.h, but if it is an SDK from before Windows 10 // version 1703 then these types could be missing. In the absence of other // options, handle this by skipping all the code and making QRhi::create() fail // in such builds. #ifdef __ID3D12Device2_INTERFACE_DEFINED__ #define QRHI_D3D12_AVAILABLE QT_BEGIN_NAMESPACE static const int QD3D12_FRAMES_IN_FLIGHT = 2; class QRhiD3D12; struct QD3D12Descriptor { D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = {}; D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = {}; bool isValid() const { return cpuHandle.ptr != 0; } }; struct QD3D12ReleaseQueue; struct QD3D12DescriptorHeap { bool isValid() const { return heap && capacity; } bool create(ID3D12Device *device, quint32 descriptorCount, D3D12_DESCRIPTOR_HEAP_TYPE heapType, D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags); void createWithExisting(const QD3D12DescriptorHeap &other, quint32 offsetInDescriptors, quint32 descriptorCount); void destroy(); void destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue); QD3D12Descriptor get(quint32 count); QD3D12Descriptor at(quint32 index) const; quint32 remainingCapacity() const { return capacity - head; } QD3D12Descriptor incremented(const QD3D12Descriptor &descriptor, quint32 offsetInDescriptors) const { D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle = descriptor.cpuHandle; cpuHandle.ptr += offsetInDescriptors * descriptorByteSize; D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle = descriptor.gpuHandle; if (gpuHandle.ptr) gpuHandle.ptr += offsetInDescriptors * descriptorByteSize; return { cpuHandle, gpuHandle }; } ID3D12DescriptorHeap *heap = nullptr; quint32 capacity = 0; QD3D12Descriptor heapStart; quint32 head = 0; quint32 descriptorByteSize = 0; D3D12_DESCRIPTOR_HEAP_TYPE heapType; D3D12_DESCRIPTOR_HEAP_FLAGS heapFlags; }; struct QD3D12CpuDescriptorPool { bool isValid() const { return !heaps.isEmpty(); } bool create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE heapType, const char *debugName = ""); void destroy(); QD3D12Descriptor allocate(quint32 count); void release(const QD3D12Descriptor &descriptor, quint32 count); static const int DESCRIPTORS_PER_HEAP = 256; struct HeapWithMap { QD3D12DescriptorHeap heap; QBitArray map; static HeapWithMap init(const QD3D12DescriptorHeap &heap, quint32 descriptorCount) { HeapWithMap result; result.heap = heap; result.map.resize(descriptorCount); return result; } }; ID3D12Device *device; quint32 descriptorByteSize; QVector heaps; const char *debugName; }; struct QD3D12QueryHeap { bool isValid() const { return heap && capacity; } bool create(ID3D12Device *device, quint32 queryCount, D3D12_QUERY_HEAP_TYPE heapType); void destroy(); ID3D12QueryHeap *heap = nullptr; quint32 capacity = 0; }; struct QD3D12StagingArea { static const quint32 ALIGNMENT = D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT; // 512 so good enough both for cb and texdata struct Allocation { quint8 *p = nullptr; D3D12_GPU_VIRTUAL_ADDRESS gpuAddr = 0; ID3D12Resource *buffer = nullptr; quint32 bufferOffset = 0; bool isValid() const { return p != nullptr; } }; bool isValid() const { return allocation && mem.isValid(); } bool create(QRhiD3D12 *rhi, quint32 capacity, D3D12_HEAP_TYPE heapType); void destroy(); void destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue); Allocation get(quint32 byteSize); quint32 remainingCapacity() const { return capacity - head; } static quint32 allocSizeForArray(quint32 size, int count = 1) { return count * ((size + ALIGNMENT - 1) & ~(ALIGNMENT - 1)); } Allocation mem; ID3D12Resource *resource = nullptr; D3D12MA::Allocation *allocation = nullptr; quint32 head; quint32 capacity; }; struct QD3D12ObjectHandle { quint32 index = 0; quint32 generation = 0; // the default, null handle is guaranteed to give ObjectPool::isValid() == false bool isNull() const { return index == 0 && generation == 0; } }; inline bool operator==(const QD3D12ObjectHandle &a, const QD3D12ObjectHandle &b) noexcept { return a.index == b.index && a.generation == b.generation; } inline bool operator!=(const QD3D12ObjectHandle &a, const QD3D12ObjectHandle &b) noexcept { return !(a == b); } template struct QD3D12ObjectPool { void create(const char *debugName = "") { this->debugName = debugName; Q_ASSERT(data.isEmpty()); data.append(Data()); // index 0 is always invalid } void destroy() { int leakCount = 0; // will nicely destroy everything here, but warn about it if enabled for (Data &d : data) { if (d.object.has_value()) { leakCount += 1; d.object->releaseResources(); } } data.clear(); #ifndef QT_NO_DEBUG // debug builds: just do it always static bool leakCheck = true; #else // release builds: opt-in static bool leakCheck = qEnvironmentVariableIntValue("QT_RHI_LEAK_CHECK"); #endif if (leakCheck) { if (leakCount > 0) { qWarning("QD3D12ObjectPool::destroy(): Pool %p '%s' had %d unreleased objects", this, debugName, leakCount); } } } bool isValid(const QD3D12ObjectHandle &handle) const { return handle.index > 0 && handle.index < quint32(data.count()) && handle.generation > 0 && handle.generation == data[handle.index].generation && data[handle.index].object.has_value(); } T lookup(const QD3D12ObjectHandle &handle) const { return isValid(handle) ? *data[handle.index].object : T(); } const T *lookupRef(const QD3D12ObjectHandle &handle) const { return isValid(handle) ? &*data[handle.index].object : nullptr; } T *lookupRef(const QD3D12ObjectHandle &handle) { return isValid(handle) ? &*data[handle.index].object : nullptr; } QD3D12ObjectHandle add(const T &object) { Q_ASSERT(!data.isEmpty()); const quint32 count = quint32(data.count()); quint32 index = 1; // index 0 is always invalid for (; index < count; ++index) { if (!data[index].object.has_value()) break; } if (index < count) { data[index].object = object; quint32 &generation = data[index].generation; generation += 1u; return { index, generation }; } else { data.append({ object, 1 }); return { count, 1 }; } } void remove(const QD3D12ObjectHandle &handle) { if (T *object = lookupRef(handle)) { object->releaseResources(); data[handle.index].object.reset(); } } const char *debugName; struct Data { std::optional object; quint32 generation = 0; }; QVector data; }; struct QD3D12Resource { ID3D12Resource *resource; D3D12_RESOURCE_STATES state; D3D12_RESOURCE_DESC desc; D3D12MA::Allocation *allocation; void *cpuMapPtr; enum { UavUsageRead = 0x01, UavUsageWrite = 0x02 }; int uavUsage; bool owns; // note that this assumes the allocation (if there is one) and the resource // are separately releaseable, see D3D12MemAlloc docs static QD3D12ObjectHandle addToPool(QD3D12ObjectPool *pool, ID3D12Resource *resource, D3D12_RESOURCE_STATES state, D3D12MA::Allocation *allocation = nullptr, void *cpuMapPtr = nullptr) { Q_ASSERT(resource); return pool->add({ resource, state, resource->GetDesc(), allocation, cpuMapPtr, 0, true }); } // for QRhiTexture::createFrom() where the ID3D12Resource is not owned by us static QD3D12ObjectHandle addNonOwningToPool(QD3D12ObjectPool *pool, ID3D12Resource *resource, D3D12_RESOURCE_STATES state) { Q_ASSERT(resource); return pool->add({ resource, state, resource->GetDesc(), nullptr, nullptr, 0, false }); } void releaseResources() { if (owns) { // order matters: resource first, then the allocation resource->Release(); if (allocation) allocation->Release(); } } }; struct QD3D12Pipeline { enum Type { Graphics, Compute }; Type type; ID3D12PipelineState *pso; static QD3D12ObjectHandle addToPool(QD3D12ObjectPool *pool, Type type, ID3D12PipelineState *pso) { return pool->add({ type, pso }); } void releaseResources() { pso->Release(); } }; struct QD3D12RootSignature { ID3D12RootSignature *rootSig; static QD3D12ObjectHandle addToPool(QD3D12ObjectPool *pool, ID3D12RootSignature *rootSig) { return pool->add({ rootSig }); } void releaseResources() { rootSig->Release(); } }; struct QD3D12ReleaseQueue { void create(QD3D12ObjectPool *resourcePool, QD3D12ObjectPool *pipelinePool, QD3D12ObjectPool *rootSignaturePool) { this->resourcePool = resourcePool; this->pipelinePool = pipelinePool; this->rootSignaturePool = rootSignaturePool; } void deferredReleaseResource(const QD3D12ObjectHandle &handle); void deferredReleaseResourceWithViews(const QD3D12ObjectHandle &handle, QD3D12CpuDescriptorPool *pool, const QD3D12Descriptor &viewsStart, int viewCount); void deferredReleasePipeline(const QD3D12ObjectHandle &handle); void deferredReleaseRootSignature(const QD3D12ObjectHandle &handle); void deferredReleaseCallback(std::function callback, void *userData); void deferredReleaseResourceAndAllocation(ID3D12Resource *resource, D3D12MA::Allocation *allocation); void deferredReleaseDescriptorHeap(ID3D12DescriptorHeap *heap); void deferredReleaseViews(QD3D12CpuDescriptorPool *pool, const QD3D12Descriptor &viewsStart, int viewCount); void activatePendingDeferredReleaseRequests(int frameSlot); void executeDeferredReleases(int frameSlot, bool forced = false); void releaseAll(); struct DeferredReleaseEntry { enum Type { Resource, Pipeline, RootSignature, Callback, ResourceAndAllocation, DescriptorHeap, Views }; Type type = Resource; std::optional frameSlotToBeReleasedIn; QD3D12ObjectHandle handle; QD3D12CpuDescriptorPool *poolForViews = nullptr; QD3D12Descriptor viewsStart; int viewCount = 0; std::function callback = nullptr; void *callbackUserData = nullptr; QPair resourceAndAllocation = {}; ID3D12DescriptorHeap *descriptorHeap = nullptr; }; QVector queue; QD3D12ObjectPool *resourcePool = nullptr; QD3D12ObjectPool *pipelinePool = nullptr; QD3D12ObjectPool *rootSignaturePool = nullptr; }; struct QD3D12CommandBuffer; struct QD3D12ResourceBarrierGenerator { static const int PREALLOC = 16; void create(QD3D12ObjectPool *resourcePool) { this->resourcePool = resourcePool; } void addTransitionBarrier(const QD3D12ObjectHandle &resourceHandle, D3D12_RESOURCE_STATES stateAfter); void enqueueBufferedTransitionBarriers(QD3D12CommandBuffer *cbD); void enqueueSubresourceTransitionBarrier(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &resourceHandle, UINT subresource, D3D12_RESOURCE_STATES stateBefore, D3D12_RESOURCE_STATES stateAfter); void enqueueUavBarrier(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &resourceHandle); struct TransitionResourceBarrier { QD3D12ObjectHandle resourceHandle; D3D12_RESOURCE_STATES stateBefore; D3D12_RESOURCE_STATES stateAfter; }; QVarLengthArray transitionResourceBarriers; QD3D12ObjectPool *resourcePool = nullptr; }; struct QD3D12ShaderBytecodeCache { struct Shader { Shader() = default; Shader(const QByteArray &bytecode, const QShader::NativeResourceBindingMap &rbm) : bytecode(bytecode), nativeResourceBindingMap(rbm) { } QByteArray bytecode; QShader::NativeResourceBindingMap nativeResourceBindingMap; }; QHash data; void insertWithCapacityLimit(const QRhiShaderStage &key, const Shader &s); }; struct QD3D12ShaderVisibleDescriptorHeap { bool create(ID3D12Device *device, D3D12_DESCRIPTOR_HEAP_TYPE type, quint32 perFrameDescriptorCount); void destroy(); void destroyWithDeferredRelease(QD3D12ReleaseQueue *releaseQueue); QD3D12DescriptorHeap heap; QD3D12DescriptorHeap perFrameHeapSlice[QD3D12_FRAMES_IN_FLIGHT]; }; // wrap foreign struct so we can legally supply equality operators and qHash: struct Q_D3D12_SAMPLER_DESC { D3D12_SAMPLER_DESC desc; friend bool operator==(const Q_D3D12_SAMPLER_DESC &lhs, const Q_D3D12_SAMPLER_DESC &rhs) noexcept { return lhs.desc.Filter == rhs.desc.Filter && lhs.desc.AddressU == rhs.desc.AddressU && lhs.desc.AddressV == rhs.desc.AddressV && lhs.desc.AddressW == rhs.desc.AddressW && lhs.desc.MipLODBias == rhs.desc.MipLODBias && lhs.desc.MaxAnisotropy == rhs.desc.MaxAnisotropy && lhs.desc.ComparisonFunc == rhs.desc.ComparisonFunc // BorderColor is never used, skip it && lhs.desc.MinLOD == rhs.desc.MinLOD && lhs.desc.MaxLOD == rhs.desc.MaxLOD; } friend bool operator!=(const Q_D3D12_SAMPLER_DESC &lhs, const Q_D3D12_SAMPLER_DESC &rhs) noexcept { return !(lhs == rhs); } friend size_t qHash(const Q_D3D12_SAMPLER_DESC &key, size_t seed = 0) noexcept { QtPrivate::QHashCombine hash; seed = hash(seed, key.desc.Filter); seed = hash(seed, key.desc.AddressU); seed = hash(seed, key.desc.AddressV); seed = hash(seed, key.desc.AddressW); seed = hash(seed, key.desc.MipLODBias); seed = hash(seed, key.desc.MaxAnisotropy); seed = hash(seed, key.desc.ComparisonFunc); // BorderColor is never used, skip it seed = hash(seed, key.desc.MinLOD); seed = hash(seed, key.desc.MaxLOD); return seed; } }; struct QD3D12SamplerManager { const quint32 MAX_SAMPLERS = 512; bool create(ID3D12Device *device); void destroy(); QD3D12Descriptor getShaderVisibleDescriptor(const D3D12_SAMPLER_DESC &desc); ID3D12Device *device = nullptr; QD3D12ShaderVisibleDescriptorHeap shaderVisibleSamplerHeap; QHash gpuMap; }; enum QD3D12Stage { VS = 0, HS, DS, GS, PS, CS }; static inline QD3D12Stage qd3d12_stage(QRhiShaderStage::Type type) { switch (type) { case QRhiShaderStage::Vertex: return VS; case QRhiShaderStage::TessellationControl: return HS; case QRhiShaderStage::TessellationEvaluation: return DS; case QRhiShaderStage::Geometry: return GS; case QRhiShaderStage::Fragment: return PS; case QRhiShaderStage::Compute: return CS; } Q_UNREACHABLE_RETURN(VS); } static inline D3D12_SHADER_VISIBILITY qd3d12_stageToVisibility(QD3D12Stage s) { switch (s) { case VS: return D3D12_SHADER_VISIBILITY_VERTEX; case HS: return D3D12_SHADER_VISIBILITY_HULL; case DS: return D3D12_SHADER_VISIBILITY_DOMAIN; case GS: return D3D12_SHADER_VISIBILITY_GEOMETRY; case PS: return D3D12_SHADER_VISIBILITY_PIXEL; case CS: return D3D12_SHADER_VISIBILITY_ALL; } Q_UNREACHABLE_RETURN(D3D12_SHADER_VISIBILITY_ALL); } static inline QRhiShaderResourceBinding::StageFlag qd3d12_stageToSrb(QD3D12Stage s) { switch (s) { case VS: return QRhiShaderResourceBinding::VertexStage; case HS: return QRhiShaderResourceBinding::TessellationControlStage; case DS: return QRhiShaderResourceBinding::TessellationEvaluationStage; case GS: return QRhiShaderResourceBinding::GeometryStage; case PS: return QRhiShaderResourceBinding::FragmentStage; case CS: return QRhiShaderResourceBinding::ComputeStage; } Q_UNREACHABLE_RETURN(QRhiShaderResourceBinding::VertexStage); } struct QD3D12ShaderStageData { bool valid = false; // to allow simple arrays where unused stages are indicated by !valid QD3D12Stage stage = VS; QShader::NativeResourceBindingMap nativeResourceBindingMap; }; struct QD3D12ShaderResourceBindings; struct QD3D12ShaderResourceVisitor { enum StorageOp { Load = 0, Store, LoadStore }; QD3D12ShaderResourceVisitor(const QD3D12ShaderResourceBindings *srb, const QD3D12ShaderStageData *stageData, int stageCount) : srb(srb), stageData(stageData), stageCount(stageCount) { } std::function uniformBuffer = nullptr; std::function texture = nullptr; std::function sampler = nullptr; std::function storageImage = nullptr; std::function storageBuffer = nullptr; void visit(); const QD3D12ShaderResourceBindings *srb; const QD3D12ShaderStageData *stageData; int stageCount; }; struct QD3D12Readback { // common int frameSlot = -1; QRhiReadbackResult *result = nullptr; QD3D12StagingArea staging; quint32 byteSize = 0; // textures quint32 bytesPerLine = 0; QSize pixelSize; QRhiTexture::Format format = QRhiTexture::UnknownFormat; quint32 stagingRowPitch = 0; }; struct QD3D12MipmapGenerator { bool create(QRhiD3D12 *rhiD); void destroy(); void generate(QD3D12CommandBuffer *cbD, const QD3D12ObjectHandle &textureHandle); QRhiD3D12 *rhiD; QD3D12ObjectHandle rootSigHandle; QD3D12ObjectHandle pipelineHandle; }; struct QD3D12MemoryAllocator { bool create(ID3D12Device *device, IDXGIAdapter1 *adapter); void destroy(); HRESULT createResource(D3D12_HEAP_TYPE heapType, const D3D12_RESOURCE_DESC *resourceDesc, D3D12_RESOURCE_STATES initialState, const D3D12_CLEAR_VALUE *optimizedClearValue, D3D12MA::Allocation **maybeAllocation, REFIID riidResource, void **ppvResource); void getBudget(D3D12MA::Budget *localBudget, D3D12MA::Budget *nonLocalBudget); bool isUsingD3D12MA() const { return allocator != nullptr; } ID3D12Device *device = nullptr; D3D12MA::Allocator *allocator = nullptr; }; struct QD3D12Buffer : public QRhiBuffer { QD3D12Buffer(QRhiImplementation *rhi, Type type, UsageFlags usage, quint32 size); ~QD3D12Buffer(); void destroy() override; bool create() override; QRhiBuffer::NativeBuffer nativeBuffer() override; char *beginFullDynamicBufferUpdateForCurrentFrame() override; void endFullDynamicBufferUpdateForCurrentFrame() override; void executeHostWritesForFrameSlot(int frameSlot); QD3D12ObjectHandle handles[QD3D12_FRAMES_IN_FLIGHT] = {}; struct HostWrite { quint32 offset; QRhiBufferData data; }; QVarLengthArray pendingHostWrites[QD3D12_FRAMES_IN_FLIGHT]; friend class QRhiD3D12; friend struct QD3D12CommandBuffer; }; struct QD3D12RenderBuffer : public QRhiRenderBuffer { QD3D12RenderBuffer(QRhiImplementation *rhi, Type type, const QSize &pixelSize, int sampleCount, Flags flags, QRhiTexture::Format backingFormatHint); ~QD3D12RenderBuffer(); void destroy() override; bool create() override; QRhiTexture::Format backingFormat() const override; static const DXGI_FORMAT DS_FORMAT = DXGI_FORMAT_D24_UNORM_S8_UINT; QD3D12ObjectHandle handle; QD3D12Descriptor rtv; QD3D12Descriptor dsv; DXGI_FORMAT dxgiFormat; DXGI_SAMPLE_DESC sampleDesc; uint generation = 0; friend class QRhiD3D12; }; struct QD3D12Texture : public QRhiTexture { QD3D12Texture(QRhiImplementation *rhi, Format format, const QSize &pixelSize, int depth, int arraySize, int sampleCount, Flags flags); ~QD3D12Texture(); void destroy() override; bool create() override; bool createFrom(NativeTexture src) override; NativeTexture nativeTexture() override; void setNativeLayout(int layout) override; bool prepareCreate(QSize *adjustedSize = nullptr); bool finishCreate(); QD3D12ObjectHandle handle; QD3D12Descriptor srv; DXGI_FORMAT dxgiFormat; DXGI_FORMAT srvFormat; DXGI_FORMAT rtFormat; // RTV/DSV/UAV uint mipLevelCount; DXGI_SAMPLE_DESC sampleDesc; uint generation = 0; friend class QRhiD3D12; friend struct QD3D12CommandBuffer; }; struct QD3D12Sampler : public QRhiSampler { QD3D12Sampler(QRhiImplementation *rhi, Filter magFilter, Filter minFilter, Filter mipmapMode, AddressMode u, AddressMode v, AddressMode w); ~QD3D12Sampler(); void destroy() override; bool create() override; QD3D12Descriptor lookupOrCreateShaderVisibleDescriptor(); D3D12_SAMPLER_DESC desc = {}; QD3D12Descriptor shaderVisibleDescriptor; }; struct QD3D12RenderPassDescriptor : public QRhiRenderPassDescriptor { QD3D12RenderPassDescriptor(QRhiImplementation *rhi); ~QD3D12RenderPassDescriptor(); void destroy() override; bool isCompatible(const QRhiRenderPassDescriptor *other) const override; QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() const override; QVector serializedFormat() const override; void updateSerializedFormat(); static const int MAX_COLOR_ATTACHMENTS = 8; int colorAttachmentCount = 0; bool hasDepthStencil = false; int colorFormat[MAX_COLOR_ATTACHMENTS]; int dsFormat; QVector serializedFormatData; }; struct QD3D12RenderTargetData { QD3D12RenderTargetData(QRhiImplementation *) { } QD3D12RenderPassDescriptor *rp = nullptr; QSize pixelSize; float dpr = 1; int sampleCount = 1; int colorAttCount = 0; int dsAttCount = 0; QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList; static const int MAX_COLOR_ATTACHMENTS = QD3D12RenderPassDescriptor::MAX_COLOR_ATTACHMENTS; D3D12_CPU_DESCRIPTOR_HANDLE rtv[MAX_COLOR_ATTACHMENTS]; D3D12_CPU_DESCRIPTOR_HANDLE dsv; }; struct QD3D12SwapChainRenderTarget : public QRhiSwapChainRenderTarget { QD3D12SwapChainRenderTarget(QRhiImplementation *rhi, QRhiSwapChain *swapchain); ~QD3D12SwapChainRenderTarget(); void destroy() override; QSize pixelSize() const override; float devicePixelRatio() const override; int sampleCount() const override; QD3D12RenderTargetData d; }; struct QD3D12TextureRenderTarget : public QRhiTextureRenderTarget { QD3D12TextureRenderTarget(QRhiImplementation *rhi, const QRhiTextureRenderTargetDescription &desc, Flags flags); ~QD3D12TextureRenderTarget(); void destroy() override; QSize pixelSize() const override; float devicePixelRatio() const override; int sampleCount() const override; QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; bool create() override; QD3D12RenderTargetData d; bool ownsRtv[QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS]; QD3D12Descriptor rtv[QD3D12RenderTargetData::MAX_COLOR_ATTACHMENTS]; bool ownsDsv = false; QD3D12Descriptor dsv; friend class QRhiD3D12; }; struct QD3D12ShaderResourceBindings : public QRhiShaderResourceBindings { QD3D12ShaderResourceBindings(QRhiImplementation *rhi); ~QD3D12ShaderResourceBindings(); void destroy() override; bool create() override; void updateResources(UpdateFlags flags) override; QD3D12ObjectHandle createRootSignature(const QD3D12ShaderStageData *stageData, int stageCount); struct VisitorData { QVarLengthArray cbParams[6]; D3D12_ROOT_PARAMETER1 srvTables[6] = {}; QVarLengthArray srvRanges[6]; quint32 currentSrvRangeOffset[6] = {}; QVarLengthArray samplerTables[6]; std::array samplerRanges[6] = {}; int samplerRangeHeads[6] = {}; D3D12_ROOT_PARAMETER1 uavTables[6] = {}; QVarLengthArray uavRanges[6]; quint32 currentUavRangeOffset[6] = {}; } visitorData; void visitUniformBuffer(QD3D12Stage s, const QRhiShaderResourceBinding::Data::UniformBufferData &d, int shaderRegister, int binding); void visitTexture(QD3D12Stage s, const QRhiShaderResourceBinding::TextureAndSampler &d, int shaderRegister); void visitSampler(QD3D12Stage s, const QRhiShaderResourceBinding::TextureAndSampler &d, int shaderRegister); void visitStorageBuffer(QD3D12Stage s, const QRhiShaderResourceBinding::Data::StorageBufferData &d, QD3D12ShaderResourceVisitor::StorageOp op, int shaderRegister); void visitStorageImage(QD3D12Stage s, const QRhiShaderResourceBinding::Data::StorageImageData &d, QD3D12ShaderResourceVisitor::StorageOp op, int shaderRegister); bool hasDynamicOffset = false; uint generation = 0; friend class QRhiD3D12; friend struct QD3D12ShaderResourceVisitor; }; struct QD3D12GraphicsPipeline : public QRhiGraphicsPipeline { QD3D12GraphicsPipeline(QRhiImplementation *rhi); ~QD3D12GraphicsPipeline(); void destroy() override; bool create() override; QD3D12ObjectHandle handle; QD3D12ObjectHandle rootSigHandle; std::array stageData; D3D12_PRIMITIVE_TOPOLOGY topology; UINT viewInstanceMask = 0; uint generation = 0; friend class QRhiD3D12; }; struct QD3D12ComputePipeline : public QRhiComputePipeline { QD3D12ComputePipeline(QRhiImplementation *rhi); ~QD3D12ComputePipeline(); void destroy() override; bool create() override; QD3D12ObjectHandle handle; QD3D12ObjectHandle rootSigHandle; QD3D12ShaderStageData stageData; uint generation = 0; friend class QRhiD3D12; }; struct QD3D12CommandBuffer : public QRhiCommandBuffer { QD3D12CommandBuffer(QRhiImplementation *rhi); ~QD3D12CommandBuffer(); void destroy() override; const QRhiNativeHandles *nativeHandles(); ID3D12GraphicsCommandList1 *cmdList = nullptr; // not owned QRhiD3D12CommandBufferNativeHandles nativeHandlesStruct; enum PassType { NoPass, RenderPass, ComputePass }; void resetState() { recordingPass = NoPass; currentTarget = nullptr; resetPerPassState(); } void resetPerPassState() { currentGraphicsPipeline = nullptr; currentComputePipeline = nullptr; currentPipelineGeneration = 0; currentGraphicsSrb = nullptr; currentComputeSrb = nullptr; currentSrbGeneration = 0; currentIndexBuffer = {}; currentIndexOffset = 0; currentIndexFormat = DXGI_FORMAT_R16_UINT; currentVertexBuffers = {}; currentVertexOffsets = {}; } // per-frame PassType recordingPass; QRhiRenderTarget *currentTarget; // per-pass QD3D12GraphicsPipeline *currentGraphicsPipeline; QD3D12ComputePipeline *currentComputePipeline; uint currentPipelineGeneration; QRhiShaderResourceBindings *currentGraphicsSrb; QRhiShaderResourceBindings *currentComputeSrb; uint currentSrbGeneration; QD3D12ObjectHandle currentIndexBuffer; quint32 currentIndexOffset; DXGI_FORMAT currentIndexFormat; std::array currentVertexBuffers; std::array currentVertexOffsets; // global double lastGpuTime = 0; // per-setShaderResources struct VisitorData { QVarLengthArray, 4> cbufs[6]; QVarLengthArray srvs[6]; QVarLengthArray samplers[6]; QVarLengthArray, 4> uavs[6]; } visitorData; void visitUniformBuffer(QD3D12Stage s, const QRhiShaderResourceBinding::Data::UniformBufferData &d, int shaderRegister, int binding, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets); void visitTexture(QD3D12Stage s, const QRhiShaderResourceBinding::TextureAndSampler &d, int shaderRegister); void visitSampler(QD3D12Stage s, const QRhiShaderResourceBinding::TextureAndSampler &d, int shaderRegister); void visitStorageBuffer(QD3D12Stage s, const QRhiShaderResourceBinding::Data::StorageBufferData &d, QD3D12ShaderResourceVisitor::StorageOp op, int shaderRegister); void visitStorageImage(QD3D12Stage s, const QRhiShaderResourceBinding::Data::StorageImageData &d, QD3D12ShaderResourceVisitor::StorageOp op, int shaderRegister); }; struct QD3D12SwapChain : public QRhiSwapChain { QD3D12SwapChain(QRhiImplementation *rhi); ~QD3D12SwapChain(); void destroy() override; QRhiCommandBuffer *currentFrameCommandBuffer() override; QRhiRenderTarget *currentFrameRenderTarget() override; QRhiRenderTarget *currentFrameRenderTarget(StereoTargetBuffer targetBuffer) override; QSize surfacePixelSize() override; bool isFormatSupported(Format f) override; QRhiSwapChainHdrInfo hdrInfo() override; QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() override; bool createOrResize() override; void releaseBuffers(); void waitCommandCompletionForFrameSlot(int frameSlot); void addCommandCompletionSignalForCurrentFrameSlot(); void chooseFormats(); QWindow *window = nullptr; IDXGISwapChain1 *sourceSwapChain1 = nullptr; IDXGISwapChain3 *swapChain = nullptr; QSize pixelSize; UINT swapInterval = 1; UINT swapChainFlags = 0; BOOL stereo = false; DXGI_FORMAT colorFormat; DXGI_FORMAT srgbAdjustedColorFormat; DXGI_COLOR_SPACE_TYPE hdrColorSpace; IDCompositionTarget *dcompTarget = nullptr; IDCompositionVisual *dcompVisual = nullptr; static const UINT BUFFER_COUNT = 3; QD3D12ObjectHandle colorBuffers[BUFFER_COUNT]; QD3D12Descriptor rtvs[BUFFER_COUNT]; QD3D12Descriptor rtvsRight[BUFFER_COUNT]; DXGI_SAMPLE_DESC sampleDesc; QD3D12ObjectHandle msaaBuffers[BUFFER_COUNT]; QD3D12Descriptor msaaRtvs[BUFFER_COUNT]; QD3D12RenderBuffer *ds = nullptr; UINT currentBackBufferIndex = 0; QD3D12SwapChainRenderTarget rtWrapper; QD3D12SwapChainRenderTarget rtWrapperRight; QD3D12CommandBuffer cbWrapper; struct FrameResources { ID3D12Fence *fence = nullptr; HANDLE fenceEvent = nullptr; UINT64 fenceCounter = 0; ID3D12GraphicsCommandList1 *cmdList = nullptr; } frameRes[QD3D12_FRAMES_IN_FLIGHT]; int currentFrameSlot = 0; // index in frameRes }; template struct alignas(void*) QD3D12PipelineStateSubObject { D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type = Type; T object = {}; }; class QRhiD3D12 : public QRhiImplementation { public: // 16MB * QD3D12_FRAMES_IN_FLIGHT; buffer and texture upload staging data that // gets no space from this will get their own temporary staging areas. static const quint32 SMALL_STAGING_AREA_BYTES_PER_FRAME = 16 * 1024 * 1024; static const quint32 SHADER_VISIBLE_CBV_SRV_UAV_HEAP_PER_FRAME_START_SIZE = 16384; QRhiD3D12(QRhiD3D12InitParams *params, QRhiD3D12NativeHandles *importDevice = nullptr); bool create(QRhi::Flags flags) override; void destroy() override; QRhiGraphicsPipeline *createGraphicsPipeline() override; QRhiComputePipeline *createComputePipeline() override; QRhiShaderResourceBindings *createShaderResourceBindings() override; QRhiBuffer *createBuffer(QRhiBuffer::Type type, QRhiBuffer::UsageFlags usage, quint32 size) override; QRhiRenderBuffer *createRenderBuffer(QRhiRenderBuffer::Type type, const QSize &pixelSize, int sampleCount, QRhiRenderBuffer::Flags flags, QRhiTexture::Format backingFormatHint) override; QRhiTexture *createTexture(QRhiTexture::Format format, const QSize &pixelSize, int depth, int arraySize, int sampleCount, QRhiTexture::Flags flags) override; QRhiSampler *createSampler(QRhiSampler::Filter magFilter, QRhiSampler::Filter minFilter, QRhiSampler::Filter mipmapMode, QRhiSampler:: AddressMode u, QRhiSampler::AddressMode v, QRhiSampler::AddressMode w) override; QRhiTextureRenderTarget *createTextureRenderTarget(const QRhiTextureRenderTargetDescription &desc, QRhiTextureRenderTarget::Flags flags) override; QRhiSwapChain *createSwapChain() override; QRhi::FrameOpResult beginFrame(QRhiSwapChain *swapChain, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endFrame(QRhiSwapChain *swapChain, QRhi::EndFrameFlags flags) override; QRhi::FrameOpResult beginOffscreenFrame(QRhiCommandBuffer **cb, QRhi::BeginFrameFlags flags) override; QRhi::FrameOpResult endOffscreenFrame(QRhi::EndFrameFlags flags) override; QRhi::FrameOpResult finish() override; void resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; void beginPass(QRhiCommandBuffer *cb, QRhiRenderTarget *rt, const QColor &colorClearValue, const QRhiDepthStencilClearValue &depthStencilClearValue, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override; void endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; void setGraphicsPipeline(QRhiCommandBuffer *cb, QRhiGraphicsPipeline *ps) override; void setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBindings *srb, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets) override; void setVertexInput(QRhiCommandBuffer *cb, int startBinding, int bindingCount, const QRhiCommandBuffer::VertexInput *bindings, QRhiBuffer *indexBuf, quint32 indexOffset, QRhiCommandBuffer::IndexFormat indexFormat) override; void setViewport(QRhiCommandBuffer *cb, const QRhiViewport &viewport) override; void setScissor(QRhiCommandBuffer *cb, const QRhiScissor &scissor) override; void setBlendConstants(QRhiCommandBuffer *cb, const QColor &c) override; void setStencilRef(QRhiCommandBuffer *cb, quint32 refValue) override; void draw(QRhiCommandBuffer *cb, quint32 vertexCount, quint32 instanceCount, quint32 firstVertex, quint32 firstInstance) override; void drawIndexed(QRhiCommandBuffer *cb, quint32 indexCount, quint32 instanceCount, quint32 firstIndex, qint32 vertexOffset, quint32 firstInstance) override; void debugMarkBegin(QRhiCommandBuffer *cb, const QByteArray &name) override; void debugMarkEnd(QRhiCommandBuffer *cb) override; void debugMarkMsg(QRhiCommandBuffer *cb, const QByteArray &msg) override; void beginComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates, QRhiCommandBuffer::BeginPassFlags flags) override; void endComputePass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates) override; void setComputePipeline(QRhiCommandBuffer *cb, QRhiComputePipeline *ps) override; void dispatch(QRhiCommandBuffer *cb, int x, int y, int z) override; const QRhiNativeHandles *nativeHandles(QRhiCommandBuffer *cb) override; void beginExternal(QRhiCommandBuffer *cb) override; void endExternal(QRhiCommandBuffer *cb) override; double lastCompletedGpuTime(QRhiCommandBuffer *cb) override; QList supportedSampleCounts() const override; int ubufAlignment() const override; bool isYUpInFramebuffer() const override; bool isYUpInNDC() const override; bool isClipDepthZeroToOne() const override; QMatrix4x4 clipSpaceCorrMatrix() const override; bool isTextureFormatSupported(QRhiTexture::Format format, QRhiTexture::Flags flags) const override; bool isFeatureSupported(QRhi::Feature feature) const override; int resourceLimit(QRhi::ResourceLimit limit) const override; const QRhiNativeHandles *nativeHandles() override; QRhiDriverInfo driverInfo() const override; QRhiStats statistics() override; bool makeThreadLocalNativeContextCurrent() override; void releaseCachedResources() override; bool isDeviceLost() const override; QByteArray pipelineCacheData() override; void setPipelineCacheData(const QByteArray &data) override; void waitGpu(); DXGI_SAMPLE_DESC effectiveSampleDesc(int sampleCount, DXGI_FORMAT format) const; bool ensureDirectCompositionDevice(); bool startCommandListForCurrentFrameSlot(ID3D12GraphicsCommandList1 **cmdList); void enqueueResourceUpdates(QD3D12CommandBuffer *cbD, QRhiResourceUpdateBatch *resourceUpdates); void finishActiveReadbacks(bool forced = false); bool ensureShaderVisibleDescriptorHeapCapacity(QD3D12ShaderVisibleDescriptorHeap *h, D3D12_DESCRIPTOR_HEAP_TYPE type, int frameSlot, quint32 neededDescriptorCount, bool *gotNew); void bindShaderVisibleHeaps(QD3D12CommandBuffer *cbD); bool debugLayer = false; ID3D12Device2 *dev = nullptr; D3D_FEATURE_LEVEL minimumFeatureLevel = D3D_FEATURE_LEVEL(0); LUID adapterLuid = {}; bool importedDevice = false; bool importedCommandQueue = false; QRhi::Flags rhiFlags; IDXGIFactory2 *dxgiFactory = nullptr; bool supportsAllowTearing = false; IDXGIAdapter1 *activeAdapter = nullptr; QRhiDriverInfo driverInfoStruct; QRhiD3D12NativeHandles nativeHandlesStruct; bool deviceLost = false; ID3D12CommandQueue *cmdQueue = nullptr; ID3D12Fence *fullFence = nullptr; HANDLE fullFenceEvent = nullptr; UINT64 fullFenceCounter = 0; ID3D12CommandAllocator *cmdAllocators[QD3D12_FRAMES_IN_FLIGHT] = {}; QD3D12MemoryAllocator vma; QD3D12CpuDescriptorPool rtvPool; QD3D12CpuDescriptorPool dsvPool; QD3D12CpuDescriptorPool cbvSrvUavPool; QD3D12ObjectPool resourcePool; QD3D12ObjectPool pipelinePool; QD3D12ObjectPool rootSignaturePool; QD3D12ReleaseQueue releaseQueue; QD3D12ResourceBarrierGenerator barrierGen; QD3D12SamplerManager samplerMgr; QD3D12MipmapGenerator mipmapGen; QD3D12StagingArea smallStagingAreas[QD3D12_FRAMES_IN_FLIGHT]; QD3D12ShaderVisibleDescriptorHeap shaderVisibleCbvSrvUavHeap; UINT64 timestampTicksPerSecond = 0; QD3D12QueryHeap timestampQueryHeap; QD3D12StagingArea timestampReadbackArea; IDCompositionDevice *dcompDevice = nullptr; QD3D12SwapChain *currentSwapChain = nullptr; QSet swapchains; QD3D12ShaderBytecodeCache shaderBytecodeCache; QVarLengthArray activeReadbacks; bool offscreenActive = false; QD3D12CommandBuffer *offscreenCb[QD3D12_FRAMES_IN_FLIGHT] = {}; struct { bool multiView = false; bool textureViewFormat = false; } caps; }; QT_END_NAMESPACE #endif // __ID3D12Device2_INTERFACE_DEFINED__ #endif