Files
UnrealEngineUWP/Engine/Source/Editor/MovieSceneCaptureDialog/Private/MovieSceneCaptureDialogModule.cpp
Max Chen 0cd05e1a6b Copying //UE4/Dev-Sequencer to //UE4/Dev-Main (Source: //UE4/Dev-Sequencer @ 3048536)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3004054 on 2016/06/07 by Max.Chen

	Movie Capture: Expose compressed EXR frames to Sequencer Capture UI and command line.

Change 3007048 on 2016/06/09 by Max.Chen

	Sequencer: Allow showing the context menu for more than one selected node. Certain operations (ie. Lock, Active, Label) now operate on all the selected nodes.

	#jira UE-31762

Change 3007795 on 2016/06/09 by Max.Chen

	Sequencer: Select actors for corresponding selected keys or sections.

	#jira UE-30727

Change 3009689 on 2016/06/10 by Max.Chen

	Movie Capture: Add {shot} description to movie capture filename format.

Change 3010180 on 2016/06/11 by Max.Chen

	Sequencer: Add support for copying and converting linear color tracks from matinee.

	#jira UE-31260

Change 3012472 on 2016/06/14 by Max.Chen

	Sequence Recorder: Optimize adding keys to rich curves. Set the times and values at once as an ordered set.

Change 3012473 on 2016/06/14 by Max.Chen

	Sequence Recorder: Find corresponding PIE world actor for Actor to Record. This fixes a bug where if you set the actor before PIE and then record, the recording doesn't work.

Change 3012474 on 2016/06/14 by Max.Chen

	Sequence Recorder: Fix countdown timer so that it draws in Simulate mode as well as Play.

	#jira UE-31630

Change 3014868 on 2016/06/15 by Max.Chen

	Sequencer: For legacy, properties with the CPF_Interp flag can be animated in Matinee. It doesn't require the CPF_Edit flag as well. This makes Sequencer consistent with Matinee.

	#jira UE-32067

Change 3014869 on 2016/06/15 by Max.Chen

	Sequencer: Rename "Expose to Matinee" to "Expose to Cinematics"

	#jira UE-31500

Change 3016137 on 2016/06/16 by Max.Chen

	Sequencer: Added support for the named "PerformanceCapture" event which like Matinee, calls GEngine->PerformanceCapture to output a screenshot when the event fires. Refactor event track/sections so that the player is passed to the trigger events evaluation.

	#jira UE-32093

Change 3018996 on 2016/06/18 by Max.Chen

	Sequencer: Add play/pause/stop events to level sequence player ala matinee actor.

	#jira UETOOL-899

Change 3019763 on 2016/06/20 by Max.Chen

	Sequencer: Fix key editor commit when user tabs away (lose focus). Also, make the key navigation buttons and track color button not focusable.

	Slate: Make editable labels not focusable if they are not editable.

	#jira UE-24566, UE-31913

Change 3019768 on 2016/06/20 by Max.Chen

	Sequencer: Event track no longer fires if the playback status is stopped. This fixes a bug where when playback is stopped and the movie scene sequence is returned to the start of playback, we don't want all the events from the last playback position to the start of playback to fire.

	#jira UE-31494

Change 3020849 on 2016/06/21 by Andrew.Rodham

	Sequencer: Fixed blueprint classes as spawnables not being reinstanced correctly

	  - The RF_ArchetypeObject flag was previously used to denote spawnable object templates, however this caused blueprint reinstancing to skip such objects, which resulted in crashes, or data loss
	  - Added code to rename expired object templates to ensure there is no name collision
	  - Ensured that register functions are only called on actors that are part of a world (when finalizing blueprint reinstancing)

	#jira UE-31637

Change 3021400 on 2016/06/21 by Frank.Fella

	Sequencer - Add support for exporting to fbx.

Change 3022941 on 2016/06/22 by Andrew.Rodham

	Sequencer: Thumbnail improvements

	  - Fixed poor performance when continually zooming (thumbnails were being constantly and immediately rendered in this case)
	  - Added quality setting to thumbnail sections (draft/normal/best)
	  - Improved fade transition to use proper alpha blending
	  - Fixed needlessly recreating the entire world's render state when creating a new editor viewport client. This caused a significant hitch opening sequences in certain large worlds.

	#jira UE-31264

Change 3022944 on 2016/06/22 by Andrew.Rodham

	Sequencer: Fixed jitter when jumping around

	  - bEditorCameraCut was not being reset on the next frame, which prevented temporal effects from running

