You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ============================ MAJOR FEATURES & CHANGES ============================ Change 4060527 by Anousack.Kitisa Added support for importing FBX user properties as metadata on StaticMesh when importing FBX. Added support for exporting StaticMesh metadata as FBX user properties when exporting StaticMesh to FBX. #jira UESP-567 Change 4060835 by Jamie.Dale Added assign method to Python exposed structs This lets you assign the value of one struct instance onto another instance (rather than copy the pointer in Python). It also accepts anything that casts to the destination struct. Change 4060838 by Jamie.Dale Include unary operator function tooltips in doc string Change 4060843 by Jamie.Dale Fixed PythonizeValue including deprecated properties in the init function for a struct Change 4060908 by Jamie.Dale Fixed some name conflicts in generated Python glue Change 4061065 by Jamie.Dale Stubbed struct return values are now default constructed Change 4061205 by David.Hibbitts Added blueprint functions to create a message bus source, for use in projects where the Editor UI is not available or is impractical. Added a blueprint function to get available subject names for the LiveLink Client Added a RemoveSource method to ILiveLinkClient Added a GetSubjectNames method to ILiveLinkClient Fixed a crash when RequestShutdown was called on a MessageBusSource after the HeartbeatManager had already been shut down. Change 4061421 by Patrick.Boutot [AJA] Warn the user if he requested the key and the backbuffer is not setup properly. #jira UE-58614 Change 4061620 by Jamie.Dale Made the Sphinx config a template so we can inject the current engine version into it Change 4062578 by Jamie.Dale Optimized Python stub and doc gen file writes - Files are now only written when they've changed. - We now only remove files that are stale. - No requests to generate stub and doc files are queued before the first Tick. Change 4062634 by Jamie.Dale No longer export FDateTime defaults to struct __init__ as they can be non-deterministic Change 4064275 by Jamie.Dale Added callbacks for when Python is initialized and shutdown so that external modules can hook-in appropriately Change 4064613 by James.McNatton Change to initialization for FVirtualCameraWaypoint and FVirtualCameraSettingsPreset to remove non-deterministic constructors and a few resulting cleanup items Change 4064878 by Patrick.Boutot Add timecode provider plugin to capture from the Audio jack. Change 4064910 by Patrick.Boutot [AJA] Add AjaTimecodeProvider that provider the timecode from a SDI input source. Change 4067451 by Jamie.Dale Added command line options to enable all plugins, optionally excluding certain plugins Change 4067489 by Simon.Tourangeau Support for DX12 quad buffer stereo rendering Change 4068640 by Patrick.Boutot Add a state to CustomTimeStep. Show the state of the CustomTimeStep in "stat fps". Change 4069147 by Patrick.Boutot Move Mediasmith console to Engine. Renamed to TimecodeSynchronizer. Change 4071727 by Matt.Hoffman Initial pass at exposing Sequencer's Render to Movie functionality to Python. All settings that can be adjusted via the UI can be set from Python and renders can be invoked for both in-editor capture as well as new process capture. A basic API is provided which enables querying if a render in progress and canceling an in progress one. #jira UESP-541 [CL 4071957 by JeanMichel Dignard in Main branch]
917 lines
27 KiB
C++
917 lines
27 KiB
C++
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "MovieSceneCaptureDialogModule.h"
|
|
#include "Dom/JsonValue.h"
|
|
#include "Dom/JsonObject.h"
|
|
#include "Layout/Visibility.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "Layout/Margin.h"
|
|
#include "GenericPlatform/GenericApplication.h"
|
|
#include "Input/Reply.h"
|
|
#include "UObject/GCObject.h"
|
|
#include "Widgets/SWidget.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/SCompoundWidget.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "Widgets/SWindow.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Framework/Docking/TabManager.h"
|
|
#include "Widgets/Input/NumericTypeInterface.h"
|
|
#include "FrameNumberDetailsCustomization.h"
|
|
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/App.h"
|
|
#include "Serialization/ObjectWriter.h"
|
|
#include "Serialization/ObjectReader.h"
|
|
#include "Templates/SubclassOf.h"
|
|
#include "Engine/GameViewportClient.h"
|
|
#include "GameFramework/GameModeBase.h"
|
|
#include "Slate/SceneViewport.h"
|
|
#include "MovieSceneCapture.h"
|
|
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Settings/LevelEditorPlaySettings.h"
|
|
#include "Editor.h"
|
|
|
|
#include "Widgets/Input/SHyperlink.h"
|
|
#include "Widgets/Images/SThrobber.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "AudioDevice.h"
|
|
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "JsonObjectConverter.h"
|
|
#include "Widgets/Notifications/INotificationWidget.h"
|
|
|
|
#include "PropertyEditorModule.h"
|
|
#include "IDetailsView.h"
|
|
#include "FileHelpers.h"
|
|
|
|
#include "ISessionManager.h"
|
|
#include "ISessionServicesModule.h"
|
|
|
|
#include "ErrorCodes.h"
|
|
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "FrameNumberNumericInterface.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MovieSceneCaptureDialog"
|
|
|
|
const TCHAR* MovieCaptureSessionName = TEXT("Movie Scene Capture");
|
|
|
|
DECLARE_DELEGATE_RetVal_OneParam(FText, FOnStartCapture, UMovieSceneCapture*);
|
|
|
|
class SRenderMovieSceneSettings : public SCompoundWidget, public FGCObject
|
|
{
|
|
SLATE_BEGIN_ARGS(SRenderMovieSceneSettings) : _InitialObject(nullptr) {}
|
|
SLATE_EVENT(FOnStartCapture, OnStartCapture)
|
|
SLATE_ARGUMENT(UMovieSceneCapture*, InitialObject)
|
|
SLATE_ARGUMENT(TSharedPtr<INumericTypeInterface<double>>, NumericTypeInterface)
|
|
SLATE_END_ARGS()
|
|
|
|
void Construct(const FArguments& InArgs)
|
|
{
|
|
FPropertyEditorModule& PropertyEditor = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
DetailsViewArgs.bUpdatesFromSelection = false;
|
|
DetailsViewArgs.bLockable = false;
|
|
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
|
|
DetailsViewArgs.ViewIdentifier = "RenderMovieScene";
|
|
|
|
DetailView = PropertyEditor.CreateDetailView(DetailsViewArgs);
|
|
DetailView->RegisterInstancedCustomPropertyTypeLayout("FrameNumber", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FFrameNumberDetailsCustomization::MakeInstance, InArgs._NumericTypeInterface));
|
|
|
|
OnStartCapture = InArgs._OnStartCapture;
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
DetailView.ToSharedRef()
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(ErrorText, STextBlock)
|
|
.Visibility(EVisibility::Hidden)
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Right)
|
|
.Padding(5.f)
|
|
[
|
|
SNew(SButton)
|
|
.IsEnabled(this, &SRenderMovieSceneSettings::CanStartCapture)
|
|
.ContentPadding(FMargin(10, 5))
|
|
.Text(this, &SRenderMovieSceneSettings::GetStartCaptureText)
|
|
.OnClicked(this, &SRenderMovieSceneSettings::OnStartClicked)
|
|
]
|
|
|
|
];
|
|
|
|
MovieSceneCapture = nullptr;
|
|
|
|
if (InArgs._InitialObject)
|
|
{
|
|
SetObject(InArgs._InitialObject);
|
|
}
|
|
}
|
|
|
|
void SetObject(UMovieSceneCapture* InMovieSceneCapture)
|
|
{
|
|
MovieSceneCapture = InMovieSceneCapture;
|
|
|
|
DetailView->SetObject(InMovieSceneCapture);
|
|
|
|
ErrorText->SetText(FText());
|
|
ErrorText->SetVisibility(EVisibility::Hidden);
|
|
}
|
|
|
|
virtual void AddReferencedObjects( FReferenceCollector& Collector ) override
|
|
{
|
|
Collector.AddReferencedObject(MovieSceneCapture);
|
|
}
|
|
|
|
private:
|
|
|
|
FReply OnStartClicked()
|
|
{
|
|
FText Error;
|
|
if (OnStartCapture.IsBound())
|
|
{
|
|
Error = OnStartCapture.Execute(MovieSceneCapture);
|
|
}
|
|
|
|
ErrorText->SetText(Error);
|
|
ErrorText->SetVisibility(Error.IsEmpty() ? EVisibility::Hidden : EVisibility::Visible);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FText GetStartCaptureText() const
|
|
{
|
|
if (MovieSceneCapture && !MovieSceneCapture->bUseSeparateProcess)
|
|
{
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::PIE)
|
|
{
|
|
return LOCTEXT("ExportExitPIE", "(Exit PIE to start)");
|
|
}
|
|
}
|
|
}
|
|
|
|
return LOCTEXT("Export", "Capture Movie");
|
|
}
|
|
|
|
bool CanStartCapture() const
|
|
{
|
|
if (!MovieSceneCapture)
|
|
{
|
|
return false;
|
|
}
|
|
else if (MovieSceneCapture->bUseSeparateProcess)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::PIE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TSharedPtr<IDetailsView> DetailView;
|
|
TSharedPtr<STextBlock> ErrorText;
|
|
FOnStartCapture OnStartCapture;
|
|
UMovieSceneCapture* MovieSceneCapture;
|
|
};
|
|
|
|
DECLARE_DELEGATE_OneParam(FOnCaptureFinished, bool /*bCancelled*/);
|
|
|
|
class SCaptureMovieNotification : public SCompoundWidget, public INotificationWidget
|
|
{
|
|
public:
|
|
SLATE_BEGIN_ARGS(SCaptureMovieNotification){}
|
|
|
|
SLATE_ATTRIBUTE(FCaptureState, CaptureState)
|
|
|
|
SLATE_EVENT(FOnCaptureFinished, OnCaptureFinished)
|
|
|
|
SLATE_EVENT(FSimpleDelegate, OnCancel)
|
|
|
|
SLATE_ARGUMENT(FString, CapturePath)
|
|
|
|
SLATE_END_ARGS()
|
|
|
|
void Construct(const FArguments& InArgs)
|
|
{
|
|
CaptureState = InArgs._CaptureState;
|
|
OnCaptureFinished = InArgs._OnCaptureFinished;
|
|
OnCancel = InArgs._OnCancel;
|
|
|
|
CachedState = FCaptureState(ECaptureStatus::Pending);
|
|
|
|
FString CapturePath = FPaths::ConvertRelativePathToFull(InArgs._CapturePath);
|
|
CapturePath.RemoveFromEnd(TEXT("\\"));
|
|
|
|
auto OnBrowseToFolder = [=]{
|
|
FPlatformProcess::ExploreFolder(*CapturePath);
|
|
};
|
|
|
|
ChildSlot
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(FMargin(15.0f))
|
|
.BorderImage(FCoreStyle::Get().GetBrush("NotificationList.ItemBackground"))
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(FMargin(0,0,0,5.0f))
|
|
.HAlign(HAlign_Right)
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(TextBlock, STextBlock)
|
|
.Font(FCoreStyle::Get().GetFontStyle(TEXT("NotificationList.FontBold")))
|
|
.Text(LOCTEXT("RenderingVideo", "Capturing video"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(15.f,0,0,0))
|
|
[
|
|
SAssignNew(Throbber, SThrobber)
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(0,0,0,5.0f))
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SAssignNew(DetailedTextBlock, STextBlock)
|
|
.Visibility(EVisibility::Collapsed)
|
|
.Font(FCoreStyle::Get().GetFontStyle(TEXT("NotificationList.FontLight")))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(Hyperlink, SHyperlink)
|
|
.Text(LOCTEXT("OpenFolder", "Open Capture Folder..."))
|
|
.OnNavigate_Lambda(OnBrowseToFolder)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(5.0f,0,0,0))
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(Button, SButton)
|
|
.Text(LOCTEXT("StopButton", "Stop Capture"))
|
|
.OnClicked(this, &SCaptureMovieNotification::ButtonClicked)
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
if (State != SNotificationItem::CS_Pending)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FCaptureState StateThisFrame = CaptureState.Get();
|
|
|
|
if (CachedState.Status != StateThisFrame.Status)
|
|
{
|
|
CachedState = StateThisFrame;
|
|
|
|
if (CachedState.Status == ECaptureStatus::Success)
|
|
{
|
|
TextBlock->SetText(LOCTEXT("CaptureFinished", "Capture Finished"));
|
|
OnCaptureFinished.ExecuteIfBound(true);
|
|
}
|
|
else if (CachedState.Status == ECaptureStatus::Failure)
|
|
{
|
|
TextBlock->SetText(LOCTEXT("CaptureFailed", "Capture Failed"));
|
|
FText DetailText = CachedState.GetDetailText();
|
|
if (!DetailText.IsEmpty())
|
|
{
|
|
DetailedTextBlock->SetText(DetailText);
|
|
DetailedTextBlock->SetVisibility(EVisibility::Visible);
|
|
}
|
|
OnCaptureFinished.ExecuteIfBound(false);
|
|
}
|
|
else
|
|
{
|
|
ensureMsgf(false, TEXT("Cannot move from a finished to a pending state."));
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void OnSetCompletionState(SNotificationItem::ECompletionState InState)
|
|
{
|
|
State = InState;
|
|
if (State != SNotificationItem::CS_Pending)
|
|
{
|
|
Throbber->SetVisibility(EVisibility::Collapsed);
|
|
Button->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
}
|
|
|
|
virtual TSharedRef< SWidget > AsWidget()
|
|
{
|
|
return AsShared();
|
|
}
|
|
|
|
private:
|
|
|
|
FReply ButtonClicked()
|
|
{
|
|
if (State == SNotificationItem::CS_Pending)
|
|
{
|
|
OnCancel.ExecuteIfBound();
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
private:
|
|
TSharedPtr<SWidget> Button, Throbber, Hyperlink;
|
|
TSharedPtr<STextBlock> TextBlock;
|
|
TSharedPtr<STextBlock> DetailedTextBlock;
|
|
SNotificationItem::ECompletionState State;
|
|
|
|
FSimpleDelegate OnCancel;
|
|
FCaptureState CachedState;
|
|
TAttribute<FCaptureState> CaptureState;
|
|
FOnCaptureFinished OnCaptureFinished;
|
|
};
|
|
|
|
void FInEditorCapture::Start()
|
|
{
|
|
ULevelEditorPlaySettings* PlayInEditorSettings = GetMutableDefault<ULevelEditorPlaySettings>();
|
|
|
|
bScreenMessagesWereEnabled = GAreScreenMessagesEnabled;
|
|
GAreScreenMessagesEnabled = false;
|
|
|
|
if (!CaptureObject->Settings.bEnableTextureStreaming)
|
|
{
|
|
const int32 UndefinedTexturePoolSize = -1;
|
|
IConsoleVariable* CVarStreamingPoolSize = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Streaming.PoolSize"));
|
|
if (CVarStreamingPoolSize)
|
|
{
|
|
BackedUpStreamingPoolSize = CVarStreamingPoolSize->GetInt();
|
|
CVarStreamingPoolSize->Set(UndefinedTexturePoolSize, ECVF_SetByConsole);
|
|
}
|
|
|
|
IConsoleVariable* CVarUseFixedPoolSize = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Streaming.UseFixedPoolSize"));
|
|
if (CVarUseFixedPoolSize)
|
|
{
|
|
BackedUpUseFixedPoolSize = CVarUseFixedPoolSize->GetInt();
|
|
CVarUseFixedPoolSize->Set(0, ECVF_SetByConsole);
|
|
}
|
|
}
|
|
|
|
FObjectWriter(PlayInEditorSettings, BackedUpPlaySettings);
|
|
OverridePlaySettings(PlayInEditorSettings);
|
|
|
|
CaptureObject->AddToRoot();
|
|
CaptureObject->OnCaptureFinished().AddRaw(this, &FInEditorCapture::OnLevelSequenceFinished);
|
|
|
|
UGameViewportClient::OnViewportCreated().AddRaw(this, &FInEditorCapture::OnPIEViewportStarted);
|
|
FEditorDelegates::EndPIE.AddRaw(this, &FInEditorCapture::OnEndPIE);
|
|
|
|
FAudioDevice* AudioDevice = GEngine->GetMainAudioDevice();
|
|
if (AudioDevice != nullptr)
|
|
{
|
|
TransientMasterVolume = AudioDevice->GetTransientMasterVolume();
|
|
AudioDevice->SetTransientMasterVolume(0.0f);
|
|
}
|
|
|
|
GEditor->RequestPlaySession(true, nullptr, false);
|
|
}
|
|
|
|
void FInEditorCapture::Cancel()
|
|
{
|
|
// If the user cancels through the UI then we request that the editor shut down the PIE instance.
|
|
// We capture the PIE shutdown request (which calls OnEndPIE) and further process it. This unifies
|
|
// closing PIE via the close button and the UI into one code path.
|
|
GEditor->RequestEndPlayMap();
|
|
}
|
|
|
|
void FInEditorCapture::OverridePlaySettings(ULevelEditorPlaySettings* PlayInEditorSettings)
|
|
{
|
|
const FMovieSceneCaptureSettings& Settings = CaptureObject->GetSettings();
|
|
|
|
PlayInEditorSettings->NewWindowWidth = Settings.Resolution.ResX;
|
|
PlayInEditorSettings->NewWindowHeight = Settings.Resolution.ResY;
|
|
PlayInEditorSettings->CenterNewWindow = true;
|
|
PlayInEditorSettings->LastExecutedPlayModeType = EPlayModeType::PlayMode_InEditorFloating;
|
|
|
|
TSharedRef<SWindow> CustomWindow = SNew(SWindow)
|
|
.Title(LOCTEXT("MovieRenderPreviewTitle", "Movie Render - Preview"))
|
|
.AutoCenter(EAutoCenter::PrimaryWorkArea)
|
|
.UseOSWindowBorder(true)
|
|
.FocusWhenFirstShown(false)
|
|
.ActivationPolicy(EWindowActivationPolicy::Never)
|
|
.HasCloseButton(true)
|
|
.SupportsMaximize(false)
|
|
.SupportsMinimize(true)
|
|
.MaxWidth( Settings.Resolution.ResX )
|
|
.MaxHeight( Settings.Resolution.ResY )
|
|
.SizingRule(ESizingRule::FixedSize);
|
|
|
|
FSlateApplication::Get().AddWindow(CustomWindow);
|
|
|
|
PlayInEditorSettings->CustomPIEWindow = CustomWindow;
|
|
|
|
// Reset everything else
|
|
PlayInEditorSettings->GameGetsMouseControl = false;
|
|
PlayInEditorSettings->ShowMouseControlLabel = false;
|
|
PlayInEditorSettings->ViewportGetsHMDControl = false;
|
|
PlayInEditorSettings->ShouldMinimizeEditorOnVRPIE = true;
|
|
PlayInEditorSettings->EnableGameSound = false;
|
|
PlayInEditorSettings->bOnlyLoadVisibleLevelsInPIE = false;
|
|
PlayInEditorSettings->bPreferToStreamLevelsInPIE = false;
|
|
PlayInEditorSettings->PIEAlwaysOnTop = false;
|
|
PlayInEditorSettings->DisableStandaloneSound = true;
|
|
PlayInEditorSettings->AdditionalLaunchParameters = TEXT("");
|
|
PlayInEditorSettings->BuildGameBeforeLaunch = EPlayOnBuildMode::PlayOnBuild_Never;
|
|
PlayInEditorSettings->LaunchConfiguration = EPlayOnLaunchConfiguration::LaunchConfig_Default;
|
|
PlayInEditorSettings->SetPlayNetMode(EPlayNetMode::PIE_Standalone);
|
|
PlayInEditorSettings->SetRunUnderOneProcess(true);
|
|
PlayInEditorSettings->SetPlayNetDedicated(false);
|
|
PlayInEditorSettings->SetPlayNumberOfClients(1);
|
|
}
|
|
|
|
void FInEditorCapture::OnPIEViewportStarted()
|
|
{
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::PIE)
|
|
{
|
|
FSlatePlayInEditorInfo* SlatePlayInEditorSession = GEditor->SlatePlayInEditorMap.Find(Context.ContextHandle);
|
|
if (SlatePlayInEditorSession)
|
|
{
|
|
CapturingFromWorld = Context.World();
|
|
|
|
TSharedPtr<SWindow> Window = SlatePlayInEditorSession->SlatePlayInEditorWindow.Pin();
|
|
|
|
const FMovieSceneCaptureSettings& Settings = CaptureObject->GetSettings();
|
|
|
|
SlatePlayInEditorSession->SlatePlayInEditorWindowViewport->SetViewportSize(Settings.Resolution.ResX,Settings.Resolution.ResY);
|
|
|
|
FVector2D PreviewWindowSize(Settings.Resolution.ResX, Settings.Resolution.ResY);
|
|
|
|
// Keep scaling down the window size while we're bigger than half the desktop width/height
|
|
{
|
|
FDisplayMetrics DisplayMetrics;
|
|
FSlateApplication::Get().GetDisplayMetrics(DisplayMetrics);
|
|
|
|
while(PreviewWindowSize.X >= DisplayMetrics.PrimaryDisplayWidth*.5f || PreviewWindowSize.Y >= DisplayMetrics.PrimaryDisplayHeight*.5f)
|
|
{
|
|
PreviewWindowSize *= .5f;
|
|
}
|
|
}
|
|
|
|
// Resize and move the window into the desktop a bit
|
|
FVector2D PreviewWindowPosition(50, 50);
|
|
Window->ReshapeWindow(PreviewWindowPosition, PreviewWindowSize);
|
|
|
|
if (CaptureObject->Settings.GameModeOverride != nullptr)
|
|
{
|
|
CachedGameMode = CapturingFromWorld->GetWorldSettings()->DefaultGameMode;
|
|
CapturingFromWorld->GetWorldSettings()->DefaultGameMode = CaptureObject->Settings.GameModeOverride;
|
|
}
|
|
|
|
CaptureObject->Initialize(SlatePlayInEditorSession->SlatePlayInEditorWindowViewport, Context.PIEInstance);
|
|
OnCaptureStarted();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogTemp, Warning, TEXT("Recieved PIE Creation callback but failed to find PIE World or missing FSlatePlayInEditorInfo for world."));
|
|
}
|
|
|
|
void FInEditorCapture::Shutdown()
|
|
{
|
|
FEditorDelegates::EndPIE.RemoveAll(this);
|
|
UGameViewportClient::OnViewportCreated().RemoveAll(this);
|
|
CaptureObject->OnCaptureFinished().RemoveAll(this);
|
|
|
|
GAreScreenMessagesEnabled = bScreenMessagesWereEnabled;
|
|
|
|
if (!CaptureObject->Settings.bEnableTextureStreaming)
|
|
{
|
|
IConsoleVariable* CVarStreamingPoolSize = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Streaming.PoolSize"));
|
|
if (CVarStreamingPoolSize)
|
|
{
|
|
CVarStreamingPoolSize->Set(BackedUpStreamingPoolSize, ECVF_SetByConsole);
|
|
}
|
|
|
|
IConsoleVariable* CVarUseFixedPoolSize = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Streaming.UseFixedPoolSize"));
|
|
if (CVarUseFixedPoolSize)
|
|
{
|
|
CVarUseFixedPoolSize->Set(BackedUpUseFixedPoolSize, ECVF_SetByConsole);
|
|
}
|
|
}
|
|
|
|
if (CaptureObject->Settings.GameModeOverride != nullptr && CapturingFromWorld != nullptr)
|
|
{
|
|
CapturingFromWorld->GetWorldSettings()->DefaultGameMode = CachedGameMode;
|
|
}
|
|
|
|
FObjectReader(GetMutableDefault<ULevelEditorPlaySettings>(), BackedUpPlaySettings);
|
|
|
|
FAudioDevice* AudioDevice = GEngine->GetMainAudioDevice();
|
|
if (AudioDevice != nullptr)
|
|
{
|
|
AudioDevice->SetTransientMasterVolume(TransientMasterVolume);
|
|
}
|
|
|
|
CaptureObject->Close();
|
|
CaptureObject->RemoveFromRoot();
|
|
|
|
}
|
|
|
|
void FInEditorCapture::OnEndPIE(bool bIsSimulating)
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
void FInEditorCapture::OnLevelSequenceFinished()
|
|
{
|
|
Shutdown();
|
|
|
|
GEditor->RequestEndPlayMap();
|
|
}
|
|
|
|
void FInEditorCapture::OnCaptureStarted()
|
|
{
|
|
FNotificationInfo Info
|
|
(
|
|
SNew(SCaptureMovieNotification)
|
|
.CaptureState_Raw(this, &FInEditorCapture::GetCaptureState)
|
|
.CapturePath(CaptureObject->Settings.OutputDirectory.Path)
|
|
.OnCaptureFinished_Raw(this, &FInEditorCapture::OnCaptureFinished)
|
|
.OnCancel_Raw(this, &FInEditorCapture::Cancel)
|
|
);
|
|
|
|
Info.bFireAndForget = false;
|
|
Info.ExpireDuration = 5.f;
|
|
InProgressCaptureNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Pending);
|
|
}
|
|
|
|
FCaptureState FInEditorCapture::GetCaptureState() const
|
|
{
|
|
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
|
{
|
|
if (Context.WorldType == EWorldType::PIE)
|
|
{
|
|
return FCaptureState(ECaptureStatus::Pending);
|
|
}
|
|
}
|
|
|
|
return FCaptureState(ECaptureStatus::Success);
|
|
}
|
|
|
|
void FMovieSceneCaptureBase::OnCaptureFinished(bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Success);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("MovieSceneCapture failed to capture."));
|
|
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Fail);
|
|
}
|
|
|
|
InProgressCaptureNotification->ExpireAndFadeout();
|
|
InProgressCaptureNotification = nullptr;
|
|
|
|
if (OnFinishedCallback)
|
|
{
|
|
OnFinishedCallback(bSuccess);
|
|
}
|
|
}
|
|
|
|
void FNewProcessCapture::Start()
|
|
{
|
|
// Save out the capture manifest to json
|
|
FString Filename = FPaths::ProjectSavedDir() / TEXT("MovieSceneCapture/Manifest.json");
|
|
|
|
TSharedRef<FJsonObject> Object = MakeShareable(new FJsonObject);
|
|
if (FJsonObjectConverter::UStructToJsonObject(CaptureObject->GetClass(), CaptureObject, Object, 0, 0))
|
|
{
|
|
TSharedRef<FJsonObject> RootObject = MakeShareable(new FJsonObject);
|
|
RootObject->SetField(TEXT("Type"), MakeShareable(new FJsonValueString(CaptureObject->GetClass()->GetPathName())));
|
|
RootObject->SetField(TEXT("Data"), MakeShareable(new FJsonValueObject(Object)));
|
|
|
|
TSharedRef<FJsonObject> AdditionalJson = MakeShareable(new FJsonObject);
|
|
CaptureObject->SerializeJson(*AdditionalJson);
|
|
RootObject->SetField(TEXT("AdditionalData"), MakeShareable(new FJsonValueObject(AdditionalJson)));
|
|
|
|
FString Json;
|
|
TSharedRef<TJsonWriter<> > JsonWriter = TJsonWriterFactory<>::Create(&Json, 0);
|
|
if (FJsonSerializer::Serialize(RootObject, JsonWriter))
|
|
{
|
|
FFileHelper::SaveStringToFile(Json, *Filename);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString EditorCommandLine = FString::Printf(TEXT("%s -MovieSceneCaptureManifest=\"%s\" -game -NoLoadingScreen -ForceRes -Windowed"), *MapNameToLoad, *Filename);
|
|
|
|
// Spit out any additional, user-supplied command line args
|
|
if (!CaptureObject->AdditionalCommandLineArguments.IsEmpty())
|
|
{
|
|
EditorCommandLine.AppendChar(' ');
|
|
EditorCommandLine.Append(CaptureObject->AdditionalCommandLineArguments);
|
|
}
|
|
|
|
// Spit out any inherited command line args
|
|
if (!CaptureObject->InheritedCommandLineArguments.IsEmpty())
|
|
{
|
|
EditorCommandLine.AppendChar(' ');
|
|
EditorCommandLine.Append(CaptureObject->InheritedCommandLineArguments);
|
|
}
|
|
|
|
// Disable texture streaming if necessary
|
|
if (!CaptureObject->Settings.bEnableTextureStreaming)
|
|
{
|
|
EditorCommandLine.Append(TEXT(" -NoTextureStreaming"));
|
|
}
|
|
|
|
// Set the game resolution - we always want it windowed
|
|
EditorCommandLine += FString::Printf(TEXT(" -ResX=%d -ResY=%d -Windowed"), CaptureObject->Settings.Resolution.ResX, CaptureObject->Settings.Resolution.ResY);
|
|
|
|
// Ensure game session is correctly set up
|
|
EditorCommandLine += FString::Printf(TEXT(" -messaging -SessionName=\"%s\""), MovieCaptureSessionName);
|
|
|
|
FString Params;
|
|
if (FPaths::IsProjectFilePathSet())
|
|
{
|
|
Params = FString::Printf(TEXT("\"%s\" %s %s"), *FPaths::GetProjectFilePath(), *EditorCommandLine, *FCommandLine::GetSubprocessCommandline());
|
|
}
|
|
else
|
|
{
|
|
Params = FString::Printf(TEXT("%s %s %s"), FApp::GetProjectName(), *EditorCommandLine, *FCommandLine::GetSubprocessCommandline());
|
|
}
|
|
|
|
FString GamePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration());
|
|
FProcHandle ProcessHandle = FPlatformProcess::CreateProc(*GamePath, *Params, true, false, false, nullptr, 0, nullptr, nullptr);
|
|
|
|
if (ProcessHandle.IsValid())
|
|
{
|
|
if (CaptureObject->bCloseEditorWhenCaptureStarts)
|
|
{
|
|
FPlatformMisc::RequestExit(false);
|
|
return;
|
|
}
|
|
|
|
SharedProcHandle = MakeShareable(new FProcHandle(ProcessHandle));
|
|
|
|
OnCaptureStarted();
|
|
}
|
|
else
|
|
{
|
|
OnCaptureFinished(false);
|
|
}
|
|
}
|
|
|
|
void FNewProcessCapture::Cancel()
|
|
{
|
|
// If they cancel the capture via the UI we need to try and find a running session with the right name
|
|
bool bFoundInstance = false;
|
|
|
|
// Attempt to send a remote command to gracefully terminate the process
|
|
ISessionServicesModule& SessionServices = FModuleManager::Get().LoadModuleChecked<ISessionServicesModule>("SessionServices");
|
|
TSharedRef<ISessionManager> SessionManager = SessionServices.GetSessionManager();
|
|
|
|
TArray<TSharedPtr<ISessionInfo>> Sessions;
|
|
SessionManager->GetSessions(Sessions);
|
|
|
|
for (const TSharedPtr<ISessionInfo>& Session : Sessions)
|
|
{
|
|
if (Session->GetSessionName() == MovieCaptureSessionName)
|
|
{
|
|
TArray<TSharedPtr<ISessionInstanceInfo>> Instances;
|
|
Session->GetInstances(Instances);
|
|
|
|
for (const TSharedPtr<ISessionInstanceInfo>& Instance : Instances)
|
|
{
|
|
Instance->ExecuteCommand("exit");
|
|
bFoundInstance = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFoundInstance)
|
|
{
|
|
FPlatformProcess::TerminateProc(*SharedProcHandle);
|
|
}
|
|
}
|
|
|
|
void FNewProcessCapture::OnCaptureStarted()
|
|
{
|
|
FNotificationInfo Info
|
|
(
|
|
SNew(SCaptureMovieNotification)
|
|
.CaptureState_Raw(this, &FNewProcessCapture::GetCaptureState)
|
|
.CapturePath(CaptureObject->Settings.OutputDirectory.Path)
|
|
.OnCaptureFinished_Raw(this, &FNewProcessCapture::OnCaptureFinished)
|
|
.OnCancel_Raw(this, &FNewProcessCapture::Cancel)
|
|
);
|
|
|
|
Info.bFireAndForget = false;
|
|
Info.ExpireDuration = 5.f;
|
|
InProgressCaptureNotification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Pending);
|
|
}
|
|
|
|
FCaptureState FNewProcessCapture::GetCaptureState() const
|
|
{
|
|
if (!FPlatformProcess::IsProcRunning(*SharedProcHandle))
|
|
{
|
|
int32 RetCode = 0;
|
|
FPlatformProcess::GetProcReturnCode(*SharedProcHandle, &RetCode);
|
|
return FCaptureState(RetCode);
|
|
}
|
|
else
|
|
{
|
|
return FCaptureState(ECaptureStatus::Pending);
|
|
}
|
|
}
|
|
|
|
class FMovieSceneCaptureDialogModule : public IMovieSceneCaptureDialogModule
|
|
{
|
|
virtual UWorld* GetCurrentlyRecordingWorld() override
|
|
{
|
|
return CurrentCapture.IsValid() ? CurrentCapture->GetWorld() : nullptr;
|
|
}
|
|
|
|
virtual TSharedPtr<FMovieSceneCaptureBase> GetCurrentCapture() const override
|
|
{
|
|
return CurrentCapture;
|
|
}
|
|
|
|
virtual void StartCapture(UMovieSceneCapture* InCaptureSettings) override
|
|
{
|
|
/** Called when the capture object finishes its capture (success or fail). */
|
|
auto OnCaptureFinished = [this](bool bSuccess)
|
|
{
|
|
UE_LOG(LogTemp, Log, TEXT("Movie Capture finished. Success: %d"), bSuccess);
|
|
CurrentCapture = nullptr;
|
|
};
|
|
|
|
if (InCaptureSettings->bUseSeparateProcess)
|
|
{
|
|
const FString WorldPackageName = GWorld->GetOutermost()->GetName();
|
|
FString MapNameToLoad = WorldPackageName;
|
|
|
|
// Allow the game mode to be overridden
|
|
if (InCaptureSettings->Settings.GameModeOverride != nullptr)
|
|
{
|
|
const FString GameModeName = InCaptureSettings->Settings.GameModeOverride->GetPathName();
|
|
MapNameToLoad += FString::Printf(TEXT("?game=%s"), *GameModeName);
|
|
}
|
|
|
|
CurrentCapture = MakeShared<FNewProcessCapture>(InCaptureSettings, MapNameToLoad, OnCaptureFinished);
|
|
}
|
|
else
|
|
{
|
|
CurrentCapture = MakeShared<FInEditorCapture>(InCaptureSettings, OnCaptureFinished);
|
|
}
|
|
|
|
UE_LOG(LogTemp, Log, TEXT("Starting movie scene capture..."));
|
|
CurrentCapture->Start();
|
|
}
|
|
|
|
virtual void OpenDialog(const TSharedRef<FTabManager>& TabManager, UMovieSceneCapture* CaptureObject, TSharedPtr<INumericTypeInterface<double>> InNumericTypeInterface) override
|
|
{
|
|
// Ensure the session services module is loaded otherwise we won't necessarily receive status updates from the movie capture session
|
|
FModuleManager::Get().LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionManager();
|
|
|
|
TSharedPtr<SWindow> ExistingWindow = CaptureSettingsWindow.Pin();
|
|
if (ExistingWindow.IsValid())
|
|
{
|
|
ExistingWindow->BringToFront();
|
|
}
|
|
else
|
|
{
|
|
ExistingWindow = SNew(SWindow)
|
|
.Title( LOCTEXT("RenderMovieSettingsTitle", "Render Movie Settings") )
|
|
.HasCloseButton(true)
|
|
.SupportsMaximize(false)
|
|
.SupportsMinimize(false)
|
|
.ClientSize(FVector2D(500, 700));
|
|
|
|
TSharedPtr<SDockTab> OwnerTab = TabManager->GetOwnerTab();
|
|
TSharedPtr<SWindow> RootWindow = OwnerTab.IsValid() ? OwnerTab->GetParentWindow() : TSharedPtr<SWindow>();
|
|
if(RootWindow.IsValid())
|
|
{
|
|
FSlateApplication::Get().AddWindowAsNativeChild(ExistingWindow.ToSharedRef(), RootWindow.ToSharedRef());
|
|
}
|
|
else
|
|
{
|
|
FSlateApplication::Get().AddWindow(ExistingWindow.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
ExistingWindow->SetContent(
|
|
SNew(SRenderMovieSceneSettings)
|
|
.InitialObject(CaptureObject)
|
|
.NumericTypeInterface(InNumericTypeInterface)
|
|
.OnStartCapture_Raw(this, &FMovieSceneCaptureDialogModule::OnUserRequestStartCapture)
|
|
);
|
|
|
|
CaptureSettingsWindow = ExistingWindow;
|
|
}
|
|
|
|
FText OnUserRequestStartCapture(UMovieSceneCapture* CaptureObject)
|
|
{
|
|
if (CurrentCapture.IsValid())
|
|
{
|
|
return LOCTEXT("AlreadyCapturing", "There is already a movie scene capture process open. Please close it and try again.");
|
|
}
|
|
|
|
// Prompt the user to save their changes so that they'll be in the movie, since we're not saving temporary copies of the level.
|
|
bool bPromptUserToSave = true;
|
|
bool bSaveMapPackages = true;
|
|
bool bSaveContentPackages = true;
|
|
if( !FEditorFileUtils::SaveDirtyPackages( bPromptUserToSave, bSaveMapPackages, bSaveContentPackages ) )
|
|
{
|
|
return LOCTEXT( "UserCancelled", "Capturing was cancelled from the save dialog." );
|
|
}
|
|
|
|
CaptureObject->SaveToConfig();
|
|
|
|
StartCapture(CaptureObject);
|
|
|
|
// If we managed to get this far, we've done our best to start the capture and don't have a error to report at this time.
|
|
return FText();
|
|
}
|
|
private:
|
|
TWeakPtr<SWindow> CaptureSettingsWindow;
|
|
TSharedPtr<FMovieSceneCaptureBase> CurrentCapture;
|
|
};
|
|
|
|
FText FCaptureState::GetDetailText() const
|
|
{
|
|
switch (uint32(Code))
|
|
{
|
|
case uint32(EMovieSceneCaptureExitCode::WorldNotFound):
|
|
{
|
|
return LOCTEXT("WorldNotFound", "Specified world does not exist. Did you forget to save it?");
|
|
}
|
|
default:
|
|
{
|
|
return FText();
|
|
}
|
|
}
|
|
}
|
|
|
|
IMPLEMENT_MODULE( FMovieSceneCaptureDialogModule, MovieSceneCaptureDialog )
|
|
|
|
#undef LOCTEXT_NAMESPACE
|