Add DecoupledOutputProvider module to allow pixel streaming output provider to be loaded on any platform

#jira UE-182897
#jira UE-182898
#rb Jason.Walter Luke.Bermingham William.Belcher
#preflight https://horde.devtools.epicgames.com/job/64516e2f6538e45f75650138

[CL 25314976 by Dominik Peacock in ue5-main branch]
This commit is contained in:
Dominik Peacock
2023-05-03 03:22:00 -04:00
parent f6a1696fc8
commit 0841fbf864
23 changed files with 1244 additions and 526 deletions

View File

@@ -5,3 +5,6 @@
+FunctionRedirects=(OldName="VCamComponent.FindModifierByName",NewName="VCamComponent.GetModifierByName")
+FunctionRedirects=(OldName="VCamComponent.FindModifiersByClass",NewName="VCamComponent.GetModifiersByClass")
+FunctionRedirects=(OldName="VCamComponent.FindModifierByInterface",NewName="VCamComponent.GetModifiersByInterface")
; 5.2 > 5.3
+ClassRedirects=(OldName="/Script/PixelStreamingVCam.VCamPixelStreamingSession",NewName="/Script/DecoupledOutputProvider.VCamPixelStreamingSession")

View File

@@ -0,0 +1,27 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.IO;
using System.Collections.Generic;
namespace UnrealBuildTool.Rules
{
public class DecoupledOutputProvider : ModuleRules
{
public DecoupledOutputProvider(ReadOnlyTargetRules Target) : base(Target)
{
// This is so for game projects using our public headers don't have to include extra modules they might not know about.
PublicDependencyModuleNames.AddRange(new string[]
{
"VCamCore"
});
PrivateDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine"
});
}
}
}

View File

@@ -0,0 +1,9 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "BuiltinProviders/VCamPixelStreamingSession.h"
UVCamPixelStreamingSession::UVCamPixelStreamingSession()
{
DisplayType = EVPWidgetDisplayType::PostProcessSceneViewExtension;
InitViewTargetPolicyInSubclass();
}

View File

@@ -0,0 +1,125 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DecoupledOutputProvider.h"
#include "DecoupledOutputProviderModule.h"
#include "IOutputProviderLogic.h"
namespace UE::DecoupledOutputProvider::Private
{
/** Handles calling the of the super function. */
class FOutputProviderEvent : public IOutputProviderEvent
{
UDecoupledOutputProvider& OutputProvider;
bool bCalledSuper = false;
const TFunctionRef<void()> SuperFunc;
public:
FOutputProviderEvent(UDecoupledOutputProvider& OutputProvider, TFunctionRef<void()> SuperFunc)
: OutputProvider(OutputProvider)
, SuperFunc(MoveTemp(SuperFunc))
{}
virtual ~FOutputProviderEvent() override
{
if (!bCalledSuper)
{
SuperFunc();
}
}
virtual void ExecuteSuperFunction() override
{
bCalledSuper = true;
SuperFunc();
}
virtual UDecoupledOutputProvider& GetOutputProvider() const override
{
return OutputProvider;
}
};
}
void UDecoupledOutputProvider::Initialize()
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this](){ Super::Initialize(); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnInitialize(EventScope);
}
void UDecoupledOutputProvider::Deinitialize()
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this](){ Super::Deinitialize(); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnDeinitialize(EventScope);
}
void UDecoupledOutputProvider::Tick(const float DeltaTime)
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this, DeltaTime](){ Super::Tick(DeltaTime); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnTick(EventScope, DeltaTime);
}
void UDecoupledOutputProvider::OnActivate()
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this](){ Super::OnActivate(); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnActivate(EventScope);
}
void UDecoupledOutputProvider::OnDeactivate()
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this](){ Super::OnDeactivate(); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnDeactivate(EventScope);
}
void UDecoupledOutputProvider::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
using namespace UE::DecoupledOutputProvider::Private;
UDecoupledOutputProvider* CastThis = CastChecked<UDecoupledOutputProvider>(InThis);
const auto SuperFunc = [InThis, &Collector](){ Super::AddReferencedObjects(InThis, Collector); };
FOutputProviderEvent EventScope(*CastThis, SuperFunc);
FDecoupledOutputProviderModule::Get().OnAddReferencedObjects(EventScope, Collector);
}
void UDecoupledOutputProvider::BeginDestroy()
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this](){ Super::BeginDestroy(); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnBeginDestroy(EventScope);
}
void UDecoupledOutputProvider::Serialize(FArchive& Ar)
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this, &Ar](){ Super::Serialize(Ar); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnSerialize(EventScope, Ar);
}
void UDecoupledOutputProvider::PostLoad()
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this](){ Super::PostLoad(); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnPostLoad(EventScope);
}
#if WITH_EDITOR
void UDecoupledOutputProvider::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
using namespace UE::DecoupledOutputProvider::Private;
const auto SuperFunc = [this, &PropertyChangedEvent](){ Super::PostEditChangeProperty(PropertyChangedEvent); };
FOutputProviderEvent EventScope(*this, SuperFunc);
FDecoupledOutputProviderModule::Get().OnPostEditChangeProperty(EventScope, PropertyChangedEvent);
}
#endif

View File

