// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VulkanViewport.cpp: Vulkan viewport RHI implementation. =============================================================================*/ #include "VulkanRHIPrivate.h" #include "VulkanSwapChain.h" #include "VulkanPendingState.h" #include "VulkanContext.h" #include "VulkanBarriers.h" #include "GlobalShader.h" #include "HAL/PlatformAtomics.h" #include "Engine/RendererSettings.h" #include "StereoRenderUtils.h" #include "CommonRenderResources.h" #include "ScreenRendering.h" #include "RHIStaticStates.h" FVulkanBackBuffer::FVulkanBackBuffer(FVulkanDevice& Device, FVulkanViewport* InViewport, EPixelFormat Format, uint32 SizeX, uint32 SizeY, ETextureCreateFlags UEFlags) : FVulkanTexture(Device, FRHITextureCreateDesc::Create2D(TEXT("FVulkanBackBuffer"), SizeX, SizeY, Format).SetFlags(UEFlags).SetInitialState(ERHIAccess::Present), VK_NULL_HANDLE, {}) , Viewport(InViewport) { } void FVulkanBackBuffer::ReleaseAcquiredImage() { if (DefaultView) { // Do not invalidate view here, just remove a reference to it DefaultView = nullptr; PartialView = nullptr; } Image = VK_NULL_HANDLE; } void FVulkanBackBuffer::ReleaseViewport() { Viewport = nullptr; ReleaseAcquiredImage(); } void FVulkanBackBuffer::OnGetBackBufferImage(FRHICommandListImmediate& RHICmdList) { check(Viewport); if (GVulkanDelayAcquireImage == EDelayAcquireImageType::None) { FVulkanCommandListContext& Context = (FVulkanCommandListContext&)RHICmdList.GetContext().GetLowestLevelContext(); AcquireBackBufferImage(Context); } } void FVulkanBackBuffer::OnAdvanceBackBufferFrame(FRHICommandListImmediate& RHICmdList) { check(Viewport); ReleaseAcquiredImage(); } void FVulkanBackBuffer::AcquireBackBufferImage(FVulkanCommandListContext& Context) { check(Viewport); if (Image == VK_NULL_HANDLE) { if (Viewport->TryAcquireImageIndex()) { int32 AcquiredImageIndex = Viewport->AcquiredImageIndex; check(AcquiredImageIndex >= 0 && AcquiredImageIndex < Viewport->TextureViews.Num()); FVulkanView& ImageView = Viewport->TextureViews[AcquiredImageIndex]; Image = ImageView.GetTextureView().Image; DefaultView = &ImageView; PartialView = &ImageView; // Wait for semaphore signal before writing to backbuffer image Context.AddWaitSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, Viewport->AcquiredSemaphore); // :todo-jn: transition from unknown the first time } else { // fallback to a 'dummy' backbuffer check(Viewport->RenderingBackBuffer); FVulkanView* DummyView = Viewport->RenderingBackBuffer->DefaultView; Image = DummyView->GetTextureView().Image; DefaultView = DummyView; PartialView = DummyView; } } } FVulkanBackBuffer::~FVulkanBackBuffer() { check(IsImageOwner() == false); // Clear ImageOwnerType so ~FVulkanTexture2D() doesn't try to re-destroy it ImageOwnerType = EImageOwnerType::None; ReleaseAcquiredImage(); } FVulkanViewport::FVulkanViewport(FVulkanDevice* InDevice, void* InWindowHandle, uint32 InSizeX, uint32 InSizeY, bool bInIsFullscreen, EPixelFormat InPreferredPixelFormat) : VulkanRHI::FDeviceChild(InDevice) , SizeX(InSizeX) , SizeY(InSizeY) , bIsFullscreen(bInIsFullscreen) , PixelFormat(InPreferredPixelFormat) , AcquiredImageIndex(-1) , SwapChain(nullptr) , WindowHandle(InWindowHandle) , PresentCount(0) , bRenderOffscreen(false) , AcquiredSemaphore(nullptr) { check(IsInGameThread()); static IConsoleVariable* CVarVsync = IConsoleManager::Get().FindConsoleVariable(TEXT("r.VSync")); LockToVsync = CVarVsync->GetInt() != 0; FVulkanDynamicRHI::Get().Viewports.Add(this); // Make sure Instance is created FVulkanDynamicRHI::Get().InitInstance(); bRenderOffscreen = FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")); FVulkanPlatformWindowContext WindowContext(InWindowHandle); ENQUEUE_RENDER_COMMAND(CreateSwapchain)( [&WindowContext, VulkanViewport = this](FRHICommandListImmediate& RHICmdList) { RHICmdList.EnqueueLambda([&WindowContext, VulkanViewport](FRHICommandListBase& ExecutingCmdList) { VulkanViewport->CreateSwapchain(FVulkanCommandListContext::Get(ExecutingCmdList), nullptr, WindowContext); }); RHICmdList.SubmitAndBlockUntilGPUIdle(); }); FlushRenderingCommands(); if (SupportsStandardSwapchain()) { FCoreDelegates::OnSystemResolutionChanged.AddRaw(this, &FVulkanViewport::OnSystemResolutionChanged); } } FVulkanViewport::~FVulkanViewport() { RenderingBackBuffer = nullptr; if (RHIBackBuffer) { RHIBackBuffer->ReleaseViewport(); RHIBackBuffer = nullptr; } if (SupportsStandardSwapchain()) { TextureViews.Empty(); for (int32 Index = 0, NumBuffers = RenderingDoneSemaphores.Num(); Index < NumBuffers; ++Index) { RenderingDoneSemaphores[Index]->Release(); // FIXME: race condition on TransitionAndLayoutManager, could this be called from RT while RHIT is active? Device->NotifyDeletedImage(BackBufferImages[Index]->Image, true); BackBufferImages[Index] = nullptr; } SwapChain->Destroy(nullptr); delete SwapChain; SwapChain = nullptr; FCoreDelegates::OnSystemResolutionChanged.RemoveAll(this); } FVulkanDynamicRHI::Get().Viewports.Remove(this); } bool FVulkanViewport::DoCheckedSwapChainJob(FVulkanCommandListContext& Context, TFunction SwapChainJob) { int32 AttemptsPending = FVulkanPlatform::RecreateSwapchainOnFail() ? 4 : 0; int32 Status = SwapChainJob(this); while (FVulkanPlatformWindowContext::CanCreateSwapchainOnDemand() && Status < 0 && AttemptsPending > 0) { if (Status == (int32)FVulkanSwapChain::EStatus::OutOfDate) { UE_LOG(LogVulkanRHI, Verbose, TEXT("Swapchain is out of date! Trying to recreate the swapchain.")); } else if (Status == (int32)FVulkanSwapChain::EStatus::SurfaceLost) { UE_LOG(LogVulkanRHI, Warning, TEXT("Swapchain surface lost! Trying to recreate the swapchain.")); } else { check(0); } FVulkanPlatformWindowContext WindowContext(WindowHandle); RecreateSwapchain(Context, WindowContext); // Swapchain creation pushes some commands - flush the command buffers now to begin with a fresh state Context.FlushCommands(EVulkanFlushFlags::WaitForCompletion); Status = SwapChainJob(this); --AttemptsPending; } return Status >= 0; } bool FVulkanViewport::TryAcquireImageIndex() { if (SwapChain) { int32 Result = SwapChain->AcquireImageIndex(&AcquiredSemaphore); if (Result >= 0) { AcquiredImageIndex = Result; return true; } } return false; } FTextureRHIRef FVulkanViewport::GetBackBuffer(FRHICommandListImmediate& RHICmdList) { check(IsInRenderingThread()); // make sure we aren't in the middle of swapchain recreation (which can happen on e.g. RHI thread) FScopeLock LockSwapchain(&RecreatingSwapchain); if (SupportsStandardSwapchain() && GVulkanDelayAcquireImage != EDelayAcquireImageType::DelayAcquire) { check(RHICmdList.IsImmediate()); check(RHIBackBuffer); RHICmdList.EnqueueLambda([this](FRHICommandListImmediate& CmdList) { this->RHIBackBuffer->OnGetBackBufferImage(CmdList); }); return RHIBackBuffer.GetReference(); } return RenderingBackBuffer.GetReference(); } void FVulkanViewport::AdvanceBackBufferFrame(FRHICommandListImmediate& RHICmdList) { check(IsInRenderingThread()); if (SupportsStandardSwapchain() && GVulkanDelayAcquireImage != EDelayAcquireImageType::DelayAcquire) { check(RHIBackBuffer); RHICmdList.EnqueueLambda([this](FRHICommandListImmediate& CmdList) { this->RHIBackBuffer->OnAdvanceBackBufferFrame(CmdList); }); } } void FVulkanViewport::WaitForFrameEventCompletion() { if (FVulkanPlatform::RequiresWaitingForFrameCompletionEvent()) { static FCriticalSection CS; FScopeLock ScopeLock(&CS); if (LastFrameSyncPoint.IsValid()) { // If last frame's fence hasn't been signaled already, wait for it here if (!LastFrameSyncPoint->IsComplete()) { FVulkanDynamicRHI::Get().ProcessInterruptQueueUntil(LastFrameSyncPoint); } } } } void FVulkanViewport::IssueFrameEvent() { if (FVulkanPlatform::RequiresWaitingForFrameCompletionEvent()) { FVulkanCommandListContextImmediate& ImmediateContext = Device->GetImmediateContext(); LastFrameSyncPoint = ImmediateContext.GetContextSyncPoint(); ImmediateContext.FlushCommands(); } } FVulkanFramebuffer::FVulkanFramebuffer(FVulkanDevice& Device, const FRHISetRenderTargetsInfo& InRTInfo, const FVulkanRenderTargetLayout& RTLayout, const FVulkanRenderPass& RenderPass) : Framebuffer(VK_NULL_HANDLE) , NumColorRenderTargets(InRTInfo.NumColorRenderTargets) , NumColorAttachments(0) , DepthStencilRenderTargetImage(VK_NULL_HANDLE) , FragmentDensityImage(VK_NULL_HANDLE) { FMemory::Memzero(ColorRenderTargetImages); FMemory::Memzero(ColorResolveTargetImages); AttachmentTextureViews.Empty(RTLayout.GetNumAttachmentDescriptions()); auto CreateOwnedView = [&]() { const VkDescriptorType DescriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; FVulkanView* View = new FVulkanView(Device, DescriptorType); AttachmentTextureViews.Add(View); OwnedTextureViews.Add(View); return View; }; auto AddExternalView = [&](FVulkanView const* View) { AttachmentTextureViews.Add(View); }; uint32 MipIndex = 0; const VkExtent3D& RTExtents = RTLayout.GetExtent3D(); // Adreno does not like zero size RTs check(RTExtents.width != 0 && RTExtents.height != 0); uint32 NumLayers = RTExtents.depth; for (int32 Index = 0; Index < InRTInfo.NumColorRenderTargets; ++Index) { FRHITexture* RHITexture = InRTInfo.ColorRenderTarget[Index].Texture; if (!RHITexture) { continue; } FVulkanTexture* Texture = ResourceCast(RHITexture); const FRHITextureDesc& Desc = Texture->GetDesc(); // this could fire in case one of the textures is FVulkanBackBuffer and it has not acquired an image // with EDelayAcquireImageType::LazyAcquire acquire happens when texture transition to Writeable state // make sure you call TransitionResource(Writable, Tex) before using this texture as a render-target check(Texture->Image != VK_NULL_HANDLE); ColorRenderTargetImages[Index] = Texture->Image; MipIndex = InRTInfo.ColorRenderTarget[Index].MipIndex; if (Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D || Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { uint32 ArraySliceIndex = 0; uint32 NumArraySlices = 1; if (InRTInfo.ColorRenderTarget[Index].ArraySliceIndex == -1) { ArraySliceIndex = 0; NumArraySlices = Texture->GetNumberOfArrayLevels(); } else { ArraySliceIndex = InRTInfo.ColorRenderTarget[Index].ArraySliceIndex; NumArraySlices = 1; check(ArraySliceIndex < Texture->GetNumberOfArrayLevels()); } // About !RTLayout.GetIsMultiView(), from https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkFramebufferCreateInfo.html: // If the render pass uses multiview, then layers must be one if (Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY && !RTLayout.GetIsMultiView()) { NumLayers = NumArraySlices; } CreateOwnedView()->InitAsTextureView( Texture->Image , Texture->GetViewType() , Texture->GetFullAspectMask() , Desc.Format , Texture->ViewFormat , MipIndex , 1 , ArraySliceIndex , NumArraySlices , true , VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | (Texture->ImageUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) ); } else if (Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_CUBE || Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) { // Cube always renders one face at a time INC_DWORD_STAT(STAT_VulkanNumImageViews); CreateOwnedView()->InitAsTextureView( Texture->Image , VK_IMAGE_VIEW_TYPE_2D , Texture->GetFullAspectMask() , Desc.Format , Texture->ViewFormat , MipIndex , 1 , InRTInfo.ColorRenderTarget[Index].ArraySliceIndex , 1 , true , VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | (Texture->ImageUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) ); } else if (Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_3D) { CreateOwnedView()->InitAsTextureView( Texture->Image , VK_IMAGE_VIEW_TYPE_2D_ARRAY , Texture->GetFullAspectMask() , Desc.Format , Texture->ViewFormat , MipIndex , 1 , 0 , Desc.Depth , true , VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | (Texture->ImageUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) ); } else { ensure(0); } ++NumColorAttachments; // Check the RTLayout as well to make sure the resolve attachment is needed (Vulkan and Feature level specific) // See: FVulkanRenderTargetLayout constructor with FRHIRenderPassInfo if (InRTInfo.bHasResolveAttachments && RTLayout.GetHasResolveAttachments() && RTLayout.GetResolveAttachmentReferences()[Index].layout != VK_IMAGE_LAYOUT_UNDEFINED) { FRHITexture* ResolveRHITexture = InRTInfo.ColorResolveRenderTarget[Index].Texture; FVulkanTexture* ResolveTexture = ResourceCast(ResolveRHITexture); ColorResolveTargetImages[Index] = ResolveTexture->Image; //resolve attachments only supported for 2d/2d array textures if (ResolveTexture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D || ResolveTexture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { CreateOwnedView()->InitAsTextureView( ResolveTexture->Image , ResolveTexture->GetViewType() , ResolveTexture->GetFullAspectMask() , ResolveTexture->GetDesc().Format , ResolveTexture->ViewFormat , MipIndex , 1 , FMath::Max(0, (int32)InRTInfo.ColorRenderTarget[Index].ArraySliceIndex) , ResolveTexture->GetNumberOfArrayLevels() , true ); } } } if (RTLayout.GetHasDepthStencil()) { FVulkanTexture* Texture = ResourceCast(InRTInfo.DepthStencilRenderTarget.Texture); const FRHITextureDesc& Desc = Texture->GetDesc(); DepthStencilRenderTargetImage = Texture->Image; bool bHasStencil = (Texture->GetDesc().Format == PF_DepthStencil || Texture->GetDesc().Format == PF_X24_G8); check(Texture->PartialView); PartialDepthTextureView = Texture->PartialView; ensure(Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D || Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY || Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_CUBE); if (NumColorAttachments == 0 && Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_CUBE) { CreateOwnedView()->InitAsTextureView( Texture->Image , VK_IMAGE_VIEW_TYPE_2D_ARRAY , Texture->GetFullAspectMask() , Texture->GetDesc().Format , Texture->ViewFormat , MipIndex , 1 , 0 , 6 , true ); NumLayers = 6; } else if (Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D || Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { // depth attachments need a separate view to have no swizzle components, for validation correctness CreateOwnedView()->InitAsTextureView( Texture->Image , Texture->GetViewType() , Texture->GetFullAspectMask() , Texture->GetDesc().Format , Texture->ViewFormat , MipIndex , 1 , 0 , Texture->GetNumberOfArrayLevels() , true ); } else { AddExternalView(Texture->DefaultView); } if (RTLayout.GetHasDepthStencilResolve() && RTLayout.GetDepthStencilResolveAttachmentReference()->layout != VK_IMAGE_LAYOUT_UNDEFINED) { FRHITexture* ResolveRHITexture = InRTInfo.DepthStencilResolveRenderTarget.Texture; FVulkanTexture* ResolveTexture = ResourceCast(ResolveRHITexture); DepthStencilResolveRenderTargetImage = ResolveTexture->Image; // Resolve attachments only supported for 2d/2d array textures if (ResolveTexture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D || ResolveTexture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY) { CreateOwnedView()->InitAsTextureView( ResolveTexture->Image , ResolveTexture->GetViewType() , ResolveTexture->GetFullAspectMask() , ResolveTexture->GetDesc().Format , ResolveTexture->ViewFormat , MipIndex , 1 , 0 , ResolveTexture->GetNumberOfArrayLevels() , true ); } } } if (GRHISupportsAttachmentVariableRateShading && RTLayout.GetHasFragmentDensityAttachment()) { FVulkanTexture* Texture = ResourceCast(InRTInfo.ShadingRateTexture); FragmentDensityImage = Texture->Image; ensure(Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D || Texture->GetViewType() == VK_IMAGE_VIEW_TYPE_2D_ARRAY); CreateOwnedView()->InitAsTextureView( Texture->Image , Texture->GetViewType() , Texture->GetFullAspectMask() , Texture->GetDesc().Format , Texture->ViewFormat , MipIndex , 1 , 0 , Texture->GetNumberOfArrayLevels() , true ); } TArray AttachmentViews; AttachmentViews.Reserve(AttachmentTextureViews.Num()); for (FVulkanView const* View : AttachmentTextureViews) { AttachmentViews.Add(View->GetTextureView().View); } VkFramebufferCreateInfo CreateInfo; ZeroVulkanStruct(CreateInfo, VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO); CreateInfo.renderPass = RenderPass.GetHandle(); CreateInfo.attachmentCount = AttachmentViews.Num(); CreateInfo.pAttachments = AttachmentViews.GetData(); CreateInfo.width = RTExtents.width; CreateInfo.height = RTExtents.height; CreateInfo.layers = NumLayers; VERIFYVULKANRESULT_EXPANDED(VulkanRHI::vkCreateFramebuffer(Device.GetInstanceHandle(), &CreateInfo, VULKAN_CPU_ALLOCATOR, &Framebuffer)); RenderArea.offset.x = 0; RenderArea.offset.y = 0; RenderArea.extent.width = RTExtents.width; RenderArea.extent.height = RTExtents.height; INC_DWORD_STAT(STAT_VulkanNumFrameBuffers); } FVulkanFramebuffer::~FVulkanFramebuffer() { ensure(Framebuffer == VK_NULL_HANDLE); } void FVulkanFramebuffer::Destroy(FVulkanDevice& Device) { VulkanRHI::FDeferredDeletionQueue2& Queue = Device.GetDeferredDeletionQueue(); // will be deleted in reverse order Queue.EnqueueResource(VulkanRHI::FDeferredDeletionQueue2::EType::Framebuffer, Framebuffer); Framebuffer = VK_NULL_HANDLE; DEC_DWORD_STAT(STAT_VulkanNumFrameBuffers); } bool FVulkanFramebuffer::Matches(const FRHISetRenderTargetsInfo& InRTInfo) const { if (NumColorRenderTargets != InRTInfo.NumColorRenderTargets) { return false; } { const FRHIDepthRenderTargetView& B = InRTInfo.DepthStencilRenderTarget; if (B.Texture) { VkImage AImage = DepthStencilRenderTargetImage; VkImage BImage = ResourceCast(B.Texture)->Image; if (AImage != BImage) { return false; } } } { const FRHIDepthRenderTargetView& R = InRTInfo.DepthStencilResolveRenderTarget; if (R.Texture) { VkImage AImage = DepthStencilResolveRenderTargetImage; VkImage BImage = ResourceCast(R.Texture)->Image; if (AImage != BImage) { return false; } } } { FRHITexture* Texture = InRTInfo.ShadingRateTexture; if (Texture) { VkImage AImage = FragmentDensityImage; VkImage BImage = ResourceCast(Texture)->Image; if (AImage != BImage) { return false; } } } int32 AttachementIndex = 0; for (int32 Index = 0; Index < InRTInfo.NumColorRenderTargets; ++Index) { if (InRTInfo.bHasResolveAttachments) { const FRHIRenderTargetView& R = InRTInfo.ColorResolveRenderTarget[Index]; if (R.Texture) { VkImage AImage = ColorResolveTargetImages[AttachementIndex]; VkImage BImage = ResourceCast(R.Texture)->Image; if (AImage != BImage) { return false; } } } const FRHIRenderTargetView& B = InRTInfo.ColorRenderTarget[Index]; if (B.Texture) { VkImage AImage = ColorRenderTargetImages[AttachementIndex]; VkImage BImage = ResourceCast(B.Texture)->Image; if (AImage != BImage) { return false; } AttachementIndex++; } } return true; } // Tear down and recreate swapchain and related resources. void FVulkanViewport::RecreateSwapchain(FVulkanCommandListContext& Context, FVulkanPlatformWindowContext& WindowContext) { // Make sure everything is submitted and submission queue is idle Context.FlushCommands(EVulkanFlushFlags::WaitForCompletion); FScopeLock LockSwapchain(&RecreatingSwapchain); FVulkanSwapChainRecreateInfo RecreateInfo = { VK_NULL_HANDLE, VK_NULL_HANDLE }; DestroySwapchain(&RecreateInfo); CreateSwapchain(Context, &RecreateInfo, WindowContext); check(RecreateInfo.Surface == VK_NULL_HANDLE); check(RecreateInfo.SwapChain == VK_NULL_HANDLE); } void FVulkanViewport::Tick(float DeltaTime) { check(IsInGameThread()); if (SwapChain && FPlatformAtomics::AtomicRead(&LockToVsync) != SwapChain->DoesLockToVsync()) { FVulkanPlatformWindowContext WindowContext(WindowHandle); ENQUEUE_RENDER_COMMAND(UpdateVsync)( [this, &WindowContext](FRHICommandListImmediate& RHICmdList) { RecreateSwapchainFromRT(RHICmdList, PixelFormat, WindowContext); }); FlushRenderingCommands(); } } void FVulkanViewport::Resize(FRHICommandListImmediate& RHICmdList, uint32 InSizeX, uint32 InSizeY, bool bInIsFullscreen, EPixelFormat PreferredPixelFormat, FVulkanPlatformWindowContext& WindowContext) { check(IsInRenderingThread()); RHICmdList.EnqueueLambda([this, InSizeX, InSizeY, bInIsFullscreen, PreferredPixelFormat](FRHICommandListBase& ExecutingCmdList) { SizeX = InSizeX; SizeY = InSizeY; bIsFullscreen = bInIsFullscreen; PixelFormat = PreferredPixelFormat; }); RecreateSwapchainFromRT(RHICmdList, PreferredPixelFormat, WindowContext); } void FVulkanViewport::RecreateSwapchainFromRT(FRHICommandListImmediate& RHICmdList, EPixelFormat PreferredPixelFormat, FVulkanPlatformWindowContext& WindowContext) { check(IsInRenderingThread()); RHICmdList.EnqueueLambda([this, PreferredPixelFormat, &WindowContext](FRHICommandListBase& ExecutingCmdList) { FVulkanSwapChainRecreateInfo RecreateInfo = { VK_NULL_HANDLE, VK_NULL_HANDLE }; DestroySwapchain(&RecreateInfo); PixelFormat = PreferredPixelFormat; CreateSwapchain(FVulkanCommandListContext::Get(ExecutingCmdList), &RecreateInfo, WindowContext); check(RecreateInfo.Surface == VK_NULL_HANDLE); check(RecreateInfo.SwapChain == VK_NULL_HANDLE); }); RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread); } void FVulkanViewport::InitImages(FVulkanContextCommon& Context, TConstArrayView Images) { FVulkanCommandBuffer& CommandBuffer = Context.GetCommandBuffer(); VkClearColorValue ClearColor; FMemory::Memzero(ClearColor); const VkImageSubresourceRange Range = FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT); for (int32 Index = 0; Index < Images.Num(); ++Index) { uint32 ImageSizeX = SizeX; uint32 ImageSizeY = SizeY; VkSurfaceTransformFlagBitsKHR CachedSurfaceTransform = SwapChain->GetCachedSurfaceTransform(); if (CachedSurfaceTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || CachedSurfaceTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) { Swap(ImageSizeX, ImageSizeY); } BackBufferImages[Index] = (FVulkanTexture*)FVulkanDynamicRHI::Get().RHICreateTexture2DFromResource(PixelFormat, ImageSizeX, ImageSizeY, 1, 1, Images[Index], ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Presentable).GetReference(); const VkDescriptorType DescriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; TextureViews.Add((new FVulkanView(*Device, DescriptorType))->InitAsTextureView( Images[Index], VK_IMAGE_VIEW_TYPE_2D, VK_IMAGE_ASPECT_COLOR_BIT, PixelFormat, UEToVkTextureFormat(PixelFormat, false), 0, 1, 0, 1, false)); // Clear the swapchain to avoid a validation warning, and transition to PresentSrc { VulkanSetImageLayout(&CommandBuffer, Images[Index], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, Range); VulkanRHI::vkCmdClearColorImage(CommandBuffer.GetHandle(), Images[Index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &ClearColor, 1, &Range); VulkanSetImageLayout(&CommandBuffer, Images[Index], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, Range); } #if VULKAN_ENABLE_DRAW_MARKERS if (Device->GetSetDebugName()) { VulkanRHI::SetDebugName(Device->GetSetDebugName(), Device->GetInstanceHandle(), BackBufferImages[Index]->Image, "VulkanBackBuffer"); } #endif } } void FVulkanViewport::CreateSwapchain(FVulkanCommandListContext& Context, FVulkanSwapChainRecreateInfo* RecreateInfo, FVulkanPlatformWindowContext& WindowContext) { // Release a previous swapchain 'dummy' and a real backbuffer if any RenderingBackBuffer = nullptr; RHIBackBuffer = nullptr; if (SupportsStandardSwapchain()) { check(SwapChain == nullptr); if (WindowContext.IsValid()) { uint32 DesiredNumBackBuffers = NUM_BUFFERS; TArray Images; SwapChain = new FVulkanSwapChain( FVulkanDynamicRHI::Get().Instance, *Device, PixelFormat, SizeX, SizeY, bIsFullscreen, &DesiredNumBackBuffers, Images, LockToVsync, WindowContext, RecreateInfo ); checkf(Images.Num() >= NUM_BUFFERS, TEXT("We wanted at least %i images, actual Num: %i"), NUM_BUFFERS, Images.Num()); bool bCreateSemaphores = RenderingDoneSemaphores.IsEmpty(); checkf(bCreateSemaphores || RenderingDoneSemaphores.Num() == Images.Num(), TEXT("CreateSwapchain, image count is not expected to change")); BackBufferImages.SetNum(Images.Num()); RenderingDoneSemaphores.SetNum(Images.Num()); InitImages(Context, Images); if (bCreateSemaphores) { for (int32 Index = 0, NumBuffers = RenderingDoneSemaphores.Num(); Index < NumBuffers; ++Index) { RenderingDoneSemaphores[Index] = new VulkanRHI::FSemaphore(*Device); RenderingDoneSemaphores[Index]->AddRef(); } } } RHIBackBuffer = new FVulkanBackBuffer(*Device, this, PixelFormat, SizeX, SizeY, TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_ResolveTargetable); } else { PixelFormat = GetPixelFormatForNonDefaultSwapchain(); if (RecreateInfo != nullptr) { if (RecreateInfo->SwapChain) { FVulkanPlatform::DestroySwapchainKHR(Device->GetInstanceHandle(), RecreateInfo->SwapChain, VULKAN_CPU_ALLOCATOR); RecreateInfo->SwapChain = VK_NULL_HANDLE; } if (RecreateInfo->Surface) { VulkanRHI::vkDestroySurfaceKHR(FVulkanDynamicRHI::Get().Instance, RecreateInfo->Surface, VULKAN_CPU_ALLOCATOR); RecreateInfo->Surface = VK_NULL_HANDLE; } } } // We always create a 'dummy' backbuffer to gracefully handle SurfaceLost cases { uint32 BackBufferSizeX = RequiresRenderingBackBuffer() ? SizeX : 1; uint32 BackBufferSizeY = RequiresRenderingBackBuffer() ? SizeY : 1; const UE::StereoRenderUtils::FStereoShaderAspects Aspects(GMaxRHIShaderPlatform); const int kMultiViewCount = 2; // TODO: number of subresources may change in the future const FRHITextureCreateDesc CreateDesc = (Aspects.IsMobileMultiViewEnabled() ? FRHITextureCreateDesc::Create2DArray(TEXT("RenderingBackBufferArr"), BackBufferSizeX, BackBufferSizeY, kMultiViewCount, PixelFormat) : FRHITextureCreateDesc::Create2D(TEXT("RenderingBackBuffer"), BackBufferSizeX, BackBufferSizeY, PixelFormat)) .SetClearValue(FClearValueBinding::None) .SetFlags(ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::ShaderResource | ETextureCreateFlags::ResolveTargetable) .SetInitialState(ERHIAccess::Present); RenderingBackBuffer = FVulkanDynamicRHI::Get().CreateTextureInternal(CreateDesc, {}); FVulkanPipelineBarrier Barrier; Barrier.AddImageLayoutTransition(RenderingBackBuffer->Image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0, 1)); Barrier.Execute(&Context.GetCommandBuffer()); #if VULKAN_ENABLE_DRAW_MARKERS if (Device->GetSetDebugName()) { VulkanRHI::SetDebugName(Device->GetSetDebugName(), Device->GetInstanceHandle(), RenderingBackBuffer->Image, "RenderingBackBuffer"); } #endif } AcquiredImageIndex = -1; } void FVulkanViewport::DestroySwapchain(FVulkanSwapChainRecreateInfo* RecreateInfo) { FVulkanDynamicRHI::Get().RHIBlockUntilGPUIdle(); // Intentionally leave RenderingBackBuffer alive, so it can be used a dummy backbuffer while we don't have swapchain images // RenderingBackBuffer = nullptr; if (RHIBackBuffer) { RHIBackBuffer->ReleaseAcquiredImage(); // We release this RHIBackBuffer when we create a new swapchain } if (SupportsStandardSwapchain() && SwapChain) { TextureViews.Empty(); for (int32 Index = 0, NumBuffers = BackBufferImages.Num(); Index < NumBuffers; ++Index) { Device->NotifyDeletedImage(BackBufferImages[Index]->Image, true); BackBufferImages[Index] = nullptr; } Device->GetDeferredDeletionQueue().ReleaseResources(true); SwapChain->Destroy(RecreateInfo); delete SwapChain; SwapChain = nullptr; Device->GetDeferredDeletionQueue().ReleaseResources(true); } AcquiredImageIndex = -1; } inline static void CopyImageToBackBuffer(FVulkanCommandListContext& Context, FVulkanTexture& SrcSurface, FVulkanTexture& DstSurface, int32 SizeX, int32 SizeY, int32 WindowSizeX, int32 WindowSizeY, VkSurfaceTransformFlagBitsKHR CachedSurfaceTransform) { RHI_BREADCRUMB_EVENT(Context, "CopyImageToBackBuffer"); const bool bNeedsVulkanPreTransform = CachedSurfaceTransform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; FVulkanCommandBuffer* CmdBuffer = &Context.GetCommandBuffer(); check(CmdBuffer->IsOutsideRenderPass()); const VkImageLayout SrcSurfaceLayout = bNeedsVulkanPreTransform ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; const VkImageLayout DstSurfaceLayout = bNeedsVulkanPreTransform ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; { FVulkanPipelineBarrier Barrier; Barrier.AddImageLayoutTransition(SrcSurface.Image, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, SrcSurfaceLayout, FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0, 1)); Barrier.AddImageLayoutTransition(DstSurface.Image, VK_IMAGE_LAYOUT_UNDEFINED, DstSurfaceLayout, FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0, 1)); Barrier.Execute(CmdBuffer); } VulkanRHI::DebugHeavyWeightBarrier(CmdBuffer->GetHandle(), 32); // Copy and rotate the intermediate image to the BackBuffer with a pixel shader if (bNeedsVulkanPreTransform) { FGraphicsPipelineStateInitializer GraphicsPSOInit; // No alpha blending, no depth tests or writes, no stencil tests or writes, no backface culling. GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); TRHICommandList_RecursiveHazardous RHICmdList(&Context); RHICmdList.BeginRenderPass(FRHIRenderPassInfo(&DstSurface, ERenderTargetActions::DontLoad_Store), TEXT("SurfaceTransform")); auto ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel); TShaderMapRef VertexShader(ShaderMap); TShaderMapRef PixelShader(ShaderMap); RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI; GraphicsPSOInit.PrimitiveType = PT_TriangleStrip; SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); FImagePreTransformVS::FParameters VSParameters; FMatrix44f RenderPassTransformMatrix = FRotationMatrix44f(FRotator3f(0.0f, -180.0f * (FMath::Log2(static_cast(CachedSurfaceTransform))) / 2, 0.0f)); VSParameters.PreTransform.X = RenderPassTransformMatrix.M[0][0]; VSParameters.PreTransform.Y = RenderPassTransformMatrix.M[0][1]; VSParameters.PreTransform.Z = RenderPassTransformMatrix.M[1][0]; VSParameters.PreTransform.W = RenderPassTransformMatrix.M[1][1]; SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VSParameters); SetShaderParametersLegacyPS(RHICmdList, PixelShader, TStaticSamplerState::GetRHI(), &SrcSurface); RHICmdList.DrawPrimitive(0, 2, 1); RHICmdList.EndRenderPass(); } else { if (SizeX != WindowSizeX || SizeY != WindowSizeY) { VkImageBlit Region; FMemory::Memzero(Region); Region.srcOffsets[0].x = 0; Region.srcOffsets[0].y = 0; Region.srcOffsets[0].z = 0; Region.srcOffsets[1].x = SizeX; Region.srcOffsets[1].y = SizeY; Region.srcOffsets[1].z = 1; Region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; Region.srcSubresource.layerCount = 1; Region.dstOffsets[0].x = 0; Region.dstOffsets[0].y = 0; Region.dstOffsets[0].z = 0; Region.dstOffsets[1].x = WindowSizeX; Region.dstOffsets[1].y = WindowSizeY; Region.dstOffsets[1].z = 1; Region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; Region.dstSubresource.baseArrayLayer = 0; Region.dstSubresource.layerCount = 1; VulkanRHI::vkCmdBlitImage(CmdBuffer->GetHandle(), SrcSurface.Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, DstSurface.Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &Region, VK_FILTER_LINEAR); } else { VkImageCopy Region; FMemory::Memzero(Region); Region.extent.width = SizeX; Region.extent.height = SizeY; Region.extent.depth = 1; Region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; //Region.srcSubresource.baseArrayLayer = 0; Region.srcSubresource.layerCount = 1; //Region.srcSubresource.mipLevel = 0; Region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; //Region.dstSubresource.baseArrayLayer = 0; Region.dstSubresource.layerCount = 1; //Region.dstSubresource.mipLevel = 0; VulkanRHI::vkCmdCopyImage(CmdBuffer->GetHandle(), SrcSurface.Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, DstSurface.Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &Region); } } { FVulkanPipelineBarrier Barrier; Barrier.AddImageLayoutTransition(SrcSurface.Image, SrcSurfaceLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0, 1)); Barrier.AddImageLayoutTransition(DstSurface.Image, DstSurfaceLayout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, 0, 1)); Barrier.Execute(CmdBuffer); } } bool FVulkanViewport::Present(FVulkanCommandListContext& Context, FVulkanQueue* PresentQueue, bool bLockToVsync) { check(Context.IsImmediate()); FPlatformAtomics::AtomicStore(&LockToVsync, bLockToVsync ? 1 : 0); //Transition back buffer to presentable and submit that command if (SupportsStandardSwapchain()) { bool bFailedToDelayAcquireBackbuffer = false; if (GVulkanDelayAcquireImage == EDelayAcquireImageType::DelayAcquire && RenderingBackBuffer) { SCOPE_CYCLE_COUNTER(STAT_VulkanAcquireBackBuffer); // swapchain can go out of date, do not crash at this point if (LIKELY(TryAcquireImageIndex())) { // Wait for semaphore signal before writing to backbuffer image Context.AddWaitSemaphore(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, AcquiredSemaphore); uint32 WindowSizeX = FMath::Min(SizeX, SwapChain->InternalWidth); uint32 WindowSizeY = FMath::Min(SizeY, SwapChain->InternalHeight); CopyImageToBackBuffer(Context, *RenderingBackBuffer.GetReference(), *BackBufferImages[AcquiredImageIndex].GetReference(), SizeX, SizeY, WindowSizeX, WindowSizeY, SwapChain->GetCachedSurfaceTransform()); } else { bFailedToDelayAcquireBackbuffer = true; } } else { if (AcquiredImageIndex != -1) { FVulkanCommandBuffer& CommandBuffer = Context.GetCommandBuffer(); check(CommandBuffer.IsOutsideRenderPass()); check(RHIBackBuffer != nullptr && RHIBackBuffer->Image == BackBufferImages[AcquiredImageIndex]->Image); VulkanSetImageLayout(&CommandBuffer, BackBufferImages[AcquiredImageIndex]->Image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, FVulkanPipelineBarrier::MakeSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT)); } else { // When we have failed to acquire backbuffer image we fallback to using 'dummy' backbuffer check(RHIBackBuffer != nullptr && RHIBackBuffer->Image == RenderingBackBuffer->Image); } } if (LIKELY(!bFailedToDelayAcquireBackbuffer)) { if (AcquiredImageIndex >= 0) { Context.AddSignalSemaphore(RenderingDoneSemaphores[AcquiredImageIndex]); } } else { if(FVulkanPlatformWindowContext::CanCreateSwapchainOnDemand()) // on android we dont want to attempt to recreate the window when we dont have the window lock... { // failing to do the delayacquire can only happen if we were in this mode to begin with check(GVulkanDelayAcquireImage == EDelayAcquireImageType::DelayAcquire); UE_LOG(LogVulkanRHI, Log, TEXT("AcquireNextImage() failed due to the outdated swapchain, not even attempting to present.")); FVulkanPlatformWindowContext WindowContext(GetWindowHandle()); RecreateSwapchain(Context, WindowContext); // Swapchain creation pushes some commands - flush the command buffers now to begin with a fresh state Context.FlushCommands(EVulkanFlushFlags::WaitForCompletion); } // early exit return (int32)FVulkanSwapChain::EStatus::Healthy; } } // Submit any accumulated commands or syncs, wait until they hit the queue so that we can present Context.FlushCommands(EVulkanFlushFlags::WaitForSubmission); //#todo-rco: Proper SyncInterval bLockToVsync ? RHIConsoleVariables::SyncInterval : 0 int32 SyncInterval = 0; bool bNeedNativePresent = true; const bool bHasCustomPresent = IsValidRef(CustomPresent); if (bHasCustomPresent) { SCOPE_CYCLE_COUNTER(STAT_VulkanCustomPresentTime); bNeedNativePresent = CustomPresent->Present(Context, SyncInterval); } bool bResult = false; if (bNeedNativePresent && (!SupportsStandardSwapchain() || GVulkanDelayAcquireImage == EDelayAcquireImageType::DelayAcquire || RHIBackBuffer != nullptr)) { // Present the back buffer to the viewport window. auto SwapChainJob = [PresentQueue](FVulkanViewport* Viewport) { // May happend if swapchain was recreated in DoCheckedSwapChainJob() if (Viewport->AcquiredImageIndex == -1) { // Skip present silently if image has not been acquired return (int32)FVulkanSwapChain::EStatus::Healthy; } return (int32)Viewport->SwapChain->Present(PresentQueue, Viewport->RenderingDoneSemaphores[Viewport->AcquiredImageIndex]); }; if (SupportsStandardSwapchain() && !DoCheckedSwapChainJob(Context, SwapChainJob)) { UE_LOG(LogVulkanRHI, Error, TEXT("Swapchain present failed!")); bResult = false; } else { bResult = true; } if (bHasCustomPresent) { CustomPresent->PostPresent(); } } if (FVulkanPlatform::RequiresWaitingForFrameCompletionEvent() && !bHasCustomPresent) { // Wait for the GPU to finish rendering the previous frame before finishing this frame. WaitForFrameEventCompletion(); IssueFrameEvent(); } // If the input latency timer has been triggered, block until the GPU is completely // finished displaying this frame and calculate the delta time. //if (GInputLatencyTimer.RenderThreadTrigger) //{ // WaitForFrameEventCompletion(); // uint32 EndTime = FPlatformTime::Cycles(); // GInputLatencyTimer.DeltaTime = EndTime - GInputLatencyTimer.StartTime; // GInputLatencyTimer.RenderThreadTrigger = false; //} AcquiredImageIndex = -1; ++PresentCount; ++GVulkanRHI->TotalPresentCount; return bResult; } VkFormat FVulkanViewport::GetSwapchainImageFormat() const { return SwapChain->ImageFormat; } bool FVulkanViewport::SupportsStandardSwapchain() { return !bRenderOffscreen && !FVulkanDynamicRHI::Get().bIsStandaloneStereoDevice; } bool FVulkanViewport::RequiresRenderingBackBuffer() { return !FVulkanDynamicRHI::Get().bIsStandaloneStereoDevice; } EPixelFormat FVulkanViewport::GetPixelFormatForNonDefaultSwapchain() { if (bRenderOffscreen || FVulkanDynamicRHI::Get().bIsStandaloneStereoDevice) { return PF_R8G8B8A8; } else { checkf(0, TEXT("Platform Requires Standard Swapchain!")); return PF_Unknown; } } void FVulkanViewport::OnSystemResolutionChanged(uint32 ResX, uint32 ResY) { EDeviceScreenOrientation CurrentOrientation = FPlatformMisc::GetDeviceOrientation(); // The swap chain needs to be recreated after a rotation // Only 180-degree rotations need to be handled here because 90-degree rotations will resize the viewport and recreate the swap chain. if ((CachedOrientation == EDeviceScreenOrientation::Portrait && CurrentOrientation == EDeviceScreenOrientation::PortraitUpsideDown) || (CachedOrientation == EDeviceScreenOrientation::PortraitUpsideDown && CurrentOrientation == EDeviceScreenOrientation::Portrait) || (CachedOrientation == EDeviceScreenOrientation::LandscapeRight && CurrentOrientation == EDeviceScreenOrientation::LandscapeLeft) || (CachedOrientation == EDeviceScreenOrientation::LandscapeLeft && CurrentOrientation == EDeviceScreenOrientation::LandscapeRight)) { check(IsInGameThread()); FVulkanPlatformWindowContext WindowContext(GetWindowHandle()); ENQUEUE_RENDER_COMMAND(RecreateSwapchain)( [this,&WindowContext](FRHICommandListImmediate& RHICmdList) { RecreateSwapchainFromRT(RHICmdList, WindowContext); }); FlushRenderingCommands(); } CachedOrientation = CurrentOrientation; } /*============================================================================= * The following RHI functions must be called from the main thread. *=============================================================================*/ FViewportRHIRef FVulkanDynamicRHI::RHICreateViewport(void* WindowHandle, uint32 SizeX, uint32 SizeY, bool bIsFullscreen, EPixelFormat PreferredPixelFormat) { check( IsInGameThread() ); // Use a default pixel format if none was specified if (PreferredPixelFormat == PF_Unknown) { static const auto* CVarDefaultBackBufferPixelFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DefaultBackBufferPixelFormat")); PreferredPixelFormat = EDefaultBackBufferPixelFormat::Convert2PixelFormat(EDefaultBackBufferPixelFormat::FromInt(CVarDefaultBackBufferPixelFormat->GetValueOnAnyThread())); } return new FVulkanViewport(Device, WindowHandle, SizeX, SizeY, bIsFullscreen, PreferredPixelFormat); } void FVulkanDynamicRHI::RHIResizeViewport(FRHIViewport* ViewportRHI, uint32 SizeX, uint32 SizeY, bool bIsFullscreen, EPixelFormat PreferredPixelFormat) { check(IsInGameThread()); FVulkanViewport* Viewport = ResourceCast(ViewportRHI); // Use a default pixel format if none was specified if (PreferredPixelFormat == PF_Unknown) { static const auto* CVarDefaultBackBufferPixelFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DefaultBackBufferPixelFormat")); PreferredPixelFormat = EDefaultBackBufferPixelFormat::Convert2PixelFormat(EDefaultBackBufferPixelFormat::FromInt(CVarDefaultBackBufferPixelFormat->GetValueOnAnyThread())); } if (Viewport->GetSizeXY() != FIntPoint(SizeX, SizeY) || Viewport->IsFullscreen() != bIsFullscreen) { FVulkanPlatformWindowContext WindowContext(Viewport->WindowHandle); ENQUEUE_RENDER_COMMAND(ResizeViewport)( [Viewport, SizeX, SizeY, bIsFullscreen, PreferredPixelFormat,&WindowContext](FRHICommandListImmediate& RHICmdList) { Viewport->Resize(RHICmdList, SizeX, SizeY, bIsFullscreen, PreferredPixelFormat, WindowContext); }); FlushRenderingCommands(); } } void FVulkanDynamicRHI::RHIResizeViewport(FRHIViewport* ViewportRHI, uint32 SizeX, uint32 SizeY, bool bIsFullscreen) { check(IsInGameThread()); FVulkanViewport* Viewport = ResourceCast(ViewportRHI); if (Viewport->GetSizeXY() != FIntPoint(SizeX, SizeY)) { FVulkanPlatformWindowContext WindowContext(Viewport->WindowHandle); ENQUEUE_RENDER_COMMAND(ResizeViewport)( [Viewport, SizeX, SizeY, bIsFullscreen, &WindowContext](FRHICommandListImmediate& RHICmdList) { Viewport->Resize(RHICmdList, SizeX, SizeY, bIsFullscreen, PF_Unknown, WindowContext); }); FlushRenderingCommands(); } } void FVulkanDynamicRHI::RHITick(float DeltaTime) { check(IsInGameThread()); } FTextureRHIRef FVulkanDynamicRHI::RHIGetViewportBackBuffer(FRHIViewport* ViewportRHI) { FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get(); check(ViewportRHI); FVulkanViewport* Viewport = ResourceCast(ViewportRHI); if (Viewport->SwapChain) { Viewport->SwapChain->RenderThreadPacing(); } return Viewport->GetBackBuffer(RHICmdList); } void FVulkanDynamicRHI::RHIAdvanceFrameForGetViewportBackBuffer(FRHIViewport* ViewportRHI) { FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get(); check(ViewportRHI); FVulkanViewport* Viewport = ResourceCast(ViewportRHI); Viewport->AdvanceBackBufferFrame(RHICmdList); } void FVulkanCommandListContext::RHISetViewport(float MinX, float MinY, float MinZ, float MaxX, float MaxY, float MaxZ) { PendingGfxState->SetViewport(MinX, MinY, MinZ, MaxX, MaxY, MaxZ); } void FVulkanCommandListContext::RHISetStereoViewport(float LeftMinX, float RightMinX, float LeftMinY, float RightMinY, float MinZ, float LeftMaxX, float RightMaxX, float LeftMaxY, float RightMaxY, float MaxZ) { TStaticArray Viewports; Viewports[0].x = FMath::FloorToInt(LeftMinX); Viewports[0].y = FMath::FloorToInt(LeftMinY); Viewports[0].width = FMath::CeilToInt(LeftMaxX - LeftMinX); Viewports[0].height = FMath::CeilToInt(LeftMaxY - LeftMinY); Viewports[0].minDepth = MinZ; Viewports[0].maxDepth = MaxZ; Viewports[1].x = FMath::FloorToInt(RightMinX); Viewports[1].y = FMath::FloorToInt(RightMinY); Viewports[1].width = FMath::CeilToInt(RightMaxX - RightMinX); Viewports[1].height = FMath::CeilToInt(RightMaxY - RightMinY); Viewports[1].minDepth = MinZ; Viewports[1].maxDepth = MaxZ; PendingGfxState->SetMultiViewport(Viewports); } void FVulkanCommandListContext::RHISetMultipleViewports(uint32 Count, const FViewportBounds* Data) { VULKAN_SIGNAL_UNIMPLEMENTED(); } void FVulkanCommandListContext::RHISetScissorRect(bool bEnable, uint32 MinX, uint32 MinY, uint32 MaxX, uint32 MaxY) { PendingGfxState->SetScissor(bEnable, MinX, MinY, MaxX, MaxY); }