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 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]
824 lines
24 KiB
C++
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 |