@@ -0,0 +1,224 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "DecoupledOutputProviderModule.h"
#include "IOutputProviderLogic.h"
#include "Engine/World.h"
namespace UE::DecoupledOutputProvider::Private
{
void FDecoupledOutputProviderModule::StartupModule()
{
FWorldDelegates::OnWorldCleanup.AddRaw(this, &FDecoupledOutputProviderModule::OnWorldCleanup);
}
void FDecoupledOutputProviderModule::ShutdownModule()
{
FWorldDelegates::OnWorldCleanup.RemoveAll(this);
}
void FDecoupledOutputProviderModule::RegisterLogicFactory(TSubclassOf<UDecoupledOutputProvider> Class, FOutputProviderLogicFactoryDelegate FactoryDelegate)
{
check(FactoryDelegate.IsBound() && Class != UDecoupledOutputProvider::StaticClass());
FOutputProviderClassMetaData& ClassMetaData = RegisteredFactories.FindOrAdd(Class);
if (!ClassMetaData.Factory.IsBound())
{
ClassMetaData.Factory = FactoryDelegate;
}
}
void FDecoupledOutputProviderModule::UnregisterLogicFactory(TSubclassOf<UDecoupledOutputProvider> Class)
{
RegisteredFactories.Remove(Class);
}
void FDecoupledOutputProviderModule::ForEachOutputProvidersWithLogicObject(TFunctionRef<EBreakBehavior(UDecoupledOutputProvider&)> Callback, TSubclassOf<UDecoupledOutputProvider> Class) const
{
for (const TPair<TWeakObjectPtr<UDecoupledOutputProvider>, TSharedRef<IOutputProviderLogic>>& Pair : ActiveLogic)
{
TWeakObjectPtr<UDecoupledOutputProvider> OutputProvider = Pair.Key;
if (OutputProvider.IsValid() && Callback(*OutputProvider) == EBreakBehavior::Break)
{
return;
}
}
}
void FDecoupledOutputProviderModule::OnInitialize(IOutputProviderEvent& Args)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnInitialize(Args);
}
}
void FDecoupledOutputProviderModule::OnDeinitialize(IOutputProviderEvent& Args)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnDeinitialize(Args);
}
}
void FDecoupledOutputProviderModule::OnTick(IOutputProviderEvent& Args, const float DeltaTime)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnTick(Args, DeltaTime);
}
}
void FDecoupledOutputProviderModule::OnActivate(IOutputProviderEvent& Args)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnActivate(Args);
}
}
void FDecoupledOutputProviderModule::OnDeactivate(IOutputProviderEvent& Args)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnDeactivate(Args);
}
}
void FDecoupledOutputProviderModule::OnAddReferencedObjects(IOutputProviderEvent& Args, FReferenceCollector& Collector)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnAddReferencedObjects(Args, Collector);
}
}
void FDecoupledOutputProviderModule::OnBeginDestroy(IOutputProviderEvent& Args)
{
UDecoupledOutputProvider& OutputProvider = Args.GetOutputProvider();
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(OutputProvider))
{
Logic->OnBeginDestroy(Args);
ActiveLogic.Remove(&OutputProvider);
}
}
void FDecoupledOutputProviderModule::OnSerialize(IOutputProviderEvent& Args, FArchive& Ar)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnSerialize(Args, Ar);
}
}
void FDecoupledOutputProviderModule::OnPostLoad(IOutputProviderEvent& Args)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnPostLoad(Args);
}
}
#if WITH_EDITOR
void FDecoupledOutputProviderModule::OnPostEditChangeProperty(IOutputProviderEvent& Args, FPropertyChangedEvent& PropertyChangedEvent)
{
if (const TSharedPtr<IOutputProviderLogic> Logic = GetOrCreateLogicFor(Args.GetOutputProvider()))
{
Logic->OnPostEditChangeProperty(Args, PropertyChangedEvent);
}
}
#endif
TSharedPtr<IOutputProviderLogic> FDecoupledOutputProviderModule::GetOrCreateLogicFor(UDecoupledOutputProvider& OutputProviderBase)
{
if (const TSharedRef<IOutputProviderLogic>* Logic = ActiveLogic.Find(&OutputProviderBase))
{
return *Logic;
}
TPair<UClass*, FOutputProviderClassMetaData*> FoundClassData = FindRegisteredFactoryInClassHierarchy(OutputProviderBase.GetClass());
const bool bHasRegisteredLogicFactory = FoundClassData.Key && FoundClassData.Value;
if (!bHasRegisteredLogicFactory
// CDOs and archetypes do not perform any outputting so skip them!
|| OutputProviderBase.HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject)
// Users should not be able to register for the UDecoupledOutputProvider base class
|| OutputProviderBase.GetClass() == UDecoupledOutputProvider::StaticClass())
{
return nullptr;
}
FOutputProviderClassMetaData& MetaData = *FoundClassData.Value;
const TSharedPtr<IOutputProviderLogic> Result = MetaData.Factory.Execute({ &OutputProviderBase });
if (!Result)
{
return nullptr;
}
ActiveLogic.Add(&OutputProviderBase, Result.ToSharedRef());
return Result;
}
void FDecoupledOutputProviderModule::ForEachRegisteredClassInClassHierarchy(UClass* Class, TFunctionRef<EBreakBehavior(UClass& Class, FOutputProviderClassMetaData&)> Callback)
{
const_cast<const FDecoupledOutputProviderModule*>(this)->ForEachRegisteredClassInClassHierarchy(Class, [&Callback](UClass& Class, const FOutputProviderClassMetaData& Data)
{
return Callback(Class, const_cast<FOutputProviderClassMetaData&>(Data));
});
}
void FDecoupledOutputProviderModule::ForEachRegisteredClassInClassHierarchy(UClass* Class, TFunctionRef<EBreakBehavior(UClass& Class, const FOutputProviderClassMetaData&)> Callback) const
{
// Recursion termination
if (!Class
|| !ensure(Class->IsChildOf(UDecoupledOutputProvider::StaticClass()))
|| Class == UDecoupledOutputProvider::StaticClass())
{
return;
}
// Keep walking up the hierarchy until the callback says to stop or we reach UDecoupledOutputProvider
const FOutputProviderClassMetaData* Data = RegisteredFactories.Find(Class);
const EBreakBehavior BreakBehavior = Data
? Callback(*Class, *Data)
: EBreakBehavior::Continue;
if (BreakBehavior == EBreakBehavior::Continue)
{
ForEachRegisteredClassInClassHierarchy(Class->GetSuperClass(), Callback);
}
}
TPair<UClass*, FDecoupledOutputProviderModule::FOutputProviderClassMetaData*> FDecoupledOutputProviderModule::FindRegisteredFactoryInClassHierarchy(UClass* Class)
{
TPair<UClass*, const FOutputProviderClassMetaData*> Result = const_cast<const FDecoupledOutputProviderModule*>(this)->FindRegisteredFactoryInClassHierarchy(Class);
return { Result.Key, const_cast<FOutputProviderClassMetaData*>(Result.Value) };
}
TPair<UClass*, const FDecoupledOutputProviderModule::FOutputProviderClassMetaData*> FDecoupledOutputProviderModule::FindRegisteredFactoryInClassHierarchy(UClass* Class) const
{
UClass* ResultClass = nullptr;
const FOutputProviderClassMetaData* FoundData = nullptr;
// Walk up the class hierarchy and return the first registered factory (the parent closest to the passed in Class)
ForEachRegisteredClassInClassHierarchy(Class, [&ResultClass, &FoundData](UClass& Class, const FOutputProviderClassMetaData& Data)
{
ResultClass = &Class;
FoundData = &Data;
return EBreakBehavior::Break;
});
return { ResultClass, FoundData };
}
void FDecoupledOutputProviderModule::OnWorldCleanup(UWorld* World, bool bSessionEnded, bool bCleanupResources)
{
// It's incorrect to call ActiveLogic.Reset() because there are many worlds, e.g. PIE, editor, editor preview, etc.
for (auto ActiveLogicIterator = ActiveLogic.CreateIterator(); ActiveLogicIterator; ++ActiveLogicIterator)
{
if (!ActiveLogicIterator.Key().IsValid() || ActiveLogicIterator.Key()->IsIn(World))
{
ActiveLogicIterator.RemoveCurrent();
}
}
}
}
IMPLEMENT_MODULE(UE::DecoupledOutputProvider::Private::FDecoupledOutputProviderModule, DecoupledOutputProvider);

View File

@@ -0,0 +1,75 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "IDecoupledOutputProviderModule.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
class UClass;
namespace UE::DecoupledOutputProvider
{
class IOutputProviderEvent;
}
namespace UE::DecoupledOutputProvider::Private
{
class FDecoupledOutputProviderModule
: public IDecoupledOutputProviderModule
{
public:
static FDecoupledOutputProviderModule& Get() { return FModuleManager::Get().LoadModuleChecked<FDecoupledOutputProviderModule>(TEXT("DecoupledOutputProvider")); }
//~ Begin IModuleInterface Interface
virtual void StartupModule() override;
virtual void ShutdownModule() override;
//~ End IModuleInterface Interface
//~ Begin IDecoupledOutputProviderModule Interface
virtual void RegisterLogicFactory(TSubclassOf<UDecoupledOutputProvider> Class, FOutputProviderLogicFactoryDelegate FactoryDelegate) override;
virtual void UnregisterLogicFactory(TSubclassOf<UDecoupledOutputProvider> Class) override;
virtual void ForEachOutputProvidersWithLogicObject(TFunctionRef<EBreakBehavior(UDecoupledOutputProvider&)> Callback, TSubclassOf<UDecoupledOutputProvider> Class) const override;
//~ End IDecoupledOutputProviderModule Interface
void OnInitialize(IOutputProviderEvent& Args);
void OnDeinitialize(IOutputProviderEvent& Args);
void OnTick(IOutputProviderEvent& Args, const float DeltaTime);
void OnActivate(IOutputProviderEvent& Args);
void OnDeactivate(IOutputProviderEvent& Args);
void OnAddReferencedObjects(IOutputProviderEvent& Args, FReferenceCollector& Collector);
void OnBeginDestroy(IOutputProviderEvent& Args);
void OnSerialize(IOutputProviderEvent& Args, FArchive& Ar);
void OnPostLoad(IOutputProviderEvent& Args);
#if WITH_EDITOR
void OnPostEditChangeProperty(IOutputProviderEvent& Args, FPropertyChangedEvent& PropertyChangedEvent);
#endif
private:
struct FOutputProviderClassMetaData
{
/** Used to create new logic objects. */
FOutputProviderLogicFactoryDelegate Factory;
};
/** Keeps track of registered factory functions */
TMap<UClass*, FOutputProviderClassMetaData> RegisteredFactories;
/** Maps output providers to their logic. There can be at most one logic object per output provider. */
TMap<TWeakObjectPtr<UDecoupledOutputProvider>, TSharedRef<IOutputProviderLogic>> ActiveLogic;
/** Creates a new logic object that will be associated with OutputProviderBase, or if one was previously associated, returns the preexisting one. */
TSharedPtr<IOutputProviderLogic> GetOrCreateLogicFor(UDecoupledOutputProvider& OutputProviderBase);
// Data queries
void ForEachRegisteredClassInClassHierarchy(UClass* Class, TFunctionRef<EBreakBehavior(UClass& Key, FOutputProviderClassMetaData&)> Callback);
void ForEachRegisteredClassInClassHierarchy(UClass* Class, TFunctionRef<EBreakBehavior(UClass& Key, const FOutputProviderClassMetaData&)> Callback) const;
TPair<UClass*, FOutputProviderClassMetaData*> FindRegisteredFactoryInClassHierarchy(UClass* Class);
TPair<UClass*, const FOutputProviderClassMetaData*> FindRegisteredFactoryInClassHierarchy(UClass* Class) const;
// Delegates
void OnWorldCleanup(UWorld* World, bool bArg, bool bCond);
};
}

