Files
UnrealEngineUWP/Engine/Source/Editor/AdvancedPreviewScene/Private/AdvancedPreviewScene.cpp
jason hoerner 11ea386451 RHI validation fix: avoid redundant RHI frame begin/end when cubemap capture occurs during UEngine::Tick, and other Tick related functions where the RHI frame will already be active. A boolean "bInsideTick" flag is passed to UReflectionCaptureComponent::UpdateReflectionCaptureContents, specifying whether the cubemap capture is happening during Tick.
The four places this applies include UEditorEngine::Tick, UGameEngine::Tick, FAdvancedPreviewScene::Tick, and FPreviewScene::UpdateCaptureContents.  The last example is called from FBlueprintEditor::Tick and FThumbnailPreviewScene::Tick.

The counter-example is "UEditorEngine::BuildReflectionCaptures", which doesn't appear to be called anywhere from a Tick related function.  It's called in startup functions, various commands, and as part of static lighting generation.  If we run into further validation errors, we could add a flag to that function to differentiate those cases.  Even though there's only one counter-example, I figured it was best to err on the side of not changing behavior of client code that may also be using the function, so by default "bInsideTick" is set to false.

#jira UE-153604
#rb mihnea.balta jason.nadro
#rnx
#preflight 628d89082f2409bc1e08a42e

[CL 20362380 by jason hoerner in ue5-main branch]
2022-05-25 07:11:28 -04:00

