Files
2025-05-18 13:04:45 +08:00

328 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Input/DisplayClusterMediaInputBase.h"
#include "DisplayClusterMediaHelpers.h"
#include "DisplayClusterMediaLog.h"
#include "IDisplayClusterShaders.h"
#include "MediaPlayer.h"
#include "MediaSource.h"
#include "MediaTexture.h"
#include "TextureResource.h"
#include "RHIUtilities.h"
#include "OpenColorIOColorSpace.h"
#include "OpenColorIORendering.h"
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h"
#include "ScreenPass.h"
#include "Engine/Engine.h"
#include "ShaderParameters/DisplayClusterShaderParameters_Media.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/Package.h"
TAutoConsoleVariable<bool> CVarTempRivermaxCropWorkaround(
TEXT("nDisplay.Media.Rivermax.CropWorkaround"),
true,
TEXT("nDisplay workaround for Rivermax input\n")
TEXT("0 : Disabled\n")
TEXT("1 : Enabled\n"),
ECVF_RenderThreadSafe
);
// Based on the discussion, it looks like the problem is the incoming 2110 textures
// may have up to 3 ExtraPixelsThreshold extra pixels.
TAutoConsoleVariable<int32> CVarTempRivermaxExtraPixelsThreshold(
TEXT("nDisplay.Media.Rivermax.ExtraPixelsThreshold"),
3,
TEXT("nDisplay workaround for Rivermax input\n"),
ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> CVarTempRivermaxExtraPixelsRemove(
TEXT("nDisplay.Media.Rivermax.ExtraPixelsRemove"),
0,
TEXT("nDisplay workaround for Rivermax input\n"),
ECVF_RenderThreadSafe
);
FDisplayClusterMediaInputBase::FDisplayClusterMediaInputBase(
const FString& InMediaId,
const FString& InClusterNodeId,
UMediaSource* InMediaSource
)
: FDisplayClusterMediaBase(InMediaId, InClusterNodeId)
{
checkSlow(InMediaSource);
MediaSource = DuplicateObject(InMediaSource, GetTransientPackage());
checkSlow(MediaSource);
// Instantiate media player
MediaPlayer = NewObject<UMediaPlayer>();
if (MediaPlayer)
{
MediaPlayer->SetLooping(false);
MediaPlayer->PlayOnOpen = false;
// Instantiate media texture
MediaTexture = NewObject<UMediaTexture>();
if (MediaTexture)
{
MediaTexture->NewStyleOutput = true;
MediaTexture->SetRenderMode(UMediaTexture::ERenderMode::JustInTime);
MediaTexture->SetMediaPlayer(MediaPlayer);
MediaTexture->UpdateResource();
}
}
}
void FDisplayClusterMediaInputBase::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(MediaSource);
Collector.AddReferencedObject(MediaPlayer);
Collector.AddReferencedObject(MediaTexture);
}
bool FDisplayClusterMediaInputBase::Play()
{
if (MediaSource && MediaPlayer && MediaTexture)
{
MediaPlayer->PlayOnOpen = true;
MediaPlayer->OnMediaEvent().AddRaw(this, &FDisplayClusterMediaInputBase::OnMediaEvent);
bWasPlayerStarted = MediaPlayer->OpenSource(MediaSource);
static FName RiverMaxPlayerName(TEXT("RivermaxMedia"));
bRunningRivermaxMedia = MediaPlayer->GetPlayerName() == RiverMaxPlayerName;
return bWasPlayerStarted;
}
return false;
}
void FDisplayClusterMediaInputBase::Stop()
{
if (MediaPlayer)
{
bWasPlayerStarted = false;
MediaPlayer->Close();
MediaPlayer->OnMediaEvent().RemoveAll(this);
}
bRunningRivermaxMedia = false;
}
void FDisplayClusterMediaInputBase::OverrideTextureRegions_RenderThread(FIntRect& InOutSrcRect, FIntRect& InOutDstRect) const
{
const FIntPoint SrcSize = InOutSrcRect.Size();
const FIntPoint DstSize = InOutDstRect.Size();
if (SrcSize == DstSize)
{
return;
}
// [Workaround]
// Based on the discussion, it looks like the problem is the incoming 2110 textures
// may have up to 3 ExtraPixelsThreshold extra pixels.
// If this is the only difference, we just copy the required subregion.
if (bRunningRivermaxMedia && CVarTempRivermaxCropWorkaround.GetValueOnRenderThread())
{
const int32 ExtraPixelsThreshold = CVarTempRivermaxExtraPixelsThreshold.GetValueOnRenderThread();
// Crop if required
if (SrcSize.Y == DstSize.Y
&& SrcSize.X >= DstSize.X
&& (SrcSize.X - DstSize.X) <= ExtraPixelsThreshold)
{
// Use Dest size
InOutSrcRect.Max.X = InOutSrcRect.Min.X + DstSize.X;
return;
}
// By default we always remove extra pixels from the right side.
const int32 ExtraPixelsRemove = CVarTempRivermaxExtraPixelsRemove.GetValueOnRenderThread();
InOutSrcRect.Max.X -= ExtraPixelsRemove;
}
}
void FDisplayClusterMediaInputBase::ImportMediaData_RenderThread(FRHICommandListImmediate& RHICmdList, const FMediaInputTextureInfo& TextureInfo)
{
UE_LOG(LogDisplayClusterMedia, Verbose, TEXT("MediaInput '%s': importing texture on RT frame '%llu'..."), *GetMediaId(), GFrameCounterRenderThread);
// Render media texture
MediaTexture->JustInTimeRender();
FRHITexture* SrcTexture = MediaTexture->GetResource() ? MediaTexture->GetResource()->GetTextureRHI() : nullptr;
FRHITexture* const DstTexture = TextureInfo.Texture;
if (!SrcTexture || !DstTexture)
{
UE_LOG(LogDisplayClusterMedia, Warning, TEXT("MediaInput '%s': wrong texture on RT frame '%llu'..."), *GetMediaId(), GFrameCounterRenderThread);
return;
}
// [Temp workaround]
// There is an extra pixel issue in Rivermax. Allow to work around it.
FIntRect SrcRect{ { 0, 0 }, SrcTexture->GetDesc().Extent };
FIntRect DstRect{ TextureInfo.Region };
OverrideTextureRegions_RenderThread(SrcRect, DstRect);
// Process import
if (IsLateOCIO())
{
ImportMediaDataOCIO_RenderThread(RHICmdList, SrcTexture, SrcRect, DstTexture, DstRect, TextureInfo.OCIOPassResources);
}
else
{
ImportMediaDataDirect_RenderThread(RHICmdList, SrcTexture, SrcRect, DstTexture, DstRect);
}
}
void FDisplayClusterMediaInputBase::ImportMediaDataDirect_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* SrcTexture, const FIntRect& SrcRect, FRHITexture* DstTexture, const FIntRect& DstRect)
{
if (!SrcTexture || !DstTexture)
{
return;
}
const bool bSrcSrgb = EnumHasAnyFlags(SrcTexture->GetFlags(), TexCreate_SRGB);
const bool bDstSrgb = EnumHasAnyFlags(DstTexture->GetFlags(), TexCreate_SRGB);
const bool bSameSrgb = (bSrcSrgb == bDstSrgb);
const bool bSameFormat = (SrcTexture->GetDesc().Format == DstTexture->GetDesc().Format);
const bool bSameSize = (SrcRect.Size() == DstRect.Size());
const bool bCanCopy = (bSameFormat && bSameSize && bSameSrgb);
// Based on the texture properties, copy it directly or resample
if (bCanCopy)
{
FRHICopyTextureInfo CopyInfo;
CopyInfo.SourcePosition = FIntVector(SrcRect.Min.X, SrcRect.Min.Y, 0);
CopyInfo.DestPosition = FIntVector(DstRect.Min.X, DstRect.Min.Y, 0);
CopyInfo.Size = FIntVector(DstRect.Size().X, DstRect.Size().Y, 0);
TransitionAndCopyTexture(RHICmdList, SrcTexture, DstTexture, CopyInfo);
}
else
{
DisplayClusterMediaHelpers::ResampleTexture_RenderThread(RHICmdList, SrcTexture, DstTexture, SrcRect, DstRect);
}
}
void FDisplayClusterMediaInputBase::ImportMediaDataOCIO_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* InSrcTexture, const FIntRect& InSrcRect, FRHITexture* InDstTexture, const FIntRect& InDstRect, const FOpenColorIORenderPassResources& OCIOResources)
{
FRDGBuilder GraphBuilder(RHICmdList);
// Register RHI textures for further processing
FRDGTextureRef SrcTexture = RegisterExternalTexture(GraphBuilder, InSrcTexture, TEXT("DC.MediaTextureSrc"));
FRDGTextureRef DstTexture = RegisterExternalTexture(GraphBuilder, InDstTexture, TEXT("DC.MediaTextureDst"));
// A helper reference to the actual input texture
FRDGTextureRef InputTexture = SrcTexture;
// Is PQ-decode pass required?
if (IsTransferPQ())
{
// An intermediate texture to process PQ decoding and OCIO in linear space
FRDGTextureDesc InterimTextureDesc = FRDGTextureDesc::Create2D(InDstRect.Size(), PF_FloatRGBA, FClearValueBinding::Black, TexCreate_ShaderResource | TexCreate_RenderTargetable);
FRDGTextureRef TextureLinear = GraphBuilder.CreateTexture(InterimTextureDesc, TEXT("DC.MediaTextureTempLinear"));
// Add PQ-decode pass
FDisplayClusterShaderParameters_MediaPQ Parameters{ SrcTexture, InSrcRect, TextureLinear, FIntRect{ FIntPoint::ZeroValue, InDstRect.Size() }};
IDisplayClusterShaders::Get().AddPQToLinearPass(GraphBuilder, Parameters);
// Use this texture in the OCIO pass
InputTexture = TextureLinear;
}
// Now apply OCIO and store to the destination
{
FOpenColorIORendering::AddPass_RenderThread(
GraphBuilder,
FScreenPassViewInfo(),
GEngine->GetDefaultWorldFeatureLevel(),
FScreenPassTexture(InputTexture),
FScreenPassRenderTarget(DstTexture, InDstRect, ERenderTargetLoadAction::EClear),
OCIOResources,
1.0f,
EOpenColorIOTransformAlpha::None
);
}
GraphBuilder.Execute();
}
void FDisplayClusterMediaInputBase::OnMediaEvent(EMediaEvent MediaEvent)
{
switch (MediaEvent)
{
/** The player started connecting to the media source. */
case EMediaEvent::MediaConnecting:
UE_LOG(LogDisplayClusterMedia, Log, TEXT("Media event for '%s': Connection"), *GetMediaId());
break;
/** A new media source has been opened. */
case EMediaEvent::MediaOpened:
UE_LOG(LogDisplayClusterMedia, Log, TEXT("Media event for '%s': Opened"), *GetMediaId());
break;
/** The current media source has been closed. */
case EMediaEvent::MediaClosed:
UE_LOG(LogDisplayClusterMedia, Log, TEXT("Media event for '%s': Closed"), *GetMediaId());
OnPlayerClosed();
break;
/** A media source failed to open. */
case EMediaEvent::MediaOpenFailed:
UE_LOG(LogDisplayClusterMedia, Log, TEXT("Media event for '%s': OpenFailed"), *GetMediaId());
break;
default:
UE_LOG(LogDisplayClusterMedia, Log, TEXT("Media event for '%s': %d"), *GetMediaId(), static_cast<int32>(MediaEvent));
break;
}
}
bool FDisplayClusterMediaInputBase::StartPlayer()
{
const bool bIsPlaying = MediaPlayer->OpenSource(MediaSource);
if (bIsPlaying)
{
UE_LOG(LogDisplayClusterMedia, Log, TEXT("Started playing media: %s"), *GetMediaId());
}
else
{
UE_LOG(LogDisplayClusterMedia, Warning, TEXT("Couldn't start playing media: %s"), *GetMediaId());
}
return bIsPlaying;
}
void FDisplayClusterMediaInputBase::OnPlayerClosed()
{
if (MediaPlayer && bWasPlayerStarted)
{
constexpr double Interval = 1.0;
const double CurrentTime = FPlatformTime::Seconds();
if (CurrentTime - LastRestartTimestamp > Interval)
{
UE_LOG(LogDisplayClusterMedia, Log, TEXT("MediaPlayer '%s' is in error, restarting it."), *GetMediaId());
StartPlayer();
LastRestartTimestamp = CurrentTime;
}
}
}