View File

@@ -0,0 +1,5 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "LogDecoupledOutputProvider.h"
DEFINE_LOG_CATEGORY(LogDecoupledOutputProvider);

View File

@@ -0,0 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Logging/LogMacros.h"
DECLARE_LOG_CATEGORY_EXTERN(LogDecoupledOutputProvider, Log, All);

View File

@@ -0,0 +1,39 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "DecoupledOutputProvider.h"
#include "VCamPixelStreamingSession.generated.h"
UCLASS(meta = (DisplayName = "Pixel Streaming Provider"))
class DECOUPLEDOUTPUTPROVIDER_API UVCamPixelStreamingSession : public UDecoupledOutputProvider
{
GENERATED_BODY()
public:
UVCamPixelStreamingSession();
/** If using the output from a Composure Output Provider, specify it here */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "10"))
int32 FromComposureOutputProviderIndex = INDEX_NONE;
/** If true the streamed UE viewport will match the resolution of the remote device. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "11"))
bool bMatchRemoteResolution = true;
/** Check this if you wish to control the corresponding CineCamera with transform data received from the LiveLink app */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "12"))
bool EnableARKitTracking = true;
/** If not selected, when the editor is not the foreground application, input through the vcam session may seem sluggish or unresponsive. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "13"))
bool PreventEditorIdle = true;
/** If true then the Live Link Subject of the owning VCam Component will be set to the subject created by this Output Provider when the Provider is enabled */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "14"))
bool bAutoSetLiveLinkSubject = true;
/** Set the name of this stream to be reported to the signalling server. If none is supplied a default will be used. If ids are not unique issues can occur. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "15"))
FString StreamerId;
};

View File

@@ -0,0 +1,43 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Output/VCamOutputProviderBase.h"
#include "DecoupledOutputProvider.generated.h"
namespace UE::DecoupledOutputProvider
{
class IOutputProviderLogic;
}
/**
* A decoupled output provider only contains data and forwards all important events to an IOutputProviderLogic, which
* may or may not exist. This allows the data to be loaded on all platforms but perform no operations on unsupported platforms.
* This decoupling is important to avoid failing LoadPackage warnings during cooking.
*
* Example: Pixel Streaming.
*/
UCLASS(Abstract, NotBlueprintable)
class DECOUPLEDOUTPUTPROVIDER_API UDecoupledOutputProvider : public UVCamOutputProviderBase
{
GENERATED_BODY()
public:
//~ Begin UVCamOutputProviderBase Interface
virtual void Initialize() override;
virtual void Deinitialize() override;
virtual void Tick(const float DeltaTime) override;
virtual void OnActivate() override;
virtual void OnDeactivate() override;
//~ End UVCamOutputProviderBase Interface
//~ Begin UObject Interface
static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector);
virtual void BeginDestroy() override;
virtual void Serialize(FArchive& Ar) override;
virtual void PostLoad() override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
//~ End UObject Interface
};

View File

@@ -0,0 +1,56 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "DecoupledOutputProvider.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
template<typename T>
class TSubclassOf;
namespace UE::DecoupledOutputProvider
{
class IOutputProviderLogic;
struct FOutputProviderLogicCreationArgs
{
UDecoupledOutputProvider* Provider;
};
DECLARE_DELEGATE_RetVal_OneParam(TSharedPtr<IOutputProviderLogic>, FOutputProviderLogicFactoryDelegate, const FOutputProviderLogicCreationArgs&);
/** Provides an interface for managing decoupled output provider logic objects. */
class DECOUPLEDOUTPUTPROVIDER_API IDecoupledOutputProviderModule : public IModuleInterface
{
public:
static IDecoupledOutputProviderModule& Get(){ return FModuleManager::Get().LoadModuleChecked<IDecoupledOutputProviderModule>(TEXT("DecoupledOutputProvider")); }
/**
* Registers a factory delegate for Class and its subclasses. You can overwrite subclasses to use a different delegate.
* @param Class The base class for which to register the logic
* @param FactoryDelegate The delegate that will create the logic object
*/
virtual void RegisterLogicFactory(TSubclassOf<UDecoupledOutputProvider> Class, FOutputProviderLogicFactoryDelegate FactoryDelegate) = 0;
/**
* Unregisters a previously registered delegate. Existing logic objects will continue to exist; if you want them to be removed: BeginDestroy will implicitly destroy the associated logic object.
* @param Class You must supply the exact class previously passed to RegisterLogicFactory. This does not remove subclass overrides.
*/
virtual void UnregisterLogicFactory(TSubclassOf<UDecoupledOutputProvider> Class) = 0;
enum class EBreakBehavior
{
Continue,
Break
};
/** Iterates all output providers with the given class for which a logic object has been created. Note this includes subclass overrides as well. */
virtual void ForEachOutputProvidersWithLogicObject(TFunctionRef<EBreakBehavior(UDecoupledOutputProvider&)> Callback, TSubclassOf<UDecoupledOutputProvider> Class = UDecoupledOutputProvider::StaticClass()) const = 0;
/** Gets all output providers for which a logic object has been created. Note this includes subclass overrides as well. */
TArray<UDecoupledOutputProvider*> GetOutputProvidersWithLogicObjects(TSubclassOf<UDecoupledOutputProvider> Class = UDecoupledOutputProvider::StaticClass()) const
{
TArray<UDecoupledOutputProvider*> Result;
ForEachOutputProvidersWithLogicObject([&Result](UDecoupledOutputProvider& OutputProvider){ Result.Emplace(&OutputProvider); return EBreakBehavior::Continue; }, Class);
return Result;
}
};
}

View File

@@ -0,0 +1,65 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Templates/Function.h"
#include "Templates/SharedPointer.h"
class FReferenceCollector;
class UDecoupledOutputProvider;
class UVCamOutputProviderBase;
#if WITH_EDITOR
struct FPropertyChangedEvent;
#endif
namespace UE::DecoupledOutputProvider
{
/** Exposes certain data and manages the call to the super function. */
class DECOUPLEDOUTPUTPROVIDER_API IOutputProviderEvent
{
public:
/**
* Calls the super UVCamOutputProviderBase function version.
* If this is not called, the call to super takes place automatically after the IOutputProviderLogic function call.
*/
virtual void ExecuteSuperFunction() = 0;
/** Gets the associated output provider */
virtual UDecoupledOutputProvider& GetOutputProvider() const = 0;
virtual ~IOutputProviderEvent() = default;
};
/**
* Receives events from an output provider's and implements its logic.
*
* This pattern decouples the output provider's data from its logic allowing the data to be loaded
* on all platforms but only have the logic be loaded on supported platforms.
*
* A logic's lifetime is bound to that of the output provider and is managed by the DecoupledOutputProvider's module.
* You can register a factory function with IDecoupledOutputProviderModule::RegisterLogicFactory.
*/
class DECOUPLEDOUTPUTPROVIDER_API IOutputProviderLogic : public TSharedFromThis<IOutputProviderLogic>
{
public:
// UVCamOutputProviderBase events
virtual void OnInitialize(IOutputProviderEvent& Args) {}
virtual void OnDeinitialize(IOutputProviderEvent& Args) {}
virtual void OnActivate(IOutputProviderEvent& Args) {}
virtual void OnDeactivate(IOutputProviderEvent& Args) {}
virtual void OnTick(IOutputProviderEvent& Args, const float DeltaTime) {}
// UObject events
virtual void OnAddReferencedObjects(IOutputProviderEvent& Args, FReferenceCollector& Collector) {}
virtual void OnBeginDestroy(IOutputProviderEvent& Args) {}
virtual void OnSerialize(IOutputProviderEvent& Args, FArchive& Ar) {}
virtual void OnPostLoad(IOutputProviderEvent& Args) {}
#if WITH_EDITOR
virtual void OnPostEditChangeProperty(IOutputProviderEvent& Args, FPropertyChangedEvent& PropertyChangedEvent) {}
#endif
virtual ~IOutputProviderLogic() = default;
};
}