473 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AdvancedPreviewScene.h"
#include "UnrealClient.h"
#include "Components/PostProcessComponent.h"
#include "Engine/Texture.h"
#include "Engine/TextureCube.h"
#include "Materials/Material.h"
#include "Materials/MaterialInstanceConstant.h"
#include "UObject/Package.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Components/DirectionalLightComponent.h"
#include "Components/SkyLightComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SphereReflectionCaptureComponent.h"
#include "AssetViewerSettings.h"
#include "Engine/StaticMesh.h"
#include "AdvancedPreviewSceneCommands.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/Application/SlateApplication.h"
#include "Editor.h"
FAdvancedPreviewScene::FAdvancedPreviewScene(ConstructionValues CVS, float InFloorOffset)
: FPreviewScene(CVS)
{
DefaultSettings = UAssetViewerSettings::Get();
check(DefaultSettings);
RefreshDelegate = DefaultSettings->OnAssetViewerSettingsChanged().AddRaw(this, &FAdvancedPreviewScene::OnAssetViewerSettingsRefresh);
CurrentProfileIndex = DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex) ? GetDefault<UEditorPerProjectUserSettings>()->AssetViewerProfileIndex : 0;
ensureMsgf(DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex), TEXT("Invalid default settings pointer or current profile index"));
FPreviewSceneProfile& Profile = DefaultSettings->Profiles[CurrentProfileIndex];
Profile.LoadEnvironmentMap();
const FTransform Transform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(1));
// Always set up sky light using the set cube map texture, reusing the sky light from PreviewScene class
SetSkyCubemap(Profile.EnvironmentCubeMap.Get());
SetSkyBrightness(Profile.SkyLightIntensity);
// Large scale to prevent sphere from clipping
const FTransform SphereTransform(FRotator(0, 0, 0), FVector(0, 0, 0), FVector(2000));
SkyComponent = NewObject<UStaticMeshComponent>(GetTransientPackage());
// Set up sky sphere showing the same cube map as used by the sky light
UStaticMesh* SkySphere = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/EditorMeshes/AssetViewer/Sphere_inversenormals.Sphere_inversenormals"), NULL, LOAD_None, NULL);
check(SkySphere);
SkyComponent->SetStaticMesh(SkySphere);
SkyComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
UMaterial* SkyMaterial = LoadObject<UMaterial>(NULL, TEXT("/Engine/EditorMaterials/AssetViewer/M_SkyBox.M_SkyBox"), NULL, LOAD_None, NULL);
check(SkyMaterial);
InstancedSkyMaterial = NewObject<UMaterialInstanceConstant>(GetTransientPackage());
InstancedSkyMaterial->Parent = SkyMaterial;
UTextureCube* DefaultTexture = LoadObject<UTextureCube>(NULL, TEXT("/Engine/MapTemplates/Sky/SunsetAmbientCubemap.SunsetAmbientCubemap"));
InstancedSkyMaterial->SetTextureParameterValueEditorOnly(FName("SkyBox"), ( Profile.EnvironmentCubeMap.Get() != nullptr ) ? Profile.EnvironmentCubeMap.Get() : DefaultTexture );
InstancedSkyMaterial->SetScalarParameterValueEditorOnly(FName("CubemapRotation"), Profile.LightingRigRotation / 360.0f);
InstancedSkyMaterial->SetScalarParameterValueEditorOnly(FName("Intensity"), Profile.SkyLightIntensity);
InstancedSkyMaterial->PostLoad();
SkyComponent->SetMaterial(0, InstancedSkyMaterial);
AddComponent(SkyComponent, SphereTransform);
PostProcessComponent = NewObject<UPostProcessComponent>();
PostProcessComponent->Settings = Profile.PostProcessingSettings;
PostProcessComponent->bUnbound = true;
AddComponent(PostProcessComponent, Transform);
UStaticMesh* FloorMesh = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/EditorMeshes/AssetViewer/Floor_Mesh.Floor_Mesh"), NULL, LOAD_None, NULL);
check(FloorMesh);
FloorMeshComponent = NewObject<UStaticMeshComponent>(GetTransientPackage());
FloorMeshComponent->SetStaticMesh(FloorMesh);
FloorMeshComponent->bSelectable = false;
FTransform FloorTransform(FRotator(0, 0, 0), FVector(0, 0, -(InFloorOffset)), FVector(4.0f, 4.0f, 1.0f ));
AddComponent(FloorMeshComponent, FloorTransform);
SetLightDirection(Profile.DirectionalLightRotation);
bRotateLighting = Profile.bRotateLightingRig;
CurrentRotationSpeed = Profile.RotationSpeed;
bSkyChanged = false;
BindCommands();
// since advance preview scenes are used in conjunction with material/mesh editors we should match their feature level with the global one
if (GIsEditor && GEditor != nullptr)
{
PreviewWorld->ChangeFeatureLevel(GEditor->DefaultWorldFeatureLevel);
}
PreviousRotation = Profile.LightingRigRotation;
UILightingRigRotationDelta = 0.0f;
}
FAdvancedPreviewScene::~FAdvancedPreviewScene()
{
if (UObjectInitialized())
{
DefaultSettings = UAssetViewerSettings::Get();
if (DefaultSettings)
{
DefaultSettings->OnAssetViewerSettingsChanged().Remove(RefreshDelegate);
}
}
}
void FAdvancedPreviewScene::UpdateScene(FPreviewSceneProfile& Profile, bool bUpdateSkyLight /*= true*/, bool bUpdateEnvironment /*= true*/, bool bUpdatePostProcessing /*= true*/, bool bUpdateDirectionalLight /*= true*/)
{
if (bUpdateSkyLight)
{
// Threshold to ensure we only update the intensity if it is going to make a difference
if (!FMath::IsNearlyEqual(SkyLight->Intensity, Profile.SkyLightIntensity, 0.05f))
{
static const FName IntensityName("Intensity");
SetSkyBrightness(Profile.SkyLightIntensity);
InstancedSkyMaterial->SetScalarParameterValueEditorOnly(IntensityName, Profile.SkyLightIntensity);
bSkyChanged = true;
}
}
if (bUpdateEnvironment)
{
static const FName SkyBoxName("SkyBox");
static const FName CubeMapRotationName("CubemapRotation");
UTextureCube* EnvironmentTexture = Profile.EnvironmentCubeMap.LoadSynchronous();
UTexture* Texture = EnvironmentTexture;
InstancedSkyMaterial->GetTextureParameterValue(SkyBoxName, Texture);
if (Texture != EnvironmentTexture)
{
InstancedSkyMaterial->SetTextureParameterValueEditorOnly(SkyBoxName, EnvironmentTexture);
SetSkyCubemap(EnvironmentTexture);
bSkyChanged = true;
}
static const float OneOver360 = 1.0f / 360.0f;
float Rotation = Profile.LightingRigRotation;
InstancedSkyMaterial->GetScalarParameterValue(CubeMapRotationName, Rotation);
const bool bLightRighRotationChanged = !FMath::IsNearlyEqual(Rotation, Profile.LightingRigRotation, 0.05f);
// We also test any change of light direction because the default profile light orientation might not match the LightingRigRotation.
FRotator LightDir = GetLightDirection();
const bool bLightDirChanged = LightDir != Profile.DirectionalLightRotation;
if (bLightRighRotationChanged || bLightDirChanged)
{
InstancedSkyMaterial->SetScalarParameterValueEditorOnly(CubeMapRotationName, Profile.LightingRigRotation * OneOver360);
// NOTE: this code is only executed when the UI rig rotation angle is interacted with.
// the l+mouse shortcut and the rig smooth rotation are handled at other places for the directional light
LightDir.Yaw += UILightingRigRotationDelta;
UILightingRigRotationDelta = 0;
SetLightDirection(LightDir);
DefaultSettings->Profiles[CurrentProfileIndex].DirectionalLightRotation = LightDir;
SkyLight->SourceCubemapAngle = Profile.LightingRigRotation;
bSkyChanged = true;
}
}
if (bUpdatePostProcessing)
{
PostProcessComponent->Settings = Profile.PostProcessingSettings;
PostProcessComponent->bEnabled = Profile.bPostProcessingEnabled;
bPostProcessing = Profile.bPostProcessingEnabled;
}
if (bUpdateDirectionalLight)
{
if (!FMath::IsNearlyEqual(DirectionalLight->Intensity, Profile.DirectionalLightIntensity, 0.05f))
{
DirectionalLight->SetIntensity(Profile.DirectionalLightIntensity);
}
DirectionalLight->SetLightColor(Profile.DirectionalLightColor);
}
SkyComponent->SetVisibility(Profile.bShowEnvironment, true);
SkyLight->SetVisibility(Profile.bUseSkyLighting, true);
FloorMeshComponent->SetVisibility(Profile.bShowFloor, true);
bRotateLighting = Profile.bRotateLightingRig;
CurrentRotationSpeed = Profile.RotationSpeed;
}
FLinearColor FAdvancedPreviewScene::GetBackgroundColor() const
{
FLinearColor Color = DefaultSettings->Profiles[CurrentProfileIndex].EnvironmentColor;
return Color * DefaultSettings->Profiles[CurrentProfileIndex].EnvironmentIntensity;
}
float FAdvancedPreviewScene::GetFloorOffset() const
{
return -(float)FloorMeshComponent->GetRelativeTransform().GetLocation().Z;
}
void FAdvancedPreviewScene::SetFloorOffset(const float InFloorOffset)
{
FTransform FloorTransform(FRotator(0, 0, 0), FVector(0, 0, -(InFloorOffset)), FVector(4.0f, 4.0f, 1.0f));
FloorMeshComponent->SetRelativeTransform(FloorTransform);
}
void FAdvancedPreviewScene::SetProfileIndex(const int32 InProfileIndex)
{
CurrentProfileIndex = InProfileIndex;
DefaultSettings->Profiles[CurrentProfileIndex].LoadEnvironmentMap();
SetLightDirection(DefaultSettings->Profiles[CurrentProfileIndex].DirectionalLightRotation);
PreviousRotation = DefaultSettings->Profiles[CurrentProfileIndex].LightingRigRotation;
UILightingRigRotationDelta = 0.0f;
UpdateScene(DefaultSettings->Profiles[CurrentProfileIndex]);
DefaultSettings->OnAssetViewerSettingsChanged().Broadcast(NAME_None);
}
void FAdvancedPreviewScene::Tick(float DeltaTime)
{
checkf(DefaultSettings && DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex), TEXT("Invalid default settings pointer or current profile index"));
UpdateCaptureContents();
FPreviewSceneProfile& Profile = DefaultSettings->Profiles[CurrentProfileIndex];
if (Profile.bRotateLightingRig)
{
CurrentRotationSpeed = Profile.RotationSpeed;
Profile.LightingRigRotation = FMath::Fmod(FMath::Max(FMath::Min(Profile.LightingRigRotation + (CurrentRotationSpeed * DeltaTime), 360.0f), 0.0f), 360.0f);
FRotator LightDir = GetLightDirection();
LightDir.Yaw += DeltaTime * -CurrentRotationSpeed;
SetLightDirection(LightDir);
DefaultSettings->Profiles[CurrentProfileIndex].DirectionalLightRotation = LightDir;
}
if (!FMath::IsNearlyEqual(PreviousRotation, Profile.LightingRigRotation, 0.05f))
{
SkyLight->SourceCubemapAngle = Profile.LightingRigRotation;
SkyLight->SetCaptureIsDirty();
SkyLight->MarkRenderStateDirty();
SkyLight->UpdateSkyCaptureContents(PreviewWorld);
InstancedSkyMaterial->SetScalarParameterValueEditorOnly(FName("CubemapRotation"), Profile.LightingRigRotation / 360.0f);
InstancedSkyMaterial->PostEditChange();
const bool bInsideTick = true;
UReflectionCaptureComponent::UpdateReflectionCaptureContents(PreviewWorld, nullptr, false, false, bInsideTick);
PreviewWorld->UpdateAllSkyCaptures();
PreviousRotation = Profile.LightingRigRotation;
UILightingRigRotationDelta = 0.0f;
}
// Update the sky every tick rather than every mouse move (UpdateScene call)
if (bSkyChanged)
{
SkyLight->SetCaptureIsDirty();
SkyLight->MarkRenderStateDirty();
SkyLight->UpdateSkyCaptureContents(PreviewWorld);
InstancedSkyMaterial->PostEditChange();
bSkyChanged = false;
}
}
TStatId FAdvancedPreviewScene::GetStatId() const
{
return TStatId();
}
const bool FAdvancedPreviewScene::HandleViewportInput(FViewport* InViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
{
bool bResult = false;
const bool bMouseButtonDown = InViewport->KeyState(EKeys::LeftMouseButton) || InViewport->KeyState(EKeys::MiddleMouseButton) || InViewport->KeyState(EKeys::RightMouseButton);
const bool bSkyMove = InViewport->KeyState(EKeys::K);
const bool bLightMoveDown = InViewport->KeyState(EKeys::L);
// Look at which axis is being dragged and by how much
const float DragX = (Key == EKeys::MouseX) ? Delta : 0.f;
const float DragY = (Key == EKeys::MouseY) ? Delta : 0.f;
// Move the sky around if K is down and the mouse has moved on the X-axis
if (bSkyMove && bMouseButtonDown)
{
static const float SkyRotationSpeed = 0.22f;
float SkyRotation = GetSkyRotation();
SkyRotation += -DragX * SkyRotationSpeed;
SetSkyRotation(SkyRotation);
bResult = true;
}
if (bLightMoveDown && (!FMath::IsNearlyZero(DragX) || !FMath::IsNearlyZero(DragY)))
{
// Save light rotation
DefaultSettings->Profiles[CurrentProfileIndex].DirectionalLightRotation = GetLightDirection();
}
return bResult;
}
const bool FAdvancedPreviewScene::HandleInputKey(FViewport* InViewport, int32 ControllerId, FKey Key, EInputEvent Event, float AmountDepressed, bool Gamepad)
{
if (Event == IE_Pressed)
{
FModifierKeysState KeyState = FSlateApplication::Get().GetModifierKeys();
if (UICommandList->ProcessCommandBindings(Key, KeyState, (Event == IE_Repeat))) //-V547
{
return true;
}
}
return false;
}
bool FAdvancedPreviewScene::GetFloorVisibility() const
{
if(FloorMeshComponent)
{
return FloorMeshComponent->GetVisibleFlag();
}
return false;
}
void FAdvancedPreviewScene::SetFloorVisibility(const bool bVisible, const bool bDirect)
{
// If not direct set visibility in profile and refresh the scene
if (!bDirect)
{
FName PropertyName("bShowFloor");
FProperty* FloorProperty = FindFProperty<FProperty>(FPreviewSceneProfile::StaticStruct(), PropertyName);
DefaultSettings->Profiles[CurrentProfileIndex].bShowFloor = bVisible;
FPropertyChangedEvent PropertyEvent(FloorProperty);
DefaultSettings->PostEditChangeProperty(PropertyEvent);
}
else
{
// Otherwise set visibility directly on the component
FloorMeshComponent->SetVisibility(bVisible ? DefaultSettings->Profiles[CurrentProfileIndex].bShowFloor : bVisible);
}
}
void FAdvancedPreviewScene::SetEnvironmentVisibility(const bool bVisible, const bool bDirect)
{
// If not direct set visibility in profile and refresh the scene
if (!bDirect)
{
FProperty* EnvironmentProperty = FindFProperty<FProperty>(FPreviewSceneProfile::StaticStruct(), GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bShowEnvironment));
DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment = bVisible;
FPropertyChangedEvent PropertyEvent(EnvironmentProperty);
DefaultSettings->PostEditChangeProperty(PropertyEvent);
}
else
{
// Otherwise set visibility directly on the component
SkyComponent->SetVisibility(bVisible);
}
}
const float FAdvancedPreviewScene::GetSkyRotation() const
{
checkf(DefaultSettings && DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex), TEXT("Invalid default settings pointer or current profile index"));
return DefaultSettings->Profiles[CurrentProfileIndex].LightingRigRotation;
}
void FAdvancedPreviewScene::SetSkyRotation(const float SkyRotation)
{
checkf(DefaultSettings && DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex), TEXT("Invalid default settings pointer or current profile index"));
float ClampedSkyRotation = FMath::Fmod(SkyRotation, 360.0f);
// Clamp and wrap around sky rotation
if (ClampedSkyRotation < 0.0f)
{
ClampedSkyRotation += 360.0f;
}
DefaultSettings->Profiles[CurrentProfileIndex].LightingRigRotation = ClampedSkyRotation;
}
const bool FAdvancedPreviewScene::IsUsingPostProcessing() const
{
return bPostProcessing;
}
const int32 FAdvancedPreviewScene::GetCurrentProfileIndex() const
{
return CurrentProfileIndex;
}
const UStaticMeshComponent* FAdvancedPreviewScene::GetFloorMeshComponent() const
{
checkf(FloorMeshComponent != nullptr, TEXT("Invalid floor mesh component pointer"));
return FloorMeshComponent;
}
void FAdvancedPreviewScene::BindCommands()
{
UICommandList = MakeShared<FUICommandList>();
const FAdvancedPreviewSceneCommands& Commands = FAdvancedPreviewSceneCommands::Get();
UICommandList->MapAction(Commands.ToggleFloor,
FExecuteAction::CreateRaw(this, &FAdvancedPreviewScene::HandleToggleFloor));
UICommandList->MapAction(Commands.ToggleEnvironment,
FExecuteAction::CreateRaw(this, &FAdvancedPreviewScene::HandleToggleEnvironment));
UICommandList->MapAction(Commands.TogglePostProcessing,
FExecuteAction::CreateRaw(this, &FAdvancedPreviewScene::HandleTogglePostProcessing));
}
void FAdvancedPreviewScene::HandleToggleEnvironment()
{
SetEnvironmentVisibility(!DefaultSettings->Profiles[CurrentProfileIndex].bShowEnvironment);
}
void FAdvancedPreviewScene::HandleToggleFloor()
{
SetFloorVisibility(!DefaultSettings->Profiles[CurrentProfileIndex].bShowFloor);
}
void FAdvancedPreviewScene::HandleTogglePostProcessing()
{
FPreviewSceneProfile& Profile = DefaultSettings->Profiles[CurrentProfileIndex];
Profile.bPostProcessingEnabled = !Profile.bPostProcessingEnabled;
PostProcessComponent->bEnabled = Profile.bPostProcessingEnabled;
bPostProcessing = Profile.bPostProcessingEnabled;
FName PropertyName("bPostProcessingEnabled");
FProperty* PostProcessingProperty = FindFProperty<FProperty>(FPreviewSceneProfile::StaticStruct(), PropertyName);
FPropertyChangedEvent PropertyEvent(PostProcessingProperty);
DefaultSettings->PostEditChangeProperty(PropertyEvent);
}
void FAdvancedPreviewScene::OnAssetViewerSettingsRefresh(const FName& InPropertyName)
{
// If the profile was changed, update the current index and the scene.
if (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, ProfileName))
{
CurrentProfileIndex = GetDefault<UEditorPerProjectUserSettings>()->AssetViewerProfileIndex;
CurrentProfileIndex = DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex) ? CurrentProfileIndex : 0;
PreviousRotation = DefaultSettings->Profiles[CurrentProfileIndex].LightingRigRotation;
UILightingRigRotationDelta = 0.0f;
UpdateScene(DefaultSettings->Profiles[CurrentProfileIndex]);
}
else if (DefaultSettings->Profiles.IsValidIndex(CurrentProfileIndex))
{
const bool bNameNone = InPropertyName == NAME_None;
const bool bUpdateEnvironment = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, EnvironmentCubeMap)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, LightingRigRotation) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles)));
const bool bUpdateSkyLight = bUpdateEnvironment || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, SkyLightIntensity) || InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bUseSkyLighting) || (InPropertyName == GET_MEMBER_NAME_CHECKED(UAssetViewerSettings, Profiles)));
const bool bUpdateDirectionalLight = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, DirectionalLightIntensity)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, DirectionalLightColor));
const bool bUpdatePostProcessing = (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, PostProcessingSettings)) || (InPropertyName == GET_MEMBER_NAME_CHECKED(FPreviewSceneProfile, bPostProcessingEnabled));
UILightingRigRotationDelta += PreviousRotation - DefaultSettings->Profiles[CurrentProfileIndex].LightingRigRotation;
PreviousRotation = DefaultSettings->Profiles[CurrentProfileIndex].LightingRigRotation;
UpdateScene(DefaultSettings->Profiles[CurrentProfileIndex], bUpdateSkyLight || bNameNone, bUpdateEnvironment || bNameNone, bUpdatePostProcessing || bNameNone, bUpdateDirectionalLight || bNameNone);
}
}