概述
代码版本5.3,目前讨论PIE状态下的,能打断点
https://www.yuque.com/chenweilin-tryw7/gbfomp/ygwycvgmwps8niby?singleDoc# 《UE 渲染入门 v2》
FViewport::Draw
void UEditorEngine::Tick( float DeltaSeconds, bool bIdleMode )
{
// 。。。
if( FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
{
for (auto ContextIt = WorldList.CreateIterator(); ContextIt; ++ContextIt)
{
FWorldContext &PieContext = *ContextIt;
if (PieContext.WorldType != EWorldType::PIE)
{
continue;
}
PlayWorld = PieContext.World();
GameViewport = PieContext.GameViewport;
// Render playworld. This needs to happen after the other viewports for screenshots to work correctly in PIE.
if (PlayWorld && GameViewport && !bIsSimulatingInEditor)
{
// Use the PlayWorld as the GWorld, because who knows what will happen in the Tick.
UWorld* OldGWorld = SetPlayInEditorWorld( PlayWorld );
// Render everything.
GameViewport->LayoutPlayers();
check(GameViewport->Viewport);
GameViewport->Viewport->Draw();
// Pop the world
RestoreEditorWorld( OldGWorld );
}
}
}
// 。。。
}
在Tick中,如果这一帧允许昂贵的任务,引擎就会遍历所有PIE World
GameViewport = PieContext.GameViewport;
GameViewport->Viewport->Draw();
然后调用Draw
UPROPERTY()
TObjectPtr<class UGameViewportClient> GameViewport;
/** The platform-specific viewport which this viewport client is attached to. */
FViewport* Viewport;
void FViewport::Draw( bool bShouldPresent /*= true */)
{
SCOPED_NAMED_EVENT(FViewport_Draw, FColor::Red);
UWorld* World = GetClient()->GetWorld();
// Ignore reentrant draw calls, since we can only redraw one viewport at a time.
static bool bReentrant = false; // 避免重入绘制
if(!bReentrant)
{
// 。。。
}
}
void FViewport::Draw( bool bShouldPresent /*= true */)
{
SCOPED_NAMED_EVENT(FViewport_Draw, FColor::Red);
UWorld* World = GetClient()->GetWorld();
// Ignore reentrant draw calls, since we can only redraw one viewport at a time.
static bool bReentrant = false;
if(!bReentrant)
{
// See what screenshot related features are required
static const auto CVarDumpFrames = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.BufferVisualizationDumpFrames"));
GIsHighResScreenshot = GIsHighResScreenshot || bTakeHighResScreenShot;
bool bAnyScreenshotsRequired = FScreenshotRequest::IsScreenshotRequested() || GIsHighResScreenshot || GIsDumpingMovie;
bool bBufferVisualizationDumpingRequired = bAnyScreenshotsRequired && CVarDumpFrames && CVarDumpFrames->GetValueOnGameThread();
// if this is a game viewport, and game rendering is disabled, then we don't want to actually draw anything
if ( World && World->IsGameWorld() && !bIsGameRenderingEnabled)
{
// 不绘制 viewport,但是仍然需要更新关卡流
// since we aren't drawing the viewport, we still need to update streaming
World->UpdateLevelStreaming();
}
else
{
if( GIsHighResScreenshot ) // 是高清截图
{
// 准备参数
const bool bShowUI = false;
const bool bAddFilenameSuffix = GetHighResScreenshotConfig().FilenameOverride.IsEmpty();
const bool bHDRScreenshot = GetSceneHDREnabled();
// 配置保存路径,并且设置 bIsScreenshotRequested = true
FScreenshotRequest::RequestScreenshot( FString(), bShowUI, bAddFilenameSuffix, bHDRScreenshot);
// 进入高清截图
HighResScreenshot();
}
else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired) // 有任何截图请求 && 游戏线程 r.BufferVisualizationDumpFrames == 1 (变量在上面)
{
// request the screenshot early so we have the name setup that BufferVisualization can dump it's content
const bool bShowUI = false;
const bool bAddFilenameSuffix = true;
// 配置截图保存路径
FScreenshotRequest::RequestScreenshot( FString(), bShowUI, bAddFilenameSuffix );
}
// 屏幕尺寸 > 0
if( SizeX > 0 && SizeY > 0 )
{
// 准备参数 垂直同步相关
static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VSync"));
bool bLockToVsync = CVar->GetValueOnGameThread() != 0;
ULocalPlayer* Player = (GEngine && World) ? GEngine->GetFirstGamePlayer(World) : NULL;
if ( Player )
{
bLockToVsync |= (Player && Player->PlayerController && Player->PlayerController->bCinematicMode);
}
// 渲染线程执行 Viewport->BeginRenderFrame(RHICmdList);
EnqueueBeginRenderFrame(bShouldPresent);
// Calculate gamethread time (excluding idle time)
// 一些时间计算
{
// 。。。
}
UWorld* ViewportWorld = ViewportClient->GetWorld();
// 准备 FCanvas 画布,然后调用Draw准备一些数据丢进命令列表,最后 Flush_GameThread 绘制
FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
{
ViewportClient->Draw(this, &Canvas);
}
Canvas.Flush_GameThread(); // 刷新游戏画布
// 事件,截图相关,设置垂直同步
UGameViewportClient::OnViewportRendered().Broadcast(this);
ViewportClient->ProcessScreenShots(this);
SetRequiresVsync(bLockToVsync);
// 和上面 EnqueueBeginRenderFrame 对应,结束绘制
EnqueueEndRenderFrame(bLockToVsync, bShouldPresent);
GInputLatencyTimer.GameThreadTrigger = false;
}
}
// 渲染后,摄像机和电影的一些逻辑略
// 。。。
}
}
可以看到
if ( World && World->IsGameWorld() && !bIsGameRenderingEnabled)
{
}
else
{
}
如果有绘制,会走下面的逻辑
if( GIsHighResScreenshot )
{
// 。。。
HighResScreenshot();
}
else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired)
{
}
if( SizeX > 0 && SizeY > 0 )
{
// 。。。
FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
{
ViewportClient->Draw(this, &Canvas);
}
Canvas.Flush_GameThread();
// 。。。
}
核心逻辑是
FCanvas Canvas; // 准备画布
ViewportClient->Draw(this, &Canvas); // 把viewport画到画布上面去,这里是准备一些,绘制的数据,命令等塞进命令列表中。
Canvas.Flush_GameThread(); // 还需要调用更新,才能真正刷新画布。
另外高清截图分支,看 HighResScreenshot 也是一样的,也能找到类似的逻辑,所以可以从这里开始。
FViewport::HighResScreenshot
准备变量
void FViewport::HighResScreenshot()
{
if (!ViewportClient->GetEngineShowFlags())
{
return;
}
// We need to cache this as FScreenshotRequest is a global and the filename is
// cleared out before we use it below
const FString CachedScreenshotName = FScreenshotRequest::GetFilename();
// 设置HDR,SizeXY, 然后渲染线程 Resource->InitResource(RHICmdList);,这里的 DummyViewport 是 FRenderResource的子类
FDummyViewport* DummyViewport = new FDummyViewport(ViewportClient);
DummyViewport->SetupHDR(GetDisplayColorGamut(), GetDisplayOutputFormat(), GetSceneHDREnabled());
DummyViewport->SizeX = (GScreenshotResolutionX > 0) ? GScreenshotResolutionX : SizeX;
DummyViewport->SizeY = (GScreenshotResolutionY > 0) ? GScreenshotResolutionY : SizeY;
BeginInitResource(DummyViewport);
// 自己看英文 SetHighResScreenshotMask SetMotionBlur
ViewportClient->GetEngineShowFlags()->SetHighResScreenshotMask(GetHighResScreenshotConfig().bMaskEnabled);
ViewportClient->GetEngineShowFlags()->SetMotionBlur(false);
// 一些控制台参数
// Forcing 128-bit rendering pipeline
static IConsoleVariable* SceneColorFormatVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.SceneColorFormat"));
static IConsoleVariable* PostColorFormatVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PostProcessingColorFormat"));
static IConsoleVariable* ForceLODVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ForceLOD"));
check(SceneColorFormatVar && PostColorFormatVar);
const int32 OldSceneColorFormat = SceneColorFormatVar->GetInt();
const int32 OldPostColorFormat = PostColorFormatVar->GetInt();
const int32 OldForceLOD = ForceLODVar ? ForceLODVar->GetInt() : -1;
// 是否强制使用128位渲染管线
if (GetHighResScreenshotConfig().bForce128BitRendering)
{
SceneColorFormatVar->Set(5, ECVF_SetByCode);
PostColorFormatVar->Set(1, ECVF_SetByCode);
}
if (ForceLODVar)
{
// Force highest LOD
ForceLODVar->Set(0, ECVF_SetByCode);
}
// Render the requested number of frames (at least once)
static const auto HighResScreenshotDelay = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HighResScreenshotDelay"));
const uint32 DefaultScreenshotDelay = 4;
uint32 FrameDelay = HighResScreenshotDelay ? FMath::Max(HighResScreenshotDelay->GetValueOnGameThread(), 1) : DefaultScreenshotDelay;
开头缓存了一些截图保存路径,然后准备了一些变量。
- 包括 FDummyViewport,是否HDR,渲染的XY大小,设置完让渲染线程初始化资源
- 包括 SceneColorFormat,PostColorFormat,ForceLOD,bForce128BitRendering,
- HighResScreenshotDelay,DefaultScreenshotDelay,FrameDelay 等
什么意思自己看英文
// End the frame that was started before HighResScreenshot() was called. Pass nullptr for the viewport because there's no need to
// call EndRenderFrame on the real viewport, as BeginRenderFrame hasn't been called yet.
HighResScreenshotEndFrame(nullptr);
GIsHighResScreenshot = false;
重置状态,
HighResScreenshot() 之前开始的帧结束掉,没有实际需要绘制的viewport,所以传了一个nullptr
然后设置 正在高清截图为false
根据帧数延迟渲染
// Perform run-up.
while (FrameDelay)
{
HighResScreenshotBeginFrame(DummyViewport);
if (FrameDelay == 1)
{
GIsHighResScreenshot = true;
}
FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();
// Draw the debug canvas 调试画布
DummyViewport->GetDebugCanvas()->SetAllowedModes(FCanvas::Allow_DeleteOnRender);
DummyViewport->GetDebugCanvas()->Flush_GameThread(true);
HighResScreenshotEndFrame(DummyViewport); // 提交到渲染队列
FlushRenderingCommands();
--FrameDelay;
}
可以看到这里有上面提到的结构
FCanvas Canvas 创建画布
ViewportClient->Draw 绘制
Canvas.Flush_GameThread(); 刷新游戏线程
同时也注意到这个包在了 begin end 里面
HighResScreenshotBeginFrame(DummyViewport);
HighResScreenshotEndFrame(DummyViewport);
我们暂时先不管这里是如何渲染的,往下看
这里可以明显看出来,他在设置一些OLD的值
恢复一些旧变量的值
我们暂时先不管这里是如何渲染的,往下看
这里可以明显看出来,他在设置一些OLD的值
ViewportClient->GetEngineShowFlags()->SetHighResScreenshotMask(MaskShowFlagBackup);
ViewportClient->GetEngineShowFlags()->MotionBlur = MotionBlurShowFlagBackup;
bool bIsScreenshotSaved = ViewportClient->ProcessScreenShots(DummyViewport);
SceneColorFormatVar->Set(OldSceneColorFormat, ECVF_SetByCode);
PostColorFormatVar->Set(OldPostColorFormat, ECVF_SetByCode);
if (ForceLODVar)
{
ForceLODVar->Set(OldForceLOD, ECVF_SetByCode);
}
说明这些变量在截图过程中可能被改掉了。
结束
释放资源
刷新渲染命令,这里面可以看到fence,在等待gpu完成任务
BeginReleaseResource(DummyViewport);
FlushRenderingCommands();
delete DummyViewport;
// once the screenshot is done we disable the feature to get only one frame
GIsHighResScreenshot = false;
bTakeHighResScreenShot = false;
// Notification of a successful screenshot
// 弹框,然用户选择保存
if ((GIsEditor || !IsFullscreen()) && !GIsAutomationTesting && bIsScreenshotSaved)
{
auto Message = NSLOCTEXT("UnrealClient", "HighResScreenshotSavedAs", "High resolution screenshot saved as");
FNotificationInfo Info(Message);
Info.bFireAndForget = true;
Info.ExpireDuration = 5.0f;
Info.bUseSuccessFailIcons = false;
Info.bUseLargeFont = false;
const FString HyperLinkText = FPaths::ConvertRelativePathToFull(CachedScreenshotName);
Info.Hyperlink = FSimpleDelegate::CreateStatic([](FString SourceFilePath)
{
FPlatformProcess::ExploreFolder(*(FPaths::GetPath(SourceFilePath)));
}, HyperLinkText);
Info.HyperlinkText = FText::FromString(HyperLinkText);
FSlateNotificationManager::Get().AddNotification(Info);
UE_LOG(LogClient, Log, TEXT("%s %s"), *Message.ToString(), *HyperLinkText);
}
// Start a new frame for the real viewport. Same as above, pass nullptr because BeginRenderFrame will be called
// after this function returns.
HighResScreenshotBeginFrame(nullptr);
}
FCanvas
在gank这个之前要了解一下canvas
FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();
/**
* Constructor.
*/
ENGINE_API FCanvas(FRenderTarget* InRenderTarget, FHitProxyConsumer* InHitProxyConsumer, UWorld* InWorld, ERHIFeatureLevel::Type InFeatureLevel, ECanvasDrawMode DrawMode = CDM_DeferDrawing, float InDPIScale = 1.0f);
看构造函数,
需要一个 render target,
一个命中代理消费者这里是空,比如处理鼠标点击命中
World,
渲染Level(其实就是渲染支持的特性的级别)
里面的局部数据
/**
* Encapsulates the canvas state.
*/
class FCanvas
{
public:
class FCanvasSortElement
{
public:
bool operator==(const FCanvasSortElement& Other) const { return DepthSortKey == Other.DepthSortKey; }
int32 DepthSortKey;
TArray<class FCanvasBaseRenderItem*> RenderBatchArray;
};
TArray<FCanvasSortElement> SortedElements; // 从后往前排序的 Batched canvas elements
TMap<int32,int32> SortedElementLookupMap; // Map from sortkey to array index of SortedElements for faster lookup of existing entries
int32 LastElementIndex; // 最后一个元素的索引 Store index of last Element off to avoid semi expensive Find()
ENGINE_API FCanvasSortElement& GetSortElement(int32 DepthSortKey); // Get the sort element for the given sort key. Allocates a new entry if one does not exist
};
Canvas理解为画布吧。
里面有一个 TArray
FCanvasSortElement 又是一个 Array。FCanvasBaseRenderItem
有这么多类都继承他
class FMeshMaterialRenderItem : public FCanvasBaseRenderItem {};
class FMeshMaterialRenderItem2 : public FCanvasBaseRenderItem {};
class FCanvasBatchedElementRenderItem : public FCanvasBaseRenderItem {};
class FCanvasTileRendererItem : public FCanvasBaseRenderItem {};
class FCanvasTriangleRendererItem : public FCanvasBaseRenderItem {};
基类要求重写get和渲染方法
/**
* Base interface for canvas items which can be batched for rendering
*/
class FCanvasBaseRenderItem
{
public:
virtual ~FCanvasBaseRenderItem()
{}
virtual bool Render_RenderThread(FCanvasRenderContext& RenderContext, FMeshPassProcessorRenderState& DrawRenderState, const FCanvas* Canvas) = 0;
virtual bool Render_GameThread(const FCanvas* Canvas, FCanvasRenderThreadScope& RenderScope) = 0;
virtual class FCanvasBatchedElementRenderItem* GetCanvasBatchedElementRenderItem() { return NULL; }
virtual class FCanvasTileRendererItem* GetCanvasTileRendererItem() { return NULL; }
virtual class FCanvasTriangleRendererItem* GetCanvasTriangleRendererItem() { return NULL; }
};
比如我们看看三角形,如果打断点一般会出现瓦片的那个类。
class FCanvasTriangleRendererItem : public FCanvasBaseRenderItem
{
class FRenderData
{
// ...
struct FTriangleInst
{
FCanvasUVTri Tri;
FHitProxyId HitProxyId;
};
TArray<FTriangleInst> Triangles;
};
TSharedPtr<FRenderData> Data;
}
FCanvasUVTri 在点进去,就可以明显看到我们写DX的时候都会定义一个缓冲区,然后传递一些数据过去的那个结构。我们的canvas理解到这,有了一个大体认识,然后来看draw
/** Simple 2d triangle with UVs */
USTRUCT(BlueprintType)
struct FCanvasUVTri
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FVector2D V0_Pos;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FVector2D V0_UV;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FLinearColor V0_Color;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FVector2D V1_Pos;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FVector2D V1_UV;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FLinearColor V1_Color;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FVector2D V2_Pos;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FVector2D V2_UV;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
FLinearColor V2_Color;
};
FEditorViewportClient::Draw
FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();
根据断点,我们跑到这里了。开头做了一些变量的缓存设置,包括viewport和 time,world
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
FViewport* ViewportBackup = Viewport;
Viewport = InViewport ? InViewport : Viewport;
UWorld* World = GetWorld();
FGameTime Time;
if (!World || (GetScene() != World->Scene) || UseAppTime())
{
Time = FGameTime::GetTimeSinceAppStart();
}
else
{
Time = World->GetTime();
}
// Early out if we are changing maps in editor as there is no reason to render the scene and it may not even be valid (For unsaved maps)
if (World && World->IsPreparingMapChange())
{
return;
}
// 。。。
}
然后继续设置一些变量 bStereoRendering 中文是立体渲染,其实就是VR
然后是 DebugCanvas 的一些设置,OverrideShowFlagsFunc 的一个函数钩子。
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
// 。。。
// Allow HMD to modify the view later, just before rendering
const bool bStereoRendering = GEngine->IsStereoscopic3D( InViewport );
FCanvas* DebugCanvas = Viewport->GetDebugCanvas();
if (DebugCanvas)
{
DebugCanvas->SetScaledToRenderTarget(bStereoRendering);
DebugCanvas->SetStereoRendering(bStereoRendering);
}
Canvas->SetScaledToRenderTarget(bStereoRendering);
Canvas->SetStereoRendering(bStereoRendering);
FEngineShowFlags UseEngineShowFlags = EngineShowFlags;
if (OverrideShowFlagsFunc)
{
OverrideShowFlagsFunc(UseEngineShowFlags);
}
// 。。。
}
FSceneViewFamilyContext & FSceneView
后面一大段,主要解决两个点
- FSceneViewFamilyContext ViewFamily 构造了一个这个对象,然后对他设置一堆参数
-
然后通过这个对象,通过 CalcSceneView 创建一个 FSceneView
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas) { // 。。。 // Setup a FSceneViewFamily/FSceneView for the viewport. FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues( Canvas->GetRenderTarget(), GetScene(), UseEngineShowFlags) .SetTime(Time) .SetRealtimeUpdate( IsRealtime() && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() ) .SetViewModeParam( ViewModeParam, ViewModeParamName ) ); // 。。。 // 省略了一堆 ViewFamily 属性的设置 // 。。。 FSceneView* View = nullptr; // Stereo rendering const bool bStereoDeviceActive = bStereoRendering && GEngine->StereoRenderingDevice.IsValid(); int32 NumViews = bStereoRendering ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering) : 1; for( int StereoViewIndex = 0; StereoViewIndex < NumViews; ++StereoViewIndex ) { View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE); // 。。。 } // 。。。
所以首先了解一下 FSceneViewFamilyContext
FSceneViewFamilyContext
/**
* A view family which deletes its views when it goes out of scope.
*/
class FSceneViewFamilyContext : public FSceneViewFamily
{
public:
/** Initialization constructor. */
FSceneViewFamilyContext( const ConstructionValues& CVS)
: FSceneViewFamily(CVS)
{}
/** Destructor. */
ENGINE_API virtual ~FSceneViewFamilyContext();
};
/**
* A set of views into a scene which only have different view transforms and owner actors.
*/
class FSceneViewFamily {};
A set of views into a scene which only have different view transforms and owner actors
场景中的一组视图,仅仅view transforms 和 owner actors 不同
class FSceneViewFamily
{
public:
struct ConstructionValues
{
ENGINE_API ConstructionValues(
const FRenderTarget* InRenderTarget,
FSceneInterface* InScene,
const FEngineShowFlags& InEngineShowFlags
);
/** The views which make up the family. */
const FRenderTarget* RenderTarget;
/** The render target which the views are being rendered to. */
FSceneInterface* Scene;
// 。。。
};
// 组成这个视图组的视图
TArray<const FSceneView*> Views;
// 视图模式枚举,比如DX中我们会描述把三角形处理为线框,面,显然为basecolor,的那个枚举
EViewModeIndex ViewMode;
// RT
const FRenderTarget* RenderTarget;
大概看看,类内有一个协助给构造函数传递参数的结构,里面有RT, Scene对象等。
成员函数 TArray<const FSceneView*> Views; 表示这个视图组下的所有视图,
EViewModeIndex 视图的渲染模式,
RT指针
FRenderTarget 是什么呀
/**
* A render target.
*/
class FRenderTarget
{
public:
FRenderTarget() {};
virtual ~FRenderTarget() {};
ENGINE_API virtual const FTextureRHIRef& GetRenderTargetTexture() const; // 返回 RenderTargetTextureRHI,可以看到整个类只有这一个属性,都在描述它
ENGINE_API virtual FUnorderedAccessViewRHIRef GetRenderTargetUAV() const; // 新构造一个FUnorderedAccessViewRHIRef(),然后返回
// 返回一个RDG纹理,RDG是 UE5的一个render pass 管理系统,这里pass是渲染层的意思
ENGINE_API virtual FRDGTextureRef GetRenderTargetTexture(FRDGBuilder& GraphBuilder) const;
// 获取 RT 的一些属性,比如大小,gama,色域,是否HDR等
virtual FIntPoint GetSizeXY() const = 0;
ENGINE_API virtual float GetDisplayGamma() const;
virtual EDisplayColorGamut GetDisplayColorGamut() const { return EDisplayColorGamut::sRGB_D65; }
virtual EDisplayOutputFormat GetDisplayOutputFormat() const { return EDisplayOutputFormat::SDR_sRGB; }
virtual bool GetSceneHDREnabled() const { return false; }
//
virtual void ProcessToggleFreezeCommand() {};
virtual bool HasToggleFreezeCommand() { return false; };
// 读取像素到 xxx
ENGINE_API bool ReadPixels(TArray< FColor >& OutImageData,FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0) );
ENGINE_API bool ReadPixelsPtr(FColor* OutImageBytes, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX), FIntRect InSrcRect = FIntRect(0, 0, 0, 0));
ENGINE_API bool ReadFloat16Pixels(TArray<FFloat16Color>& OutputBuffer,ECubeFace CubeFace=CubeFace_PosX);
ENGINE_API bool ReadLinearColorPixels(TArray<FLinearColor>& OutputBuffer, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0));
ENGINE_API bool ReadLinearColorPixelsPtr(FLinearColor* OutImageBytes, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0));
virtual FRHIGPUMask GetGPUMask(FRHICommandListImmediate& RHICmdList) const { return FRHIGPUMask::GPU0(); }
protected:
FTextureRHIRef RenderTargetTextureRHI;
bool ReadFloat16Pixels(FFloat16Color* OutImageBytes,ECubeFace CubeFace=CubeFace_PosX);
};
缩减一下注释,可以看到类内只有
FTextureRHIRef RenderTargetTextureRHI; 这一属性,整个类都在描述他。
又
using FTextureRHIRef = TRefCountPtr<FRHITexture>;
总之就是一种描述资源的
什么资源
看这个枚举,找几个单词眼熟的 RRT_VertexShader,RRT_PixelShader,RRT_Texture2D,RRT_GPUFence
/** An enumeration of the different RHI reference types. */
enum ERHIResourceType : uint8
{
RRT_None,
RRT_SamplerState,
RRT_RasterizerState,
RRT_DepthStencilState,
RRT_BlendState,
RRT_VertexDeclaration,
RRT_VertexShader,
RRT_MeshShader,
RRT_AmplificationShader,
RRT_PixelShader,
RRT_GeometryShader,
RRT_RayTracingShader,
RRT_ComputeShader,
RRT_GraphicsPipelineState,
RRT_ComputePipelineState,
RRT_RayTracingPipelineState,
RRT_BoundShaderState,
RRT_UniformBufferLayout,
RRT_UniformBuffer,
RRT_Buffer,
RRT_Texture,
// @todo: texture type unification - remove these
RRT_Texture2D,
RRT_Texture2DArray,
RRT_Texture3D,
RRT_TextureCube,
// @todo: texture type unification - remove these
RRT_TextureReference,
RRT_TimestampCalibrationQuery,
RRT_GPUFence,
RRT_RenderQuery,
RRT_RenderQueryPool,
RRT_ComputeFence,
RRT_Viewport,
RRT_UnorderedAccessView,
RRT_ShaderResourceView,
RRT_RayTracingAccelerationStructure,
RRT_StagingBuffer,
RRT_CustomPresent,
RRT_ShaderLibrary,
RRT_PipelineBinaryLibrary,
RRT_Num
};
回到 FSceneViewFamily
class FSceneViewFamily
{
public:
// 组成这个视图组的视图
TArray<const FSceneView*> Views;
// 视图模式枚举,比如DX中我们会描述把三角形处理为线框,面,显然为basecolor,的那个枚举
EViewModeIndex ViewMode;
// RT
const FRenderTarget* RenderTarget;
FEngineShowFlags EngineShowFlags; // 标志位
FGameTime Time; // 时间
// 计数
uint32 FrameNumber;
uint64 FrameCounter = 0;
bool bAdditionalViewFamily; // 当前视图组是否是附加的
bool bRealtimeUpdate; // 是否实时更新
bool bDeferClear; // 是否延迟清除后缓冲区
bool bResolveScene; // 是否把场景渲染的结果复制给RT
bool bMultiGPUForkAndJoin; // 是否使用相同的GPU Mask渲染
bool bForceCopyCrossGPU = false; // 强制把GPU中复制到一个持久资源
bool bIsMultipleViewFamily = false; // 是否是单帧中的多个视图组之一。会影响遮挡逻辑
bool bIsFirstViewInMultipleViewFamily = true; // 是否多视图组的第一个视图
bool bIsSceneTexturesInitialized = false;
bool bIsViewFamilyInfo = false; // 是否是视图组的信息?
ESceneCaptureSource SceneCaptureSource; // 场景捕获的来源,例如 ESceneCaptureSource::SCS_SceneColorHDR 以 SceneColor (HDR) in RGB, Inv Opacity in A 输出颜色
ESceneCaptureCompositeMode SceneCaptureCompositeMode; // 场景捕捉的合成方式,覆盖,叠加,合成
bool bThumbnailRendering = false; // 是否用于缩略图渲染
bool bWorldIsPaused;
bool bIsHDR;
bool bRequireMultiView; // 是否为 scenecolor and depth 分配多视图
float GammaCorrection; // 是否使用gama矫正值
FExposureSettings ExposureSettings; // 相机设置的曝光设置
TArray<TSharedRef<class ISceneViewExtension, ESPMode::ThreadSafe> > ViewExtensions; // 渲染线程中修改视图参数的扩展
FDisplayInternalsData DisplayInternalsData; // 调试用的一个面板内容,控制台修改 0 1
/**
* Secondary view fraction to support High DPI monitor still with same primary screen percentage range for temporal
* upscale to test content consistently in editor no mater of the HighDPI scale.
*/
float SecondaryViewFraction;
ESecondaryScreenPercentageMethod SecondaryScreenPercentageMethod;
int8 LandscapeLODOverride; // 覆盖视口landscape LOD 的标志
bool bCurrentlyBeingEdited; // 场景是否可编辑
bool bOverrideVirtualTextureThrottle; // 是否禁用虚拟纹理的标志
int32 VirtualTextureFeedbackFactor;
bool bDrawBaseInfo; // 是否应绘制基本附件体积
bool bNullifyWorldSpacePosition; // 是否把着色器世界空间的位置强设为0
FString ProfileDescription; // Unreal Insights 分析的视图系列的可选描述
float* ProfileSceneRenderTime; // 场景渲染的时间追踪
ENGINE_API ERHIFeatureLevel::Type GetFeatureLevel() const; // 获取RHI的特性级别
EShaderPlatform GetShaderPlatform() const { return GShaderPlatformForFeatureLevel[GetFeatureLevel()]; } // 获取着色器的平台
FORCEINLINE EDebugViewShaderMode GetDebugViewShaderMode() const { return DVSM_None; }
FORCEINLINE int32 GetViewModeParam() const { return -1; }
FORCEINLINE FName GetViewModeParamName() const { return NAME_None; }
FORCEINLINE bool UseDebugViewVSDSHS() const { return false; }
FORCEINLINE bool UseDebugViewPS() const { return false; }
ENGINE_API bool SupportsScreenPercentage() const; // 返回的视图,是否支持屏幕的百分比
FORCEINLINE bool AllowTranslucencyAfterDOF() const { return bAllowTranslucencyAfterDOF; } // 是否运行景深之后渲染半透明的物体
ORCEINLINE bool AllowStandardTranslucencySeparated() const { return bAllowStandardTranslucencySeparated; } // 允许标准半透明分离?
FORCEINLINE const ISceneViewFamilyScreenPercentage* GetScreenPercentageInterface() const { return ScreenPercentageInterface; } // 屏幕的百分比接口
FORCEINLINE void SetScreenPercentageInterface(ISceneViewFamilyScreenPercentage* InScreenPercentageInterface);
// 扩展数据
template<typename TExtensionData> TExtensionData* GetExtentionData()
template<typename TExtensionData> TExtensionData* GetOrCreateExtentionData()
FORCEINLINE void SetTemporalUpscalerInterface(UE::Renderer::Private::ITemporalUpscaler* InTemporalUpscalerInterface)
FORCEINLINE const UE::Renderer::Private::ITemporalUpscaler* GetTemporalUpscalerInterface() const
FORCEINLINE void SetSecondarySpatialUpscalerInterface(ISpatialUpscaler* InSpatialUpscalerInterface)
FORCEINLINE const ISpatialUpscaler* GetSecondarySpatialUpscalerInterface() const
void SetSceneRenderer(ISceneRenderer* NewSceneRenderer) { SceneRenderer = NewSceneRenderer; }
ISceneRenderer* GetSceneRenderer() const { check(SceneRenderer); return SceneRenderer; }
ISceneRenderer* SceneRenderer; // The scene renderer that is rendering this view family. This is only initialized in the rendering thread's copies of the FSceneViewFamily.
ISceneViewFamilyScreenPercentage* ScreenPercentageInterface; /** Interface to handle screen percentage of the views of the family. */
/** Renderer private interfaces, automatically have same lifetime as FSceneViewFamily. */ // 采样器接口
UE::Renderer::Private::ITemporalUpscaler* TemporalUpscalerInterface;
ISpatialUpscaler* PrimarySpatialUpscalerInterface;
ISpatialUpscaler* SecondarySpatialUpscalerInterface;
bool bAllowTranslucencyAfterDOF;
bool bAllowStandardTranslucencySeparated;
bool bIsInFocus = true;
过一下变量,大概这么个意思,后面根据这些参数构造成SceneView
CalcSceneView
View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);
注意这里先是 FLevelEditorViewportClient::CalcSceneView
FSceneView* FLevelEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
bWasControlledByOtherViewport = false; // 是否被其他viewport控制
// set all other matching viewports to my location, if the LOD locking is enabled,
// unless another viewport already set me this frame (otherwise they fight)
// 是否锁定LOD
if (GEditor->bEnableLODLocking)
{
for (FLevelEditorViewportClient* ViewportClient : GEditor->GetLevelViewportClients()) // 拿到关卡编辑器视口客户端
{
//only change camera for a viewport that is looking at the same scene
if (ViewportClient == NULL || GetScene() != ViewportClient->GetScene())
{
continue;
}
// go over all other level viewports
if (ViewportClient->Viewport && ViewportClient != this)
{
// force camera of same-typed viewports
if (ViewportClient->GetViewportType() == GetViewportType()) // 关卡编辑器,上面一排有一个可以显示出正顶侧三视图,主视口位置动了,同步更新其他的视口。这里的set属性的意思
{
ViewportClient->SetViewLocation( GetViewLocation() );
ViewportClient->SetViewRotation( GetViewRotation() );
ViewportClient->SetOrthoZoom( GetOrthoZoom() );
// don't let this other viewport update itself in its own CalcSceneView
ViewportClient->bWasControlledByOtherViewport = true;
}
// when we are LOD locking, ortho views get their camera position from this view, so make sure it redraws
else if (IsPerspective() && !ViewportClient->IsPerspective()) // 当前是透视,其他不是
{
// don't let this other viewport update itself in its own CalcSceneView
ViewportClient->bWasControlledByOtherViewport = true;
}
}
// if the above code determined that this viewport has changed, delay the update unless
// an update is already in the pipe
if (ViewportClient->bWasControlledByOtherViewport && ViewportClient->TimeForForceRedraw == 0.0)
{
ViewportClient->TimeForForceRedraw = FPlatformTime::Seconds() + 0.9 + FMath::FRand() * 0.2; // 延迟更新视口
}
}
}
// FSceneView 是场景空间到2D屏幕的一个投影
FSceneView* View = FEditorViewportClient::CalcSceneView(ViewFamily, StereoViewIndex);
View->ViewActor = ActorLocks.GetLock().GetLockedActor(); // 摄像机看着的对象
View->SpriteCategoryVisibility = SpriteCategoryVisibility; // bit array 控制 sprite 可见性
View->bCameraCut = bEditorCameraCut; // 摄像机是否能在编辑器中被切换
View->bHasSelectedComponents = GEditor->GetSelectedComponentCount() > 0; // 关卡编辑器选中物体,物体detail中选中了组件
return View;
}
核心位于最后面
FSceneView* View = FEditorViewportClient::CalcSceneView(ViewFamily, StereoViewIndex);
其他部分
这个函数主要多了一些编辑器的特殊逻辑,比如视口可以点出三视图,多了一些相关的操作
这一大段都是为了构造 FSceneViewInitOptions 这个初始化选项,然后作为构造函数参数给 FSceneView,
new 完再做一些后续设置,最后返回
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
const bool bStereoRendering = StereoViewIndex != INDEX_NONE;
FSceneViewInitOptions ViewInitOptions;
// 。。。
FSceneView* View = new FSceneView(ViewInitOptions);
// 。。。
}
FSceneViewInitOptions
为了构造 FSceneView,我们需要 FSceneViewInitOptions 帮我们传递一些参数,内容大概是这一
挺复杂的
// Construction parameters for a FSceneView
struct FSceneViewInitOptions : public FSceneViewProjectionData
,我们先看父类
// Projection data for a FSceneView
struct FSceneViewProjectionData
{
FVector ViewOrigin; // 视图的原点 /** The view origin. */
FMatrix ViewRotationMatrix; // 世界空间 -》 视口空间 的旋转矩阵 /** Rotation matrix transforming from world space to view space. */
FMatrix ProjectionMatrix; // 投影矩阵 /** UE projection matrix projects such that clip space Z=1 is the near plane, and Z=0 is the infinite far plane. */
protected:
FIntRect ViewRect; //The unconstrained (no aspect ratio bars applied) view rectangle (also unscaled)
FIntRect ConstrainedViewRect; // The constrained view rectangle (identical to UnconstrainedUnscaledViewRect if aspect ratio is not constrained)
public:
void SetViewRectangle(const FIntRect& InViewRect); // set ViewRect = ConstrainedViewRect = InViewRect
void SetConstrainedViewRectangle(const FIntRect& InViewRect); // set ConstrainedViewRect = InViewRect;
bool IsValidViewRectangle() const; // ConstrainedViewRect.Min.X 和 ConstrainedViewRect.Min.Y >= 0 且 ConstrainedViewRect.Width() 和 ConstrainedViewRect.Height() > 0
bool IsPerspectiveProjection() const; // return ProjectionMatrix.M[3][3] < 1.0f;
FMatrix ComputeViewProjectionMatrix() const { return FTranslationMatrix(-ViewOrigin) * ViewRotationMatrix * ProjectionMatrix; } // VP
};
回到这个类
// Construction parameters for a FSceneView
struct FSceneViewInitOptions : public FSceneViewProjectionData
{
const FSceneViewFamily* ViewFamily;
FSceneViewStateInterface* SceneViewStateInterface;
const AActor* ViewActor; // 视图所属的玩家actor
int32 PlayerIndex; // 玩家索引
FViewElementDrawer* ViewElementDrawer; // 提供场景交互接口
// 视图的属性
FLinearColor BackgroundColor;
FLinearColor OverlayColor;
FLinearColor ColorScale;
// 立体渲染(VR)的属性
EStereoscopicPass StereoPass; /** For stereoscopic rendering, whether or not this is a full pass(全部渲染), or a primary(主要渲染通道) / secondary pass(共用渲染通道) */
int32 StereoViewIndex; /** For stereoscopic rendering, a unique index to identify the view across view families */
float WorldToMetersScale; /** Conversion from world units (uu) to meters, so we can scale motion to the world appropriately */
// 视口位置和旋转 /** The view origin and rotation without any stereo offsets applied to it */
FVector ViewLocation;
FRotator ViewRotation;
// 隐藏 和 显示组件的ID集合
TSet<FPrimitiveComponentId> HiddenPrimitives;
TOptional<TSet<FPrimitiveComponentId>> ShowOnlyPrimitives; /** The primitives which are visible for this view. If the array is not empty, all other primitives will be hidden. */
FIntPoint CursorPos; // 鼠标位置 -1,-1 if not setup
float LODDistanceFactor; // LOD距离缩放
float OverrideFarClippingPlaneDistance; // /** If > 0, overrides the view's far clipping plane with a plane at the specified distance. */
FVector OriginOffsetThisFrame; /** World origin offset value. Non-zero only for a single frame when origin is rebased */
bool bInCameraCut; // /** Was there a camera cut this frame? */
bool bUseFieldOfViewForLOD; /** Whether to use FOV when computing mesh LOD. */
// FOV相关 /** Actual field of view and that desired by the camera originally */
float FOV;
float DesiredFOV;
bool bUseFauxOrthoViewPos; /** In case of ortho, generate a fake view position that has a non-zero W component. The view position will be derived based on the view matrix. */
bool bIsSceneCapture; /** Whether this view is being used to render a scene capture. */
bool bIsSceneCaptureCube; /** Whether the scene capture is a cube map (bIsSceneCapture will also be set). */
bool bSceneCaptureUsesRayTracing; /** Whether this view uses ray tracing, for views that are used to render a scene capture. */
bool bIsReflectionCapture; /** Whether this view is being used to render a reflection capture. */
bool bIsPlanarReflection; /** Whether this view is being used to render a planar reflection. */
#if WITH_EDITOR
uint64 EditorViewBitflag; /** default to 0'th view index, which is a bitfield of 1 */
FVector OverrideLODViewOrigin; /** this can be specified for ortho views so that it's min draw distance/LOD parenting etc, is controlled by a perspective viewport */
bool bDisableGameScreenPercentage; /** Whether game screen percentage should be disabled. */
#endif
// ...
};
CalcSceneView
回来
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
const bool bStereoRendering = StereoViewIndex != INDEX_NONE;
FSceneViewInitOptions ViewInitOptions;
// 。。。
FSceneView* View = new FSceneView(ViewInitOptions);
// 。。。
}
熟悉了 FSceneViewInitOptions 后,我们可以一步一步看看这些变量是如何计算出来并设置的
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
const bool bStereoRendering = StereoViewIndex != INDEX_NONE;
FSceneViewInitOptions ViewInitOptions;
// FViewportCameraTransform view Location, Rotation, LookAt, StartLocation,可以看出来这是描述摄像机的
FViewportCameraTransform& ViewTransform = GetViewTransform();
// 类型 比如正交,透视
const ELevelViewportType EffectiveViewportType = GetViewportType();
// Apply view modifiers.
FEditorViewportViewModifierParams ViewModifierParams;
{
// 。。。
}
// 。。。
}
FEditorViewportViewModifierParams
切出去看一下这个结构
/** Parameter struct for editor viewport view modifiers */
struct FEditorViewportViewModifierParams
{
FMinimalViewInfo ViewInfo;
// 后处理混合,输入一个后处理的设置,和混合权重
void AddPostProcessBlend(const FPostProcessSettings& Settings, float Weight)
{
check(PostProcessSettings.Num() == PostProcessBlendWeights.Num());
PostProcessSettings.Add(Settings);
PostProcessBlendWeights.Add(Weight);
}
private:
TArray<FPostProcessSettings> PostProcessSettings;
TArray<float> PostProcessBlendWeights;
friend class FEditorViewportClient;
};
主要是后处理设置和权重,另外就是一个 FMinimalViewInfo ViewInfo; 结构
USTRUCT(BlueprintType)
struct FMinimalViewInfo
{
GENERATED_USTRUCT_BODY()
// 都是摄像机的一些属性,为了篇幅看起来不太多,清晰一点,我把注解去掉了
FVector Location;
FRotator Rotation;
float FOV;
float DesiredFOV;
// 正交模式 宽度,近景,远景层面的距离
float OrthoWidth; /** The desired width (in world units) of the orthographic view (ignored in Perspective mode) */
float OrthoNearClipPlane; /** The near plane distance of the orthographic view (in world units) */
float OrthoFarClipPlane; /** The far plane distance of the orthographic view (in world units) */
// 透视模式 的近景和横纵比
float PerspectiveNearClipPlane; /** near plane distance of the perspective view (in world units). Set to a negative value to use the default global value of GNearClippingPlane */
float AspectRatio; // Aspect Ratio (Width/Height)
// Aspect ratio axis constraint override
TOptional<EAspectRatioAxisConstraint> AspectRatioAxisConstraint;
uint32 bConstrainAspectRatio:1; // If bConstrainAspectRatio is true, black bars will be added if the destination view has a different aspect ratio than this camera requested.
uint32 bUseFieldOfViewForLOD:1; // If true, account for the field of view angle when computing which level of detail to use for meshes.
TEnumAsByte<ECameraProjectionMode::Type> ProjectionMode; // The type of camera,是透视还是正交
// 后处理参数
float PostProcessBlendWeight; /** Indicates if PostProcessSettings should be applied. */
struct FPostProcessSettings PostProcessSettings; /** Post-process settings to use if PostProcessBlendWeight is non-zero. */
//
FVector2D OffCenterProjectionOffset; /** Off-axis / off-center projection offset as proportion of screen dimensions */
TOptional<FTransform> PreviousViewTransform; /** Optional transform to be considered as this view's previous transform */
// ...
};
CalcSceneView
回来
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
// 。。。
// Apply view modifiers.
FEditorViewportViewModifierParams ViewModifierParams;
{
// Location Rotation FOV
ViewModifierParams.ViewInfo.Location = ViewTransform.GetLocation();
ViewModifierParams.ViewInfo.Rotation = ViewTransform.GetRotation();
if (bUseControllingActorViewInfo)
{
ViewModifierParams.ViewInfo.FOV = ControllingActorViewInfo.FOV;
}
else
{
ViewModifierParams.ViewInfo.FOV = ViewFOV;
}
if (bShouldApplyViewModifiers)
{
ViewModifiers.Broadcast(ViewModifierParams);
}
}
// 临时保存一下变量
const FVector ModifiedViewLocation = ViewModifierParams.ViewInfo.Location;
FRotator ModifiedViewRotation = ViewModifierParams.ViewInfo.Rotation;
const float ModifiedViewFOV = ViewModifierParams.ViewInfo.FOV;
if (bUseControllingActorViewInfo)
{
ControllingActorViewInfo.Location = ViewModifierParams.ViewInfo.Location;
ControllingActorViewInfo.Rotation = ViewModifierParams.ViewInfo.Rotation;
ControllingActorViewInfo.FOV = ViewModifierParams.ViewInfo.FOV;
}
// 这一段目的都是为了这个赋值
ViewInitOptions.ViewOrigin = ModifiedViewLocation;
// 。。。
VR相关
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
// 。。。
// Apply head tracking! Note that this won't affect what the editor *thinks* the view location and rotation is, it will
// only affect the rendering of the scene.
if( bStereoRendering && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed() )
{
FQuat CurrentHmdOrientation;
FVector CurrentHmdPosition;
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, CurrentHmdOrientation, CurrentHmdPosition );
const FQuat VisualRotation = ModifiedViewRotation.Quaternion() * CurrentHmdOrientation; // 四元数乘法 等于角度相加
ModifiedViewRotation = VisualRotation.Rotator();
ModifiedViewRotation.Normalize();
}
SetViewRectangle 设置视口矩形,并限制viewport 大小,并且处理调试命令 CVarEditorViewportTest 的影响
FIntPoint ViewportSize = Viewport->GetSizeXY();
// 限制范围
ViewportSize.X = FMath::Max(ViewportSize.X, 1);
ViewportSize.Y = FMath::Max(ViewportSize.Y, 1);
FIntPoint ViewportOffset(0, 0);
// We expect some size to avoid problems with the view rect manipulation
if (ViewportSize.X > 50 && ViewportSize.Y > 50)
{
int32 Value = CVarEditorViewportTest.GetValueOnGameThread(); // 获取一个测试命令变量的值
if (Value)
{
int InsetX = ViewportSize.X / 4;
int InsetY = ViewportSize.Y / 4;
// this allows to test various typical view port situations
// 出于测试目的的数值修改
switch (Value)
{
case 1: ViewportOffset.X += InsetX; ViewportOffset.Y += InsetY; ViewportSize.X -= InsetX * 2; ViewportSize.Y -= InsetY * 2; break;
case 2: ViewportOffset.Y += InsetY; ViewportSize.Y -= InsetY * 2; break;
case 3: ViewportOffset.X += InsetX; ViewportSize.X -= InsetX * 2; break;
case 4: ViewportSize.X /= 2; ViewportSize.Y /= 2; break;
case 5: ViewportSize.X /= 2; ViewportSize.Y /= 2; ViewportOffset.X += ViewportSize.X; break;
case 6: ViewportSize.X /= 2; ViewportSize.Y /= 2; ViewportOffset.Y += ViewportSize.Y; break;
case 7: ViewportSize.X /= 2; ViewportSize.Y /= 2; ViewportOffset.X += ViewportSize.X; ViewportOffset.Y += ViewportSize.Y; break;
}
}
}
// 应用视口矩形
ViewInitOptions.SetViewRectangle(FIntRect(ViewportOffset, ViewportOffset + ViewportSize));
处理宽高比例的约束
// no matter how we are drawn (forced or otherwise), reset our time here
TimeForForceRedraw = 0.0;
// 宽高比的调整
const bool bConstrainAspectRatio = bUseControllingActorViewInfo && ControllingActorViewInfo.bConstrainAspectRatio; // 调整宽度,高度是否要变换
EAspectRatioAxisConstraint AspectRatioAxisConstraint = GetDefault<ULevelEditorViewportSettings>()->AspectRatioAxisConstraint; // 约束宽高比的方式
if (bUseControllingActorViewInfo && ControllingActorAspectRatioAxisConstraint.IsSet())
{
AspectRatioAxisConstraint = ControllingActorAspectRatioAxisConstraint.GetValue();
}
拿到world settings 设置 WorldToMetersScale
AWorldSettings* WorldSettings = nullptr;
if( GetScene() != nullptr && GetScene()->GetWorld() != nullptr )
{
WorldSettings = GetScene()->GetWorld()->GetWorldSettings();
}
if( WorldSettings != nullptr )
{
ViewInitOptions.WorldToMetersScale = WorldSettings->WorldToMeters;
}
计算正交和投影矩阵
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
// 。。。
if (bUseControllingActorViewInfo)
{
// 后面再看
}
else
{
//
if (EffectiveViewportType == LVT_Perspective)
{
// If stereo rendering is enabled, update the size and offset appropriately for this pass
// @todo vreditor: Also need to update certain other use cases of ViewFOV like culling, streaming, etc. (needs accessor)
// VR 先不管,显然我们这里进不来
if( bStereoRendering )
{
// 。。。
}
// Calc view rotation matrix 计算view矩阵
ViewInitOptions.ViewRotationMatrix = CalcViewRotationMatrix(ModifiedViewRotation);
// 。。。
}
else
{
// 。。。
}
// 。。。
}
}
所以我们逻辑首先看的是
ViewInitOptions.ViewRotationMatrix = CalcViewRotationMatrix(ModifiedViewRotation);
FMatrix FEditorViewportClient::CalcViewRotationMatrix(const FRotator& InViewRotation) const
{
const FViewportCameraTransform& ViewTransform = GetViewTransform();
if (bUsingOrbitCamera)
{
// @todo vreditor: Not stereo friendly yet
// 带轨迹的相机, 这么算
return FTranslationMatrix(ViewTransform.GetLocation()) * ViewTransform.ComputeOrbitMatrix();
}
else
{
// Create the view matrix
return FInverseRotationMatrix(InViewRotation);
}
}
看 FInverseRotationMatrix
template<typename T>
FORCEINLINE TInverseRotationMatrix<T>::TInverseRotationMatrix(const TRotator<T>& Rot)
: TMatrix<T>(
TMatrix<T>( // Yaw
TPlane<T>(+FMath::Cos(Rot.Yaw * UE_PI / 180.f), -FMath::Sin(Rot.Yaw * UE_PI / 180.f), 0.0f, 0.0f),
TPlane<T>(+FMath::Sin(Rot.Yaw * UE_PI / 180.f), +FMath::Cos(Rot.Yaw * UE_PI / 180.f), 0.0f, 0.0f),
TPlane<T>(0.0f, 0.0f, 1.0f, 0.0f),
TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f)) *
TMatrix<T>( // Pitch
TPlane<T>(+FMath::Cos(Rot.Pitch * UE_PI / 180.f), 0.0f, -FMath::Sin(Rot.Pitch * UE_PI / 180.f), 0.0f),
TPlane<T>(0.0f, 1.0f, 0.0f, 0.0f),
TPlane<T>(+FMath::Sin(Rot.Pitch * UE_PI / 180.f), 0.0f, +FMath::Cos(Rot.Pitch * UE_PI / 180.f), 0.0f),
TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f)) *
TMatrix<T>( // Roll
TPlane<T>(1.0f, 0.0f, 0.0f, 0.0f),
TPlane<T>(0.0f, +FMath::Cos(Rot.Roll * UE_PI / 180.f), +FMath::Sin(Rot.Roll * UE_PI / 180.f), 0.0f),
TPlane<T>(0.0f, -FMath::Sin(Rot.Roll * UE_PI / 180.f), +FMath::Cos(Rot.Roll * UE_PI / 180.f), 0.0f),
TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f))
)
{ }
假设绕X, Y, Z 三个轴旋转的角度为 Yaw, Row,Pitch。View矩阵为
回来
if (bUseControllingActorViewInfo)
{
// 。。。
}
else
{
//
if (EffectiveViewportType == LVT_Perspective)
{
// 。。。
// Calc view rotation matrix
ViewInitOptions.ViewRotationMatrix = CalcViewRotationMatrix(ModifiedViewRotation);
// Rotate view 90 degrees 乘一个单位矩阵?
ViewInitOptions.ViewRotationMatrix = ViewInitOptions.ViewRotationMatrix * FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
if( bStereoRendering ) // VR 先不看
{
// 。。。
}
else
{
const float MinZ = GetNearClipPlane();
const float MaxZ = MinZ;
// Avoid zero ViewFOV's which cause divide by zero's in projection matrix
const float MatrixFOV = FMath::Max(0.001f, ModifiedViewFOV) * (float)PI / 360.0f; // 弧度的一个角
if (bConstrainAspectRatio) // 是否约束屏幕的宽高比
{
if ((bool)ERHIZBuffer::IsInverted) // 近景是否大于远景,那么API就在使用反向的Z缓冲区
{
ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix( // 算投影矩阵
MatrixFOV,
MatrixFOV,
1.0f,
AspectRatio,
MinZ,
MaxZ
);
}
else
{
ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix( // 算投影矩阵
MatrixFOV,
MatrixFOV,
1.0f,
AspectRatio,
MinZ,
MaxZ
);
}
}
else
{
float XAxisMultiplier;
float YAxisMultiplier;
// X 轴为1 还是 Y轴为1
if (((ViewportSize.X > ViewportSize.Y) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) || (AspectRatioAxisConstraint == AspectRatio_MaintainXFOV))
{
//if the viewport is wider than it is tall
XAxisMultiplier = 1.0f;
YAxisMultiplier = ViewportSize.X / (float)ViewportSize.Y;
}
else
{
//if the viewport is taller than it is wide
XAxisMultiplier = ViewportSize.Y / (float)ViewportSize.X;
YAxisMultiplier = 1.0f;
}
if ((bool)ERHIZBuffer::IsInverted) // 近景是否大于远景,那么API就在使用反向的Z缓冲区
{
ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix( // 投影矩阵
MatrixFOV,
MatrixFOV,
XAxisMultiplier,
YAxisMultiplier,
MinZ,
MaxZ
);
}
else
{
ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix( // 投影矩阵
MatrixFOV,
MatrixFOV,
XAxisMultiplier,
YAxisMultiplier,
MinZ,
MaxZ
);
}
}
}
}
else
{
// 。。。
}
if (bConstrainAspectRatio)
{
ViewInitOptions.SetConstrainedViewRectangle(Viewport->CalculateViewExtents(AspectRatio, ViewInitOptions.GetViewRect()));
}
}
ERHIZBuffer::IsInverted 反向Z是为啥呢?
是为了提升深度精度的利用率。传统的是近是0,远是1。反向就是近是1,远是0。什么原理不知道。┑( ̄Д  ̄)┍
这个点进去就是我们推的投影矩阵公式
template<typename T>
FORCEINLINE TReversedZPerspectiveMatrix<T>::TReversedZPerspectiveMatrix(T HalfFOVX, T HalfFOVY, T MultFOVX, T MultFOVY, T MinZ, T MaxZ)
: TMatrix<T>(
TPlane<T>(MultFOVX / FMath::Tan(HalfFOVX), 0.0f, 0.0f, 0.0f),
TPlane<T>(0.0f, MultFOVY / FMath::Tan(HalfFOVY), 0.0f, 0.0f),
TPlane<T>(0.0f, 0.0f, ((MinZ == MaxZ) ? 0.0f : MinZ / (MinZ - MaxZ)), 1.0f),
TPlane<T>(0.0f, 0.0f, ((MinZ == MaxZ) ? MinZ : -MaxZ * MinZ / (MinZ - MaxZ)), 0.0f)
)
{ }
另外else里面是正交情况下的计算
if (bUseControllingActorViewInfo)
{
// 。。。
}
else
{
//
if (EffectiveViewportType == LVT_Perspective)
{
// 。。。
}
else
{
// 正交
static_assert((bool)ERHIZBuffer::IsInverted, "Check all the Rotation Matrix transformations!");
FMatrix::FReal ZScale = 0.5f / UE_OLD_HALF_WORLD_MAX; // LWC_TODO: WORLD_MAX misuse?
FMatrix::FReal ZOffset = UE_OLD_HALF_WORLD_MAX;
//The divisor for the matrix needs to match the translation code.
// 为了屏幕尺寸发生变化的时候,它也发生变化
const float Zoom = GetOrthoUnitsPerPixel(Viewport);
float OrthoWidth = Zoom * ViewportSize.X / 2.0f;
float OrthoHeight = Zoom * ViewportSize.Y / 2.0f;
if (EffectiveViewportType == LVT_OrthoXY)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(1, 0, 0, 0),
FPlane(0, -1, 0, 0),
FPlane(0, 0, -1, 0),
FPlane(0, 0, 0, 1));
}
else if (EffectiveViewportType == LVT_OrthoXZ)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(1, 0, 0, 0),
FPlane(0, 0, -1, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
}
else if (EffectiveViewportType == LVT_OrthoYZ)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
}
else if (EffectiveViewportType == LVT_OrthoNegativeXY)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(-1, 0, 0, 0),
FPlane(0, -1, 0, 0),
FPlane(0, 0, 1, 0),
FPlane(0, 0, 0, 1));
}
else if (EffectiveViewportType == LVT_OrthoNegativeXZ)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(-1, 0, 0, 0),
FPlane(0, 0, 1, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
}
else if (EffectiveViewportType == LVT_OrthoNegativeYZ)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(0, 0, -1, 0),
FPlane(-1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
}
else if (EffectiveViewportType == LVT_OrthoFreelook)
{
ViewInitOptions.ViewRotationMatrix = FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
}
else
{
// Unknown viewport type
check(false);
}
ViewInitOptions.ProjectionMatrix = FReversedZOrthoMatrix(
OrthoWidth,
OrthoHeight,
ZScale,
ZOffset
);
}
if (bConstrainAspectRatio)
{
ViewInitOptions.SetConstrainedViewRectangle(Viewport->CalculateViewExtents(AspectRatio, ViewInitOptions.GetViewRect()));
}
}
最开始,还漏一个
if (bUseControllingActorViewInfo)
{
// @todo vreditor: Not stereo friendly yet
ViewInitOptions.ViewRotationMatrix = FInverseRotationMatrix(ModifiedViewRotation) * FMatrix(
FPlane(0, 0, 1, 0),
FPlane(1, 0, 0, 0),
FPlane(0, 1, 0, 0),
FPlane(0, 0, 0, 1));
FMinimalViewInfo::CalculateProjectionMatrixGivenView(ControllingActorViewInfo, AspectRatioAxisConstraint, Viewport, /*inout*/ ViewInitOptions);
}
使用控制角色的view info
void FMinimalViewInfo::CalculateProjectionMatrixGivenView(const FMinimalViewInfo& ViewInfo, TEnumAsByte<enum EAspectRatioAxisConstraint> AspectRatioAxisConstraint, FViewport* Viewport, FSceneViewProjectionData& InOutProjectionData)
{
FIntRect ViewExtents = Viewport->CalculateViewExtents(ViewInfo.AspectRatio, InOutProjectionData.GetViewRect());
CalculateProjectionMatrixGivenViewRectangle(ViewInfo, AspectRatioAxisConstraint, ViewExtents, InOutProjectionData);
}
void FMinimalViewInfo::CalculateProjectionMatrixGivenViewRectangle(const FMinimalViewInfo& ViewInfo, TEnumAsByte<enum EAspectRatioAxisConstraint> AspectRatioAxisConstraint, const FIntRect& ConstrainedViewRectangle, FSceneViewProjectionData& InOutProjectionData)
{
// 。。。
}
CalcSceneView
回到这继续
FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
const bool bStereoRendering = StereoViewIndex != INDEX_NONE;
FSceneViewInitOptions ViewInitOptions;
// 。。。
// 保护性设置,避免非法值
if (!ViewInitOptions.IsValidViewRectangle())
{
// Zero sized rects are invalid, so fake to 1x1 to avoid asserts later on
ViewInitOptions.SetViewRectangle(FIntRect(0, 0, 1, 1));
}
// 。。。
}
VR相关可以先掠过
// Allocate our stereo view state on demand, so that only viewports that actually use stereo features have one
const int32 ViewStateIndex = (StereoViewIndex != INDEX_NONE) ? StereoViewIndex : 0;
if (bStereoRendering)
{
if (StereoViewStates.Num() <= ViewStateIndex)
{
StereoViewStates.SetNum(ViewStateIndex + 1);
}
if (StereoViewStates[ViewStateIndex].GetReference() == nullptr)
{
FSceneInterface* Scene = GetScene();
StereoViewStates[ViewStateIndex].Allocate(Scene ? Scene->GetFeatureLevel() : GMaxRHIFeatureLevel);
}
}
一些变量的赋值
ViewInitOptions.ViewFamily = ViewFamily;
ViewInitOptions.SceneViewStateInterface = ( (ViewStateIndex == 0) ? ViewState.GetReference() : StereoViewStates[ViewStateIndex].GetReference() );
ViewInitOptions.StereoViewIndex = StereoViewIndex;
ViewInitOptions.ViewElementDrawer = this;
ViewInitOptions.BackgroundColor = GetBackgroundColor();
ViewInitOptions.EditorViewBitflag = (uint64)1 << ViewIndex, // send the bit for this view - each actor will check it's visibility bits against this
// for ortho views to steal perspective view origin
ViewInitOptions.OverrideLODViewOrigin = FVector::ZeroVector;
ViewInitOptions.FOV = ModifiedViewFOV;
if (bUseControllingActorViewInfo)
{
ViewInitOptions.bUseFieldOfViewForLOD = ControllingActorViewInfo.bUseFieldOfViewForLOD;
ViewInitOptions.FOV = ControllingActorViewInfo.FOV;
}
ViewInitOptions.OverrideFarClippingPlaneDistance = FarPlane;
ViewInitOptions.CursorPos = CurrentMousePos;
获取 r.Test.ConstrainedView 这个值
最后 ViewInitOptions.SetConstrainedViewRectangle(ConstrainedViewRect); 修改 FIntRect ConstrainedViewRect; 这个值
干嘛用的在PIE运行敲一下,看下效果就知道了
r.Test.ConstrainedView 是一个控制台变量,用于测试不同的视口矩形配置。这在使用电影/编辑器时可能会发生。它的值可以是以下之一:
0:关闭(默认)
1:将视口的上下部分约束到一定的范围内
2:将视口的左右部分约束到一定的范围内
其他值:对视口进行不同的约束配置
这个变量主要用于开发和测试阶段,以确保在各种视口配置下,游戏的渲染和表现都是正确的。
static const auto CVarVSync = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Test.ConstrainedView"));
int32 Value = CVarVSync->GetValueOnGameThread();
if (Value)
{
// 。。。
ViewInitOptions.SetConstrainedViewRectangle(ConstrainedViewRect);
}
FSceneView* View = new FSceneView(ViewInitOptions);
new FSceneView
然后 FSceneView 终于出现了
然后后续做一些设置
FSceneView* View = new FSceneView(ViewInitOptions);
// ViewLocation 和 ViewRotation
View->ViewLocation = ModifiedViewLocation;
View->ViewRotation = ModifiedViewRotation;
// Color初始化
View->SubduedSelectionOutlineColor = GEngine->GetSubduedSelectionOutlineColor();
const UEditorStyleSettings* EditorStyle = GetDefault<UEditorStyleSettings>();
check(View->AdditionalSelectionOutlineColors.Num() <= UE_ARRAY_COUNT(EditorStyle->AdditionalSelectionColors))
for (int OutlineColorIndex = 0; OutlineColorIndex < View->AdditionalSelectionOutlineColors.Num(); ++OutlineColorIndex)
{
View->AdditionalSelectionOutlineColors[OutlineColorIndex] = EditorStyle->AdditionalSelectionColors[OutlineColorIndex];
}
// 加进视图里面
int32 FamilyIndex = ViewFamily->Views.Add(View);
check(FamilyIndex == View->StereoViewIndex || View->StereoViewIndex == INDEX_NONE);
// 后处理相关
View->StartFinalPostprocessSettings( View->ViewLocation );
if (bUseControllingActorViewInfo)
{
// Pass on the previous view transform of the controlling actor to the view
View->PreviousViewTransform = ControllingActorViewInfo.PreviousViewTransform;
View->OverridePostProcessSettings(ControllingActorViewInfo.PostProcessSettings, ControllingActorViewInfo.PostProcessBlendWeight);
for (int32 ExtraPPBlendIdx = 0; ExtraPPBlendIdx < ControllingActorExtraPostProcessBlends.Num(); ++ExtraPPBlendIdx)
{
FPostProcessSettings const& PPSettings = ControllingActorExtraPostProcessBlends[ExtraPPBlendIdx];
float const Weight = ControllingActorExtraPostProcessBlendWeights[ExtraPPBlendIdx];
View->OverridePostProcessSettings(PPSettings, Weight);
}
}
else
{
OverridePostProcessSettings(*View);
}
if (ViewModifierParams.ViewInfo.PostProcessBlendWeight > 0.f)
{
View->OverridePostProcessSettings(ViewModifierParams.ViewInfo.PostProcessSettings, ViewModifierParams.ViewInfo.PostProcessBlendWeight);
}
const int32 PPNum = FMath::Min(ViewModifierParams.PostProcessSettings.Num(), ViewModifierParams.PostProcessBlendWeights.Num());
for (int32 PPIndex = 0; PPIndex < PPNum; ++PPIndex)
{
const FPostProcessSettings& PPSettings = ViewModifierParams.PostProcessSettings[PPIndex];
const float PPWeight = ViewModifierParams.PostProcessBlendWeights[PPIndex];
View->OverridePostProcessSettings(PPSettings, PPWeight);
}
View->EndFinalPostprocessSettings(ViewInitOptions);
// 允许自定义一些扩展
for (int ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ViewExt++)
{
ViewFamily->ViewExtensions[ViewExt]->SetupView(*ViewFamily, *View);
}
return View;
FEditorViewportClient::Draw
回到这里接着
View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE); 继续看
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
// 。。。
// Setup a FSceneViewFamily/FSceneView for the viewport.
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Canvas->GetRenderTarget(),
GetScene(),
UseEngineShowFlags)
.SetTime(Time)
.SetRealtimeUpdate( IsRealtime() && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
.SetViewModeParam( ViewModeParam, ViewModeParamName ) );
// 。。。
// 省略了一堆 ViewFamily 属性的设置
// 。。。
FSceneView* View = nullptr;
// Stereo rendering
const bool bStereoDeviceActive = bStereoRendering && GEngine->StereoRenderingDevice.IsValid();
int32 NumViews = bStereoRendering ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering) : 1;
for( int StereoViewIndex = 0; StereoViewIndex < NumViews; ++StereoViewIndex )
{
View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);
SetupViewForRendering(ViewFamily,*View); // 一些灯光参数,标志位的设置。另外还有像素调试器,拖拽,音频设置
FSlateRect SafeFrame; // 视口的约束矩形
View->CameraConstrainedViewRect = View->UnscaledViewRect;
// 根据视口大小和DPI缩放算出一个矩形,保证窗口不会超过编辑器
if (CalculateEditorConstrainedViewRect(SafeFrame, Viewport, Canvas->GetDPIScale()))
{
View->CameraConstrainedViewRect = FIntRect(SafeFrame.Left, SafeFrame.Top, SafeFrame.Right, SafeFrame.Bottom);
}
if (World)
{
// 更新上一帧的view info
FWorldCachedViewInfo& WorldViewInfo = World->CachedViewInfoRenderedLastFrame.AddDefaulted_GetRef();
WorldViewInfo.ViewMatrix = View->ViewMatrices.GetViewMatrix();
WorldViewInfo.ProjectionMatrix = View->ViewMatrices.GetProjectionMatrix();
WorldViewInfo.ViewProjectionMatrix = View->ViewMatrices.GetViewProjectionMatrix();
WorldViewInfo.ViewToWorld = View->ViewMatrices.GetInvViewMatrix();
World->LastRenderTime = World->GetTimeSeconds();
}
}
// 。。。
}
根据预览分辨率比例和全局分辨率比例设置视口的屏幕百分比接口,并确保接口已设置。它还处理了在编辑器视口中忽略特定的屏幕百分比设置
// 设置 Screen 百分比 接口
{
// If a screen percentage interface was not set by one of the view extension, then set the legacy one.
// 设置 Screen 百分比 接口
if (ViewFamily.GetScreenPercentageInterface() == nullptr)
{
float GlobalResolutionFraction = 1.0f;
// Apply preview resolution fraction. Supported in stereo for VR Editor Mode only
if ((!bStereoRendering || bInVREditViewMode) &&
SupportsPreviewResolutionFraction() && ViewFamily.SupportsScreenPercentage())
{
if (PreviewResolutionFraction.IsSet() && bIsPreviewingResolutionFraction)
{
GlobalResolutionFraction = PreviewResolutionFraction.GetValue();
}
else
{
GlobalResolutionFraction = GetDefaultPrimaryResolutionFractionTarget();
}
// Force screen percentage's engine show flag to be turned on for preview screen percentage.
ViewFamily.EngineShowFlags.ScreenPercentage = (GlobalResolutionFraction != 1.0);
}
// In editor viewport, we ignore r.ScreenPercentage and FPostProcessSettings::ScreenPercentage by design.
ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(
ViewFamily, GlobalResolutionFraction));
}
check(ViewFamily.GetScreenPercentageInterface() != nullptr);
}
// 约束了宽高比,会把画布先化成黑色的
if (IsAspectRatioConstrained())
{
// Clear the background to black if the aspect ratio is constrained, as the scene view won't write to all pixels.
Canvas->Clear(FLinearColor::Black);
}
BeginRenderingViewFamily
回顾一下
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
// 。。。
// Setup a FSceneViewFamily/FSceneView for the viewport.
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
Canvas->GetRenderTarget(),
GetScene(),
UseEngineShowFlags)
.SetTime(Time)
.SetRealtimeUpdate( IsRealtime() && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
.SetViewModeParam( ViewModeParam, ViewModeParamName ) );
// 。。。
// 省略了一堆 ViewFamily 属性的设置
// 。。。
FSceneView* View = nullptr;
// Stereo rendering
const bool bStereoDeviceActive = bStereoRendering && GEngine->StereoRenderingDevice.IsValid();
int32 NumViews = bStereoRendering ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering) : 1;
for( int StereoViewIndex = 0; StereoViewIndex < NumViews; ++StereoViewIndex )
{
View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);
// 。。。
}
// 。。。
//
GetRendererModule().BeginRenderingViewFamily(Canvas,&ViewFamily);
我们构造
FSceneViewFamilyContext ViewFamily;
然后创建了 FSceneView* View;
并且在创建后做了一些设置。
我们要开始画3D场景了
// Draw the 3D scene
GetRendererModule().BeginRenderingViewFamily(Canvas,&ViewFamily);
void FRendererModule::BeginRenderingViewFamily(FCanvas* Canvas, FSceneViewFamily* ViewFamily)
{
BeginRenderingViewFamilies(Canvas, TArrayView<FSceneViewFamily*>(&ViewFamily, 1));
}
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BeginRenderingViewFamily);
check(Canvas);
// 验证,多个view families的scene必须一样。一个view families可以理解为一个摄像机,一个families里面可以有多个各种的视图
for (FSceneViewFamily* ViewFamily : ViewFamilies)
{
check(ViewFamily);
check(ViewFamily->Scene == ViewFamilies[0]->Scene);
}
FScene* const Scene = ViewFamilies[0]->Scene ? ViewFamilies[0]->Scene->GetRenderScene() : nullptr;
if (Scene)
{
World = Scene->GetWorld();
if (World)
{
// Guarantee that all render proxies are up to date before kicking off a BeginRenderViewFamily.
World->SendAllEndOfFrameUpdates(); // 确保渲染代理都是最新的
GetNaniteVisualizationData().Pick(World);
}
}
SendAllEndOfFrameUpdates
SendAllEndOfFrameUpdates 在渲染开始之前,确保渲染代理都是最新的
/**
* Send all render updates to the rendering thread.
*/
void UWorld::SendAllEndOfFrameUpdates()
{
SCOPED_NAMED_EVENT(UWorld_SendAllEndOfFrameUpdates, FColor::Yellow);
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EndOfFrameUpdates);
CSV_SCOPED_SET_WAIT_STAT(EndOfFrameUpdates);
// Allow systems to complete async work that could introduce additional components to end of frame updates
FWorldDelegates::OnWorldPreSendAllEndOfFrameUpdates.Broadcast(this);
if (!HasEndOfFrameUpdates()) // 确保延迟组件更新到了渲染线程
{
return;
}
// 。。。
}
bool UWorld::HasEndOfFrameUpdates() const
{
return ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num() > 0 || ComponentsThatNeedEndOfFrameUpdate.Num() > 0 || bMaterialParameterCollectionInstanceNeedsDeferredUpdate;
}
其实在tick里面,收集所有world并执行 SendAllEndOfFrameUpdates
已经执行过了,这里面不一定有东西要执行
/////////////////////////////
// Redraw viewports.
{
// Gather worlds that need EOF updates
// This must be done in two steps as the object hash table is locked during ForEachObjectOfClass so any NewObject calls would fail
TArray<UWorld*, TInlineAllocator<4>> WorldsToEOFUpdate;
ForEachObjectOfClass(UWorld::StaticClass(), [&WorldsToEOFUpdate](UObject* WorldObj)
{
UWorld* World = CastChecked<UWorld>(WorldObj);
if (World->HasEndOfFrameUpdates())
{
WorldsToEOFUpdate.Add(World);
}
});
// Make sure deferred component updates have been sent to the rendering thread.
for (UWorld* World : WorldsToEOFUpdate)
{
World->SendAllEndOfFrameUpdates();
}
}
// UpdateSingleViewportClient
/**
* Send all render updates to the rendering thread.
*/
void UWorld::SendAllEndOfFrameUpdates()
{
// 。。。
// 遍历所有需要末尾帧同步的组件列表
// Wait for tasks that are generating data for the render proxies, but are not awaited in any TickFunctions
// E.g., see cloth USkeletalMeshComponent::UpdateClothStateAndSimulate
for (UActorComponent* Component : ComponentsThatNeedPreEndOfFrameSync)
{
if (Component)
{
check(!IsValid(Component) || Component->GetMarkedForPreEndOfFrameSync());
if (IsValid(Component))
{
Component->OnPreEndOfFrameSync(); // 执行
}
FMarkComponentEndOfFrameUpdateState::ClearMarkedForPreEndOfFrameSync(Component); // 清除
}
}
ComponentsThatNeedPreEndOfFrameSync.Reset(); // reset
//If we call SendAllEndOfFrameUpdates during a tick, we must ensure that all marked objects have completed any async work etc before doing the updates.
// 如果这个函数在tick中被调用,那么就对所有组件 需要的 OnEndOfFrameUpdateDuringTick
if (bInTick)
{
SCOPE_CYCLE_COUNTER(STAT_OnEndOfFrameUpdateDuringTick);
//If this proves too slow we can possibly have the mark set a complimentary bit array that marks which components need this call? Or just another component array?
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate)
{
if (Component)
{
Component->OnEndOfFrameUpdateDuringTick();
}
}
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
{
if (Component)
{
Component->OnEndOfFrameUpdateDuringTick();
}
}
}
// Issue a GPU event to wrap GPU work done during SendAllEndOfFrameUpdates, like skin cache updates
// 皮肤缓存的更新,比如蒙皮
FSendAllEndOfFrameUpdates SendAllEndOfFrameUpdates(Scene);
BeginSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
// update all dirty components. 更新所有标记为脏的组件
FGuardValue_Bitfield(bPostTickComponentUpdate, true);
// 收集合并 LocalComponentsThatNeedEndOfFrameUpdate
static TArray<UActorComponent*> LocalComponentsThatNeedEndOfFrameUpdate;
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate_Gather);
check(IsInGameThread() && !LocalComponentsThatNeedEndOfFrameUpdate.Num());
LocalComponentsThatNeedEndOfFrameUpdate.Append(ComponentsThatNeedEndOfFrameUpdate);
}
// 是否有并行的通知事件
// 控制台变量 允许异步渲染线程更新 在游戏线程更新的时候 && 上面收集的组件数量 > 工作线程数量 && 工作线程数量 > 2
const bool IsUsingParallelNotifyEvents = CVarAllowAsyncRenderThreadUpdatesDuringGamethreadUpdates.GetValueOnGameThread() > 0 &&
LocalComponentsThatNeedEndOfFrameUpdate.Num() > FTaskGraphInterface::Get().GetNumWorkerThreads() &&
FTaskGraphInterface::Get().GetNumWorkerThreads() > 2;
跳过几个lambda,用到了再看
if (IsUsingParallelNotifyEvents)
{
#if WITH_EDITOR
FObjectCacheEventSink::BeginQueueNotifyEvents();
#endif
ParallelForWithPreWork(LocalComponentsThatNeedEndOfFrameUpdate.Num(), ParallelWork, GTWork);
#if WITH_EDITOR
// Any remaining events will be flushed with this call
FObjectCacheEventSink::EndQueueNotifyEvents();
#endif
}
else
{
GTWork();
ParallelFor(LocalComponentsThatNeedEndOfFrameUpdate.Num(), ParallelWork); // ParallelFor 用于在多线程中执行相同的任务
}
先看 GTWork
auto GTWork = [this]()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate_ForcedGameThread);
// To avoid any problems in case of reentrancy during the deferred update pass, we gather everything and clears the buffers first
// Reentrancy can occur if a render update need to force wait on an async resource and a progress bar ticks the game-thread during that time.
// 准备一个 这么大的 UActorComponent 数组
TArray< UActorComponent*> DeferredUpdates;
DeferredUpdates.Reserve(ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num());
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
{
if (Component)
{
if (Component->IsRegistered() && !Component->IsTemplate() && IsValid(Component))
{
DeferredUpdates.Add(Component);
}
// 确保标记帧在结束线程
check(!IsValid(Component) || Component->GetMarkedForEndOfFrameUpdateState() == EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread);
// 已经添加进来了,就可以设置为未标记
FMarkComponentEndOfFrameUpdateState::Set(Component, INDEX_NONE, EComponentMarkedForEndOfFrameUpdateState::Unmarked);
}
}
ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Reset();
ComponentsThatNeedEndOfFrameUpdate.Reset();
// We are only regenerating render state here, not components
FStaticMeshComponentBulkReregisterContext ReregisterContext(Scene, DeferredUpdates, EBulkReregister::RenderState);
// 然后对刚刚的array执行 DoDeferredRenderUpdates_Concurrent
for (UActorComponent* Component : DeferredUpdates)
{
Component->DoDeferredRenderUpdates_Concurrent();
}
};
void UActorComponent::DoDeferredRenderUpdates_Concurrent()
{
LLM_SCOPE(ELLMTag::SceneRender);
LLM_SCOPED_TAG_WITH_OBJECT_IN_SET(GetOutermost(), ELLMTagSet::Assets);
checkf(!IsUnreachable(), TEXT("%s"), *GetFullName());
checkf(!IsTemplate(), TEXT("%s"), *GetFullName());
checkf(IsValid(this), TEXT("%s"), *GetFullName());
FScopeCycleCounterUObject ContextScope(this);
FScopeCycleCounterUObject AdditionalScope(STATS ? AdditionalStatObject() : nullptr); // 统计用的
if(!IsRegistered())
{
UE_LOG(LogActorComponent, Log, TEXT("UpdateComponent: (%s) Not registered, Aborting."), *GetPathName());
return;
}
if(bRenderStateDirty) // 组件是否已经把整个状态发送到渲染线程了
{
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentRecreate);
RecreateRenderState_Concurrent();
checkf(!bRenderStateDirty, TEXT("Failed to route CreateRenderState_Concurrent (%s)"), *GetFullName());
}
else // 如果没有
{
SCOPE_CYCLE_COUNTER(STAT_PostTickComponentLW);
// 是否xxx是dirty的,如果是就调用 SendXXX_Concurrent
if(bRenderTransformDirty)
{
// Update the component's transform if the actor has been moved since it was last updated.
SendRenderTransform_Concurrent();
}
if(bRenderDynamicDataDirty)
{
SendRenderDynamicData_Concurrent();
}
if (bRenderInstancesDirty)
{
SendRenderInstanceData_Concurrent();
}
}
}
比如这里
SendRenderTransform_Concurrent
继续往下看,就能找到 CPU 的 Transform 数据是如何传递到 GPU的。
ParallelWork
...
SendRenderTransform_Concurrent
SendRenderTransform_Concurrent
这里是 UActorComponet 的 SendRenderTransform_Concurrent
但是打断点啥的就会发现其实是这个类
他的注释是这样的
/**
* PrimitiveComponents are SceneComponents that contain or generate some sort of geometry, generally to be rendered or used as collision data.
* There are several subclasses for the various types of geometry, but the most common by far are the ShapeComponents (Capsule, Sphere, Box), StaticMeshComponent, and SkeletalMeshComponent.
* ShapeComponents generate geometry that is used for collision detection but are not rendered, while StaticMeshComponents and SkeletalMeshComponents contain pre-built geometry that is rendered, but can also be used for collision detection.
*/
UCLASS(abstract, HideCategories=(Mobility, VirtualTexture), ShowCategories=(PhysicsVolume), MinimalAPI)
class UPrimitiveComponent : public USceneComponent, public INavRelevantInterface, public IInterface_AsyncCompilation, public IPhysicsComponent
{
};
大概就是说这个类是用于渲染和碰撞的。
void UPrimitiveComponent::SendRenderTransform_Concurrent()
{
UpdateBounds(); // 更新物体边界,包围盒,用于碰撞AABB之类的,从 UStaticMeshComponent::UpdateBounds() 开始
// 。。。
}
void UStaticMeshComponent::UpdateBounds()
{
NotifyIfStaticMeshChanged();
Super::UpdateBounds();
}
void UPrimitiveComponent::SendRenderTransform_Concurrent()
{
UpdateBounds();
// If the primitive isn't hidden update its transform.
const bool bDetailModeAllowsRendering = DetailMode <= GetCachedScalabilityCVars().DetailMode; // 细节模式是否允许被渲染
if( bDetailModeAllowsRendering && (ShouldRender() || bCastHiddenShadow || bAffectIndirectLightingWhileHidden || bRayTracingFarField))
{
// Update the scene info's transform for this primitive.
GetWorld()->Scene->UpdatePrimitiveTransform(this);
}
Super::SendRenderTransform_Concurrent();
}
看
GetWorld()->Scene->UpdatePrimitiveTransform(this);
FScene::UpdatePrimitiveTransform
一些时间记录
void FScene::UpdatePrimitiveTransform(UPrimitiveComponent* Primitive)
{
SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveTransformGT);
SCOPED_NAMED_EVENT(FScene_UpdatePrimitiveTransform, FColor::Yellow);
// Save the world transform for next time the primitive is added to the scene
const float WorldTime = GetWorld()->GetTimeSeconds();
float DeltaTime = WorldTime - Primitive->LastSubmitTime;
if (DeltaTime < -0.0001f || Primitive->LastSubmitTime < 0.0001f)
{
// Time was reset?
Primitive->LastSubmitTime = WorldTime;
}
else if (DeltaTime > 0.0001f)
{
// First call for the new frame?
Primitive->LastSubmitTime = WorldTime;
}
// 。。。
}
void FScene::UpdatePrimitiveTransform(UPrimitiveComponent* Primitive)
{
// ...
if (Primitive->SceneProxy) // 这个代理,相当于从 UPrimitiveComponent 里面拷贝了一些数据给渲染线程用
{
}
else // 如果没有就要去加
{
// If the primitive doesn't have a scene info object yet, it must be added from scratch.
AddPrimitive(Primitive);
}
如果有
if (Primitive->SceneProxy)
{
// Check if the primitive needs to recreate its proxy for the transform update.
// 是否创建资源代理来更新transform
if (Primitive->ShouldRecreateProxyOnUpdateTransform()) // return (Mobility != EComponentMobility::Movable);
{
// Re-add the primitive from scratch to recreate the primitive's proxy.
RemovePrimitive(Primitive);
AddPrimitive(Primitive);
}
else
{
// root position
FVector AttachmentRootPosition = Primitive->GetActorPositionForRenderer();
// 构造一个更新参数
struct FPrimitiveUpdateParams
{
FScene* Scene;
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FBoxSphereBounds WorldBounds;
FBoxSphereBounds LocalBounds;
FMatrix LocalToWorld;
TOptional<FTransform> PreviousTransform;
FVector AttachmentRootPosition;
};
FPrimitiveUpdateParams UpdateParams;
UpdateParams.Scene = this;
UpdateParams.PrimitiveSceneProxy = Primitive->SceneProxy;
UpdateParams.WorldBounds = Primitive->Bounds;
UpdateParams.LocalToWorld = Primitive->GetRenderMatrix(); // world matrix
UpdateParams.AttachmentRootPosition = AttachmentRootPosition;
UpdateParams.LocalBounds = Primitive->GetLocalBounds();
UpdateParams.PreviousTransform = FMotionVectorSimulation::Get().GetPreviousTransform(Primitive);
// Help track down primitive with bad bounds way before the it gets to the renderer.
ensureMsgf(!UpdateParams.WorldBounds.BoxExtent.ContainsNaN() && !UpdateParams.WorldBounds.Origin.ContainsNaN() && !FMath::IsNaN(UpdateParams.WorldBounds.SphereRadius) && FMath::IsFinite(UpdateParams.WorldBounds.SphereRadius),
TEXT("NaNs found on Bounds for Primitive %s: Owner: %s, Resource: %s, Level: %s, Origin: %s, BoxExtent: %s, SphereRadius: %f"),
*Primitive->GetName(),
*Primitive->SceneProxy->GetOwnerName().ToString(),
*Primitive->SceneProxy->GetResourceName().ToString(),
*Primitive->SceneProxy->GetLevelName().ToString(),
*UpdateParams.WorldBounds.Origin.ToString(),
*UpdateParams.WorldBounds.BoxExtent.ToString(),
UpdateParams.WorldBounds.SphereRadius
);
bool bPerformUpdate = true;
// Redundant 冗余更新,和dirty是类似的意思
const bool bAllowSkip = GSkipRedundantTransformUpdate && Primitive->SceneProxy->CanSkipRedundantTransformUpdates();
if (bAllowSkip || GWarningOnRedundantTransformUpdate)
{
// 设置为冗余的更新_任何线程,主要检查这些变量值是不是和本身发生了变化,如果没变当然不用更新了
if (Primitive->SceneProxy->WouldSetTransformBeRedundant_AnyThread(
UpdateParams.LocalToWorld,
UpdateParams.WorldBounds,
UpdateParams.LocalBounds,
UpdateParams.AttachmentRootPosition))
{
// 变量和本身比没有变化,而且跳过更新,那就 bPerformUpdate = false;
if (bAllowSkip)
{
// Do not perform the transform update!
bPerformUpdate = false;
}
else
{
// Not skipping, and warnings are enabled.
UE_LOG(LogRenderer, Warning,
TEXT("Redundant UpdatePrimitiveTransform for Primitive %s: Owner: %s, Resource: %s, Level: %s"),
*Primitive->GetName(),
*Primitive->SceneProxy->GetOwnerName().ToString(),
*Primitive->SceneProxy->GetResourceName().ToString(),
*Primitive->SceneProxy->GetLevelName().ToString()
);
}
}
}
// 需要更新,让渲染线程去执行。带着参数过去
if (bPerformUpdate)
{
ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)(
[UpdateParams](FRHICommandListImmediate& RHICmdList)
{
FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread(
UpdateParams.PrimitiveSceneProxy,
UpdateParams.WorldBounds,
UpdateParams.LocalBounds,
UpdateParams.LocalToWorld,
UpdateParams.AttachmentRootPosition,
UpdateParams.PreviousTransform
);
}
);
}
}
}
上面一大堆,就是为了准备一个 UpdateParams
执行 UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread
这个必须要在渲染线程里面执行
void FScene::UpdatePrimitiveTransform_RenderThread(FPrimitiveSceneProxy* PrimitiveSceneProxy, const FBoxSphereBounds& WorldBounds, const FBoxSphereBounds& LocalBounds, const FMatrix& LocalToWorld, const FVector& AttachmentRootPosition, const TOptional<FTransform>& PreviousTransform)
{
check(IsInRenderingThread());
#if VALIDATE_PRIMITIVE_PACKED_INDEX
if (AddedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) != nullptr)
{
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex == INDEX_NONE);
}
else
{
check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
}
check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) == nullptr);
#endif
// 游戏线程带过来的参数,更新到渲染线程的 UpdatedTransforms 里面
UpdatedTransforms.Update(PrimitiveSceneProxy, { WorldBounds, LocalBounds, LocalToWorld, AttachmentRootPosition });
// 上一个transform是有意义的,要重叠一下
if (PreviousTransform.IsSet())
{
OverridenPreviousTransforms.Update(PrimitiveSceneProxy->GetPrimitiveSceneInfo(), PreviousTransform.GetValue().ToMatrixWithScale());
}
}
另外一个分支,允许用代理更新transform
if (Primitive->ShouldRecreateProxyOnUpdateTransform()) // return (Mobility != EComponentMobility::Movable);
{
// Re-add the primitive from scratch to recreate the primitive's proxy.
RemovePrimitive(Primitive);
AddPrimitive(Primitive);
}
会先remove 再 add
void FScene::RemovePrimitive(UPrimitiveComponent* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchRemovePrimitives(MakeArrayView(&Primitive, 1));
}
FScene::RemovePrimitive
void FScene::RemovePrimitive(UPrimitiveComponent* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchRemovePrimitives(MakeArrayView(&Primitive, 1));
}
void FScene::BatchRemovePrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
SCOPE_CYCLE_COUNTER(STAT_RemoveScenePrimitiveGT);
SCOPED_NAMED_EVENT(FScene_RemovePrimitive, FColor::Yellow);
struct FPrimitiveRemoveInfo
{
FPrimitiveSceneInfo* PrimitiveSceneInfo;
FThreadSafeCounter* AttachmentCounter;
};
// 维护 RemoveInfos
TArray<FPrimitiveRemoveInfo, TInlineAllocator<1>> RemoveInfos;
RemoveInfos.Reserve(InPrimitives.Num());
for (UPrimitiveComponent* Primitive : InPrimitives)
{
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->SceneProxy;
if (PrimitiveSceneProxy)
{
FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();
// Disassociate the primitive's scene proxy.
Primitive->SceneProxy = NULL;
RemoveInfos.Add({ PrimitiveSceneInfo, &Primitive->AttachmentCounter });
}
}
// 让渲染线程处理 RemoveInfos
if (RemoveInfos.Num())
{
// Send a command to the rendering thread to remove the primitives from the scene.
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(FRemovePrimitiveCommand)(
[Scene, RemoveInfos = MoveTemp(RemoveInfos)](FRHICommandList&)
{
for (const FPrimitiveRemoveInfo& RemoveInfo : RemoveInfos)
{
RemoveInfo.PrimitiveSceneInfo->Proxy->DestroyRenderThreadResources();
Scene->RemovePrimitiveSceneInfo_RenderThread(RemoveInfo.PrimitiveSceneInfo);
RemoveInfo.AttachmentCounter->Decrement();
}
});
}
}
RemovePrimitiveSceneInfo_RenderThread
void FScene::RemovePrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
check(IsInRenderingThread()); // 这里是渲染线程
if (AddedPrimitiveSceneInfos.Remove(PrimitiveSceneInfo))
{
// 渲染线程移除xxx
check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
UpdatedTransforms.Remove(PrimitiveSceneInfo->Proxy);
UpdatedCustomPrimitiveParams.Remove(PrimitiveSceneInfo->Proxy);
OverridenPreviousTransforms.Remove(PrimitiveSceneInfo);
UpdatedOcclusionBoundsSlacks.Remove(PrimitiveSceneInfo->Proxy);
DistanceFieldSceneDataUpdates.Remove(PrimitiveSceneInfo);
UpdatedAttachmentRoots.Remove(PrimitiveSceneInfo);
{
SCOPED_NAMED_EVENT(FScene_DeletePrimitiveSceneInfo, FColor::Red);
// Delete the PrimitiveSceneInfo on the game thread after the rendering thread has processed its removal.
// This must be done on the game thread because the hit proxy references (and possibly other members) need to be freed on the game thread.
struct DeferDeleteHitProxies : FDeferredCleanupInterface
{
DeferDeleteHitProxies(TArray<TRefCountPtr<HHitProxy>>&& InHitProxies) : HitProxies(MoveTemp(InHitProxies)) {}
TArray<TRefCountPtr<HHitProxy>> HitProxies;
};
// 渲染线程已经移除了,命中代理可能引用了其他成员,需要在游戏线程中释放PrimitiveSceneInfo
BeginCleanup(new DeferDeleteHitProxies(MoveTemp(PrimitiveSceneInfo->HitProxies)));
delete PrimitiveSceneInfo->Proxy;
delete PrimitiveSceneInfo;
}
}
else // 如果删除失败了,RemovedPrimitiveSceneInfos 会添加一个
{
check(PrimitiveSceneInfo->PackedIndex != INDEX_NONE);
check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) == nullptr);
RemovedPrimitiveSceneInfos.FindOrAdd(PrimitiveSceneInfo);
}
}
FScene::AddPrimitive
void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
// If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
if (Primitive->bBulkReregister)
{
return;
}
BatchAddPrimitives(MakeArrayView(&Primitive, 1));
}
void FScene::BatchAddPrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
// ...
struct FCreateRenderThreadParameters
{
FCreateRenderThreadParameters(
FPrimitiveSceneInfo* InPrimitiveSceneInfo,
FPrimitiveSceneProxy* InPrimitiveSceneProxy,
FMatrix InRenderMatrix,
FBoxSphereBounds InWorldBounds,
FVector InAttachmentRootPosition,
FBoxSphereBounds InLocalBounds,
TOptional<FTransform> InPreviousTransform)
: PrimitiveSceneInfo(InPrimitiveSceneInfo), PrimitiveSceneProxy(InPrimitiveSceneProxy), RenderMatrix(InRenderMatrix), WorldBounds(InWorldBounds),
AttachmentRootPosition(InAttachmentRootPosition), LocalBounds(InLocalBounds), PreviousTransform(InPreviousTransform)
{}
FPrimitiveSceneInfo* PrimitiveSceneInfo;
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FMatrix RenderMatrix;
FBoxSphereBounds WorldBounds;
FVector AttachmentRootPosition;
FBoxSphereBounds LocalBounds;
TOptional<FTransform> PreviousTransform;
};
// 目的是构造这个 ParamsList
TArray<FCreateRenderThreadParameters, TInlineAllocator<1>> ParamsList;
ParamsList.Reserve(InPrimitives.Num());
for (UPrimitiveComponent* Primitive : InPrimitives)
{
checkf(!Primitive->IsUnreachable(), TEXT("%s"), *Primitive->GetFullName());
const float WorldTime = GetWorld()->GetTimeSeconds();
// Save the world transform for next time the primitive is added to the scene
// 上一次提交的时间
float DeltaTime = WorldTime - Primitive->LastSubmitTime;
if ( DeltaTime < -0.0001f || Primitive->LastSubmitTime < 0.0001f )
{
// Time was reset?
Primitive->LastSubmitTime = WorldTime;
}
else if ( DeltaTime > 0.0001f )
{
// First call for the new frame?
Primitive->LastSubmitTime = WorldTime;
}
checkf(!Primitive->SceneProxy, TEXT("Primitive has already been added to the scene!"));
// Create the primitive's scene proxy.
// 获取场景的 基源组件
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
Primitive->SceneProxy = PrimitiveSceneProxy;
if(!PrimitiveSceneProxy)
{
// Primitives which don't have a proxy are irrelevant to the scene manager.
continue;
}
// Create the primitive scene info. new 一个 FPrimitiveSceneInfo
FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
// Cache the primitives initial transform.
// 世界矩阵 和 根位置
FMatrix RenderMatrix = Primitive->GetRenderMatrix();
FVector AttachmentRootPosition = Primitive->GetActorPositionForRenderer();
// 加进去
ParamsList.Emplace(
PrimitiveSceneInfo,
PrimitiveSceneProxy,
RenderMatrix,
Primitive->Bounds,
AttachmentRootPosition,
Primitive->GetLocalBounds(),
// If this primitive has a simulated previous transform, ensure that the velocity data for the scene representation is correct.
FMotionVectorSimulation::Get().GetPreviousTransform(Primitive)
);
// Help track down primitive with bad bounds way before the it gets to the Renderer
ensureMsgf(!Primitive->Bounds.ContainsNaN(),
TEXT("Nans found on Bounds for Primitive %s: Origin %s, BoxExtent %s, SphereRadius %f"), *Primitive->GetName(), *Primitive->Bounds.Origin.ToString(), *Primitive->Bounds.BoxExtent.ToString(), Primitive->Bounds.SphereRadius);
INC_DWORD_STAT_BY( STAT_GameToRendererMallocTotal, PrimitiveSceneProxy->GetMemoryFootprint() + PrimitiveSceneInfo->GetMemoryFootprint() );
// Verify the primitive is valid
VerifyProperPIEScene(Primitive, World);
// Increment the attachment counter, the primitive is about to be attached to the scene.
// attachment counter++, 组件准备要附加到场景里面了
Primitive->AttachmentCounter.Increment();
}
}
准备把 ParamsList 丢到渲染线程了
然后再渲染线程set transform
// Create any RenderThreadResources required and send a command to the rendering thread to add the primitive to the scene.
FScene* Scene = this;
ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
[ParamsList = MoveTemp(ParamsList), Scene](FRHICommandListImmediate& RHICmdList)
{
for (const FCreateRenderThreadParameters& Params : ParamsList)
{
FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
FScopeCycleCounter Context(SceneProxy->GetStatId());
SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition);
// Create any RenderThreadResources required.
SceneProxy->CreateRenderThreadResources();
Scene->AddPrimitiveSceneInfo_RenderThread(Params.PrimitiveSceneInfo, Params.PreviousTransform);
}
});
看下 AddPrimitiveSceneInfo_RenderThread。
void FScene::AddPrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo, const TOptional<FTransform>& PreviousTransform)
{
check(IsInRenderingThread());
check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
check(AddedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) == nullptr);
AddedPrimitiveSceneInfos.FindOrAdd(PrimitiveSceneInfo);
if (PreviousTransform.IsSet())
{
OverridenPreviousTransforms.Update(PrimitiveSceneInfo, PreviousTransform.GetValue().ToMatrixWithScale());
}
}
SendAllEndOfFrameUpdates
倒退
/**
* Send all render updates to the rendering thread.
*/
void UWorld::SendAllEndOfFrameUpdates()
{
// 。。
for (UMaterialParameterCollectionInstance* ParameterCollectionInstance : ParameterCollectionInstances)
{
if (ParameterCollectionInstance)
{
ParameterCollectionInstance->DeferredUpdateRenderState(false);
}
}
bMaterialParameterCollectionInstanceNeedsDeferredUpdate = false;
LocalComponentsThatNeedEndOfFrameUpdate.Reset();
EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
}
最后是一些材质相关的更新,然后释放资源,结束
DeferredUpdateRenderState
展开看看这个
void UMaterialParameterCollectionInstance::DeferredUpdateRenderState(bool bRecreateUniformBuffer)
{
checkf(bNeedsRenderStateUpdate || !bRecreateUniformBuffer, TEXT("DeferredUpdateRenderState was told to recreate the uniform buffer, but there's nothing to update"));
if (bNeedsRenderStateUpdate && World.IsValid())
{
// Propagate the new values to the rendering thread
TArray<FVector4f> ParameterData;
GetParameterData(ParameterData);
Resource->GameThread_UpdateContents(Collection.IsValid() ? Collection->StateId : FGuid(), ParameterData, GetFName(), bRecreateUniformBuffer);
}
bNeedsRenderStateUpdate = false;
}
获取材质参数,然后 GameThread_UpdateContents
让渲染线程 Resource->UpdateContents(InGuid, Data, InOwnerName, bRecreateUniformBuffer);
void FMaterialParameterCollectionInstanceResource::GameThread_UpdateContents(const FGuid& InGuid, const TArray<FVector4f>& Data, const FName& InOwnerName, bool bRecreateUniformBuffer)
{
FMaterialParameterCollectionInstanceResource* Resource = this;
ENQUEUE_RENDER_COMMAND(UpdateCollectionCommand)(
[InGuid, Data, InOwnerName, Resource, bRecreateUniformBuffer](FRHICommandListImmediate& RHICmdList)
{
Resource->UpdateContents(InGuid, Data, InOwnerName, bRecreateUniformBuffer);
}
);
}
void FMaterialParameterCollectionInstanceResource::UpdateContents(const FGuid& InId, const TArray<FVector4f>& Data, const FName& InOwnerName, bool bRecreateUniformBuffer)
{
Id = InId;
OwnerName = InOwnerName;
if (InId != FGuid() && Data.Num() > 0)
{
const uint32 NewSize = Data.GetTypeSize() * Data.Num();
check(UniformBufferLayout == nullptr || UniformBufferLayout->Resources.Num() == 0);
if (!bRecreateUniformBuffer && IsValidRef(UniformBuffer))
{
check(NewSize == UniformBufferLayout->ConstantBufferSize);
check(UniformBuffer->GetLayoutPtr() == UniformBufferLayout);
FRHICommandListImmediate::Get().UpdateUniformBuffer(UniformBuffer, Data.GetData());
}
else
{
FRHIUniformBufferLayoutInitializer UniformBufferLayoutInitializer(TEXT("MaterialParameterCollectionInstanceResource"));
UniformBufferLayoutInitializer.ConstantBufferSize = NewSize;
UniformBufferLayoutInitializer.ComputeHash();
UniformBufferLayout = RHICreateUniformBufferLayout(UniformBufferLayoutInitializer); // 层级,用于管理 UniformBuffer,一个简单的new
UniformBuffer = RHICreateUniformBuffer(Data.GetData(), UniformBufferLayout, UniformBuffer_MultiFrame);
}
}
}
UniformBuffer 统一buff,可以简单理解为常量缓冲区
可以先看else里面,不合法的时候重新创建
UniformBufferLayout = RHICreateUniformBufferLayout(UniformBufferLayoutInitializer);
直接new
FORCEINLINE FUniformBufferLayoutRHIRef RHICreateUniformBufferLayout(const FRHIUniformBufferLayoutInitializer& Initializer)
{
LLM_SCOPE_BYNAME(TEXT("RHIMisc/CreateUniformBufferLayout"));
return new FRHIUniformBufferLayout(Initializer);
}
创建统一缓冲区(常量缓冲区)
UniformBuffer = RHICreateUniformBuffer(Data.GetData(), UniformBufferLayout, UniformBuffer_MultiFrame);
FORCEINLINE FUniformBufferRHIRef RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation = EUniformBufferValidation::ValidateResources)
{
LLM_SCOPE_BYNAME(TEXT("RHIMisc/CreateUniformBuffer"));
return GDynamicRHI->RHICreateUniformBuffer(Contents, Layout, Usage, Validation);
}
然后进入不同跨平台的接口
打断点看,默认可能会进DX11
FUniformBufferRHIRef FD3D11DynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FD3D12DynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FOpenGLDynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FVulkanDynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FMetalDynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
上面还有一个
FRHICommandListImmediate::Get().UpdateUniformBuffer(UniformBuffer, Data.GetData());
FORCEINLINE void UpdateUniformBuffer(FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
{
FRHICommandListScopedPipelineGuard ScopedPipeline(*this);
GDynamicRHI->RHIUpdateUniformBuffer(*this, UniformBufferRHI, Contents);
}
然后才到跨平台的接口
void FD3D12DynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FD3D11DynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FMetalDynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FOpenGLDynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FVulkanDynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
SendAllEndOfFrameUpdates
总结一下
/**
* Send all render updates to the rendering thread.
*/
void UWorld::SendAllEndOfFrameUpdates()
函数的目的是,要把所有要渲染的东西加到渲染队列里面了
首先必须是最后一帧
if (!HasEndOfFrameUpdates())
{
return;
}
遍历各种 UActorComponent 做结束帧相关逻辑
// Wait for tasks that are generating data for the render proxies, but are not awaited in any TickFunctions
// E.g., see cloth USkeletalMeshComponent::UpdateClothStateAndSimulate
for (UActorComponent* Component : ComponentsThatNeedPreEndOfFrameSync)
{
if (Component)
{
check(!IsValid(Component) || Component->GetMarkedForPreEndOfFrameSync());
if (IsValid(Component))
{
Component->OnPreEndOfFrameSync();
}
FMarkComponentEndOfFrameUpdateState::ClearMarkedForPreEndOfFrameSync(Component);
}
}
ComponentsThatNeedPreEndOfFrameSync.Reset();
//If we call SendAllEndOfFrameUpdates during a tick, we must ensure that all marked objects have completed any async work etc before doing the updates.
if (bInTick)
{
SCOPE_CYCLE_COUNTER(STAT_OnEndOfFrameUpdateDuringTick);
//If this proves too slow we can possibly have the mark set a complimentary bit array that marks which components need this call? Or just another component array?
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate)
{
if (Component)
{
Component->OnEndOfFrameUpdateDuringTick();
}
}
for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
{
if (Component)
{
Component->OnEndOfFrameUpdateDuringTick();
}
}
}
最后是
BeginSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
包起来一堆代码
static TArray<UActorComponent*> LocalComponentsThatNeedEndOfFrameUpdate; // 本地组件是否需要结束帧的更新
做一些延迟更新相关的,比如transform,其他数据,就是把数据拷贝到渲染线程的代理对象
还有处理材质参数
BeginRenderingViewFamilies
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
TRACE_CPUPROFILER_EVENT_SCOPE(BeginRenderingViewFamily);
check(Canvas);
for (FSceneViewFamily* ViewFamily : ViewFamilies)
{
check(ViewFamily);
check(ViewFamily->Scene == ViewFamilies[0]->Scene);
}
UWorld* World = nullptr;
FScene* const Scene = ViewFamilies[0]->Scene ? ViewFamilies[0]->Scene->GetRenderScene() : nullptr;
if (Scene)
{
World = Scene->GetWorld();
if (World)
{
// Guarantee that all render proxies are up to date before kicking off a BeginRenderViewFamily.
World->SendAllEndOfFrameUpdates();
GetNaniteVisualizationData().Pick(World); // 给nanite手机一些数据用
}
}
// 统计的
ENQUEUE_RENDER_COMMAND(SetRtWaitCriticalPath)(
[](FRHICommandList& RHICmdList)
{
// Rendering is up and running now, so waits are considered part of the RT critical path
FThreadIdleStats::BeginCriticalPath();
});
// 更新统一表达式的缓存
FUniformExpressionCacheAsyncUpdateScope AsyncUpdateScope;
// Vedio random access memery VRAM的全称
// 更新一些参数,用于优化
ENQUEUE_RENDER_COMMAND(UpdateFastVRamConfig)(
[](FRHICommandList& RHICmdList)
{
GFastVRamConfig.Update();
});
// Flush the canvas first.
// 刷新游戏线程,确保绘制已经完成了
Canvas->Flush_GameThread();
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
// 。。。
// FrameNumber 更新
if (Scene)
{
// We allow caching of per-frame, per-scene data
if (ViewFamilies[0]->bIsFirstViewInMultipleViewFamily)
{
Scene->IncrementFrameNumber();
}
for (FSceneViewFamily* ViewFamily : ViewFamilies)
{
ViewFamily->FrameNumber = Scene->GetFrameNumber();
}
}
else
{
// this is passes to the render thread, better access that than GFrameNumberRenderThread
for (FSceneViewFamily* ViewFamily : ViewFamilies)
{
ViewFamily->FrameNumber = GFrameNumber;
}
}
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
// 。。。
// 对所有 family的 ViewExtensions 扩展做 BeginRenderViewFamily
for (FSceneViewFamily* ViewFamily : ViewFamilies)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// If optional parameter OverrideFrameCounter is set, use its value instead of GFrameCounter.
ViewFamily->FrameCounter = ViewFamily->OverrideFrameCounter.IsSet() ? ViewFamily->OverrideFrameCounter.GetValue() : GFrameCounter;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
{
extern TSharedRef<ISceneViewExtension, ESPMode::ThreadSafe> GetRendererViewExtension();
ViewFamily->ViewExtensions.Add(GetRendererViewExtension());
}
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
// Force the upscalers to be set no earlier than ISceneViewExtension::BeginRenderViewFamily();
check(ViewFamily->GetTemporalUpscalerInterface() == nullptr);
check(ViewFamily->GetPrimarySpatialUpscalerInterface() == nullptr);
check(ViewFamily->GetSecondarySpatialUpscalerInterface() == nullptr);
for (int ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ViewExt++)
{
ViewFamily->ViewExtensions[ViewExt]->BeginRenderViewFamily(*ViewFamily);
}
checkf(!(ViewFamily->GetTemporalUpscalerInterface() != nullptr && ViewFamily->GetPrimarySpatialUpscalerInterface() != nullptr),
TEXT("Conflict setting up a third party primary spatial upscaler or temporal upscaler."));
}
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
// 。。。
if (Scene)
{
// Set the world's "needs full lighting rebuild" flag if the scene has any uncached static lighting interactions.
if(World)
{
// Note: reading NumUncachedStaticLightingInteractions on the game thread here which is written to by the rendering thread
// This is reliable because the RT uses interlocked mechanisms to update it
World->SetMapNeedsLightingFullyRebuilt(Scene->NumUncachedStaticLightingInteractions, Scene->NumUnbuiltReflectionCaptures); // 需要完全构建光照的标志
}
// Construct the scene renderers. This copies the view family attributes into its own structures.
TArray<FSceneRenderer*> SceneRenderers;
TArray<const FSceneViewFamily*> ViewFamiliesConst;
for (FSceneViewFamily* ViewFamily : ViewFamilies)
{
ViewFamiliesConst.Add(ViewFamily);
}
// 创建场景渲染器 SceneRenderers
FSceneRenderer::CreateSceneRenderers(ViewFamiliesConst, Canvas->GetHitProxyConsumer(), SceneRenderers);
}
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
// 。。。
if (Scene)
{
// 。。。
// 是否启用了命中代理
bool bShowHitProxies = false;
for (FSceneRenderer* SceneRenderer : SceneRenderers)
{
if (SceneRenderer->ViewFamily.EngineShowFlags.HitProxies)
{
bShowHitProxies = true;
break;
}
}
if (!bShowHitProxies)
{
USceneCaptureComponent::UpdateDeferredCaptures(Scene); // 延迟更新和当前场景相关的一些 场景捕捉组件
// 平面反射相关的
for (int32 ReflectionIndex = 0; ReflectionIndex < Scene->PlanarReflections_GameThread.Num(); ReflectionIndex++)
{
UPlanarReflectionComponent* ReflectionComponent = Scene->PlanarReflections_GameThread[ReflectionIndex];
for (FSceneRenderer* SceneRenderer : SceneRenderers)
{
Scene->UpdatePlanarReflectionContents(ReflectionComponent, *SceneRenderer);
}
}
}
for (FSceneRenderer* SceneRenderer : SceneRenderers)
{
SceneRenderer->ViewFamily.DisplayInternalsData.Setup(World); // 显示内部数据,根据一些命令行变量,决定如何显示一些内容
}
FSceneRenderer::PreallocateCrossGPUFences(SceneRenderers); // 预分配画GPU的围栏
const uint64 DrawSceneEnqueue = FPlatformTime::Cycles64();
ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)(
[LocalSceneRenderers = CopyTemp(SceneRenderers), DrawSceneEnqueue](FRHICommandListImmediate& RHICmdList)
{
uint64 SceneRenderStart = FPlatformTime::Cycles64();
const float StartDelayMillisec = FPlatformTime::ToMilliseconds64(SceneRenderStart - DrawSceneEnqueue);
CSV_CUSTOM_STAT_GLOBAL(DrawSceneCommand_StartDelay, StartDelayMillisec, ECsvCustomStatOp::Set);
RenderViewFamilies_RenderThread(RHICmdList, LocalSceneRenderers); // 渲染场景
FlushPendingDeleteRHIResources_RenderThread(); // 一些同步操作
});
// Force kick the RT if we've got RT polling on.
// This saves us having to wait until the polling period before the scene draw starts executing.
if (GRenderThreadPollingOn)
{
FTaskGraphInterface::Get().WakeNamedThread(ENamedThreads::GetRenderThread()); // 强制唤醒渲染线程
}
}
}
FCanvas::Flush_GameThread
上面有一个函数需要仔细看下
void FCanvas::Flush_GameThread(bool bForce)
{
SCOPE_CYCLE_COUNTER(STAT_Canvas_FlushTime);
if( !(AllowedModes&Allow_Flush) && !bForce ) // 是否允许刷新画布,是否强制刷新
{
return;
}
// current render target set for the canvas
check(RenderTarget);
if (!(AllowedModes & Allow_DeleteOnRender)) // 刷新的时候必须 允许 Allow_DeleteOnRender
{
ensureMsgf(false, TEXT("Flush_GameThread requires Allow_DeleteOnRender flag to be set."));
}
// no need to set the render target if we aren't going to draw anything to it!
if (SortedElements.Num() == 0)
{
return;
}
一般是在这种地方,前面是Draw,添加了要绘制的元素,SortedElements就有东西需要画
FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
{
ViewportClient->Draw(this, &Canvas);
}
Canvas.Flush_GameThread();
void FCanvas::Flush_GameThread(bool bForce)
{
// 。。。
// Update the font cache with new text before elements are drawn
// 字体服务是否初始化了
if (FEngineFontServices::IsInitialized())
{
FEngineFontServices::Get().UpdateCache(); // 如果没有
}
// FCanvasSortElement compare class
// 进行排序
struct FCompareFCanvasSortElement
{
FORCEINLINE bool operator()( const FCanvasSortElement& A, const FCanvasSortElement& B ) const
{
return B.DepthSortKey < A.DepthSortKey;
}
};
// sort the array of FCanvasSortElement entries so that higher sort keys render first (back-to-front)
SortedElements.Sort(FCompareFCanvasSortElement());
bool bEmitCanvasDrawEvents = GetEmitDrawEvents(); // 变量没用?
// Only create these render commands if we actually have something to draw.
if(SortedElements.Num() > 0 && !GUsingNullRHI)
{
// 有东西要画
FCanvasRenderThreadScope RenderThreadScope(*this);
// iterate over the FCanvasSortElements in sorted order and render all the batched items for each entry
for( int32 Idx=0; Idx < SortedElements.Num(); Idx++ )
{
FCanvasSortElement& SortElement = SortedElements[Idx];
for( int32 BatchIdx=0; BatchIdx < SortElement.RenderBatchArray.Num(); BatchIdx++ )
{
FCanvasBaseRenderItem* RenderItem = SortElement.RenderBatchArray[BatchIdx]; // 要画的东西
if( RenderItem )
{
// mark current render target as dirty since we are drawing to it
bRenderTargetDirty |= RenderItem->Render_GameThread(this, RenderThreadScope);
RenderThreadScope.DeferredDelete(RenderItem); // 延迟删除
}
}
SortElement.RenderBatchArray.Empty();
}
}
else
{
// 。。。
}
Render_GameThread
bool FCanvasBatchedElementRenderItem::Render_GameThread(const FCanvas* Canvas, FCanvasRenderThreadScope& RenderScope)
{
checkSlow(Data);
bool bDirty=false;
if( Data->BatchedElements.HasPrimsToDraw() ) // 批处理是否需要绘制图元
{
bDirty = true;
// current render target set for the canvas
// 获取 RT 和 gamma(用于后面颜色计算)
const FRenderTarget* CanvasRenderTarget = Canvas->GetRenderTarget();
float Gamma = 1.0f / CanvasRenderTarget->GetDisplayGamma();
if ( Data->Texture && Data->Texture->bIgnoreGammaConversions )
{
Gamma = 1.0f;
}
// Render the batched elements.
// 准备一个绘制参数
struct FBatchedDrawParameters
{
FRenderData* RenderData;
uint32 bHitTesting : 1;
uint32 ViewportSizeX;
uint32 ViewportSizeY;
float DisplayGamma;
uint32 AllowedCanvasModes;
ERHIFeatureLevel::Type FeatureLevel;
EShaderPlatform ShaderPlatform;
};
// all the parameters needed for rendering
FBatchedDrawParameters DrawParameters =
{
Data,
(uint32)(Canvas->IsHitTesting() ? 1 : 0),
(uint32)CanvasRenderTarget->GetSizeXY().X,
(uint32)CanvasRenderTarget->GetSizeXY().Y,
Gamma,
Canvas->GetAllowedModes(),
Canvas->GetFeatureLevel(),
Canvas->GetShaderPlatform()
};
// 添加一个pass,lambda在渲染线程去做
RenderScope.AddPass(
TEXT("CanvasBatchedElements"),
[DrawParameters](FRHICommandList& RHICmdList)
{
FSceneView SceneView = FBatchedElements::CreateProxySceneView(DrawParameters.RenderData->Transform.GetMatrix(), FIntRect(0, 0, DrawParameters.ViewportSizeX, DrawParameters.ViewportSizeY));
FMeshPassProcessorRenderState DrawRenderState;
// disable depth test & writes
DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
DrawRenderState.SetBlendState(TStaticBlendState<>::GetRHI());
// draw batched items
DrawParameters.RenderData->BatchedElements.Draw( // 《==========这里也要看看
RHICmdList,
DrawRenderState,
DrawParameters.FeatureLevel,
SceneView,
DrawParameters.bHitTesting,
DrawParameters.DisplayGamma);
if(DrawParameters.AllowedCanvasModes & FCanvas::Allow_DeleteOnRender )
{
delete DrawParameters.RenderData;
}
});
}
if( Canvas->GetAllowedModes() & FCanvas::Allow_DeleteOnRender )
{
Data = NULL;
}
return bDirty;
}
void FCanvas::Flush_GameThread(bool bForce)
{
// 。。。
// Only create these render commands if we actually have something to draw.
if(SortedElements.Num() > 0 && !GUsingNullRHI)
{
// 。。。
}
else
{
// iterate over the FCanvasSortElements in sorted order and delete all the batched items for each entry
for (int32 Idx = 0; Idx < SortedElements.Num(); Idx++)
{
FCanvasSortElement& SortElement = SortedElements[Idx];
for (int32 BatchIdx = 0; BatchIdx < SortElement.RenderBatchArray.Num(); BatchIdx++)
{
FCanvasBaseRenderItem* RenderItem = SortElement.RenderBatchArray[BatchIdx];
if (RenderItem)
{
delete RenderItem;
}
}
SortElement.RenderBatchArray.Empty();
}
}
// empty the array of FCanvasSortElement entries after finished with rendering
SortedElements.Empty();
SortedElementLookupMap.Empty();
LastElementIndex = INDEX_NONE;
}
FEditorViewportClient::Draw
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
// 。。。
// Draw the 3D scene
GetRendererModule().BeginRenderingViewFamily(Canvas,&ViewFamily);
if (View)
{
DrawCanvas( *Viewport, *View, *Canvas );
DrawSafeFrames(*Viewport, *View, *Canvas);
}
// 。。。
}
BeginRenderingViewFamily 结束了
DrawCanvas 是绘制一些UI相关的,比如可视化HUD,Debug工具UI,编辑器模式下,选中一个物体外围有一个括弧等
DrawSafeFrames 绘制一个安全宽。比屏幕小一点,提醒美术不要做的太靠边了
void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
// 。。。
if (View)
{
DrawCanvas( *Viewport, *View, *Canvas );
DrawSafeFrames(*Viewport, *View, *Canvas);
}
// 移除调试的线条
// Remove temporary debug lines.
// Possibly a hack. Lines may get added without the scene being rendered etc.
if (World && World->LineBatcher != NULL && (World->LineBatcher->BatchedLines.Num() || World->LineBatcher->BatchedPoints.Num() || World->LineBatcher->BatchedMeshes.Num() ) )
{
World->LineBatcher->Flush();
}
// 移除前景的调试的线条
if (World && World->ForegroundLineBatcher != NULL && (World->ForegroundLineBatcher->BatchedLines.Num() || World->ForegroundLineBatcher->BatchedPoints.Num() || World->ForegroundLineBatcher->BatchedMeshes.Num() ) )
{
World->ForegroundLineBatcher->Flush();
}
// Draw the widget.
// 比如编辑器编辑模式 旋转物体的时候,有一个调试字体表示角度,那个HUD
if (Widget && bShowWidget)
{
Widget->DrawHUD( Canvas );
}
// Axes indicators
// 屏幕下面,绘制的那一条缩放条 这么长线段 是 多长 的那个东西
const bool bShouldDrawAxes = (bDrawAxes && !ViewFamily.EngineShowFlags.Game) || (bDrawAxesGame && ViewFamily.EngineShowFlags.Game);
if (bShouldDrawAxes && !GLevelEditorModeTools().IsViewportUIHidden() && !IsVisualizeCalibrationMaterialEnabled())
{
switch (GetViewportType())
{
case LVT_OrthoXY:
{
const FRotator XYRot(-90.0f, -90.0f, 0.0f);
DrawAxes(Viewport, Canvas, &XYRot, EAxisList::XY);
if (View)
{
DrawScaleUnits(Viewport, Canvas, *View);
}
break;
}
// 。。。
default:
{
DrawAxes(Viewport, Canvas);
break;
}
}
}
// 。。。
}
绘制一些调试信息,比如敲命令stat 出来的帧数fps的那个字
// NOTE: DebugCanvasObject will be created by UDebugDrawService::Draw() if it doesn't already exist.
UDebugDrawService::Draw(ViewFamily.EngineShowFlags, Viewport, View, DebugCanvas);
UCanvas* DebugCanvasObject = FindObjectChecked<UCanvas>(GetTransientPackage(),TEXT("DebugCanvasObject"));
DebugCanvasObject->Canvas = DebugCanvas;
DebugCanvasObject->Init( Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, View , DebugCanvas);
// Stats display
if( IsRealtime() && ShouldShowStats() && DebugCanvas)
{
const int32 XPos = 4;
TArray< FDebugDisplayProperty > EmptyPropertyArray;
DrawStatsHUD( World, Viewport, DebugCanvas, NULL, EmptyPropertyArray, GetViewLocation(), GetViewRotation() ); // 这里面是绘制调试信息,比如你按了 stat fps
}
Viewport = ViewportBackup;
函数结束
FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();
这里Draw完成后,就到了 Flush_GameThread。
这三行逻辑在很多地方都有,不管是高清截图里面还是,游戏每帧渲染。
回到这里继续看
void FViewport::Draw( bool bShouldPresent /*= true */)
{
// ...
if(!bReentrant)
{
// if this is a game viewport, and game rendering is disabled, then we don't want to actually draw anything
if ( World && World->IsGameWorld() && !bIsGameRenderingEnabled)
{
// since we aren't drawing the viewport, we still need to update streaming
World->UpdateLevelStreaming();
}
else
{
if( GIsHighResScreenshot )
{
// 。。。
HighResScreenshot();
}
else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired)
{
}
if( SizeX > 0 && SizeY > 0 )
{
// 。。。
FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
{
ViewportClient->Draw(this, &Canvas);
}
Canvas.Flush_GameThread();
// 。。。
}
}
}
// ...
}