View File

@@ -25,19 +25,20 @@ namespace UnrealBuildTool.Rules
"ApplicationCore",
"Core",
"CoreUObject",
"Engine",
"Projects",
"UMG",
"RHI",
"CinematicCamera",
"DecoupledOutputProvider",
"Engine",
"InputCore",
"LiveLinkInterface",
"RHI",
"PixelCapture",
"PixelStreamingEditor",
"Projects",
"PixelStreamingInput",
"SlateCore",
"Slate",
"SlateCore",
"UMG",
"VPUtilities",
"InputCore"
});
// Can't package non-editor targets (e.g. games) with UnrealEd, so this dependency should only be added in editor.

View File

@@ -0,0 +1,35 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "PixelStreamingVCamModule.h"
#include "BuiltinProviders/VCamPixelStreamingSession.h"
#include "IDecoupledOutputProviderModule.h"
#include "VCamPixelStreamingSessionLogic.h"
#include "Modules/ModuleManager.h"
namespace UE::PixelStreamingVCam::Private
{
void FPixelStreamingVCamModule::StartupModule()
{
using namespace DecoupledOutputProvider;
IDecoupledOutputProviderModule& DecouplingModule = IDecoupledOutputProviderModule::Get();
DecouplingModule.RegisterLogicFactory(
UVCamPixelStreamingSession::StaticClass(),
FOutputProviderLogicFactoryDelegate::CreateLambda([](const FOutputProviderLogicCreationArgs& Args)
{
return MakeShared<FVCamPixelStreamingSessionLogic>();
})
);
}
void FPixelStreamingVCamModule::ShutdownModule()
{
if (FModuleManager::Get().IsModuleLoaded(TEXT("DecoupledOutputProvider")))
{
DecoupledOutputProvider::IDecoupledOutputProviderModule::Get().UnregisterLogicFactory(UVCamPixelStreamingSession::StaticClass());
}
}
}
IMPLEMENT_MODULE(UE::PixelStreamingVCam::Private::FPixelStreamingVCamModule, PixelStreamingVCam);

View File

@@ -0,0 +1,19 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Modules/ModuleInterface.h"
namespace UE::PixelStreamingVCam::Private
{
class FPixelStreamingVCamModule : public IModuleInterface
{
public:
//~ Begin IModuleInterface Interface
virtual void StartupModule() override;
virtual void ShutdownModule() override;
//~ End IModuleInterface Interface
};
}

View File

