// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "D3D12RHICommon.h" #include "D3D12Descriptors.h" #include "D3D12Resources.h" #include "RHIResources.h" class FD3D12Buffer; class FD3D12Texture; class FD3D12RayTracingScene; struct FD3D12ResidencyHandle; struct FD3D12DefaultViews { FD3D12OfflineDescriptor NullSRV; FD3D12OfflineDescriptor NullRTV; FD3D12OfflineDescriptor NullUAV; FD3D12OfflineDescriptor NullCBV; FD3D12OfflineDescriptor NullDSV; TRefCountPtr DefaultSampler; }; // Holds the mip, array and plane range for a view, as well as the total number of these for the underlying resource. struct FD3D12ViewRange { FD3D12ViewRange() = default; FD3D12ViewRange(D3D12_CONSTANT_BUFFER_VIEW_DESC const& ViewDesc); FD3D12ViewRange(D3D12_SHADER_RESOURCE_VIEW_DESC const& ViewDesc); FD3D12ViewRange(D3D12_UNORDERED_ACCESS_VIEW_DESC const& ViewDesc); FD3D12ViewRange(D3D12_RENDER_TARGET_VIEW_DESC const& ViewDesc); FD3D12ViewRange(D3D12_DEPTH_STENCIL_VIEW_DESC const& ViewDesc); // @todo remove this bool DoesNotOverlap(FD3D12ViewRange const& Other) const { return Mip .ExclusiveLast() <= Other.Mip .First || Other.Mip .ExclusiveLast() <= Mip .First || Array.ExclusiveLast() <= Other.Array.First || Other.Array.ExclusiveLast() <= Array.First || Plane.ExclusiveLast() <= Other.Plane.First || Other.Plane.ExclusiveLast() <= Plane.First; } // @todo remove this uint8 MostDetailedMip() const { return Mip.First; } // The subresource range covered by the view. FRHIRange16 Array; FRHIRange8 Plane; FRHIRange8 Mip; }; struct FD3D12ResourceLayout { FD3D12ResourceLayout() = default; FD3D12ResourceLayout(FD3D12ResourceDesc const& ResourceDesc) : NumArraySlices(ResourceDesc.DepthOrArraySize) , NumPlanes (UE::DXGIUtilities::GetPlaneCount(ResourceDesc.Format)) , NumMips (ResourceDesc.MipLevels) {} uint16 NumArraySlices = 0; uint8 NumPlanes = 0; uint8 NumMips = 0; }; struct FD3D12ViewSubset { FD3D12ViewSubset() = default; FD3D12ViewSubset(FD3D12ResourceLayout const& Layout, FD3D12ViewRange const& Range) : Layout(Layout) , Range (Range) {} bool IsWholeResource() const { return Range.Mip .First == 0 && Range.Mip .Num == Layout.NumMips && Range.Array.First == 0 && Range.Array.Num == Layout.NumArraySlices && Range.Plane.First == 0 && Range.Plane.Num == Layout.NumPlanes; } bool HasPlane(uint32 PlaneIndex) const { return Range.Plane.IsInRange(PlaneIndex); } FD3D12ViewSubset SelectPlane(uint32 PlaneIndex) const { check(PlaneIndex >= Range.Plane.First && PlaneIndex < Range.Plane.ExclusiveLast()); FD3D12ViewSubset Copy { *this }; Copy.Range.Plane = { PlaneIndex, 1 }; return Copy; } // // This iterator iterates over the subresources within a view subset. eg: // // for (uint32 SubresourceIndex : ViewSubset) // { // // Action for each subresource // } // class FIterator final { public: FIterator(FD3D12ViewSubset const& ViewSubset, uint8 MipSlice, uint16 ArraySlice, uint8 PlaneSlice) : MipMax (ViewSubset.Range.Mip.Num) , ArrayMax (ViewSubset.Range.Array.Num) , ArrayStride(ViewSubset.Layout.NumMips) , PlaneStride(ViewSubset.Layout.NumMips * (ViewSubset.Layout.NumArraySlices - ViewSubset.Range.Array.Num)) { MipRangeStart = D3D12CalcSubresource( MipSlice, ArraySlice, PlaneSlice, ViewSubset.Layout.NumMips, ViewSubset.Layout.NumArraySlices ); } FIterator& operator ++ () { if (++MipOffset == MipMax) { // Move to next array slice MipOffset = 0; MipRangeStart += ArrayStride; if (++ArrayOffset == ArrayMax) { // Move to next plane slice ArrayOffset = 0; MipRangeStart += PlaneStride; } } return *this; } uint32 operator * () const { return MipRangeStart + MipOffset; } bool operator == (FIterator const& RHS) const { return *(*this) == *RHS; } bool operator != (FIterator const& RHS) const { return !(*this == RHS); } private: // Constants uint32 const MipMax; uint32 const ArrayMax; uint32 const ArrayStride; uint32 const PlaneStride; // Counters uint32 MipRangeStart; uint32 MipOffset = 0; uint32 ArrayOffset = 0; }; FIterator begin() const { return FIterator(*this, Range.Mip.First, Range.Array.First, Range.Plane.First ); } FIterator end () const { return FIterator(*this, Range.Mip.First, Range.Array.First, Range.Plane.ExclusiveLast()); } FD3D12ResourceLayout Layout; FD3D12ViewRange Range; }; // Enumeration of supported view types. Each view type must pass this up to their parent FD3D12View constructor. enum class ED3D12ViewType : uint8 { ShaderResource, ConstantBuffer, UnorderedAccess, RenderTarget, DepthStencil, }; // Manages descriptor allocations and view creation class FD3D12View : public FD3D12DeviceChild, public FD3D12ShaderResourceRenameListener { typedef FD3D12OfflineDescriptor FD3D12DefaultViews::* FNullDescPtr; public: enum class EReason { InitialCreate, UpdateOrRename, }; struct FResourceInfo { FD3D12BaseShaderResource* BaseResource = nullptr; FD3D12ResourceLocation* ResourceLocation = nullptr; FD3D12Resource* Resource = nullptr; FResourceInfo() = default; // Constructor for renamable shader resources FResourceInfo(FD3D12BaseShaderResource* InBaseResource) : BaseResource (InBaseResource) , ResourceLocation(InBaseResource ? &InBaseResource->ResourceLocation : nullptr) , Resource (InBaseResource ? InBaseResource->GetResource() : nullptr) {} // Constructor for manual views (does not automatically register for resource renames) FResourceInfo(FD3D12ResourceLocation* InResourceLocation) : BaseResource (nullptr) , ResourceLocation(InResourceLocation) , Resource (InResourceLocation ? InResourceLocation->GetResource() : nullptr) {} }; FD3D12Resource* GetResource () const { check(IsInitialized()); return ResourceInfo.Resource; } FD3D12BaseShaderResource* GetShaderResource () const { check(IsInitialized()); return ResourceInfo.BaseResource; } FD3D12ResourceLocation* GetResourceLocation() const { check(IsInitialized()); return ResourceInfo.ResourceLocation; } TConstArrayView GetResidencyHandles() const { check(IsInitialized()); return ResourceInfo.Resource ? ResourceInfo.Resource->GetResidencyHandles() : TConstArrayView(); } FD3D12ViewSubset const& GetViewSubset () const { check(IsInitialized()); return ViewSubset; } FD3D12OfflineDescriptor GetOfflineCpuHandle() const { check(IsInitialized()); return OfflineCpuHandle; } #if PLATFORM_SUPPORTS_BINDLESS_RENDERING FRHIDescriptorHandle GetBindlessHandle() const { return BindlessHandle; } bool IsBindless () const { return BindlessHandle.IsValid(); } #else FRHIDescriptorHandle GetBindlessHandle() const { return FRHIDescriptorHandle(); } constexpr bool IsBindless () const { return false; } #endif protected: FD3D12View() = delete; FD3D12View(FD3D12Device* InDevice, ED3D12ViewType InViewType, ERHIDescriptorHeapType InHeapType, FD3D12View* FirstLinkedObject); virtual ~FD3D12View(); virtual void UpdateDescriptor() = 0; void UpdateResourceInfo(FResourceInfo const& InResource, FNullDescPtr NullDescriptor); void CreateView(FResourceInfo const& InResource, FNullDescPtr NullDescriptor); void UpdateView(FD3D12ContextArray const& Contexts, const FResourceInfo& InResource, FNullDescPtr NullDescriptor); bool IsInitialized() const { return ResourceInfo.ResourceLocation != nullptr; } void InitializeBindlessSlot(); void UpdateBindlessSlot(FD3D12ContextArray const& Contexts); FResourceInfo ResourceInfo; FD3D12ViewSubset ViewSubset; FD3D12OfflineDescriptor OfflineCpuHandle; #if PLATFORM_SUPPORTS_BINDLESS_RENDERING // BindlessHandle is shared across multi-GPU linked objects. First linked object is responsible for destroying the handle. // The D3D12View class doesn't have access to FD3D12LinkedAdapterObject::IsHeadLink(), so this separate bool is used. FRHIDescriptorHandle BindlessHandle; bool bFirstLinkedObject; #endif ERHIDescriptorHeapType const HeapType; }; template class TD3D12View : public FD3D12View { protected: TDesc D3DViewDesc; TD3D12View(FD3D12Device* InDevice, ED3D12ViewType InViewType, ERHIDescriptorHeapType InHeapType, TD3D12View* FirstLinkedObject) : FD3D12View(InDevice, InViewType, InHeapType, FirstLinkedObject) {} void CreateView(FResourceInfo const& InResource, TDesc const& InD3DViewDesc) { D3DViewDesc = InD3DViewDesc; ViewSubset.Range = InD3DViewDesc; FD3D12View::CreateView(InResource, TParent::Null); } void UpdateView(FD3D12ContextArray const& Contexts, FResourceInfo const& InResource, TDesc const& InD3DViewDesc) { D3DViewDesc = InD3DViewDesc; ViewSubset.Range = InD3DViewDesc; FD3D12View::UpdateView(Contexts, InResource, TParent::Null); } virtual void ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource* InRenamedResource, FD3D12ResourceLocation* InNewResourceLocation) override { // Can only be called if the base shader resource is not null. check(ResourceInfo.BaseResource == InRenamedResource && ResourceInfo.ResourceLocation == InNewResourceLocation); UpdateView(Contexts, InRenamedResource, D3DViewDesc); } public: TDesc const& GetD3DDesc() const { return D3DViewDesc; } }; class FD3D12ConstantBufferView final : public TD3D12View { public: static constexpr FD3D12OfflineDescriptor FD3D12DefaultViews::*Null { &FD3D12DefaultViews::NullCBV }; FD3D12ConstantBufferView(FD3D12Device* InParent, FD3D12ConstantBufferView* FirstLinkedObject); void CreateView(FResourceInfo const& InResource, uint32 InOffset, uint32 InAlignedSize); private: virtual void ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource* InRenamedResource, FD3D12ResourceLocation* InNewResourceLocation) override; virtual void UpdateDescriptor() override; uint32 Offset; }; class FD3D12ShaderResourceView : public TD3D12View { public: static constexpr FD3D12OfflineDescriptor FD3D12DefaultViews::*Null { &FD3D12DefaultViews::NullSRV }; enum class EFlags : uint8 { None = 0, SkipFastClearFinalize = 1 << 0, }; FRIEND_ENUM_CLASS_FLAGS(EFlags) FD3D12ShaderResourceView(FD3D12Device* InDevice, FD3D12ShaderResourceView* FirstLinkedObject); FD3D12ShaderResourceView(FD3D12Device* InDevice, FD3D12ShaderResourceView* FirstLinkedObject, FD3D12RayTracingScene* InRayTracingScene); void CreateView(FResourceInfo const& InResource, D3D12_SHADER_RESOURCE_VIEW_DESC const& InD3DViewDesc, EFlags InFlags); void UpdateView(FD3D12ContextArray const& Contexts, const FResourceInfo& InResource, const D3D12_SHADER_RESOURCE_VIEW_DESC& InD3DViewDesc, EFlags InFlags); bool GetSkipFastClearFinalize() const { return EnumHasAnyFlags(Flags, EFlags::SkipFastClearFinalize); } void UpdateMinLODClamp(FD3D12ContextArray const& Contexts, float MinLODClamp); FD3D12RayTracingScene* GetRayTracingScene() const { return RayTracingScene; } protected: void UpdateResourceInfo(const FResourceInfo& InResource, const D3D12_SHADER_RESOURCE_VIEW_DESC& InD3DViewDesc, EFlags InFlags); virtual void ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource* InRenamedResource, FD3D12ResourceLocation* InNewResourceLocation) override; virtual void UpdateDescriptor() override; // Required for resource renaming uint64 OffsetInBytes = 0; uint32 StrideInBytes = 0; EFlags Flags = EFlags::None; FD3D12RayTracingScene* RayTracingScene = nullptr; }; ENUM_CLASS_FLAGS(FD3D12ShaderResourceView::EFlags) class FD3D12UnorderedAccessView : public TD3D12View { public: static constexpr FD3D12OfflineDescriptor FD3D12DefaultViews::*Null { &FD3D12DefaultViews::NullUAV }; enum class EFlags : uint8 { None = 0, NeedsCounter = 1 << 0 }; FRIEND_ENUM_CLASS_FLAGS(EFlags) FD3D12UnorderedAccessView(FD3D12Device* InDevice, FD3D12UnorderedAccessView* FirstLinkedObject); void CreateView(FResourceInfo const& InResource, D3D12_UNORDERED_ACCESS_VIEW_DESC const& InD3DViewDesc, EFlags InFlags); void UpdateView(FD3D12ContextArray const& Contexts, const FResourceInfo& InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InD3DViewDesc, EFlags InFlags); FD3D12Resource* GetCounterResource() const { return CounterResource; } protected: void UpdateResourceInfo(const FResourceInfo& InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InD3DViewDesc, EFlags InFlags); virtual void ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource* InRenamedResource, FD3D12ResourceLocation* InNewResourceLocation) override; virtual void UpdateDescriptor() override; TRefCountPtr CounterResource; // Required for resource renaming uint64 OffsetInBytes = 0; uint32 StrideInBytes = 0; }; ENUM_CLASS_FLAGS(FD3D12UnorderedAccessView::EFlags) class FD3D12RenderTargetView final : public TD3D12View { public: static constexpr FD3D12OfflineDescriptor FD3D12DefaultViews::*Null { &FD3D12DefaultViews::NullRTV }; FD3D12RenderTargetView(FD3D12Device* InDevice, FD3D12RenderTargetView* FirstLinkedObject); using TD3D12View::CreateView; private: virtual void UpdateDescriptor() override; }; class FD3D12DepthStencilView final : public TD3D12View { public: static constexpr FD3D12OfflineDescriptor FD3D12DefaultViews::*Null { &FD3D12DefaultViews::NullDSV }; FD3D12DepthStencilView(FD3D12Device* InDevice, FD3D12DepthStencilView* FirstLinkedObject); using TD3D12View::CreateView; bool HasDepth () const { return GetViewSubset().HasPlane(0); } bool HasStencil() const { return GetViewSubset().HasPlane(1); } FD3D12ViewSubset GetDepthOnlySubset () const { return GetViewSubset().SelectPlane(0); } FD3D12ViewSubset GetStencilOnlySubset() const { return GetViewSubset().SelectPlane(1); } private: virtual void UpdateDescriptor() override; }; template struct FD3D12DeferredInitView : public FD3D12LinkedAdapterObject { void CreateViews(FRHICommandListBase& RHICmdList) { auto InitLambda = [this](FRHICommandListBase&) { for (TParent& LinkedView : *this) { LinkedView.CreateView(); } }; if (RHICmdList.IsTopOfPipe()) { // We have to defer the view initialization to the RHI thread if the resource is dynamic (and RHI threading is enabled), since dynamic resources can be renamed. // Also insert an RHI thread fence to prevent parallel translate tasks running until this command has completed. RHICmdList.EnqueueLambda(MoveTemp(InitLambda)); RHICmdList.RHIThreadFence(true); } else { // Run the command directly if we're bypassing RHI command list recording, or the buffer is not dynamic. InitLambda(RHICmdList); } } }; // Wrapper classes to expose the internal SRV/UAV types to the render as actual RHI resources. class FD3D12ShaderResourceView_RHI : public FRHIShaderResourceView , public FD3D12ShaderResourceView , public FD3D12DeferredInitView { public: FD3D12ShaderResourceView_RHI(FD3D12Device* InDevice, FRHIViewableResource* InResource, FRHIViewDesc const& InViewDesc, FD3D12ShaderResourceView_RHI* FirstLinkedObject); virtual void CreateView(); virtual void UpdateView(FD3D12ContextArray const& Contexts); virtual void ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource*, FD3D12ResourceLocation*) override { // Recreate the view from the FRHIViewDesc rather than simply updating the D3D12 descriptor handle from the existing D3D view desc. // This is because the streaming system may have replaced the underlying resource with one that has a different layout. UpdateView(Contexts); } virtual FRHIDescriptorHandle GetBindlessHandle() const override { return FD3D12ShaderResourceView::GetBindlessHandle(); } }; class FD3D12UnorderedAccessView_RHI : public FRHIUnorderedAccessView , public FD3D12UnorderedAccessView , public FD3D12DeferredInitView { public: FD3D12UnorderedAccessView_RHI(FD3D12Device* InDevice, FRHIViewableResource* InResource, FRHIViewDesc const& InViewDesc, FD3D12UnorderedAccessView_RHI* FirstLinkedObject); virtual void CreateView(); virtual void UpdateView(FD3D12ContextArray const& Contexts); virtual void ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource*, FD3D12ResourceLocation*) override { // Recreate the view from the FRHIViewDesc rather than simply updating the D3D12 descriptor handle from the existing D3D view desc. // This is because the streaming system may have replaced the underlying resource with one that has a different layout. UpdateView(Contexts); } virtual FRHIDescriptorHandle GetBindlessHandle() const override { return FD3D12UnorderedAccessView::GetBindlessHandle(); } }; template<> struct TD3D12ResourceTraits { typedef FD3D12ShaderResourceView_RHI TConcreteType; }; template<> struct TD3D12ResourceTraits { typedef FD3D12UnorderedAccessView_RHI TConcreteType; };