Change 3024774 on 2016/06/23 by Max.Chen

	Sequencer: Cache show intensity to invalidate the waveform preview when the intensity changes.

	#jira UE-32385

Change 3026170 on 2016/06/24 by Max.Chen

	Sequence Recorder: Change Actor Name to Record to a TLazyObjectPtr<AActor> so that the user can choose the actor directly rather than typing in a name.

Change 3026181 on 2016/06/24 by Max.Chen

	Sequencer: Add visibility options to show/hide/only when selected 3d trajectories per transform section.

	#jira UE-31814

Change 3026489 on 2016/06/24 by Andrew.Rodham

	Sequencer: Fixed some recorded actors not being saved into level sequences correctly
	  - The use of StaticDuplicateObject was causing temporary/transient or otherwise external data to be duplicated into template actors which caused the actor to be culled on package save.
	  - Using NewObject followed by copying the object properties guarantees we don't copy this data.
	  - This is the same method used to spawn the spawnable actor

Change 3026522 on 2016/06/24 by Max.Chen

	Sequence Recorder: Fix bug where sample rate wouldn't get set if the length is set to 0.

	#jira UE32430

Change 3027768 on 2016/06/25 by Max.Chen

	Sequencer: Fix player stopping after 60 seconds. Assign the player to a UPROPERTY so that it's not garbage collected.

	#jira UE-32420

Change 3028318 on 2016/06/27 by Andrew.Rodham

	Editor: Added safety check to prevent stack overflow populating the world outliner

	  - It was possible for the population code to get stuck in an infinite loop if an actor happened to be attached to itself.

	#jira UE-30914

Change 3034262 on 2016/06/30 by Andrew.Rodham

	Sequencer: Fixed invalid properties being recorded when creating spawnable object templates

	 - When creating a spawnable out of an attached actor, the attachment was getting copied into the template. This results in GLEO errors when saving the package.
	 - Suppressed object replacement notifications when calling CopyPropertiesForUnrelatedObjects for spawnables

Change 3035168 on 2016/06/30 by Max.Chen

	UMG: Set anim range to the playback range size. This fixes a bug where animation doesn't play the full length of the playback range if it starts negative.

	#jira UE-32066

Change 3035169 on 2016/06/30 by Max.Chen

	Sequencer: Add ReversePlay() and ChangePlaybackDirection() functions (ala Matinee).

	#jira UE-21259

Change 3035174 on 2016/06/30 by Max.Chen

	Sequencer: Fix evaluation when playback starts or loops around so that last time is enforced to be the lower bound of the playback range. This fixes a bug in the particle track where if there's a particle that triggers at time 0 and a level sequence that starts at time 0, the event will be missed since last time will be 0 but then rounded to a fixed frame with epislon .0001f.

	#jira UE-32606

Change 3035186 on 2016/06/30 by Max.Chen

	Sequencer: Add reset selection range and remove unused delete selection range.

	#jira UE-32666

Change 3035197 on 2016/07/01 by Max.Chen

	Sequencer: Fix so that adding a sub section adds to the clicked on sub track and not just the first.

	#jira UE-32665

Change 3036586 on 2016/07/02 by Max.Chen

	Sequencer: More play controls - shuttle backward, pause, shuttle forward (j, k, l)

	#jira UE-27539, UE-31424

Change 3036941 on 2016/07/04 by Andrew.Rodham

	Sequencer: Record transforms in world space where an actor is attached, and we're not recording its parent

Change 3039290 on 2016/07/06 by Andrew.Rodham

	Sequencer: Various capture fixes
	  - Movie captures no longer crash when no world is loaded (they gracefully close instead)
	    - Currently waiting on change from core to hook up the error code with an actual process termination code
	  - We now force -NoLoadingScreen, -Windowed and -ForceRes since movie captures will not work without these

	#jira UE-32802

Change 3039831 on 2016/07/06 by Frank.Fella

	Sequencer - Notify data changed refactor.
	+ Add a change type to "NotifyMovieSceneDataChanged" so that sequencer knows what parts of the system to refresh.
	+ Remove most calls for UpdateRuntimeInstances and replace them with a call to NotifyMovieSceneDataChanged.
	+ Update UMG so that it copies the animation data to the compiled class whenever it's changed.

	#jira UE-29955

Change 3044087 on 2016/07/10 by Max.Chen

	Sequencer - Prevent crashes when encountering filler shots with no valid sequence.