@@ -1,406 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "VCamPixelStreamingSession.h"
#include "Output/VCamOutputComposure.h"
#include "VCamComponent.h"
#include "VCamPixelStreamingSubsystem.h"
#include "VCamPixelStreamingLiveLink.h"
#include "Async/Async.h"
#include "Containers/UnrealString.h"
#include "Editor/EditorPerformanceSettings.h"
#include "IPixelStreamingModule.h"
#include "IPixelStreamingInputModule.h"
#include "Modules/ModuleManager.h"
#include "Math/Matrix.h"
#include "IPixelStreamingEditorModule.h"
#include "PixelStreamingVCamLog.h"
#include "PixelStreamingInputProtocol.h"
#include "PixelStreamingInputMessage.h"
#include "PixelStreamingServers.h"
#include "Serialization/MemoryReader.h"
#include "Slate/SceneViewport.h"
#include "Widgets/SVirtualWindow.h"
#include "Widgets/VPFullScreenUserWidget.h"
#include "PixelStreamingInputEnums.h"
int UVCamPixelStreamingSession::NextDefaultStreamerId = 1;
namespace UE::VCamPixelStreamingSession::Private
{
static const FSoftClassPath EmptyUMGSoftClassPath(TEXT("/VCamCore/Assets/VCam_EmptyVisibleUMG.VCam_EmptyVisibleUMG_C"));
} // namespace UE::VCamPixelStreamingSession::Private
UVCamPixelStreamingSession::UVCamPixelStreamingSession()
{
DisplayType = EVPWidgetDisplayType::PostProcessSceneViewExtension;
InitViewTargetPolicyInSubclass();
}
void UVCamPixelStreamingSession::Deinitialize()
{
if (MediaOutput)
{
MediaOutput->ConditionalBeginDestroy();
}
MediaOutput = nullptr;
Super::Deinitialize();
}
void UVCamPixelStreamingSession::OnActivate()
{
if (!IsInitialized())
{
UE_LOG(LogPixelStreamingVCam, Warning, TEXT("Trying to start Pixel Streaming, but has not been initialized yet"));
SetActive(false);
return;
}
if (StreamerId.IsEmpty())
{
StreamerId = FString::Printf(TEXT("VCam%d"), NextDefaultStreamerId++);
}
// Rename the underlying session to the streamer name
Rename(*StreamerId);
// Setup livelink source
UVCamPixelStreamingSubsystem::Get()->TryGetLiveLinkSource(this);
if (UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get())
{
PixelStreamingSubsystem->RegisterActiveOutputProvider(this);
ConditionallySetLiveLinkSubjectToThis();
}
// If we don't have a UMG assigned, we still need to create an empty 'dummy' UMG in order to properly route the input back from the RemoteSession device
if (!GetUMGClass())
{
bUsingDummyUMG = true;
SetUMGClass(UE::VCamPixelStreamingSession::Private::EmptyUMGSoftClassPath.TryLoadClass<UUserWidget>());
}
// create a new media output if we dont already have one, or its not valid, or if the id has changed
if (MediaOutput == nullptr || !MediaOutput->IsValid() || MediaOutput->GetStreamer()->GetId() != StreamerId)
{
MediaOutput = UPixelStreamingMediaOutput::Create(GetTransientPackage(), StreamerId);
MediaOutput->OnRemoteResolutionChanged().AddUObject(this, &UVCamPixelStreamingSession::OnRemoteResolutionChanged);
}
UEditorPerformanceSettings* Settings = GetMutableDefault<UEditorPerformanceSettings>();
bOldThrottleCPUWhenNotForeground = Settings->bThrottleCPUWhenNotForeground;
if (PreventEditorIdle)
{
Settings->bThrottleCPUWhenNotForeground = false;
Settings->PostEditChange();
}
// This sets up media capture and streamer
SetupCapture();
// Super::Activate() creates our UMG which we need before setting up our custom input handling
Super::OnActivate();
// We setup custom handling of ARKit transforms coming from iOS devices here
SetupCustomInputHandling();
// We need signalling server to be up before we can start streaming
SetupSignallingServer();
if (MediaOutput->IsValid())
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Activating PixelStreaming VCam Session. Endpoint: %s"), *MediaOutput->GetStreamer()->GetSignallingServerURL());
}
}
void UVCamPixelStreamingSession::SetupCapture()
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Create new media capture for Pixel Streaming VCam."));
if (MediaCapture)
{
MediaCapture->OnStateChangedNative.RemoveAll(this);
}
// Create a capturer that will capture frames from viewport and send them to streamer
MediaCapture = Cast<UPixelStreamingMediaCapture>(MediaOutput->CreateMediaCapture());
MediaCapture->OnStateChangedNative.AddUObject(this, &UVCamPixelStreamingSession::OnCaptureStateChanged);
StartCapture();
}
void UVCamPixelStreamingSession::OnCaptureStateChanged()
{
if (!MediaCapture || !MediaOutput || !MediaOutput->IsValid())
{
return;
}
switch (MediaCapture->GetState())
{
case EMediaCaptureState::Capturing:
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Starting media capture and streaming for Pixel Streaming VCam."));
MediaOutput->StartStreaming();
break;
case EMediaCaptureState::Stopped:
if (MediaCapture->WasViewportResized())
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Pixel Streaming VCam capture was stopped due to resize, going to restart capture."));
// If it was stopped and viewport resized we assume resize caused the stop, so try a restart of capture here.
SetupCapture();
}
else
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Stopping media capture and streaming for Pixel Streaming VCam."));
MediaOutput->StopStreaming();
}
break;
case EMediaCaptureState::Error:
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Pixel Streaming VCam capture hit an error, capturing will stop."));
break;
default:
break;
}
}
void UVCamPixelStreamingSession::OnRemoteResolutionChanged(const FIntPoint& RemoteResolution)
{
// Early out if match remote resolution is not enabled.
if (!bMatchRemoteResolution)
{
return;
}
// Ensure override resolution is being used
if (!bUseOverrideResolution)
{
bUseOverrideResolution = true;
}
// Set the override resolution on the output provider base, this will trigger a resize
OverrideResolution = RemoteResolution;
ApplyOverrideResolutionForViewport(GetTargetViewport());
}
void UVCamPixelStreamingSession::ConditionallySetLiveLinkSubjectToThis() const
{
UVCamComponent* VCamComponent = GetTypedOuter<UVCamComponent>();
if (bAutoSetLiveLinkSubject && IsValid(VCamComponent) && IsActive())
{
VCamComponent->SetLiveLinkSubobject(GetFName());
}
}
void UVCamPixelStreamingSession::SetupCustomInputHandling()
{
if (GetUMGWidget())
{
TSharedPtr<SVirtualWindow> InputWindow;
// If we are rendering from a ComposureOutputProvider, we need to get the InputWindow from that UMG, not the one in the PixelStreamingOutputProvider
if (UVCamOutputComposure* ComposureProvider = Cast<UVCamOutputComposure>(GetOtherOutputProviderByIndex(FromComposureOutputProviderIndex)))
{
if (UVPFullScreenUserWidget* ComposureUMGWidget = ComposureProvider->GetUMGWidget())
{
const EVPWidgetDisplayType WidgetDisplayType = ComposureUMGWidget->GetDisplayType(GetWorld());
if (ensure(UVPFullScreenUserWidget::DoesDisplayTypeUsePostProcessSettings(WidgetDisplayType)))
{
InputWindow = ComposureUMGWidget->GetPostProcessDisplayTypeSettingsFor(WidgetDisplayType)->GetSlateWindow();
UE_LOG(LogPixelStreamingVCam, Log, TEXT("InputChannel callback - Routing input to active viewport with Composure UMG"));
}
}
else
{
UE_LOG(LogPixelStreamingVCam, Warning, TEXT("InputChannel callback - Composure usage was requested, but the specified ComposureOutputProvider has no UMG set"));
}
}
else
{
checkf(UVPFullScreenUserWidget::DoesDisplayTypeUsePostProcessSettings(DisplayType), TEXT("DisplayType not set up correctly in constructor!"));
InputWindow = GetUMGWidget()->GetPostProcessDisplayTypeSettingsFor(DisplayType)->GetSlateWindow();
UE_LOG(LogPixelStreamingVCam, Log, TEXT("InputChannel callback - Routing input to active viewport with UMG"));
}
MediaOutput->GetStreamer()->SetTargetWindow(InputWindow);
MediaOutput->GetStreamer()->SetInputHandlerType(EPixelStreamingInputType::RouteToWidget);
}
else
{
MediaOutput->GetStreamer()->SetTargetWindow(GetTargetInputWindow());
MediaOutput->GetStreamer()->SetInputHandlerType(EPixelStreamingInputType::RouteToWidget);
UE_LOG(LogPixelStreamingVCam, Log, TEXT("InputChannel callback - Routing input to active viewport"));
}
if (MediaOutput)
{
IPixelStreamingInputModule& PixelStreamingInputModule = IPixelStreamingInputModule::Get();
typedef EPixelStreamingMessageTypes EType;
/*
* ====================
* ARKit Transform
* ====================
*/
FPixelStreamingInputMessage ARKitMessage = FPixelStreamingInputMessage(100, { // 4x4 Transform
EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float,
// Timestamp
EType::Double });
const TFunction<void(FMemoryReader)>& ARKitHandler = [this](FMemoryReader Ar) {
if (!EnableARKitTracking)
{
return;
}
// The buffer contains the transform matrix stored as 16 floats
FMatrix ARKitMatrix;
for (int32 Row = 0; Row < 4; ++Row)
{
float Col0, Col1, Col2, Col3;
Ar << Col0 << Col1 << Col2 << Col3;
ARKitMatrix.M[Row][0] = Col0;
ARKitMatrix.M[Row][1] = Col1;
ARKitMatrix.M[Row][2] = Col2;
ARKitMatrix.M[Row][3] = Col3;
}
ARKitMatrix.DiagnosticCheckNaN();
// Extract timestamp
double Timestamp;
Ar << Timestamp;
if (TSharedPtr<FPixelStreamingLiveLinkSource> LiveLinkSource = UVCamPixelStreamingSubsystem::Get()->TryGetLiveLinkSource(this))
{
LiveLinkSource->PushTransformForSubject(GetFName(), FTransform(ARKitMatrix), Timestamp);
}
};
FPixelStreamingInputProtocol::ToStreamerProtocol.Add("ARKitTransform", ARKitMessage);
if (TSharedPtr<IPixelStreamingInputHandler> InputHandler = MediaOutput->GetStreamer()->GetInputHandler().Pin())
{
InputHandler->RegisterMessageHandler("ARKitTransform", ARKitHandler);
}
}
else
{
UE_LOG(LogPixelStreamingVCam, Error, TEXT("Failed to setup custom input handling."));
}
}
void UVCamPixelStreamingSession::StartCapture()
{
if (!MediaCapture)
{
return;
}
FMediaCaptureOptions Options;
Options.OverrunAction = EMediaCaptureOverrunAction::Skip;
Options.ResizeMethod = EMediaCaptureResizeMethod::ResizeSource;
// If we are rendering from a ComposureOutputProvider, get the requested render target and use that instead of the viewport
if (UVCamOutputComposure* ComposureProvider = Cast<UVCamOutputComposure>(GetOtherOutputProviderByIndex(FromComposureOutputProviderIndex)))
{
if (ComposureProvider->FinalOutputRenderTarget)
{
MediaCapture->CaptureTextureRenderTarget2D(ComposureProvider->FinalOutputRenderTarget, Options);
UE_LOG(LogPixelStreamingVCam, Log, TEXT("PixelStreaming set with ComposureRenderTarget"));
}
else
{
UE_LOG(LogPixelStreamingVCam, Warning, TEXT("PixelStreaming Composure usage was requested, but the specified ComposureOutputProvider has no FinalOutputRenderTarget set"));
}
}
else
{
TWeakPtr<FSceneViewport> SceneViewport = GetTargetSceneViewport();
if (TSharedPtr<FSceneViewport> PinnedSceneViewport = SceneViewport.Pin())
{
MediaCapture->CaptureSceneViewport(PinnedSceneViewport, Options);
UE_LOG(LogPixelStreamingVCam, Log, TEXT("PixelStreaming set to capture scene viewport."));
}
}
}
void UVCamPixelStreamingSession::SetupSignallingServer()
{
// Only start the signalling server if we aren't using an external signalling server
UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get();
if (PixelStreamingSubsystem && !IPixelStreamingEditorModule::Get().UseExternalSignallingServer())
{
PixelStreamingSubsystem->LaunchSignallingServer();
}
}
void UVCamPixelStreamingSession::StopSignallingServer()
{
// Only stop the signalling server if we've been the ones to start it
UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get();
if (PixelStreamingSubsystem && !IPixelStreamingEditorModule::Get().UseExternalSignallingServer())
{
PixelStreamingSubsystem->StopSignallingServer();
}
}
void UVCamPixelStreamingSession::OnDeactivate()
{
if (UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get())
{
PixelStreamingSubsystem->UnregisterActiveOutputProvider(this);
}
if (MediaCapture)
{
if (MediaOutput && MediaOutput->IsValid())
{
// Shutting streamer down before closing signalling server prevents an ugly websocket disconnect showing in the log
MediaOutput->GetStreamer()->StopStreaming();
}
StopSignallingServer();
MediaCapture->StopCapture(true);
MediaCapture = nullptr;
}
else
{
// There is not media capture we defensively clean up the signalling server if it exists.
StopSignallingServer();
}
Super::OnDeactivate();
if (bUsingDummyUMG)
{
SetUMGClass(nullptr);
bUsingDummyUMG = false;
}
UEditorPerformanceSettings* Settings = GetMutableDefault<UEditorPerformanceSettings>();
Settings->bThrottleCPUWhenNotForeground = bOldThrottleCPUWhenNotForeground;
Settings->PostEditChange();
}
void UVCamPixelStreamingSession::Tick(const float DeltaTime)
{
Super::Tick(DeltaTime);
}
#if WITH_EDITOR
void UVCamPixelStreamingSession::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
FProperty* Property = PropertyChangedEvent.MemberProperty;
if (Property && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
const FName PropertyName = Property->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UVCamPixelStreamingSession, FromComposureOutputProviderIndex))
{
SetActive(false);
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UVCamPixelStreamingSession, bAutoSetLiveLinkSubject))
{
ConditionallySetLiveLinkSubjectToThis();
}
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
IMPLEMENT_MODULE(FDefaultModuleImpl, PixelStreamingVCam)

