// Copyright Epic Games, Inc. All Rights Reserved. #include "D3D12RHIPrivate.h" #include "ClearReplacementShaders.h" // ----------------------------------------------------------------------------------------------------- // // FD3D12UnorderedAccessView // // ----------------------------------------------------------------------------------------------------- FD3D12UnorderedAccessView::FD3D12UnorderedAccessView(FD3D12Device* InDevice, FD3D12UnorderedAccessView* FirstLinkedObject) : TD3D12View(InDevice, ED3D12ViewType::UnorderedAccess, ERHIDescriptorHeapType::Standard, FirstLinkedObject) { } void FD3D12UnorderedAccessView::UpdateResourceInfo(const FResourceInfo& InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InD3DViewDesc, EFlags InFlags) { OffsetInBytes = 0; StrideInBytes = 0; // // Buffer views can apply an offset in bytes from the start of the logical resource. // // Reconstruct this value and store it for later. We'll need it if the view is renamed, // to determine where the view should exist within the bounds of the new resource location. // if (InD3DViewDesc.ViewDimension == D3D12_UAV_DIMENSION_BUFFER) { StrideInBytes = InD3DViewDesc.Format == DXGI_FORMAT_UNKNOWN ? InD3DViewDesc.Buffer.StructureByteStride : UE::DXGIUtilities::GetFormatSizeInBytes(InD3DViewDesc.Format); check(StrideInBytes > 0); OffsetInBytes = (InD3DViewDesc.Buffer.FirstElement * StrideInBytes) - InResource.ResourceLocation->GetOffsetFromBaseOfResource(); check((OffsetInBytes % StrideInBytes) == 0); } // // UAVs optionally support a hidden counter. D3D12 requires the user to allocate this as a buffer resource. // if (EnumHasAnyFlags(InFlags, EFlags::NeedsCounter)) { FD3D12Device* Device = GetParentDevice(); const FRHIGPUMask Node = Device->GetGPUMask(); Device->GetParentAdapter()->CreateBuffer( CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT, Node.GetNative(), Node.GetNative()) , Node , D3D12_RESOURCE_STATE_UNORDERED_ACCESS , ED3D12ResourceStateMode::MultiState , D3D12_RESOURCE_STATE_UNORDERED_ACCESS , 4 , CounterResource.GetInitReference() , TEXT("Counter") , D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS ); } } void FD3D12UnorderedAccessView::CreateView(FResourceInfo const& InResource, D3D12_UNORDERED_ACCESS_VIEW_DESC const& InD3DViewDesc, EFlags InFlags) { UpdateResourceInfo(InResource, InD3DViewDesc, InFlags); TD3D12View::CreateView(InResource, InD3DViewDesc); } void FD3D12UnorderedAccessView::UpdateView(FD3D12ContextArray const& Contexts, const FResourceInfo& InResource, const D3D12_UNORDERED_ACCESS_VIEW_DESC& InD3DViewDesc, EFlags InFlags) { UpdateResourceInfo(InResource, InD3DViewDesc, InFlags); TD3D12View::UpdateView(Contexts, InResource, InD3DViewDesc); } void FD3D12UnorderedAccessView::ResourceRenamed(FD3D12ContextArray const& Contexts, FD3D12BaseShaderResource* InRenamedResource, FD3D12ResourceLocation* InNewResourceLocation) { // Buffer SRV descriptors contain offsets / GPU virtual addresses which need to be updated to match the new resource location. if (D3DViewDesc.ViewDimension == D3D12_UAV_DIMENSION_BUFFER) { D3DViewDesc.Buffer.FirstElement = (OffsetInBytes + InNewResourceLocation->GetOffsetFromBaseOfResource()) / StrideInBytes; } TD3D12View::ResourceRenamed(Contexts, InRenamedResource, InNewResourceLocation); } void FD3D12UnorderedAccessView::UpdateDescriptor() { ID3D12Resource* Resource = ResourceInfo.Resource->GetResource(); ID3D12Resource* Counter = CounterResource ? CounterResource->GetResource() : nullptr; #if !D3D12RHI_SUPPORTS_UNCOMPRESSED_UAV const D3D12_RESOURCE_DESC* AliasResourceDesc = ResourceInfo.Resource->GetUAVAccessResourceDesc(); if (AliasResourceDesc) { GetParentDevice()->CreateUnorderedAccessViewAlias( Resource, Counter, *AliasResourceDesc, D3DViewDesc, OfflineCpuHandle); } else #endif { GetParentDevice()->GetDevice()->CreateUnorderedAccessView( Resource, Counter, &D3DViewDesc, OfflineCpuHandle ); } OfflineCpuHandle.IncrementVersion(); } static FD3D12UnorderedAccessView::EFlags TranslateDesc(D3D12_UNORDERED_ACCESS_VIEW_DESC& UAVDesc, FD3D12Buffer* Buffer, const FRHIViewDesc::FBufferUAV::FViewInfo& Info) { FD3D12UnorderedAccessView::EFlags Flags = FD3D12UnorderedAccessView::EFlags::None; if (!Info.bNullView) { if (Info.bAppendBuffer || Info.bAtomicCounter) { Flags = FD3D12UnorderedAccessView::EFlags::NeedsCounter; } UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; UAVDesc.Format = UE::DXGIUtilities::FindUnorderedAccessFormat(DXGI_FORMAT(GPixelFormats[Info.Format].PlatformFormat)); UAVDesc.Buffer.FirstElement = (Info.OffsetInBytes + Buffer->ResourceLocation.GetOffsetFromBaseOfResource()) / Info.StrideInBytes; UAVDesc.Buffer.NumElements = Info.NumElements; switch (Info.BufferType) { case FRHIViewDesc::EBufferType::Raw: UAVDesc.Format = DXGI_FORMAT_R32_TYPELESS; UAVDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; break; case FRHIViewDesc::EBufferType::Structured: UAVDesc.Buffer.StructureByteStride = Info.StrideInBytes; break; case FRHIViewDesc::EBufferType::Typed: // Nothing more to specify break; default: checkNoEntry(); // unsupported / unimplemented break; } } return Flags; } static FD3D12UnorderedAccessView::EFlags TranslateDesc(D3D12_UNORDERED_ACCESS_VIEW_DESC& UAVDesc, FD3D12Texture* Texture, const FRHIViewDesc::FTextureUAV::FViewInfo& Info) { const FRHITextureDesc& TextureDesc = Texture->GetDesc(); check(TextureDesc.NumSamples == 1); DXGI_FORMAT const ViewFormat = UE::DXGIUtilities::FindUnorderedAccessFormat(DXGI_FORMAT(GPixelFormats[Info.Format].PlatformFormat)); DXGI_FORMAT const BaseFormat = UE::DXGIUtilities::GetPlatformTextureResourceFormat(DXGI_FORMAT(GPixelFormats[TextureDesc.Format].PlatformFormat), TextureDesc.Flags); UAVDesc.Format = ViewFormat; uint32 const PlaneSlice = UE::DXGIUtilities::GetPlaneSliceFromViewFormat(BaseFormat, ViewFormat); FRHIRange8 const PlaneRange(PlaneSlice, 1); FRHIViewDesc::EDimension ViewDimension = UE::RHICore::AdjustViewInfoDimensionForNarrowing(Info, TextureDesc); switch (ViewDimension) { case FRHIViewDesc::EDimension::Texture2D: UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; UAVDesc.Texture2D.MipSlice = Info.MipLevel; UAVDesc.Texture2D.PlaneSlice = PlaneSlice; break; case FRHIViewDesc::EDimension::TextureCube: case FRHIViewDesc::EDimension::TextureCubeArray: UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; UAVDesc.Texture2DArray.FirstArraySlice = Info.ArrayRange.First * 6; UAVDesc.Texture2DArray.ArraySize = Info.ArrayRange.Num * 6; UAVDesc.Texture2DArray.MipSlice = Info.MipLevel; UAVDesc.Texture2DArray.PlaneSlice = PlaneSlice; break; case FRHIViewDesc::EDimension::Texture2DArray: UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; UAVDesc.Texture2DArray.FirstArraySlice = Info.ArrayRange.First; UAVDesc.Texture2DArray.ArraySize = Info.ArrayRange.Num; UAVDesc.Texture2DArray.MipSlice = Info.MipLevel; UAVDesc.Texture2DArray.PlaneSlice = PlaneSlice; break; case FRHIViewDesc::EDimension::Texture3D: UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; UAVDesc.Texture3D.FirstWSlice = 0; UAVDesc.Texture3D.WSize = FMath::Max(TextureDesc.Depth >> Info.MipLevel, 1); UAVDesc.Texture3D.MipSlice = Info.MipLevel; break; default: checkNoEntry(); break; } return FD3D12UnorderedAccessView::EFlags::None; } void FD3D12UnorderedAccessView_RHI::CreateView() { if (IsBuffer()) { FD3D12Buffer* Buffer = FD3D12DynamicRHI::ResourceCast(GetBuffer()); D3D12_UNORDERED_ACCESS_VIEW_DESC UAVDesc{}; const EFlags CreateFlags = TranslateDesc(UAVDesc, Buffer, ViewDesc.Buffer.UAV.GetViewInfo(Buffer)); FD3D12UnorderedAccessView::CreateView(Buffer, UAVDesc, CreateFlags); } else { FD3D12Texture* Texture = FD3D12DynamicRHI::ResourceCast(GetTexture()); D3D12_UNORDERED_ACCESS_VIEW_DESC UAVDesc{}; const EFlags CreateFlags = TranslateDesc(UAVDesc, Texture, ViewDesc.Texture.UAV.GetViewInfo(Texture)); FD3D12UnorderedAccessView::CreateView(Texture, UAVDesc, CreateFlags); } } void FD3D12UnorderedAccessView_RHI::UpdateView(FD3D12ContextArray const& Contexts) { if (IsBuffer()) { FD3D12Buffer* Buffer = FD3D12DynamicRHI::ResourceCast(GetBuffer()); D3D12_UNORDERED_ACCESS_VIEW_DESC UAVDesc{}; const EFlags CreateFlags = TranslateDesc(UAVDesc, Buffer, ViewDesc.Buffer.UAV.GetViewInfo(Buffer)); FD3D12UnorderedAccessView::UpdateView(Contexts, Buffer, UAVDesc, CreateFlags); } else { FD3D12Texture* Texture = FD3D12DynamicRHI::ResourceCast(GetTexture()); D3D12_UNORDERED_ACCESS_VIEW_DESC UAVDesc{}; const EFlags CreateFlags = TranslateDesc(UAVDesc, Texture, ViewDesc.Texture.UAV.GetViewInfo(Texture)); FD3D12UnorderedAccessView::UpdateView(Contexts, Texture, UAVDesc, CreateFlags); } } FD3D12UnorderedAccessView_RHI::FD3D12UnorderedAccessView_RHI(FD3D12Device* InDevice, FRHIViewableResource* InResource, FRHIViewDesc const& InViewDesc, FD3D12UnorderedAccessView_RHI* FirstLinkedObject) : FRHIUnorderedAccessView(InResource, InViewDesc) , FD3D12UnorderedAccessView(InDevice, FirstLinkedObject) {} // ----------------------------------------------------------------------------------------------------- // // RHI Create Functions // // ----------------------------------------------------------------------------------------------------- FUnorderedAccessViewRHIRef FD3D12DynamicRHI::RHICreateUnorderedAccessView(FRHICommandListBase& RHICmdList, FRHIViewableResource* Resource, FRHIViewDesc const& ViewDesc) { FRHIGPUMask RelevantGPUs = ViewDesc.IsBuffer() ? FD3D12DynamicRHI::ResourceCast(static_cast(Resource))->GetLinkedObjectsGPUMask() : FD3D12DynamicRHI::ResourceCast(static_cast(Resource))->GetLinkedObjectsGPUMask(); FD3D12UnorderedAccessView_RHI* View = GetAdapter().CreateLinkedObject(RelevantGPUs, [&](FD3D12Device* Device, FD3D12UnorderedAccessView_RHI* FirstLinkedObject) { FRHIViewableResource* TargetResource = ViewDesc.IsBuffer() ? static_cast(FD3D12DynamicRHI::ResourceCast(static_cast(Resource), Device->GetGPUIndex())) : static_cast(FD3D12DynamicRHI::ResourceCast(static_cast(Resource), Device->GetGPUIndex())); return new FD3D12UnorderedAccessView_RHI(Device, TargetResource, ViewDesc, FirstLinkedObject); }); View->CreateViews(RHICmdList); return View; } // ----------------------------------------------------------------------------------------------------- // // UAV Clear Functions // // ----------------------------------------------------------------------------------------------------- void FD3D12CommandContext::ClearUAV(TRHICommandList_RecursiveHazardous& RHICmdList, FD3D12UnorderedAccessView_RHI* UnorderedAccessView, const void* ClearValues, bool bFloat) { const D3D12_RESOURCE_DESC& ResourceDesc = static_cast(UnorderedAccessView)->GetResource()->GetDesc(); const D3D12_UNORDERED_ACCESS_VIEW_DESC& UAVDesc = UnorderedAccessView->GetD3DDesc(); // Only structured buffers can have an unknown format check(UAVDesc.ViewDimension == D3D12_UAV_DIMENSION_BUFFER || UAVDesc.Format != DXGI_FORMAT_UNKNOWN); EClearReplacementValueType ValueType = bFloat ? EClearReplacementValueType::Float : EClearReplacementValueType::Uint32; switch (UAVDesc.Format) { case DXGI_FORMAT_R32G32B32A32_SINT: case DXGI_FORMAT_R32G32B32_SINT: case DXGI_FORMAT_R16G16B16A16_SINT: case DXGI_FORMAT_R32G32_SINT: case DXGI_FORMAT_R8G8B8A8_SINT: case DXGI_FORMAT_R16G16_SINT: case DXGI_FORMAT_R32_SINT: case DXGI_FORMAT_R8G8_SINT: case DXGI_FORMAT_R16_SINT: case DXGI_FORMAT_R8_SINT: ValueType = EClearReplacementValueType::Int32; break; case DXGI_FORMAT_R32G32B32A32_UINT: case DXGI_FORMAT_R32G32B32_UINT: case DXGI_FORMAT_R16G16B16A16_UINT: case DXGI_FORMAT_R32G32_UINT: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: case DXGI_FORMAT_R10G10B10A2_UINT: case DXGI_FORMAT_R8G8B8A8_UINT: case DXGI_FORMAT_R16G16_UINT: case DXGI_FORMAT_R32_UINT: case DXGI_FORMAT_D24_UNORM_S8_UINT: case DXGI_FORMAT_X24_TYPELESS_G8_UINT: case DXGI_FORMAT_R8G8_UINT: case DXGI_FORMAT_R16_UINT: case DXGI_FORMAT_R8_UINT: ValueType = EClearReplacementValueType::Uint32; break; } ensureMsgf((UAVDesc.Format == DXGI_FORMAT_UNKNOWN) || (bFloat == (ValueType == EClearReplacementValueType::Float)), TEXT("Attempt to clear a UAV using the wrong RHIClearUAV function. Float vs Integer mismatch.")); if (UAVDesc.ViewDimension == D3D12_UAV_DIMENSION_BUFFER) { if (UAVDesc.Format == DXGI_FORMAT_UNKNOWN || (UAVDesc.Buffer.Flags & D3D12_BUFFER_UAV_FLAG_RAW) != 0) { // Structured buffer. RHICmdList.RunOnContext([UnorderedAccessView, ClearValues](FD3D12CommandContext& Context) { const D3D12_UNORDERED_ACCESS_VIEW_DESC& UAVDesc = UnorderedAccessView->GetD3DDesc(); // Alias the structured buffer with an R32_UINT UAV to perform the clear. // We construct a temporary UAV on the offline heap, copy it to the online heap, and then call ClearUnorderedAccessViewUint. FD3D12Device* ParentDevice = Context.GetParentDevice(); ID3D12Device* Device = ParentDevice->GetDevice(); D3D12_UNORDERED_ACCESS_VIEW_DESC R32UAVDesc{}; if ((UAVDesc.Buffer.Flags & D3D12_BUFFER_UAV_FLAG_RAW) != 0) { // Raw UAVs will already be setup correctly for us. check(UAVDesc.Format == DXGI_FORMAT_R32_TYPELESS); R32UAVDesc = UAVDesc; } else { // Structured buffer stride must be a multiple of sizeof(uint32) check(UAVDesc.Buffer.StructureByteStride % sizeof(uint32) == 0); uint32 DwordsPerElement = UAVDesc.Buffer.StructureByteStride / sizeof(uint32); R32UAVDesc.Format = DXGI_FORMAT_R32_UINT; R32UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; R32UAVDesc.Buffer.FirstElement = UAVDesc.Buffer.FirstElement * DwordsPerElement; R32UAVDesc.Buffer.NumElements = UAVDesc.Buffer.NumElements * DwordsPerElement; } // Scoped view will free the offline CPU handle once we return FD3D12UnorderedAccessView UAV(ParentDevice, nullptr); // Always single GPU object, so FirstLinkedObject is nullptr UAV.CreateView(UnorderedAccessView->GetResourceLocation(), R32UAVDesc, FD3D12UnorderedAccessView::EFlags::None); FD3D12OfflineDescriptor OfflineHandle = UAV.GetOfflineCpuHandle(); D3D12_GPU_DESCRIPTOR_HANDLE GPUHandle{}; Context.OpenIfNotAlready(); #if PLATFORM_SUPPORTS_BINDLESS_RENDERING if (Context.StateCache.GetDescriptorCache()->IsUsingBindlessHeap()) { check(UAV.GetBindlessHandle().IsValid()); // We just allocated a descriptor inline. Since this could have recreated the descriptor heaps, // force a refresh of the current GPU heap. If it didn't change, no extra commands should be added. Context.GetBindlessState().RefreshDescriptorHeap(); Context.FlushPendingDescriptorUpdates(); // Make sure a descriptor heap is actually bound. This will noop if they were bound previously. Context.StateCache.GetDescriptorCache()->SetDescriptorHeaps(ED3D12SetDescriptorHeapsFlags::Bindless); FD3D12DescriptorHeap* BindlessHeap = Context.GetBindlessResourcesHeap(); UE::D3D12Descriptors::CopyDescriptor(ParentDevice, BindlessHeap, UAV.GetBindlessHandle(), OfflineHandle); GPUHandle = BindlessHeap->GetGPUSlotHandle(UAV.GetBindlessHandle().GetIndex()); } else #endif { // Check if the view heap is full and needs to rollover. if (!Context.StateCache.GetDescriptorCache()->GetCurrentViewHeap()->CanReserveSlots(1)) { Context.StateCache.GetDescriptorCache()->GetCurrentViewHeap()->RollOver(); } // Make sure a descriptor heap is actually bound. This will noop if they were bound previously. Context.StateCache.GetDescriptorCache()->SetDescriptorHeaps(ED3D12SetDescriptorHeapsFlags::None); uint32 ReservedSlot = Context.StateCache.GetDescriptorCache()->GetCurrentViewHeap()->ReserveSlots(1); D3D12_CPU_DESCRIPTOR_HANDLE DestSlot = Context.StateCache.GetDescriptorCache()->GetCurrentViewHeap()->GetCPUSlotHandle(ReservedSlot); GPUHandle = Context.StateCache.GetDescriptorCache()->GetCurrentViewHeap()->GetGPUSlotHandle(ReservedSlot); Device->CopyDescriptorsSimple(1, DestSlot, OfflineHandle, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); } Context.FlushResourceBarriers(); Context.GraphicsCommandList()->ClearUnorderedAccessViewUint(GPUHandle, OfflineHandle, UAV.GetResource()->GetResource(), *reinterpret_cast(ClearValues), 0, nullptr); Context.UpdateResidency(UAV.GetResource()); Context.ConditionalSplitCommandList(); #if (RHI_NEW_GPU_PROFILER == 0) if (Context.IsDefaultContext()) { ParentDevice->RegisterGPUWork(1); } #endif }); } else { ClearUAVShader_T(RHICmdList, UnorderedAccessView, UAVDesc.Buffer.NumElements, 1, 1, ClearValues, ValueType); } } else { if (UAVDesc.ViewDimension == D3D12_UAV_DIMENSION_TEXTURE2D) { uint32 Width = ResourceDesc.Width >> UAVDesc.Texture2D.MipSlice; uint32 Height = ResourceDesc.Height >> UAVDesc.Texture2D.MipSlice; ClearUAVShader_T(RHICmdList, UnorderedAccessView, Width, Height, 1, ClearValues, ValueType); } else if (UAVDesc.ViewDimension == D3D12_UAV_DIMENSION_TEXTURE2DARRAY) { uint32 Width = ResourceDesc.Width >> UAVDesc.Texture2DArray.MipSlice; uint32 Height = ResourceDesc.Height >> UAVDesc.Texture2DArray.MipSlice; ClearUAVShader_T(RHICmdList, UnorderedAccessView, Width, Height, UAVDesc.Texture2DArray.ArraySize, ClearValues, ValueType); } else if (UAVDesc.ViewDimension == D3D12_UAV_DIMENSION_TEXTURE3D) { // @todo - is WSize / mip index handling here correct? uint32 Width = ResourceDesc.Width >> UAVDesc.Texture2DArray.MipSlice; uint32 Height = ResourceDesc.Height >> UAVDesc.Texture2DArray.MipSlice; ClearUAVShader_T(RHICmdList, UnorderedAccessView, Width, Height, UAVDesc.Texture3D.WSize, ClearValues, ValueType); } else { ensure(0); } } } void FD3D12CommandContext::RHIClearUAVFloat(FRHIUnorderedAccessView* UnorderedAccessViewRHI, const FVector4f& Values) { TRHICommandList_RecursiveHazardous RHICmdList(this); ClearUAV(RHICmdList, RetrieveObject(UnorderedAccessViewRHI), &Values, true); } void FD3D12CommandContext::RHIClearUAVUint(FRHIUnorderedAccessView* UnorderedAccessViewRHI, const FUintVector4& Values) { TRHICommandList_RecursiveHazardous RHICmdList(this); ClearUAV(RHICmdList, RetrieveObject(UnorderedAccessViewRHI), &Values, false); }