Change 3044151 on 2016/07/10 by Max.Chen

	Sequencer: Only update selected nodes if they change. This fixes a bug in the curve editor where undo reselects and autoframes.

	#jira UE-29663

Change 3044164 on 2016/07/10 by Max.Chen

	Sequencer: Added ability to immediately record actors directly into sequencer
	  - "Record 'ActorName' In Sequencer" option is now available on the level editor context menu for selected actors when sequencer is open.
	  - This immediately triggers a countdown and records the currently selected actors into a sub sequence in the currently focussed movie scene
	  - Creates a cinematic shot track if you record a camera
	  - Removed the older "queue" and "trigger" methods for now to make it cleaner for the demo.

Change 3044180 on 2016/07/11 by Max.Chen

	Sequencer: Added ability to possess viewports while in PIE
	  - Added a new option to the level sequence editor settings to allow possession of PIE viewports

Change 3044181 on 2016/07/11 by Max.Chen

	Sequencer: Added ability to specify event contexts for FSequencer
	  - This allows us to trigger events from playback within sequencer, according to the sequencer client

Change 3044188 on 2016/07/11 by Max.Chen

	Sequencer: We no longer evaluate camera cut tracks as part of sub tracks, only shot tracks.

Change 3044193 on 2016/07/11 by Max.Chen

	Sequencer: Added cvar LevelSequence.DefaultFixedFrameIntervalPlayback to control this setting for newly created level sequences

Change 3044194 on 2016/07/11 by Max.Chen

	Sequencer: Added an option to rewind the sequence when a recording is started
	Defaults to 'on'

Change 3047334 on 2016/07/12 by Max.Chen

	Sequencer: Add transactions for creating a camera cut track and a folder.

	#jira UE-33130

Change 3047365 on 2016/07/12 by Max.Chen

	Cine Camera: Fix crash in CineCameraComponent when setting focus distance from BP

Change 3047366 on 2016/07/12 by Max.Chen

	Sequence Recorder: Arbitrary property recording

[CL 3048548 by Max Chen in Main branch]
2016-07-13 15:37:34 -04:00