View File

@@ -0,0 +1,403 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "VCamPixelStreamingSessionLogic.h"
#include "BuiltinProviders/VCamPixelStreamingSession.h"
#include "Output/VCamOutputComposure.h"
#include "VCamComponent.h"
#include "VCamPixelStreamingLiveLink.h"
#include "VCamPixelStreamingSubsystem.h"
#include "Async/Async.h"
#include "Containers/UnrealString.h"
#include "Editor/EditorPerformanceSettings.h"
#include "IPixelStreamingModule.h"
#include "IPixelStreamingInputModule.h"
#include "IPixelStreamingEditorModule.h"
#include "PixelStreamingInputEnums.h"
#include "PixelStreamingInputMessage.h"
#include "PixelStreamingInputProtocol.h"
#include "PixelStreamingMediaOutput.h"
#include "PixelStreamingServers.h"
#include "PixelStreamingVCamLog.h"
#include "Math/Matrix.h"
#include "Serialization/MemoryReader.h"
#include "Slate/SceneViewport.h"
#include "Widgets/SVirtualWindow.h"
#include "Widgets/VPFullScreenUserWidget.h"
namespace UE::PixelStreamingVCam::Private
{
int32 FVCamPixelStreamingSessionLogic::NextDefaultStreamerId = 1;
void FVCamPixelStreamingSessionLogic::OnDeinitialize(DecoupledOutputProvider::IOutputProviderEvent& Args)
{
if (MediaOutput)
{
MediaOutput->ConditionalBeginDestroy();
MediaOutput = nullptr;
}
}
void FVCamPixelStreamingSessionLogic::OnActivate(DecoupledOutputProvider::IOutputProviderEvent& Args)
{
UVCamPixelStreamingSession* This = Cast<UVCamPixelStreamingSession>(&Args.GetOutputProvider());
const TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr = This;
if (!This->IsInitialized())
{
UE_LOG(LogPixelStreamingVCam, Warning, TEXT("Trying to start Pixel Streaming, but has not been initialized yet"));
This->SetActive(false);
return;
}
if (This->StreamerId.IsEmpty())
{
This->StreamerId = FString::Printf(TEXT("VCam%d"), NextDefaultStreamerId++);
}
// Rename the underlying session to the streamer name
This->Rename(*This->StreamerId);
// Setup livelink source
UVCamPixelStreamingSubsystem::Get()->TryGetLiveLinkSource(This);
if (UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get())
{
PixelStreamingSubsystem->RegisterActiveOutputProvider(This);
ConditionallySetLiveLinkSubjectToThis(This);
}
// If we don't have a UMG assigned, we still need to create an empty 'dummy' UMG in order to properly route the input back from the RemoteSession device
if (!This->GetUMGClass())
{
bUsingDummyUMG = true;
const FSoftClassPath EmptyUMGSoftClassPath(TEXT("/VCamCore/Assets/VCam_EmptyVisibleUMG.VCam_EmptyVisibleUMG_C"));
This->SetUMGClass(EmptyUMGSoftClassPath.TryLoadClass<UUserWidget>());
}
// create a new media output if we dont already have one, or its not valid, or if the id has changed
if (MediaOutput == nullptr || !MediaOutput->IsValid() || MediaOutput->GetStreamer()->GetId() != This->StreamerId)
{
MediaOutput = UPixelStreamingMediaOutput::Create(GetTransientPackage(), This->StreamerId);
MediaOutput->OnRemoteResolutionChanged().AddSP(this, &FVCamPixelStreamingSessionLogic::OnRemoteResolutionChanged, WeakThisPtr);
}
UEditorPerformanceSettings* Settings = GetMutableDefault<UEditorPerformanceSettings>();
bOldThrottleCPUWhenNotForeground = Settings->bThrottleCPUWhenNotForeground;
if (This->PreventEditorIdle)
{
Settings->bThrottleCPUWhenNotForeground = false;
Settings->PostEditChange();
}
// This sets up media capture and streamer
SetupCapture(WeakThisPtr);
// Super::Activate() creates our UMG which we need before setting up our custom input handling
Args.ExecuteSuperFunction();
// We setup custom handling of ARKit transforms coming from iOS devices here
SetupCustomInputHandling(This);
// We need signalling server to be up before we can start streaming
SetupSignallingServer();
if (MediaOutput->IsValid())
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Activating PixelStreaming VCam Session. Endpoint: %s"), *MediaOutput->GetStreamer()->GetSignallingServerURL());
}
}
void FVCamPixelStreamingSessionLogic::OnDeactivate(DecoupledOutputProvider::IOutputProviderEvent& Args)
{
UVCamPixelStreamingSession* This = Cast<UVCamPixelStreamingSession>(&Args.GetOutputProvider());
if (UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get())
{
PixelStreamingSubsystem->UnregisterActiveOutputProvider(This);
}
if (MediaCapture)
{
if (MediaOutput && MediaOutput->IsValid())
{
// Shutting streamer down before closing signalling server prevents an ugly websocket disconnect showing in the log
MediaOutput->GetStreamer()->StopStreaming();
}
StopSignallingServer();
MediaCapture->StopCapture(true);
MediaCapture = nullptr;
}
else
{
// There is not media capture we defensively clean up the signalling server if it exists.
StopSignallingServer();
}
Args.ExecuteSuperFunction();
if (bUsingDummyUMG)
{
This->SetUMGClass(nullptr);
bUsingDummyUMG = false;
}
UEditorPerformanceSettings* Settings = GetMutableDefault<UEditorPerformanceSettings>();
Settings->bThrottleCPUWhenNotForeground = bOldThrottleCPUWhenNotForeground;
Settings->PostEditChange();
}
void FVCamPixelStreamingSessionLogic::OnAddReferencedObjects(DecoupledOutputProvider::IOutputProviderEvent& Args, FReferenceCollector& Collector)
{
Collector.AddReferencedObject(MediaOutput, &Args.GetOutputProvider());
Collector.AddReferencedObject(MediaCapture, &Args.GetOutputProvider());
}
#if WITH_EDITOR
void FVCamPixelStreamingSessionLogic::OnPostEditChangeProperty(DecoupledOutputProvider::IOutputProviderEvent& Args, FPropertyChangedEvent& PropertyChangedEvent)
{
UVCamPixelStreamingSession* This = Cast<UVCamPixelStreamingSession>(&Args.GetOutputProvider());
FProperty* Property = PropertyChangedEvent.MemberProperty;
if (Property && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
const FName PropertyName = Property->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UVCamPixelStreamingSession, FromComposureOutputProviderIndex))
{
This->SetActive(false);
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(UVCamPixelStreamingSession, bAutoSetLiveLinkSubject))
{
ConditionallySetLiveLinkSubjectToThis(This);
}
}
}
#endif
void FVCamPixelStreamingSessionLogic::SetupSignallingServer()
{
// Only start the signalling server if we aren't using an external signalling server
UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get();
if (PixelStreamingSubsystem && !IPixelStreamingEditorModule::Get().UseExternalSignallingServer())
{
PixelStreamingSubsystem->LaunchSignallingServer();
}
}
void FVCamPixelStreamingSessionLogic::StopSignallingServer()
{
// Only stop the signalling server if we've been the ones to start it
UVCamPixelStreamingSubsystem* PixelStreamingSubsystem = UVCamPixelStreamingSubsystem::Get();
if (PixelStreamingSubsystem && !IPixelStreamingEditorModule::Get().UseExternalSignallingServer())
{
PixelStreamingSubsystem->StopSignallingServer();
}
}
void FVCamPixelStreamingSessionLogic::SetupCapture(TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr)
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Create new media capture for Pixel Streaming VCam."));
if (MediaCapture)
{
MediaCapture->OnStateChangedNative.RemoveAll(this);
}
// Create a capturer that will capture frames from viewport and send them to streamer
MediaCapture = Cast<UPixelStreamingMediaCapture>(MediaOutput->CreateMediaCapture());
MediaCapture->OnStateChangedNative.AddSP(this, &FVCamPixelStreamingSessionLogic::OnCaptureStateChanged, WeakThisPtr);
StartCapture(WeakThisPtr);
}
void FVCamPixelStreamingSessionLogic::StartCapture(TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr)
{
if (!ensure(WeakThisPtr.IsValid()) || !MediaCapture)
{
return;
}
FMediaCaptureOptions Options;
Options.OverrunAction = EMediaCaptureOverrunAction::Skip;
Options.ResizeMethod = EMediaCaptureResizeMethod::ResizeSource;
// If we are rendering from a ComposureOutputProvider, get the requested render target and use that instead of the viewport
if (UVCamOutputComposure* ComposureProvider = Cast<UVCamOutputComposure>(WeakThisPtr->GetOtherOutputProviderByIndex(WeakThisPtr->FromComposureOutputProviderIndex)))
{
if (ComposureProvider->FinalOutputRenderTarget)
{
MediaCapture->CaptureTextureRenderTarget2D(ComposureProvider->FinalOutputRenderTarget, Options);
UE_LOG(LogPixelStreamingVCam, Log, TEXT("PixelStreaming set with ComposureRenderTarget"));
}
else
{
UE_LOG(LogPixelStreamingVCam, Warning, TEXT("PixelStreaming Composure usage was requested, but the specified ComposureOutputProvider has no FinalOutputRenderTarget set"));
}
}
else
{
TWeakPtr<FSceneViewport> SceneViewport = WeakThisPtr->GetTargetSceneViewport();
if (TSharedPtr<FSceneViewport> PinnedSceneViewport = SceneViewport.Pin())
{
MediaCapture->CaptureSceneViewport(PinnedSceneViewport, Options);
UE_LOG(LogPixelStreamingVCam, Log, TEXT("PixelStreaming set to capture scene viewport."));
}
}
}
void FVCamPixelStreamingSessionLogic::SetupCustomInputHandling(UVCamPixelStreamingSession* This)
{
if (This->GetUMGWidget())
{
TSharedPtr<SVirtualWindow> InputWindow;
// If we are rendering from a ComposureOutputProvider, we need to get the InputWindow from that UMG, not the one in the PixelStreamingOutputProvider
if (UVCamOutputComposure* ComposureProvider = Cast<UVCamOutputComposure>(This->GetOtherOutputProviderByIndex(This->FromComposureOutputProviderIndex)))
{
if (UVPFullScreenUserWidget* ComposureUMGWidget = ComposureProvider->GetUMGWidget())
{
const EVPWidgetDisplayType WidgetDisplayType = ComposureUMGWidget->GetDisplayType(This->GetWorld());
if (ensure(UVPFullScreenUserWidget::DoesDisplayTypeUsePostProcessSettings(WidgetDisplayType)))
{
InputWindow = ComposureUMGWidget->GetPostProcessDisplayTypeSettingsFor(WidgetDisplayType)->GetSlateWindow();
UE_LOG(LogPixelStreamingVCam, Log, TEXT("InputChannel callback - Routing input to active viewport with Composure UMG"));
}
}
else
{
UE_LOG(LogPixelStreamingVCam, Warning, TEXT("InputChannel callback - Composure usage was requested, but the specified ComposureOutputProvider has no UMG set"));
}
}
else
{
checkf(UVPFullScreenUserWidget::DoesDisplayTypeUsePostProcessSettings(EVPWidgetDisplayType::PostProcessSceneViewExtension), TEXT("DisplayType not set up correctly in constructor!"));
InputWindow = This->GetUMGWidget()->GetPostProcessDisplayTypeSettingsFor(EVPWidgetDisplayType::PostProcessSceneViewExtension)->GetSlateWindow();
UE_LOG(LogPixelStreamingVCam, Log, TEXT("InputChannel callback - Routing input to active viewport with UMG"));
}
MediaOutput->GetStreamer()->SetTargetWindow(InputWindow);
MediaOutput->GetStreamer()->SetInputHandlerType(EPixelStreamingInputType::RouteToWidget);
}
else
{
MediaOutput->GetStreamer()->SetTargetWindow(This->GetTargetInputWindow());
MediaOutput->GetStreamer()->SetInputHandlerType(EPixelStreamingInputType::RouteToWidget);
UE_LOG(LogPixelStreamingVCam, Log, TEXT("InputChannel callback - Routing input to active viewport"));
}
if (MediaOutput)
{
IPixelStreamingInputModule& PixelStreamingInputModule = IPixelStreamingInputModule::Get();
typedef EPixelStreamingMessageTypes EType;
/*
* ====================
* ARKit Transform
* ====================
*/
FPixelStreamingInputMessage ARKitMessage = FPixelStreamingInputMessage(100, { // 4x4 Transform
EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float, EType::Float,
// Timestamp
EType::Double });
const TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr = This;
const TFunction<void(FMemoryReader)> ARKitHandler = [WeakThisPtr](FMemoryReader Ar)
{
if (!WeakThisPtr.IsValid() || !WeakThisPtr->EnableARKitTracking)
{
return;
}
// The buffer contains the transform matrix stored as 16 floats
FMatrix ARKitMatrix;
for (int32 Row = 0; Row < 4; ++Row)
{
float Col0, Col1, Col2, Col3;
Ar << Col0 << Col1 << Col2 << Col3;
ARKitMatrix.M[Row][0] = Col0;
ARKitMatrix.M[Row][1] = Col1;
ARKitMatrix.M[Row][2] = Col2;
ARKitMatrix.M[Row][3] = Col3;
}
ARKitMatrix.DiagnosticCheckNaN();
// Extract timestamp
double Timestamp;
Ar << Timestamp;
if (const TSharedPtr<FPixelStreamingLiveLinkSource> LiveLinkSource = UVCamPixelStreamingSubsystem::Get()->TryGetLiveLinkSource(WeakThisPtr.Get()))
{
LiveLinkSource->PushTransformForSubject(WeakThisPtr->GetFName(), FTransform(ARKitMatrix), Timestamp);
}
};
FPixelStreamingInputProtocol::ToStreamerProtocol.Add("ARKitTransform", ARKitMessage);
if (TSharedPtr<IPixelStreamingInputHandler> InputHandler = MediaOutput->GetStreamer()->GetInputHandler().Pin())
{
InputHandler->RegisterMessageHandler("ARKitTransform", ARKitHandler);
}
}
else
{
UE_LOG(LogPixelStreamingVCam, Error, TEXT("Failed to setup custom input handling."));
}
}
void FVCamPixelStreamingSessionLogic::OnCaptureStateChanged(TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr)
{
if (!MediaCapture || !MediaOutput || !MediaOutput->IsValid())
{
return;
}
switch (MediaCapture->GetState())
{
case EMediaCaptureState::Capturing:
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Starting media capture and streaming for Pixel Streaming VCam."));
MediaOutput->StartStreaming();
break;
case EMediaCaptureState::Stopped:
if (MediaCapture->WasViewportResized())
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Pixel Streaming VCam capture was stopped due to resize, going to restart capture."));
// If it was stopped and viewport resized we assume resize caused the stop, so try a restart of capture here.
SetupCapture(WeakThisPtr);
}
else
{
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Stopping media capture and streaming for Pixel Streaming VCam."));
MediaOutput->StopStreaming();
}
break;
case EMediaCaptureState::Error:
UE_LOG(LogPixelStreamingVCam, Log, TEXT("Pixel Streaming VCam capture hit an error, capturing will stop."));
break;
default:
break;
}
}
void FVCamPixelStreamingSessionLogic::OnRemoteResolutionChanged(const FIntPoint& RemoteResolution, TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr)
{
// Early out if match remote resolution is not enabled.
if (!ensure(WeakThisPtr.IsValid()) || !WeakThisPtr->bMatchRemoteResolution)
{
return;
}
// Ensure override resolution is being used
if (!WeakThisPtr->bUseOverrideResolution)
{
WeakThisPtr->bUseOverrideResolution = true;
}
// Set the override resolution on the output provider base, this will trigger a resize
WeakThisPtr->OverrideResolution = RemoteResolution;
WeakThisPtr->ReapplyOverrideResolution();
}
void FVCamPixelStreamingSessionLogic::ConditionallySetLiveLinkSubjectToThis(UVCamPixelStreamingSession* This) const
{
UVCamComponent* VCamComponent = This->GetTypedOuter<UVCamComponent>();
if (This->bAutoSetLiveLinkSubject && IsValid(VCamComponent) && This->IsActive())
{
VCamComponent->SetLiveLinkSubobject(This->GetFName());
}
}
}

