You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
==========================
MAJOR FEATURES + CHANGES
==========================
Change 2945755 on 2016/04/15 by Frank.Fella
Sequencer - Fix issues with level visibility.
+ Don't mark sub-levels as dirty when the track evaluates.
+ Fix an issue where sequencer gets into a refresh loop because drawing thumbnails causes levels to be added which was rebuilding the tree, which was redrawing thumbnails.
+ Null check for when an objects world is null but the track is still evaluating.
+ Remove UnrealEd references.
Change 2947197 on 2016/04/18 by Max.Chen
Sequencer: Expose settings sequencer settings in the Editor Preferences page. Note, UMG and Niagara have separate sequencer settings pages.
#jira UE-29516
Change 2948468 on 2016/04/19 by Max.Chen
Sequencer: Fix particles not firing on loop.
#jira UE-27881
Change 2948590 on 2016/04/19 by Max.Chen
Sequencer: Fix spawnables not getting default tracks.
#jira UE-29644
Change 2955993 on 2016/04/26 by Max.Chen
Sequencer: Refresh instances when done recording. This fixes a bug where spawned recorded actors aren't visible when done recording.
#jira UE-29841
Change 2958567 on 2016/04/27 by Max.Preussner
RHI: Made SetReferencedTexture public, so that the referenced texture can be set
Change 2958718 on 2016/04/28 by Max.Chen
Sequencer: Folder colors. Right click on a folder and choose "Set Color"
#jira UE-28669
Change 2960172 on 2016/04/28 by Max.Preussner
Slate: Slate Remote Server (for the iOS touch input app) is now disabled by default, so we don't open up the socket unless desired by the user
Change 2960411 on 2016/04/28 by Max.Chen
Sequencer: Don't remove label if it's not being used.
#jira UE-24283
Change 2960414 on 2016/04/28 by Max.Chen
Matinee: Don't automatically turn frustums on/off when entering and exiting Matinee.
#jira UE-1020
Change 2962784 on 2016/05/02 by Max.Chen
Sequencer: Add master sequence
#jira UE-29799
Change 2964399 on 2016/05/03 by Andrew.Rodham
Sequencer: Added ability to apply cook-time optimization to tracks and objects
- For now, if a spawnable has a spawn track that is disabled, or will never spawn, the entire spawnable object will be removed from a cooked package.
- Possessables also afford the same optimization, although none is currently implemented
- We could, in future, also remove any tracks that are completely disabled
- Deprecated UMovieSceneBoolSection::DefaultValue in favor of the default stored on FIntegralCurve
Change 2967549 on 2016/05/05 by Max.Chen
Sequencer: Fix crash converting possessable to spawnable when the possessable doesn't exist.
#jira UE-30360
Change 2967670 on 2016/05/05 by Max.Chen
Sequencer: Set ui min/max for sequencer settings
#jira UE-30344
Change 2978969 on 2016/05/16 by Max.Chen
Sequencer: Restore state when focusing on a shot level sequence. This fixes issues where tracks in the movie scene that are active before switching to the new movie scene need to return to
their initial state. For example, setting a fade track in the master sequence and switching into a shot should disable the effects of the fade track in the master sequence.
#jira UE-30798
Change 2983237 on 2016/05/19 by Andrew.Rodham
Protocol settings for movie captures are now set up correctly when a capture type is specified on the command line
Thanks to original github author, yuhe00
#pr
#2257
Change 2991115 on 2016/05/26 by Andrew.Rodham
Sequencer: Added {shot} and {shot_frame} format args for movie captures
- Additionally, rendering out movie scenes as videos will now generate a new video for each unique filename it encounters. This allows us to render out a video per shot by using {shot} as
the output format.
- Frame numbers are now zero-padded as per the sequencer setting.
Change 2991920 on 2016/05/26 by Max.Chen
Sequencer: Fix movie scene getting dirtied unnecessarily when the fixed frame interval changes.
#jira UE-31343
Change 2992387 on 2016/05/26 by Max.Chen
Sequencer: Fix crash when getting the color key properties of a collapsed key that doesn't have all channels keyed.
#jira UE-31392
Change 2993553 on 2016/05/27 by Andrew.Rodham
Sequencer: Added the ability to add burn-ins to level sequences
- A default burn-in is provided which hosts a great level of flexibility
- 6 regions (L/C/R + T/B) on a 30% black border allow positioning of a range of frame statistics such as shot name ({ShotName}), frame numbers ({MasterFrame}, {ShotFrame}), and other
information.
- Watermark is provided by default (currently no tiling is exposed)
- Users can use the default built in UMG widget as a guideline for their own custom implementations.
Change 2993554 on 2016/05/27 by Andrew.Rodham
Sequencer: Default level sequence burn ins
- Also made a font asset out of our fixed width font shipped with the engine
Change 2993856 on 2016/05/30 by Max.Chen
Sequencer: Import/Export EDL
- Added a new option in the render movie dialog to export an Edit Decision List (EDL) in cmx and rv formats if there is a shot track. The default is true.
- Added "Import EDL" to shot track right click menu which imports a cmx EDL and conforms the shot order and cut information to it.
- Added "Export EDL" to shot track right click menu which exports EDLs in cmx and rv formats.
- Added "Render Shot" to shot right click menu which loads up the render movie dialog with the start and end frames of the selected shot.
#jira UETOOL-829, UETOOL-830
Change 2994761 on 2016/05/31 by Max.Chen
Sequence Recorder: Add a setting to allow recording of actors that are spawned by sequencer itself.
Change 2995648 on 2016/06/01 by HaarmPieter.Duiker
Sequencer EXR output gamut controls
Change 2996241 on 2016/06/01 by Frank.Fella
Sequencer - Add a small epsilon when "force fixed frame interval" is enabled, to make sure we're in the start of the next frame.
Change 2996244 on 2016/06/01 by Frank.Fella
Sequencer - Set the tick prerequisite for all components, not just the root.
Change 2997865 on 2016/06/02 by Max.Preussner
Sequencer: Fixed Crash in Sequencer play rate track when setting negative play rate (UE-31431)
#jira UE-31431
Change 2999631 on 2016/06/03 by Frank.Fella
Sequencer - At runtime, make sure to stop playing skeletal animations to prevent them from being double updated each frame, once by sequencer, and then again by tick.
Change 3000820 on 2016/06/03 by Max.Chen
Sequencer: Add hotkey (ctrl-T) to toggle between showing frame numbers and time.
#jira UE-31497
Change 3001056 on 2016/06/05 by Max.Chen
Sequencer: Fix fade color section crash by using an inline color picker in the details panel instead of a popup color picker.
#jira UE-31647
Change 3001057 on 2016/06/05 by Max.Chen
Movie Capture: Fix audio getting disabled after recording a movie.
Change 3001690 on 2016/06/06 by Andrew.Rodham
Sequencer: Fixed recording video sequences when not overwriting existing videos
Change 3001823 on 2016/06/06 by Max.Chen
Sequencer: Fix filtered nodes in folders so that other unfiltered children aren't visible.
#jira UE-31499
#lockdown Nick.Penwarden
[CL 3003974 by Max Chen in Main branch]
782 lines
23 KiB
C++
782 lines
23 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 "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*/);
|
|
|
|
class SCaptureMovieNotification : public SCompoundWidget, public INotificationWidget
|
|
{
|
|
public:
|
|
SLATE_BEGIN_ARGS(SCaptureMovieNotification){}
|
|
|
|
SLATE_ATTRIBUTE(ECaptureState, 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 = 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()
|
|
.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;
|
|
}
|
|
|
|
ECaptureState StateThisFrame = CaptureState.Get();
|
|
|
|
if (CachedState != StateThisFrame)
|
|
{
|
|
CachedState = StateThisFrame;
|
|
|
|
if (CachedState == ECaptureState::Success)
|
|
{
|
|
TextBlock->SetText(LOCTEXT("CaptureFinished", "Capture Finished"));
|
|
OnCaptureFinished.ExecuteIfBound(true);
|
|
}
|
|
else if (CachedState == ECaptureState::Failure)
|
|
{
|
|
TextBlock->SetText(LOCTEXT("CaptureFailed", "Capture Failed"));
|
|
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;
|
|
SNotificationItem::ECompletionState State;
|
|
|
|
FSimpleDelegate OnCancel;
|
|
ECaptureState CachedState;
|
|
TAttribute<ECaptureState> 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 = GWorld->GetAudioDevice();
|
|
if (AudioDevice != nullptr)
|
|
{
|
|
TransientMasterVolume = AudioDevice->TransientMasterVolume;
|
|
AudioDevice->TransientMasterVolume = 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 = GWorld->GetAudioDevice();
|
|
if (AudioDevice != nullptr)
|
|
{
|
|
AudioDevice->TransientMasterVolume = 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 ECaptureState::Pending;
|
|
}
|
|
}
|
|
|
|
return 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"), *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 RetCode == 0 ? ECaptureState::Success : ECaptureState::Failure;
|
|
}
|
|
else
|
|
{
|
|
return 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 |