// Copyright Epic Games, Inc. All Rights Reserved. #include "PixelInspector.h" #include "Modules/ModuleManager.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SOverlay.h" #include "Styling/CoreStyle.h" #include "SlateOptMacros.h" #include "Framework/Application/SlateApplication.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Images/SImage.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SButton.h" #include "Engine/TextureRenderTarget2D.h" #include "PixelInspectorView.h" #include "PixelInspectorStyle.h" #include "ScreenPass.h" #include "UnrealClient.h" #include "PostProcess/PostProcessMaterialInputs.h" #include "EngineGlobals.h" #include "EditorViewportClient.h" #include "Editor.h" #include "PropertyEditorModule.h" #include "IDetailsView.h" #include "LevelEditor.h" #include "TextureResource.h" #include "Widgets/Input/SNumericEntryBox.h" #define PIXEL_INSPECTOR_REQUEST_TIMEOUT 10 #define MINIMUM_TICK_BETWEEN_CREATE_REQUEST 10 #define DEFAULT_DISPLAY_GAMMA 2.2f #define LOCTEXT_NAMESPACE "PixelInspector" namespace PixelInspector { SPixelInspector::SPixelInspector() { DisplayResult = nullptr; LastViewportInspectionPosition = FIntPoint(-1, -1); LastViewportId = 0; LastViewportInspectionSize = FIntPoint(1, 1); Buffer_FinalColor_AnyFormat[0] = nullptr; Buffer_FinalColor_AnyFormat[1] = nullptr; Buffer_SceneColorBeforePost_Float[0] = nullptr; Buffer_SceneColorBeforePost_Float[1] = nullptr; Buffer_SceneColorBeforeToneMap_Float[0] = nullptr; Buffer_SceneColorBeforeToneMap_Float[1] = nullptr; Buffer_Depth_Float[0] = nullptr; Buffer_Depth_Float[1] = nullptr; Buffer_A_Float[0] = nullptr; Buffer_A_Float[1] = nullptr; Buffer_A_RGB8[0] = nullptr; Buffer_A_RGB8[1] = nullptr; Buffer_A_RGB10[0] = nullptr; Buffer_A_RGB10[1] = nullptr; Buffer_BCDEF_Float[0] = nullptr; Buffer_BCDEF_Float[1] = nullptr; Buffer_BCDEF_RGB8[0] = nullptr; Buffer_BCDEF_RGB8[1] = nullptr; TickSinceLastCreateRequest = 0; LastBufferIndex = 0; OnLevelActorDeletedDelegateHandle = GEngine->OnLevelActorDeleted().AddRaw(this, &SPixelInspector::OnLevelActorDeleted); OnEditorCloseHandle = GEditor->OnEditorClose().AddRaw(this, &SPixelInspector::ReleaseRessource); FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(FName(TEXT("LevelEditor"))); OnRedrawViewportHandle = LevelEditor.OnRedrawLevelEditingViewports().AddRaw(this, &SPixelInspector::OnRedrawViewport); OnApplicationPreInputKeyDownListenerHandle = FSlateApplication::Get().OnApplicationPreInputKeyDownListener().AddRaw(this, &SPixelInspector::OnApplicationPreInputKeyDownListener); SetPixelInspectorState(false); } SPixelInspector::~SPixelInspector() { ReleaseRessource(); } void SPixelInspector::OnApplicationPreInputKeyDownListener(const FKeyEvent& InKeyEvent) { if (InKeyEvent.GetKey() == EKeys::Escape && (bIsPixelInspectorEnable)) { // disable the pixel inspector SetPixelInspectorState(false); } } void SPixelInspector::OnWindowClosed() { if(bIsPixelInspectorEnable) { SetPixelInspectorState(false); // We need to invalide the draw as the pixel inspector message is left on from the last draw GEditor->RedrawLevelEditingViewports(); } } void SPixelInspector::ReleaseRessource() { SetPixelInspectorState(false); if (DisplayResult != nullptr) { DisplayResult->RemoveFromRoot(); DisplayResult->ClearFlags(RF_Standalone); DisplayResult = nullptr; } ReleaseAllRequests(); if (OnLevelActorDeletedDelegateHandle.IsValid()) { GEngine->OnLevelActorDeleted().Remove(OnLevelActorDeletedDelegateHandle); OnLevelActorDeletedDelegateHandle = FDelegateHandle(); } if (OnEditorCloseHandle.IsValid()) { GEditor->OnEditorClose().Remove(OnEditorCloseHandle); OnEditorCloseHandle = FDelegateHandle(); } if (OnRedrawViewportHandle.IsValid()) { FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(FName(TEXT("LevelEditor"))); LevelEditor.OnRedrawLevelEditingViewports().Remove(OnRedrawViewportHandle); OnRedrawViewportHandle = FDelegateHandle(); } if (OnApplicationPreInputKeyDownListenerHandle.IsValid()) { FSlateApplication::Get().OnApplicationPreInputKeyDownListener().Remove(OnApplicationPreInputKeyDownListenerHandle); OnApplicationPreInputKeyDownListenerHandle = FDelegateHandle(); } if (DisplayDetailsView.IsValid()) { DisplayDetailsView->SetObject(nullptr); DisplayDetailsView = nullptr; } } void SPixelInspector::ReleaseAllRequests() { //Clear all pending requests because buffer will be clear by the graphic for (int i = 0; i < 2; ++i) { Requests[i].RenderingCommandSend = true; Requests[i].RequestComplete = true; ReleaseBuffers(i); } if (DisplayResult != nullptr) { DisplayResult->RemoveFromRoot(); DisplayResult->ClearFlags(RF_Standalone); DisplayResult = nullptr; } } void SPixelInspector::OnLevelActorDeleted(AActor* Actor) { ReleaseAllRequests(); } void SPixelInspector::OnRedrawViewport(bool bInvalidateHitProxies) { ReleaseAllRequests(); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SPixelInspector::Construct(const FArguments& InArgs) { //Set the LastViewportId to point on the active viewport FViewport *ActiveViewport = GEditor->GetActiveViewport(); for (FEditorViewportClient *EditorViewport : GEditor->GetAllViewportClients()) { if (ActiveViewport == EditorViewport->Viewport && EditorViewport->ViewState.GetReference() != nullptr) { LastViewportId = EditorViewport->ViewState.GetReference()->GetViewKey(); } } TSharedPtr InspectorBox; //Create the PixelInspector UI TSharedPtr VerticalBox = SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f, 3.0f, 0.0f, 3.0f) .AutoWidth() [ SNew(SButton) .HAlign(HAlign_Center) .ToolTipText(this, &SPixelInspector::GetPixelInspectorEnableButtonTooltipText) .OnClicked(this, &SPixelInspector::HandleTogglePixelInspectorEnableButton) [ SNew(SImage) .Image(this, &SPixelInspector::GetPixelInspectorEnableButtonBrush) ] ] + SHorizontalBox::Slot() .Padding(6.0f, 3.0f, 0.0f, 3.0f) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .MinDesiredWidth(75) .Text(this, &SPixelInspector::GetPixelInspectorEnableButtonText) ] ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 3.0f, 16.0f, 3.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .MinDesiredWidth(75) .Text(LOCTEXT("PixelInspector_ViewportIdValue", "Viewport Id")) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 3.0f, 0.0f, 3.0f) [ SNew(SNumericEntryBox) .IsEnabled(false) .MinDesiredValueWidth(75) .Value(this, &SPixelInspector::GetCurrentViewportId) ] ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 3.0f, 16.0f, 3.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .MinDesiredWidth(75) .Text(LOCTEXT("PixelInspector_ViewportCoordinate", "Coordinate")) .ToolTipText(LOCTEXT("PixelInspector_ViewportCoordinateTooltip", "Coordinate relative to the inspected viewport")) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(0.0f, 3.0f, 8.0f, 3.0f) [ SNew(SNumericEntryBox) .IsEnabled(this, &SPixelInspector::IsPixelInspectorEnable) .Value(this, &SPixelInspector::GetCurrentCoordinateX) .OnValueChanged(this, &SPixelInspector::SetCurrentCoordinateX) .OnValueCommitted(this, &SPixelInspector::SetCurrentCoordinateXCommit) .AllowSpin(true) .MinValue(0) .MaxSliderValue(this, &SPixelInspector::GetMaxCoordinateX) .MinDesiredValueWidth(75) .Label() [ SNew(SBox) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("CoordinateViewport_X", "X")) ] ] ] + SHorizontalBox::Slot() .Padding(0.0f, 3.0f, 8.0f, 3.0f) .AutoWidth() [ SNew(SNumericEntryBox) .IsEnabled(this, &SPixelInspector::IsPixelInspectorEnable) .Value(this, &SPixelInspector::GetCurrentCoordinateY) .OnValueChanged(this, &SPixelInspector::SetCurrentCoordinateY) .OnValueCommitted(this, &SPixelInspector::SetCurrentCoordinateYCommit) .AllowSpin(true) .MinValue(0) .MaxSliderValue(this, &SPixelInspector::GetMaxCoordinateY) .MinDesiredValueWidth(75) .Label() [ SNew(SBox) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("CoordinateViewport_Y", "Y")) ] ] ] ] + SVerticalBox::Slot() .Padding(0.0f, 12.0f, 0.0f, 3.0f) .FillHeight(1.0f) [ SAssignNew(InspectorBox, SBox) ]; FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bAllowSearch = false; DetailsViewArgs.bLockable = false; DetailsViewArgs.bShowOptions = false; DetailsViewArgs.bUpdatesFromSelection = false; DetailsViewArgs.bHideSelectionTip = true; DetailsViewArgs.bSearchInitialKeyFocus = false; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DisplayDetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); InspectorBox->SetContent(DisplayDetailsView->AsShared()); //Create a property Detail view ChildSlot [ SNew(SBorder) .BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder")) [ SNew(SOverlay) // Overlay slot for the main HLOD window area + SOverlay::Slot() [ VerticalBox.ToSharedRef() ] ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION FReply SPixelInspector::HandleTogglePixelInspectorEnableButton() { SetPixelInspectorState(!bIsPixelInspectorEnable); if (bIsPixelInspectorEnable) { if (LastViewportInspectionPosition == FIntPoint(-1, -1)) { //Let the system inspect a pixel so the user can see the UI appear LastViewportInspectionPosition = FIntPoint(0, 0); } //Make sure the viewport is switch to realtime SetCurrentViewportInRealtime(); } return FReply::Handled(); } FText SPixelInspector::GetPixelInspectorEnableButtonText() const { if (bIsPixelInspectorEnable) { return LOCTEXT("PixelInspector_EnableCheckbox_Inspecting", "Inspecting"); } return LOCTEXT("PixelInspectorMouseHover_EnableCheckbox", "Start Pixel Inspector"); } FText SPixelInspector::GetPixelInspectorEnableButtonTooltipText() const { if (bIsPixelInspectorEnable) { return LOCTEXT("PixelInspector_EnableCheckbox_ESC", "Inspecting (ESC to stop)"); } return LOCTEXT("PixelInspectorMouseHover_EnableCheckbox", "Start Pixel Inspector"); } const FSlateBrush* SPixelInspector::GetPixelInspectorEnableButtonBrush() const { return bIsPixelInspectorEnable ? FPixelInspectorStyle::Get()->GetBrush("PixelInspector.Enabled") : FPixelInspectorStyle::Get()->GetBrush("PixelInspector.Disabled"); } void SPixelInspector::SetCurrentCoordinateXCommit(int32 NewValue, ETextCommit::Type) { ReleaseAllRequests(); SetCurrentCoordinateX(NewValue); } void SPixelInspector::SetCurrentCoordinateX(int32 NewValue) { LastViewportInspectionPosition.X = NewValue; } void SPixelInspector::SetCurrentCoordinateYCommit(int32 NewValue, ETextCommit::Type) { ReleaseAllRequests(); SetCurrentCoordinateY(NewValue); } void SPixelInspector::SetCurrentCoordinateY(int32 NewValue) { LastViewportInspectionPosition.Y = NewValue; } void SPixelInspector::SetCurrentCoordinate(FIntPoint NewCoordinate, bool ReleaseAllRequest) { if (ReleaseAllRequest) { ReleaseAllRequests(); } LastViewportInspectionPosition.X = NewCoordinate.X; LastViewportInspectionPosition.Y = NewCoordinate.Y; } TOptional SPixelInspector::GetMaxCoordinateX() const { return LastViewportInspectionSize.X - 1; } TOptional SPixelInspector::GetMaxCoordinateY() const { return LastViewportInspectionSize.Y - 1; } void SPixelInspector::SetCurrentViewportInRealtime() { //Force viewport refresh for (FEditorViewportClient *EditorViewport : GEditor->GetAllViewportClients()) { if (EditorViewport->ViewState.GetReference() != nullptr) { if (EditorViewport->ViewState.GetReference()->GetViewKey() == LastViewportId) { if (!EditorViewport->IsRealtime()) { const bool bShouldBeRealtime = true; EditorViewport->AddRealtimeOverride(bShouldBeRealtime, LOCTEXT("RealtimeOverrideMessage_PixelInspector", "Pixel Inspector")); } } } } } void SPixelInspector::SetPixelInspectorState(bool bInIsPixelInspectorEnabled) { bIsPixelInspectorEnable = bInIsPixelInspectorEnabled; if (bIsPixelInspectorEnable) { if (!PixelInspectorSceneViewExtension.IsValid()) { // create new scene view extension PixelInspectorSceneViewExtension = FSceneViewExtensions::NewExtension(); } } else { if (PixelInspectorSceneViewExtension.IsValid()) { // Release scene view extension as we no longer need it. PixelInspectorSceneViewExtension.Reset(); PixelInspectorSceneViewExtension = nullptr; } } } void SPixelInspector::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); TickSinceLastCreateRequest++; } void SPixelInspector::CreatePixelInspectorRequest(FVector2D InspectViewportUV, int32 viewportUniqueId, FSceneInterface *SceneInterface, bool bInGameViewMode, float InPreExposure) { if (TickSinceLastCreateRequest < MINIMUM_TICK_BETWEEN_CREATE_REQUEST) return; //Make sure we dont get value outside the viewport size if ( InspectViewportUV.X >= 1.0f || InspectViewportUV.Y >= 1.0f || InspectViewportUV.X <= 0.0f || InspectViewportUV.Y <= 0.0f ) { return; } TickSinceLastCreateRequest = 0; // We need to know if the GBuffer is in low, default or high precision buffer const auto CVarGBufferFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GBufferFormat")); //0: lower precision (8bit per component, for profiling) //1: low precision (default) //5: high precision const int32 GBufferFormat = CVarGBufferFormat != nullptr ? CVarGBufferFormat->GetValueOnGameThread() : 1; const bool AllowStaticLighting = IsStaticLightingAllowed(); //Try to create the request buffer int32 BufferIndex = CreateRequestBuffer(SceneInterface, GBufferFormat, bInGameViewMode); if (BufferIndex == -1) return; Requests[BufferIndex].SetRequestData(FVector2f(InspectViewportUV), BufferIndex, viewportUniqueId, GBufferFormat, AllowStaticLighting, InPreExposure); // LWC_TODO: Precision loss SceneInterface->AddPixelInspectorRequest(&(Requests[BufferIndex])); } void SPixelInspector::ReleaseBuffers(int32 BufferIndex) { check(BufferIndex >= 0 && BufferIndex < 2); if (Buffer_FinalColor_AnyFormat[BufferIndex] != nullptr) { Buffer_FinalColor_AnyFormat[BufferIndex]->ClearFlags(RF_Standalone); Buffer_FinalColor_AnyFormat[BufferIndex]->RemoveFromRoot(); Buffer_FinalColor_AnyFormat[BufferIndex] = nullptr; } if (Buffer_SceneColorBeforePost_Float[BufferIndex] != nullptr) { Buffer_SceneColorBeforePost_Float[BufferIndex]->ClearFlags(RF_Standalone); Buffer_SceneColorBeforePost_Float[BufferIndex]->RemoveFromRoot(); Buffer_SceneColorBeforePost_Float[BufferIndex] = nullptr; } if (Buffer_SceneColorBeforeToneMap_Float[BufferIndex] != nullptr) { Buffer_SceneColorBeforeToneMap_Float[BufferIndex]->ClearFlags(RF_Standalone); Buffer_SceneColorBeforeToneMap_Float[BufferIndex]->RemoveFromRoot(); Buffer_SceneColorBeforeToneMap_Float[BufferIndex] = nullptr; } if (Buffer_Depth_Float[BufferIndex] != nullptr) { Buffer_Depth_Float[BufferIndex]->ClearFlags(RF_Standalone); Buffer_Depth_Float[BufferIndex]->RemoveFromRoot(); Buffer_Depth_Float[BufferIndex] = nullptr; } if (Buffer_A_Float[BufferIndex] != nullptr) { Buffer_A_Float[BufferIndex]->ClearFlags(RF_Standalone); Buffer_A_Float[BufferIndex]->RemoveFromRoot(); Buffer_A_Float[BufferIndex] = nullptr; } if (Buffer_A_RGB8[BufferIndex] != nullptr) { Buffer_A_RGB8[BufferIndex]->ClearFlags(RF_Standalone); Buffer_A_RGB8[BufferIndex]->RemoveFromRoot(); Buffer_A_RGB8[BufferIndex] = nullptr; } if (Buffer_A_RGB10[BufferIndex] != nullptr) { Buffer_A_RGB10[BufferIndex]->ClearFlags(RF_Standalone); Buffer_A_RGB10[BufferIndex]->RemoveFromRoot(); Buffer_A_RGB10[BufferIndex] = nullptr; } if (Buffer_BCDEF_Float[BufferIndex] != nullptr) { Buffer_BCDEF_Float[BufferIndex]->ClearFlags(RF_Standalone); Buffer_BCDEF_Float[BufferIndex]->RemoveFromRoot(); Buffer_BCDEF_Float[BufferIndex] = nullptr; } if (Buffer_BCDEF_RGB8[BufferIndex] != nullptr) { Buffer_BCDEF_RGB8[BufferIndex]->ClearFlags(RF_Standalone); Buffer_BCDEF_RGB8[BufferIndex]->RemoveFromRoot(); Buffer_BCDEF_RGB8[BufferIndex] = nullptr; } } int32 SPixelInspector::CreateRequestBuffer(FSceneInterface *SceneInterface, const int32 GBufferFormat, bool bInGameViewMode) { //Toggle the last buffer Index LastBufferIndex = (LastBufferIndex + 1) % 2; //Check if we have an available request if (Requests[LastBufferIndex].RequestComplete == false) { //Put back the last buffer position LastBufferIndex = (LastBufferIndex - 1) % 2; return -1; } //Release the old buffer ReleaseBuffers(LastBufferIndex); FTextureRenderTargetResource* FinalColorResource = nullptr; FTextureRenderTargetResource* SceneColorBeforePostResource = nullptr; FTextureRenderTargetResource* SceneColorBeforeToneMapResource = nullptr; FTextureRenderTargetResource* DepthResource = nullptr; FTextureRenderTargetResource* BufferAResource = nullptr; FTextureRenderTargetResource* BufferBCDEFResource = nullptr; //Final color can be in HDR (FloatRGBA) or RGB8 formats so we should rely on scene view extension to tell us which format is being used. Buffer_FinalColor_AnyFormat[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferFinalColorTarget"), RF_Standalone); Buffer_FinalColor_AnyFormat[LastBufferIndex]->AddToRoot(); Buffer_FinalColor_AnyFormat[LastBufferIndex]->InitCustomFormat(FinalColorContextGridSize, FinalColorContextGridSize, PixelInspectorSceneViewExtension->GetFinalColorPixelFormat(), true); Buffer_FinalColor_AnyFormat[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_FinalColor_AnyFormat[LastBufferIndex]->UpdateResourceImmediate(true); FinalColorResource = Buffer_FinalColor_AnyFormat[LastBufferIndex]->GameThread_GetRenderTargetResource(); Buffer_SceneColorBeforePost_Float[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferSceneColorBeforePostTarget"), RF_Standalone); Buffer_SceneColorBeforePost_Float[LastBufferIndex]->AddToRoot(); Buffer_SceneColorBeforePost_Float[LastBufferIndex]->InitCustomFormat(1, 1, PF_FloatRGBA, true); Buffer_SceneColorBeforePost_Float[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_SceneColorBeforePost_Float[LastBufferIndex]->UpdateResourceImmediate(true); SceneColorBeforePostResource = Buffer_SceneColorBeforePost_Float[LastBufferIndex]->GameThread_GetRenderTargetResource(); Buffer_SceneColorBeforeToneMap_Float[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferSceneColorBeforeTonemapTarget"), RF_Standalone); Buffer_SceneColorBeforeToneMap_Float[LastBufferIndex]->AddToRoot(); Buffer_SceneColorBeforeToneMap_Float[LastBufferIndex]->InitCustomFormat(1, 1, PixelInspectorSceneViewExtension->GetHDRPixelFormat(), true); Buffer_SceneColorBeforeToneMap_Float[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_SceneColorBeforeToneMap_Float[LastBufferIndex]->UpdateResourceImmediate(true); SceneColorBeforeToneMapResource = Buffer_SceneColorBeforeToneMap_Float[LastBufferIndex]->GameThread_GetRenderTargetResource(); //TODO support Non render buffer to be able to read the depth stencil /* Buffer_Depth_Float[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferDepthTarget"), RF_Standalone); Buffer_Depth_Float[LastBufferIndex]->AddToRoot(); Buffer_Depth_Float[LastBufferIndex]->InitCustomFormat(1, 1, PF_DepthStencil, true); Buffer_Depth_Float[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_Depth_Float[LastBufferIndex]->UpdateResourceImmediate(true); DepthRenderTargetResource = Buffer_Depth_Float[LastBufferIndex]->GameThread_GetRenderTargetResource();*/ //Low precision GBuffer if (GBufferFormat == EGBufferFormat::Force8BitsPerChannel) { //All buffer are PF_B8G8R8A8 Buffer_A_RGB8[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferATarget"), RF_Standalone ); Buffer_A_RGB8[LastBufferIndex]->AddToRoot(); Buffer_A_RGB8[LastBufferIndex]->InitCustomFormat(1, 1, PF_B8G8R8A8, true); Buffer_A_RGB8[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_A_RGB8[LastBufferIndex]->UpdateResourceImmediate(true); BufferAResource = Buffer_A_RGB8[LastBufferIndex]->GameThread_GetRenderTargetResource(); Buffer_BCDEF_RGB8[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferBTarget"), RF_Standalone ); Buffer_BCDEF_RGB8[LastBufferIndex]->AddToRoot(); Buffer_BCDEF_RGB8[LastBufferIndex]->InitCustomFormat(4, 1, PF_B8G8R8A8, true); Buffer_BCDEF_RGB8[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_BCDEF_RGB8[LastBufferIndex]->UpdateResourceImmediate(true); BufferBCDEFResource = Buffer_BCDEF_RGB8[LastBufferIndex]->GameThread_GetRenderTargetResource(); } else if(GBufferFormat == EGBufferFormat::Default) { //Default is PF_A2B10G10R10 Buffer_A_RGB10[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferATarget"), RF_Standalone ); Buffer_A_RGB10[LastBufferIndex]->AddToRoot(); Buffer_A_RGB10[LastBufferIndex]->InitCustomFormat(1, 1, PF_A2B10G10R10, true); Buffer_A_RGB10[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_A_RGB10[LastBufferIndex]->UpdateResourceImmediate(true); BufferAResource = Buffer_A_RGB10[LastBufferIndex]->GameThread_GetRenderTargetResource(); //Default is PF_B8G8R8A8 Buffer_BCDEF_RGB8[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferBTarget"), RF_Standalone ); Buffer_BCDEF_RGB8[LastBufferIndex]->AddToRoot(); Buffer_BCDEF_RGB8[LastBufferIndex]->InitCustomFormat(4, 1, PF_B8G8R8A8, true); Buffer_BCDEF_RGB8[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_BCDEF_RGB8[LastBufferIndex]->UpdateResourceImmediate(true); BufferBCDEFResource = Buffer_BCDEF_RGB8[LastBufferIndex]->GameThread_GetRenderTargetResource(); } else if (GBufferFormat == EGBufferFormat::HighPrecisionNormals || GBufferFormat == EGBufferFormat::Force16BitsPerChannel) { //All buffer are PF_FloatRGBA Buffer_A_Float[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferATarget"), RF_Standalone ); Buffer_A_Float[LastBufferIndex]->AddToRoot(); Buffer_A_Float[LastBufferIndex]->InitCustomFormat(1, 1, PF_FloatRGBA, true); Buffer_A_Float[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_A_Float[LastBufferIndex]->UpdateResourceImmediate(true); BufferAResource = Buffer_A_Float[LastBufferIndex]->GameThread_GetRenderTargetResource(); Buffer_BCDEF_Float[LastBufferIndex] = NewObject(GetTransientPackage(), TEXT("PixelInspectorBufferBTarget"), RF_Standalone ); Buffer_BCDEF_Float[LastBufferIndex]->AddToRoot(); Buffer_BCDEF_Float[LastBufferIndex]->InitCustomFormat(4, 1, PF_FloatRGBA, true); Buffer_BCDEF_Float[LastBufferIndex]->ClearColor = FLinearColor::Black; Buffer_BCDEF_Float[LastBufferIndex]->UpdateResourceImmediate(true); BufferBCDEFResource = Buffer_BCDEF_Float[LastBufferIndex]->GameThread_GetRenderTargetResource(); } else { checkf(0, TEXT("Unhandled gbuffer format (%i) during pixel inspector initializtion."), GBufferFormat); } SceneInterface->InitializePixelInspector(FinalColorResource, SceneColorBeforePostResource, DepthResource, SceneColorBeforeToneMapResource, BufferAResource, BufferBCDEFResource, LastBufferIndex); return LastBufferIndex; } void SPixelInspector::ReadBackRequestData() { for (int RequestIndex = 0; RequestIndex < UE_ARRAY_COUNT(Requests); ++RequestIndex) { FPixelInspectorRequest& Request = Requests[RequestIndex]; if (Request.RequestComplete == false && Request.RenderingCommandSend == true) { if (Request.FrameCountAfterRenderingCommandSend >= WAIT_FRAMENUMBER_BEFOREREADING) { if (Request.SourceViewportUV == FVector2f(-1, -1)) { continue; } PixelInspectorResult PixelResult; PixelResult.ViewportUV = FVector2D(Request.SourceViewportUV); PixelResult.ViewUniqueId = Request.ViewId; PixelResult.PreExposure = Request.PreExposure; PixelResult.OneOverPreExposure = Request.PreExposure > 0.f ? (1.f / Request.PreExposure) : 1.f;; FTextureRenderTargetResource* RTResourceFinalColor = Buffer_FinalColor_AnyFormat[Request.BufferIndex]->GameThread_GetRenderTargetResource(); const EPixelFormat FinalColorPixelFormat = PixelInspectorSceneViewExtension->GetFinalColorPixelFormat(); const float Gamma = (FinalColorPixelFormat == PF_B8G8R8A8) ? 1.0f : PixelInspectorSceneViewExtension->GetGamma(); EPixelFormatChannelFlags ValidPixelChannels = GetPixelFormatValidChannels(FinalColorPixelFormat); bool bHasAlphaChannel = EnumHasAnyFlags(ValidPixelChannels, EPixelFormatChannelFlags::A); TArray BufferFinalColorValueLinear; if (RTResourceFinalColor->ReadLinearColorPixels(BufferFinalColorValueLinear) == false) { BufferFinalColorValueLinear.Empty(); } PixelResult.DecodeFinalColor(BufferFinalColorValueLinear, Gamma, bHasAlphaChannel); TArray BufferSceneColorValue; FTextureRenderTargetResource* RTResourceSceneColor = Buffer_SceneColorBeforePost_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceSceneColor->ReadLinearColorPixels(BufferSceneColorValue) == false) { BufferSceneColorValue.Empty(); } PixelResult.DecodeSceneColorBeforePostProcessing(BufferSceneColorValue); if (Buffer_Depth_Float[Request.BufferIndex] != nullptr) { TArray BufferDepthValue; FTextureRenderTargetResource* RTResourceDepth = Buffer_Depth_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceDepth->ReadLinearColorPixels(BufferDepthValue) == false) { BufferDepthValue.Empty(); } PixelResult.DecodeDepth(BufferDepthValue); } TArray BufferSceneColorBeforeToneMapValue; FTextureRenderTargetResource* RTResourceSceneColorBeforeTonemap = Buffer_SceneColorBeforeToneMap_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceSceneColorBeforeTonemap->ReadLinearColorPixels(BufferSceneColorBeforeToneMapValue) == false) { BufferSceneColorBeforeToneMapValue.Empty(); } const EPixelFormat HDRPixelFormat = PixelInspectorSceneViewExtension->GetHDRPixelFormat(); ValidPixelChannels = GetPixelFormatValidChannels(HDRPixelFormat); bHasAlphaChannel = EnumHasAnyFlags(ValidPixelChannels, EPixelFormatChannelFlags::A); PixelResult.DecodeSceneColorBeforeToneMap(BufferSceneColorBeforeToneMapValue, bHasAlphaChannel); if (Request.GBufferPrecision == EGBufferFormat::Force8BitsPerChannel) { TArray BufferAValue; FTextureRenderTargetResource* RTResourceA = Buffer_A_RGB8[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceA->ReadPixels(BufferAValue) == false) { BufferAValue.Empty(); } TArray BufferBCDEFValue; FTextureRenderTargetResource* RTResourceBCDEF = Buffer_BCDEF_RGB8[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceA->ReadPixels(BufferBCDEFValue) == false) { BufferBCDEFValue.Empty(); } PixelResult.DecodeBufferData(BufferAValue, BufferBCDEFValue, Request.AllowStaticLighting); } else if (Request.GBufferPrecision == EGBufferFormat::Default) { //PF_A2B10G10R10 format is not support yet TArray BufferAValue; FTextureRenderTargetResource* RTResourceA = Buffer_A_RGB10[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceA->ReadLinearColorPixels(BufferAValue) == false) { BufferAValue.Empty(); } TArray BufferBCDEFValue; FTextureRenderTargetResource* RTResourceBCDEF = Buffer_BCDEF_RGB8[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceBCDEF->ReadPixels(BufferBCDEFValue) == false) { BufferBCDEFValue.Empty(); } PixelResult.DecodeBufferData(BufferAValue, BufferBCDEFValue, Request.AllowStaticLighting); } else if (Request.GBufferPrecision == EGBufferFormat::HighPrecisionNormals || Request.GBufferPrecision == EGBufferFormat::Force16BitsPerChannel) { //PF_A2B10G10R10 format is not support yet TArray BufferAValue; FTextureRenderTargetResource* RTResourceA = Buffer_A_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceA->ReadFloat16Pixels(BufferAValue) == false) { BufferAValue.Empty(); } TArray BufferBCDEFValue; FTextureRenderTargetResource* RTResourceBCDEF = Buffer_BCDEF_Float[Request.BufferIndex]->GameThread_GetRenderTargetResource(); if (RTResourceA->ReadFloat16Pixels(BufferBCDEFValue) == false) { BufferBCDEFValue.Empty(); } PixelResult.DecodeBufferData(BufferAValue, BufferBCDEFValue, Request.AllowStaticLighting); } else { checkf(0, TEXT("Unhandled gbuffer format (%i) during pixel inspector readback."), Request.GBufferPrecision); } AccumulationResult.Add(PixelResult); ReleaseBuffers(RequestIndex); Request.RequestComplete = true; Request.RenderingCommandSend = true; Request.FrameCountAfterRenderingCommandSend = 0; Request.RequestTickSinceCreation = 0; } else { Requests[RequestIndex].FrameCountAfterRenderingCommandSend++; } } else if (Requests[RequestIndex].RequestComplete == false) { Requests[RequestIndex].RequestTickSinceCreation++; if (Requests[RequestIndex].RequestTickSinceCreation > PIXEL_INSPECTOR_REQUEST_TIMEOUT) { ReleaseBuffers(RequestIndex); Requests[RequestIndex].RequestComplete = true; Requests[RequestIndex].RenderingCommandSend = true; Requests[RequestIndex].FrameCountAfterRenderingCommandSend = 0; Requests[RequestIndex].RequestTickSinceCreation = 0; } } } if (AccumulationResult.Num() > 0) { if (DisplayResult == nullptr) { DisplayResult = NewObject(GetTransientPackage(), FName(TEXT("PixelInspectorDisplay")), RF_Standalone); DisplayResult->AddToRoot(); } DisplayResult->SetFromResult(AccumulationResult[0]); DisplayDetailsView->SetObject(DisplayResult, true); LastViewportInspectionPosition.X = AccumulationResult[0].ViewportUV.X * LastViewportInspectionSize.X; LastViewportInspectionPosition.Y = AccumulationResult[0].ViewportUV.Y * LastViewportInspectionSize.Y; LastViewportId = AccumulationResult[0].ViewUniqueId; AccumulationResult.RemoveAt(0); } } FPixelInspectorSceneViewExtension::FPixelInspectorSceneViewExtension(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister) { FinalColorPixelFormat = PF_B8G8R8A8; HDRPixelFormat = PF_FloatRGBA; // Default dislay gamma is hardcoded in tonemapper and is set to 2.2. // We initialize this value and then get the actual value from ViewFamily. FinalColorGamma = DEFAULT_DISPLAY_GAMMA; } void FPixelInspectorSceneViewExtension::BeginRenderViewFamily(FSceneViewFamily& InViewFamily) { bool bAnyUsePixelInspector = false; for (const FSceneView* View : InViewFamily.Views) { if (View->bUsePixelInspector) { bAnyUsePixelInspector = true; break; } } if (bAnyUsePixelInspector) { const float DisplayGamma = InViewFamily.RenderTarget->GetDisplayGamma(); // We need to apply gamma to the final color if the output is in HDR. FinalColorGamma = (InViewFamily.EngineShowFlags.Tonemapper == 0) ? DEFAULT_DISPLAY_GAMMA : DisplayGamma; } } void FPixelInspectorSceneViewExtension::SubscribeToPostProcessingPass(EPostProcessingPass PassId, const FSceneView& View, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled) { if (View.bUsePixelInspector) { if (PassId == EPostProcessingPass::FXAA) { InOutPassCallbacks.Add(FAfterPassCallbackDelegate::CreateRaw(this, &FPixelInspectorSceneViewExtension::PostProcessPassAfterFxaa_RenderThread)); } if (PassId == EPostProcessingPass::MotionBlur) { InOutPassCallbacks.Add(FAfterPassCallbackDelegate::CreateRaw(this, &FPixelInspectorSceneViewExtension::PostProcessPassAfterMotionBlur_RenderThread)); } } } FScreenPassTexture FPixelInspectorSceneViewExtension::PostProcessPassAfterFxaa_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& InOutInputs) { FinalColorPixelFormat = InOutInputs.Textures[(uint32)EPostProcessMaterialInput::SceneColor].TextureSRV->Desc.Texture->Desc.Format; // Don't need to modify anything, just return the untouched scene color texture back to post processing. return InOutInputs.ReturnUntouchedSceneColorForPostProcessing(GraphBuilder); } FScreenPassTexture FPixelInspectorSceneViewExtension::PostProcessPassAfterMotionBlur_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& InOutInputs) { HDRPixelFormat = InOutInputs.Textures[(uint32)EPostProcessMaterialInput::SceneColor].TextureSRV->Desc.Texture->Desc.Format; // Don't need to modify anything, just return the untouched scene color texture back to post processing. return InOutInputs.ReturnUntouchedSceneColorForPostProcessing(GraphBuilder); } }; #undef LOCTEXT_NAMESPACE