View File

@@ -0,0 +1,61 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/HitResult.h"
#include "IOutputProviderLogic.h"
#include "UObject/ObjectPtr.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
class UVCamPixelStreamingSession;
class UPixelStreamingMediaCapture;
class UPixelStreamingMediaOutput;
namespace UE::PixelStreamingVCam::Private
{
/** Implements logic for UVCamPixelStreamingSession so it can be loaded on all platforms. */
class FVCamPixelStreamingSessionLogic : public DecoupledOutputProvider::IOutputProviderLogic
{
public:
//~ Begin IOutputProviderLogic Interface
virtual void OnDeinitialize(DecoupledOutputProvider::IOutputProviderEvent& Args) override;
virtual void OnActivate(DecoupledOutputProvider::IOutputProviderEvent& Args) override;
virtual void OnDeactivate(DecoupledOutputProvider::IOutputProviderEvent& Args) override;
virtual void OnAddReferencedObjects(DecoupledOutputProvider::IOutputProviderEvent& Args, FReferenceCollector& Collector) override;
#if WITH_EDITOR
virtual void OnPostEditChangeProperty(DecoupledOutputProvider::IOutputProviderEvent& Args, FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
//~ End IOutputProviderLogic Interface
private:
/** Used to generate unique streamer IDs */
static int NextDefaultStreamerId;
/** Last time viewport was touched. Updated every tick. */
FHitResult LastViewportTouchResult;
/** Whether we overwrote the widget class with the empty widget class; remember: PS needs a widget. */
bool bUsingDummyUMG = false;
/** Cached setting from settings object. */
bool bOldThrottleCPUWhenNotForeground;
TObjectPtr<UPixelStreamingMediaOutput> MediaOutput = nullptr;
TObjectPtr<UPixelStreamingMediaCapture> MediaCapture = nullptr;
void SetupSignallingServer();
void StopSignallingServer();
void SetupCapture(TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr);
void StartCapture(TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr);
void SetupCustomInputHandling(UVCamPixelStreamingSession* This);
void OnCaptureStateChanged(TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr);
void OnRemoteResolutionChanged(const FIntPoint& RemoteResolution, TWeakObjectPtr<UVCamPixelStreamingSession> WeakThisPtr);
/** Sets the owning VCam's live link subject to this the subject created by this session, if this behaviour is enabled. */
void ConditionallySetLiveLinkSubjectToThis(UVCamPixelStreamingSession* This) const;
};
}

View File

@@ -2,14 +2,13 @@
#include "VCamPixelStreamingSubsystem.h"
#include "Engine/Engine.h"
#include "IPixelStreamingEditorModule.h"
#include "BuiltinProviders/VCamPixelStreamingSession.h"
#include "VCamPixelStreamingLiveLink.h"
#include "VCamPixelStreamingSession.h"
#include "Engine/Engine.h"
#include "Features/IModularFeatures.h"
#include "ILiveLinkClient.h"
#include "IPixelStreamingEditorModule.h"
void UVCamPixelStreamingSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{

View File

@@ -1,83 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Output/VCamOutputProviderBase.h"
#include "PixelStreamingMediaCapture.h"
#include "PixelStreamingMediaOutput.h"
#include "PixelStreamingServers.h"
#include "VCamPixelStreamingSession.generated.h"
UCLASS(meta = (DisplayName = "Pixel Streaming Provider"))
class PIXELSTREAMINGVCAM_API UVCamPixelStreamingSession : public UVCamOutputProviderBase
{
GENERATED_BODY()
public:
UVCamPixelStreamingSession();
//~ Begin UVCamOutputProviderBase Interface
virtual void Deinitialize() override;
virtual void OnActivate() override;
virtual void OnDeactivate() override;
virtual void Tick(const float DeltaTime) override;
//~ End UVCamOutputProviderBase Interface
#if WITH_EDITOR
//~ Begin UObject Interface
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
//~ Begin UObject Interface
#endif
// If using the output from a Composure Output Provider, specify it here
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "10"))
int32 FromComposureOutputProviderIndex = INDEX_NONE;
// If true the streamed UE viewport will match the resolution of the remote device.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "11"))
bool bMatchRemoteResolution = true;
// Check this if you wish to control the corresponding CineCamera with transform data received from the LiveLink app
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "12"))
bool EnableARKitTracking = true;
// If not selected, when the editor is not the foreground application, input through the vcam session may seem sluggish or unresponsive.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "13"))
bool PreventEditorIdle = true;
// If true then the Live Link Subject of the owning VCam Component will be set to the subject created by this Output Provider when the Provider is enabled
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "14"))
bool bAutoSetLiveLinkSubject = true;
// Set the name of this stream to be reported to the signalling server. If none is supplied a default will be used. If ids are not unique issues can occur.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Output", meta = (DisplayPriority = "15"))
FString StreamerId;
protected:
UPROPERTY(Transient)
TObjectPtr<UPixelStreamingMediaOutput> MediaOutput = nullptr;
UPROPERTY(Transient)
TObjectPtr<UPixelStreamingMediaCapture> MediaCapture = nullptr;
private:
void SetupSignallingServer();
void StopSignallingServer();
void SetupCapture();
void StartCapture();
void SetupCustomInputHandling();
void OnCaptureStateChanged();
void OnRemoteResolutionChanged(const FIntPoint& RemoteResolution);
/** Sets the owning VCam's live link subject to this the subject created by this session, if this behaviour is enabled. */
void ConditionallySetLiveLinkSubjectToThis() const;
private:
FHitResult LastViewportTouchResult;
bool bUsingDummyUMG = false;
bool bOldThrottleCPUWhenNotForeground;
static int NextDefaultStreamerId;
};

Some files were not shown because too many files have changed in this diff Show More