824 lines
24 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "MovieSceneCaptureDialogModule.h"
#include "MovieSceneCapture.h"
#include "UnrealEdMisc.h"
#include "SlateBasics.h"
#include "SlateExtras.h"
#include "SceneViewport.h"
#include "AudioDevice.h"
#include "SDockTab.h"
#include "JsonObjectConverter.h"
#include "INotificationWidget.h"
#include "SNotificationList.h"
#include "NotificationManager.h"
#include "EditorStyle.h"
#include "Editor.h"
#include "PropertyEditing.h"
#include "FileHelpers.h"
#include "ISessionServicesModule.h"
#include "ISessionInstanceInfo.h"
#include "ISessionInfo.h"
#include "ISessionManager.h"
#include "ErrorCodes.h"
#include "SLevelViewport.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_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);
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)
.ContentPadding(FMargin(10, 5))
.Text(LOCTEXT("Export", "Capture Movie"))
.OnClicked(this, &SRenderMovieSceneSettings::OnStartClicked)
]
];
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();
}
TSharedPtr<IDetailsView> DetailView;
TSharedPtr<STextBlock> ErrorText;
FOnStartCapture OnStartCapture;
UMovieSceneCapture* MovieSceneCapture;
};
enum class ECaptureState
{
Pending,
Success,
Failure
};
DECLARE_DELEGATE_OneParam(FOnCaptureFinished, bool /*bCancelled*/);
// Structure used to store the state of the capture
struct FCaptureState
{
/** Construction from an enum */
explicit FCaptureState(ECaptureState InState = ECaptureState::Pending) : State(InState), Code(0){}
/** Construction from a process exit code */
explicit FCaptureState(int32 InCode) : State(InCode == 0 ? ECaptureState::Success : ECaptureState::Failure), Code(InCode){}
/** Get any additional detailed text */
FText GetDetailText()
{
switch(uint32(Code))
{
case uint32(EMovieSceneCaptureExitCode::WorldNotFound): return LOCTEXT("WorldNotFound", "Specified world does not exist. Did you forget to save it?");
}
return FText();
}
ECaptureState State;
int32 Code;
};
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(ECaptureState::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)
.Visibility(EVisibility::Collapsed)
.Text(LOCTEXT("OpenFolder", "Open Capture Folder..."))
.OnNavigate_Lambda(OnBrowseToFolder)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.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.State != StateThisFrame.State)
{
CachedState = StateThisFrame;
if (CachedState.State == ECaptureState::Success)
{
TextBlock->SetText(LOCTEXT("CaptureFinished", "Capture Finished"));
OnCaptureFinished.ExecuteIfBound(true);
}
else if (CachedState.State == ECaptureState::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)
{
Hyperlink->SetVisibility(EVisibility::Visible);
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;
};
struct FInEditorCapture
{
FInEditorCapture(UMovieSceneCapture* InCaptureObject, TFunction<void()> InOnStarted)
: CaptureObject(InCaptureObject)
{
ULevelEditorPlaySettings* PlayInEditorSettings = GetMutableDefault<ULevelEditorPlaySettings>();
bScreenMessagesWereEnabled = GAreScreenMessagesEnabled;
GAreScreenMessagesEnabled = false;
if (!InCaptureObject->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);
}
}
OnStarted = InOnStarted;
FObjectWriter(PlayInEditorSettings, BackedUpPlaySettings);
OverridePlaySettings(PlayInEditorSettings);
CaptureObject->AddToRoot();
CaptureObject->OnCaptureFinished().AddRaw(this, &FInEditorCapture::OnEnd);
UGameViewportClient::OnViewportCreated().AddRaw(this, &FInEditorCapture::OnStart);
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 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)
.ActivateWhenFirstShown(false)
.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->EnableSound = 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 OnStart()
{
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
if (Context.WorldType == EWorldType::PIE)
{
FSlatePlayInEditorInfo* SlatePlayInEditorSession = GEditor->SlatePlayInEditorMap.Find(Context.ContextHandle);
if (SlatePlayInEditorSession)
{
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 destop width/height
{
FDisplayMetrics DisplayMetrics;
FSlateApplication::Get().GetDisplayMetrics(DisplayMetrics);
while(PreviewWindowSize.X >= DisplayMetrics.PrimaryDisplayWidth*.5f || PreviewWindowSize.Y >= DisplayMetrics.PrimaryDisplayHeight*.5f)
{
PreviewWindowSize *= .5f;
}
}
Window->Resize(PreviewWindowSize);
CaptureObject->Initialize(SlatePlayInEditorSession->SlatePlayInEditorWindowViewport, Context.PIEInstance);
OnStarted();
}
return;
}
}
// todo: error?
}
void 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);
}
}
FObjectReader(GetMutableDefault<ULevelEditorPlaySettings>(), BackedUpPlaySettings);
FAudioDevice* AudioDevice = GEngine->GetMainAudioDevice();
if (AudioDevice != nullptr)
{
AudioDevice->SetTransientMasterVolume(TransientMasterVolume);
}
CaptureObject->Close();
CaptureObject->RemoveFromRoot();
}
void OnEndPIE(bool bIsSimulating)
{
Shutdown();
delete this;
}
void OnEnd()
{
Shutdown();
delete this;
GEditor->RequestEndPlayMap();
}
TFunction<void()> OnStarted;
bool bScreenMessagesWereEnabled;
float TransientMasterVolume;
int32 BackedUpStreamingPoolSize;
int32 BackedUpUseFixedPoolSize;
TArray<uint8> BackedUpPlaySettings;
UMovieSceneCapture* CaptureObject;
};
class FMovieSceneCaptureDialogModule : public IMovieSceneCaptureDialogModule
{
virtual void OpenDialog(const TSharedRef<FTabManager>& TabManager, UMovieSceneCapture* CaptureObject) 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)
.OnStartCapture_Raw(this, &FMovieSceneCaptureDialogModule::OnStartCapture)
);
CaptureSettingsWindow = ExistingWindow;
}
void OnCaptureFinished(bool bSuccess)
{
if (bSuccess)
{
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Success);
}
else
{
// todo: error to message log
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Fail);
}
InProgressCaptureNotification->ExpireAndFadeout();
InProgressCaptureNotification = nullptr;
}
FText OnStartCapture(UMovieSceneCapture* CaptureObject)
{
FString MapNameToLoad;
if( CaptureObject->Settings.bCreateTemporaryCopiesOfLevels )
{
TArray<FString> SavedMapNames;
GEditor->SaveWorldForPlay(SavedMapNames);
if (SavedMapNames.Num() == 0)
{
return LOCTEXT("CouldNotSaveMap", "Could not save map for movie capture.");
}
MapNameToLoad = SavedMapNames[ 0 ];
}
else
{
// 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." );
}
const FString WorldPackageName = GWorld->GetOutermost()->GetName();
MapNameToLoad = WorldPackageName;
}
// Allow the game mode to be overridden
if( CaptureObject->Settings.GameModeOverride != nullptr )
{
const FString GameModeName = CaptureObject->Settings.GameModeOverride->GetPathName();
MapNameToLoad += FString::Printf( TEXT( "?game=%s" ), *GameModeName );
}
if (InProgressCaptureNotification.IsValid())
{
return LOCTEXT("AlreadyCapturing", "There is already a movie scene capture process open. Please close it and try again.");
}
CaptureObject->SaveToConfig();
return CaptureObject->bUseSeparateProcess ? CaptureInNewProcess(CaptureObject, MapNameToLoad) : CaptureInEditor(CaptureObject, MapNameToLoad);
}
FText CaptureInEditor(UMovieSceneCapture* CaptureObject, const FString& MapNameToLoad)
{
auto GetCaptureStatus = [=]{
for (const FWorldContext& Context : GEngine->GetWorldContexts())
{
if (Context.WorldType == EWorldType::PIE)
{
return FCaptureState(ECaptureState::Pending);
}
}
return FCaptureState(ECaptureState::Success);
};
auto OnCaptureStarted = [=]{
FNotificationInfo Info(
SNew(SCaptureMovieNotification)
.CaptureState_Lambda(GetCaptureStatus)
.CapturePath(CaptureObject->Settings.OutputDirectory.Path)
.OnCaptureFinished_Raw(this, &FMovieSceneCaptureDialogModule::OnCaptureFinished)
.OnCancel_Lambda([]{
GEditor->RequestEndPlayMap();
})
);
Info.bFireAndForget = false;
Info.ExpireDuration = 5.f;
InProgressCaptureNotification = FSlateNotificationManager::Get().AddNotification(Info);
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Pending);
};
// deliberately 'leak' the object, since it owns itself
new FInEditorCapture(CaptureObject, OnCaptureStarted);
return FText();
}
FText CaptureInNewProcess(UMovieSceneCapture* CaptureObject, const FString& MapNameToLoad)
{
// Save out the capture manifest to json
FString Filename = FPaths::GameSavedDir() / 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 LOCTEXT("UnableToSaveCaptureManifest", "Unable to save capture manifest");
}
FString EditorCommandLine = FString::Printf(TEXT("%s -MovieSceneCaptureManifest=\"%s\" -game -NoLoadingScreen -ForceRes -Windowed"), *MapNameToLoad, *Filename);
if( CaptureObject->Settings.bCreateTemporaryCopiesOfLevels )
{
// the PIEVIACONSOLE parameter tells UGameEngine to add the auto-save dir to the paths array and repopulate the package file cache
// this is needed in order to support streaming levels as the streaming level packages will be loaded only when needed (thus
// their package names need to be findable by the package file caching system)
// (we add to EditorCommandLine because the URL is ignored by WindowsTools)
EditorCommandLine.Append( TEXT( " -PIEVIACONSOLE" ) );
}
// 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::GetGameName(), *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 FText();
}
TSharedRef<FProcHandle> SharedProcHandle = MakeShareable(new FProcHandle(ProcessHandle));
auto GetCaptureStatus = [=]{
if (!FPlatformProcess::IsProcRunning(*SharedProcHandle))
{
int32 RetCode = 0;
FPlatformProcess::GetProcReturnCode(*SharedProcHandle, &RetCode);
return FCaptureState(RetCode);
}
else
{
return FCaptureState(ECaptureState::Pending);
}
};
auto OnCancel = [=]{
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);
}
};
FNotificationInfo Info(
SNew(SCaptureMovieNotification)
.CaptureState_Lambda(GetCaptureStatus)
.CapturePath(CaptureObject->Settings.OutputDirectory.Path)
.OnCaptureFinished_Raw(this, &FMovieSceneCaptureDialogModule::OnCaptureFinished)
.OnCancel_Lambda(OnCancel)
);
Info.bFireAndForget = false;
Info.ExpireDuration = 5.f;
InProgressCaptureNotification = FSlateNotificationManager::Get().AddNotification(Info);
InProgressCaptureNotification->SetCompletionState(SNotificationItem::CS_Pending);
}
return FText();
}
private:
/** */
TWeakPtr<SWindow> CaptureSettingsWindow;
TSharedPtr<SNotificationItem> InProgressCaptureNotification;
};
IMPLEMENT_MODULE( FMovieSceneCaptureDialogModule, MovieSceneCaptureDialog )
#undef LOCTEXT_NAMESPACE