UE4 separation of game logic and rendering logic

Posted by Randwulf on Sun, 26 Dec 2021 11:21:01 +0100

A basic idea of the framework design of illusory engine is to separate game logic from rendering logic.

That is, there is a game world: the Actor contained in the scene (and its associated Actor component). At the same time, there is a rendered world, which contains the information needed to render the game world.

The rendered world is like a set, which only presents the content that can be rendered within the current camera range.

For example, an AStaticMeshActor and its components containing astaticmeshcomponent correspond to the world of the game. Instead of dealing with rendering related logic, it performs rendering through an FStaticMeshSceneProxy scene proxy object.

Any component that can be rendered needs to call CreateSceneProxy() to create the corresponding SceneProxy object. Note: CreateSceneProxy is called when the component is registered in the world, not every frame

UActorComponent (184)
  USceneComponent (528)
    UAkPortalComponent (528)
    UAkGameObject (560)
      UAkComponent (1136)
        UAkAudioInputComponent (1152)
      UAkRoomComponent (608)
    UAkGeometryComponent (816)
    UAkLateReverbComponent (624)
    UAkSurfaceReflectorSetComponent (576)
    UMultiSourceSoundComponent (528)
    UPrimitiveComponent (1152)                                       FPrimitiveSceneProxy
      UMeshComponent (1200)                                              |
        UProceduralMeshComponent (1312)                                  |
          UCubeSphereComponent (1504)                                    |
        USkinnedMeshComponent (1760)                                     |
          USkeletalMeshComponent (3936)                                 FSkeletalMeshSceneProxy
            USkeletalMeshComponentBudgeted (3984)                        |
          UPoseableMeshComponent (2112)                                  |
        UStaticMeshComponent (1312)                                     FStaticMeshSceneProxy
          UInstancedStaticMeshComponent (1488)                               |
            UHierarchicalInstancedStaticMeshComponent (1728)                FInstancedStaticMeshSceneProxy
              UFoliageInstancedStaticMeshComponent (1776)                    |
          UInteractiveFoliageComponent (1328)                               FInteractiveFoliageSceneProxy
          UControlPointMeshComponent (1328)
          ULandscapeMeshProxyComponent (1360)
          USplineMeshComponent (1472)
        UPaperFlipbookComponent (1280)
        UPaperGroupedSpriteComponent (1248)
        UPaperSpriteComponent (1232)
        UPaperTileMapComponent (1280)
        UCableComponent (1344)
        UGeometryCacheComponent (1296)
        UGroomComponent (1472)
        UWidgetComponent (1488)
        UGeometryCollectionComponent (2352)
      UPaperTerrainComponent (1232)
      USplineComponent (1392)
        UPaperTerrainSplineComponent (1408)
      UNPCAINavMeshRenderingComponent (1152)
      UCoverPointRenderingComponent (1152)
      UShapeComponent (1168)
        UBoxComponent (1184)
          UGlassBoxComponent (1216)
        UCapsuleComponent (1184)
        USphereComponent (1184)
          UDrawSphereComponent (1184)
      UFXSystemComponent (1152)
        UNiagaraComponent (1568)
        UParticleSystemComponent (1760)
          UUIParticleComponent (1760)
      UControlRigComponent (1392)
      ULensFlareBillboardComponent (1216)
      UMRMeshComponent (1328)
      UMotionControllerComponent (1328)
      ULandscapeComponent (1696)                                      FLandscapeComponentSceneProxy : public FPrimitiveSceneProxy, public FLandscapeNeighborInfo
      ULandscapeGizmoRenderComponent (1152)
      ULandscapeHeightfieldCollisionComponent (1376)
        ULandscapeMeshCollisionComponent (1408)
      ULandscapeSplinesComponent (1200)
      UArrowComponent (1168)
      UBillboardComponent (1184)                                      FSpriteSceneProxy : public FPrimitiveSceneProxy
      UBrushComponent (1168)
      UDrawFrustumComponent (1168)
      UGlobalILCComponent (1168)
      ULineBatchComponent (1216)
      UMaterialBillboardComponent (1168)
      UModelComponent (1216)
      UTextRenderComponent (1232)
      UVectorFieldComponent (1184)
      UNavLinkComponent (1168)
      UNavLinkRenderingComponent (1152)
      UNavMeshRenderingComponent (1152)
      UNavTestRenderingComponent (1152)
      UEQSRenderingComponent (1200)
      UFuncTestRenderingComponent (1152)
      UGizmoBaseComponent (1184)
        UGizmoArrowComponent (1200)
        UGizmoBoxComponent (1232)
        UGizmoCircleComponent (1200)
        UGizmoLineHandleComponent (1216)
        UGizmoRectangleComponent (1232)
      UFieldSystemComponent (1200)
    USceneCaptureComponent (720)
      USceneCaptureComponent2D (2368)
      UPlanarReflectionComponent (960)
      USceneCaptureComponentCube (768)
    UILCTextureComponent (528)
    UILCDynamicScaleComponent (528)
    USynthComponent (1744)
      UVoipListenerSynthComponent (1856)
      USynthComponentMoto (1968)
      UMediaSoundComponent (2368)
    UMockDataMeshTrackerComponent (640)
    UARComponent (656)
      UARPlaneComponent (784)
      UARPointComponent (656)
      UARFaceComponent (752)
      UARImageComponent (752)
      UARQRCodeComponent (768)
      UARPoseComponent (720)
      UAREnvironmentProbeComponent (704)
      UARObjectComponent (704)
      UARMeshComponent (752)
      UARGeoAnchorComponent (768)
    UARLifeCycleComponent (576)
    UWidgetInteractionComponent (1056)
    UCameraComponent (2128)
      UCineCameraComponent (2384)
    UAtmosphericFogComponent (784)
    UAudioComponent (2160)
    UReflectionCaptureComponent (656)
      UBoxReflectionCaptureComponent (672)
      UPlaneReflectionCaptureComponent (672)
      USphereReflectionCaptureComponent (672)
    UCameraShakeSourceComponent (544)
    UChildActorComponent (576)
    UDecalComponent (592)                                            FDeferredDecalProxy
    ULightComponentBase (576)
      ULightComponent (816)                                          FLightSceneProxy
        UDirectionalLightComponent (1040)                                FDirectionalLightSceneProxy
        ULocalLightComponent (848)                                       FLocalLightSceneProxy
          UPointLightComponent (864)                                        FPointLightSceneProxy
            USpotLightComponent (880)                                           FSpotLightSceneProxy
          URectLightComponent (880)                                         FRectLightSceneProxy
USkyLightComponent (
1056) FSkyLightSceneProxy UExponentialHeightFogComponent (672) UForceFeedbackComponent (752) ULightmassPortalComponent (528) UPhysicsConstraintComponent (1040) UPhysicsSpringComponent (560) UPhysicsThrusterComponent (528) UPostProcessComponent (2032) URadialForceComponent (576) URuntimeVirtualTextureComponent (640) UShadowCaptureComponent (768) USkyAtmosphereComponent (752) USpringArmComponent (656) UStereoLayerComponent (752) UVolumetricCloudComponent (592) UWindDirectionalSourceComponent (560) UNavigationGraphNodeComponent (560) UTestPhaseComponent (528) UChaosDestructionListener (1072)

 

Game threads and rendering threads represent

The objects of the game thread are usually updated logically, and there is a persistent data in memory. In order to avoid the competitive conditions between the game thread and the rendering thread, an additional memory copy will be stored in the rendering thread, and another type will be used.

The following is the common type mapping relationship of UE (game thread object starts with U and rendering thread starts with F):

Game ThreadRender Thread
UWorld FScene
UPrimitiveComponent FPrimitiveSceneProxy / FPrimitiveSceneInfo
- FSceneView / FViewInfo
ULocalPlayer FSceneViewState
ULightComponent FLightSceneProxy / FLightSceneInfo

 

The game thread representative is generally operated by the game thread, and the rendering thread representative is mainly operated by the rendering thread. If you try to manipulate data across threads, it will lead to unpredictable results and race conditions.

/** SceneProxy When registering an approach scene, data is constructed and passed in the game thread. */
FStaticMeshSceneProxy::FStaticMeshSceneProxy(UStaticMeshComponent* InComponent):
    FPrimitiveSceneProxy(...),
    Owner(InComponent->GetOwner()) //<======== Here will AActor The pointer is cached
    ...

/** SceneProxy The DrawDynamicElements will be called by the renderer in the rendering thread. */
void FStaticMeshSceneProxy::DrawDynamicElements(...)
{
    if (Owner->AnyProperty) //< ============== will trigger competitive conditions!  Game thread owned AActor,UObject All States of!! also UObject Objects may be GC Drop, and then access will cause the program to crash!!
}

 

Some representatives are special, such as fplimitivesceneproxy and FLightSceneProxy. These scene agents belong to the engine module, but they also belong to the exclusive objects of rendering threads, indicating that they are the bridge connecting game threads and rendering threads and the tool person for transferring data between threads.

 

Description of each type is as follows:

typeexplain
UWorld It contains a set of actors and components that can interact with each other. Multiple levels can be loaded into or unloaded from UWorld.
ULevel The level stores a set of actors and components in the same file.
USceneComponent Scene component is the parent class of all objects that can be added to the scene, such as light, model, fog, etc.
UPrimitiveComponent Primitive component is the parent class of all objects that can be rendered or have physical simulation. Is the smallest granularity unit of CPU layer clipping,
ULightComponent Light source component, which is the parent of all light source types.
ULocalPlayer

A local player represents a client of a global life cycle. A split screen game will have multiple clients, and its member variable ugameviewportclient * FViewport* Viewport in the viewportclient (actually fsceneviewort type) describes the player's viewport.

Note: class fsceneviewort: public FViewportFrame, public FViewport, public ISlateViewport, public IViewportRenderTargetProvider

FScene It is the representative of UWorld in the rendering module. Only objects added to FScene will be perceived by the renderer. The rendering thread owns all the states of the FScene (the game thread cannot be modified directly).
FPrimitiveSceneProxy The primitive scene agent is the representative of the uplimitivecomponent in the renderer and mirrors the state of the uplimitivecomponent in the rendering thread.
FPrimitiveSceneInfo The internal state of the renderer (describing the implementation of the FRendererModule) is equivalent to the fusion of uplimitivecomponent and fplimitivesceneproxy. Only the renderer module exists, so the engine module cannot perceive its existence.
FSceneView Describes a single view in the FScene. Multiple views are allowed in the same FScene. In other words, a scene can be drawn by multiple views, or multiple views can be drawn at the same time. A new view instance will be created in each frame.
FViewInfo view is represented inside the renderer. Only the renderer module exists and the engine module is not visible.
FSceneViewState The renderer private information about the view is stored, which needs to be accessed across frames. In the Game instance, each ULocalPlayer has an FSceneViewState instance.
FSceneRenderer Each frame is created to encapsulate the temporary data between frames. Fddeferredshadingscenerer (delayed shading scene renderer) and fmobilescenerer (mobile scene renderer) are generated to represent the default renderers of PC and mobile respectively.
FLightSceneProxy The lighting agent is the representative of the ULightComponent in the renderer and mirrors the state of the ULightComponent in the rendering thread.
FLightSceneInfo Contains information for lighting calculations. Only the renderer module exists, so the engine module cannot perceive its existence.

 

Interaction between game thread and rendering thread

First, let's look at how the game thread passes data to the rendering thread.

When the game thread is ticking, it will enter the call of rendering module through UGameEngine, FViewport, UGameViewportClient and other objects:

void UGameEngine::Tick( float DeltaSeconds, bool bIdleMode )
{
    UGameEngine::RedrawViewports()
    {
        void FViewport::Draw( bool bShouldPresent)
        {
            void UGameViewportClient::Draw()
            {
                // calculation ViewFamily,View Various properties of
                ULocalPlayer::CalcSceneView();
                // Send render command
                FRendererModule::BeginRenderingViewFamily()
                {
                    World->SendAllEndOfFrameUpdates();
                    // Create scene renderer
                    FSceneRenderer* SceneRenderer = FSceneRenderer::CreateSceneRenderer(ViewFamily, ...);
                    // Send a scene drawing instruction to the rendering thread.
                    ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)(
                    [SceneRenderer](FRHICommandListImmediate& RHICmdList)
                    {
                        RenderViewFamily_RenderThread(RHICmdList, SceneRenderer)
                        {
                            (......)
                            // Call the rendering interface of the scene renderer.
                            SceneRenderer->Render(RHICmdList);
                            (......)
                        }
                        FlushPendingDeleteRHIResources_RenderThread();
                    });
                }
}}}}

 

The previous chapter also mentioned that rendering threads use objects such as SceneProxy and SceneInfo. How does the Actor component of the game relate to the data of the scene proxy? How to update the data?

 

Creation of scene proxy objects

First, find out the mechanism by which the game component transmits data to the SceneProxy. The answer lies in FScene::AddPrimitive:

// Engine\Source\Runtime\Renderer\Private\RendererScene.cpp

void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
    (......)
    
    // Create a scene proxy for the element
    FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
    Primitive->SceneProxy = PrimitiveSceneProxy;
    if(!PrimitiveSceneProxy)
    {
        return;
    }

    // Create scene information for primitive scene proxy
    FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
    PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
    
    (......)

    FScene* Scene = this;

    ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
        [Params = MoveTemp(Params), Scene, PrimitiveSceneInfo, PreviousTransform = MoveTemp(PreviousTransform)](FRHICommandListImmediate& RHICmdList)
        {
            FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
            
            (......)

            SceneProxy->CreateRenderThreadResources();
            // In the rendering thread SceneInfo Add to scene.
            Scene->AddPrimitiveSceneInfo_RenderThread(PrimitiveSceneInfo, PreviousTransform);
        });
}

There is a key sentence above: primitive - > createsceneproxy() is to create the PrimitiveSceneProxy corresponding to the component. In the constructor of PrimitiveSceneProxy, copy all the data of the component:

FPrimitiveSceneProxy::FPrimitiveSceneProxy(const UPrimitiveComponent* InComponent, FName InResourceName)
:
    CustomPrimitiveData(InComponent->GetCustomPrimitiveData())
,    TranslucencySortPriority(FMath::Clamp(InComponent->TranslucencySortPriority, SHRT_MIN, SHRT_MAX))
,    Mobility(InComponent->Mobility)
,    LightmapType(InComponent->LightmapType)
,    StatId()
,    DrawInGame(InComponent->IsVisible())
,    DrawInEditor(InComponent->GetVisibleFlag())
,    bReceivesDecals(InComponent->bReceivesDecals)

(......)

{
    (......)
}

After copying the data, the game thread modifies the data of the PrimitiveComponent, while the rendering thread modifies or accesses the data of the PrimitiveSceneProxy, which does not interfere with each other, avoiding the synchronization of the critical area and lock, and ensuring thread safety.

 

For the primitive component, in order to improve the concurrency, the game thread puts the FScene::AddPrimitive operation into the TaskGraph through the ParallelFor function. The specific implementation logic is roughly as follows:

① When registering the upprimitivecomponent, it will be added to the AddPrimitiveBatches array of FRegisterComponentContext

 

UWorld calls the FRegisterComponentContext::Process function in UpdateWorldComponents to use ParallelFor function to put FScene::AddPrimitive operation into TaskGraph and execute it concurrently.

 

 

③ in TaskGraph's working thread, FScene::AddPrimitive will call CreateSceneProxy to create a scene proxy object

 

For LightComponent, call ULightComponent:: CreateRenderState_ directly in the game thread. Use the concurrent function to execute FScene::AddLight and create the FLightSceneProxy object

void FScene::AddLight(ULightComponent* Light)
{
    LLM_SCOPE(ELLMTag::SceneRender);

    // Create the light's scene proxy.
    FLightSceneProxy* Proxy = Light->CreateSceneProxy();
    if(Proxy)
    {
        // Associate the proxy with the light.
        Light->SceneProxy = Proxy;

        // Update the light's transform and position.
        Proxy->SetTransform(Light->GetComponentTransform().ToMatrixNoScale(), Light->GetLightPosition());

        // Create the light scene info.
        Proxy->LightSceneInfo = new FLightSceneInfo(Proxy, true);

        INC_DWORD_STAT(STAT_SceneLights);

        // Adding a new light
        ++NumVisibleLights_GameThread;

        // Send a command to the rendering thread to add the light to the scene.
        FScene* Scene = this;
        FLightSceneInfo* LightSceneInfo = Proxy->LightSceneInfo;
        ENQUEUE_RENDER_COMMAND(FAddLightCommand)(
            [Scene, LightSceneInfo](FRHICommandListImmediate& RHICmdList)
            {
                CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Scene_AddLight);
                FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
                Scene->AddLightSceneInfo_RenderThread(LightSceneInfo);
            });
    }
}

The call stack is as follows:

 

However, there is still a question here, that is, when creating the PrimitiveSceneProxy, a copy of data will be copied, but how does the PrimitiveComponent update the data to the PrimitiveSceneProxy after creation?

 

Update of scene proxy objects

Each frame of the game thread will call dodeferredrenderuppdates of each UActorComponent in UWorld::SendAllEndOfFrameUpdates_ The concurrent function to check for updates

ActorComponent has several tags. As long as these tags are marked as true, the update interface will be called at the appropriate time to get the update:

// Engine\Source\Runtime\Engine\Classes\Components\ActorComponent.h

class ENGINE_API UActorComponent : public UObject, public IInterface_AssetUserData
{
protected:
    // The following interfaces update the corresponding status respectively, Subclasses can be overridden to implement their own update logic.
    virtual void DoDeferredRenderUpdates_Concurrent()
    {
        (......)
        
        if(bRenderStateDirty)
        {
            RecreateRenderState_Concurrent();
        }
        else
        {
            if(bRenderTransformDirty)
            {
                SendRenderTransform_Concurrent();
            }
            if(bRenderDynamicDataDirty)
            {
                SendRenderDynamicData_Concurrent();
            }
        }
    }
    virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context)
    {
        bRenderStateCreated = true;

        bRenderStateDirty = false;
        bRenderTransformDirty = false;
        bRenderDynamicDataDirty = false;
    }
    virtual void SendRenderTransform_Concurrent()
    {
        bRenderTransformDirty = false;
    }
    virtual void SendRenderDynamicData_Concurrent()
    {
        bRenderDynamicDataDirty = false;
    }
    
private:
    uint8 bRenderStateDirty:1; // Is the rendering state of the component dirty
    uint8 bRenderTransformDirty:1; // Is the transformation matrix of the component dirty
    uint8 bRenderDynamicDataDirty:1; // Is the rendering dynamic data of the component dirty
};

 

The above protected interface is used to refresh the component data to the corresponding SceneProxy. Specific component subclasses can rewrite it to customize their own update logic

 

For PrimitiveComponent, the transformation matrix update logic is as follows:

// Engine\Source\Runtime\Engine\Private\Components\PrimitiveComponent.cpp

void
UPrimitiveComponent::SendRenderTransform_Concurrent() { UpdateBounds(); // If the primitive isn't hidden update its transform. const bool bDetailModeAllowsRendering = DetailMode <= GetCachedScalabilityCVars().DetailMode; if( bDetailModeAllowsRendering && (ShouldRender() || bCastHiddenShadow)) {
// Update transformation information to the scene
// Update the scene info's transform for this primitive. GetWorld()->Scene->UpdatePrimitiveTransform(this); } Super::SendRenderTransform_Concurrent(); }

 

The UpdatePrimitiveTransform of the scene will assemble the data of the components and send the data to the rendering thread for execution:

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;
    }

    if(Primitive->SceneProxy)
    {
        // Check if the primitive needs to recreate its proxy for the transform update.
        if(Primitive->ShouldRecreateProxyOnUpdateTransform())
        {
            // Re-add the primitive from scratch to recreate the primitive's proxy.
            RemovePrimitive(Primitive);
            AddPrimitive(Primitive);
        }
        else
        {
            FVector AttachmentRootPosition(0);

            AActor* Actor = Primitive->GetAttachmentRootActor();
            if (Actor != NULL)
            {
                AttachmentRootPosition = Actor->GetActorLocation();
            }

            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();
            UpdateParams.AttachmentRootPosition = AttachmentRootPosition;
            UpdateParams.LocalBounds = Primitive->CalcBounds(FTransform::Identity);
            UpdateParams.PreviousTransform = FMotionVectorSimulation::Get().GetPreviousTransform(Primitive);

            // Help track down primitive with bad bounds way before the it gets to the Renderer
            ensureMsgf(!Primitive->Bounds.BoxExtent.ContainsNaN() && !Primitive->Bounds.Origin.ContainsNaN() && !FMath::IsNaN(Primitive->Bounds.SphereRadius) && FMath::IsFinite(Primitive->Bounds.SphereRadius),
                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);

            ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)(
                [UpdateParams](FRHICommandListImmediate& RHICmdList)
                {
                    FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
                    //Perform updates on the rendering thread
                    UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread(UpdateParams.PrimitiveSceneProxy, UpdateParams.WorldBounds, UpdateParams.LocalBounds, UpdateParams.LocalToWorld, UpdateParams.AttachmentRootPosition, UpdateParams.PreviousTransform);
                });
        }
    }
    else
    {
        // If the primitive doesn't have a scene info object yet, it must be added from scratch.
        AddPrimitive(Primitive);
    }
}

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 (GWarningOnRedundantTransformUpdate && PrimitiveSceneProxy->WouldSetTransformBeRedundant(LocalToWorld, WorldBounds, LocalBounds, AttachmentRootPosition))
    {
        UE_LOG(LogRenderer, Warning, TEXT("Redundant UpdatePrimitiveTransform_RenderThread Owner: %s, Resource: %s, Level: %s"), *PrimitiveSceneProxy->GetOwnerName().ToString(), *PrimitiveSceneProxy->GetResourceName().ToString(), *PrimitiveSceneProxy->GetLevelName().ToString());
    }

    if (AddedPrimitiveSceneInfos.Contains(PrimitiveSceneProxy->GetPrimitiveSceneInfo()))
    {
        check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex == INDEX_NONE);
    }
    else
    {
        check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
    }

    check(!RemovedPrimitiveSceneInfos.Contains(PrimitiveSceneProxy->GetPrimitiveSceneInfo()));
// Update transformation matrix UpdatedTransforms.Add(PrimitiveSceneProxy, { WorldBounds, LocalBounds, LocalToWorld, AttachmentRootPosition });
if (PreviousTransform.IsSet()) { OverridenPreviousTransforms.Add(PrimitiveSceneProxy->GetPrimitiveSceneInfo(), PreviousTransform.GetValue().ToMatrixWithScale()); } }

 

For ULightComponent, the transformation matrix update logic is as follows:

// Engine\Source\Runtime\Engine\Private\Components\LightComponent.cpp

void ULightComponent::SendRenderTransform_Concurrent()
{
    // Update transformation information to the scene.
    GetWorld()->Scene->UpdateLightTransform(this);
    Super::SendRenderTransform_Concurrent();
}

The UpdateLightTransform of the scene will assemble the data of the components and send the data to the rendering thread for execution:

// Engine\Source\Runtime\Renderer\Private\RendererScene.cpp

void FScene::UpdateLightTransform(ULightComponent* Light)
{
    if(Light->SceneProxy)
    {
        // Assemble the data of the component to the structure (note that you cannot Component The address of is passed to the rendering thread, but all the data to be updated is copied)
        FUpdateLightTransformParameters Parameters;
        Parameters.LightToWorld = Light->GetComponentTransform().ToMatrixNoScale();
        Parameters.Position = Light->GetLightPosition();
        FScene* Scene = this;
        FLightSceneInfo* LightSceneInfo = Light->SceneProxy->GetLightSceneInfo();
        // Send data to the rendering thread for execution.
        ENQUEUE_RENDER_COMMAND(UpdateLightTransform)(
            [Scene, LightSceneInfo, Parameters](FRHICommandListImmediate& RHICmdList)
            {
                FScopeCycleCounter Context(LightSceneInfo->Proxy->GetStatId());
                // Perform data updates on the rendering thread.
                Scene->UpdateLightTransform_RenderThread(LightSceneInfo, Parameters);
            });
    }
}

void FScene::UpdateLightTransform_RenderThread(FLightSceneInfo* LightSceneInfo, const FUpdateLightTransformParameters& Parameters)
{
    (......)

    // Update transformation matrix.
    LightSceneInfo->Proxy->SetTransform(Parameters.LightToWorld, Parameters.Position);
        
    (......)
}

At this point, the logic of how the component updates data to the scene agent has finally been clarified.

It should be specially reminded that some interfaces such as FScene and FSceneProxy are called in the game thread, while some interfaces (generally with the suffix of _RenderThread) are called in the rendering thread. Remember not to call across threads, otherwise competitive conditions will occur and cause program crash.

 

Cleanup of scene proxy objects

For PrimitiveComponent, the specific implementation logic is roughly as follows:

① When the UActorComponent is de registered (such as when switching maps or unloading levels), call uplimitivecomponent:: destroyrenderstate_concurrent to execute the FScene::RemovePrimitive function

Through the macro ENQUEUE_RENDER_COMMAND adds a FRemovePrimitiveCommand task to the rendering thread

 

② The rendering thread executes the freemoveprimitivecommand task and adds the fpprimitivesceneinfo * primitivesceneinfo to be deleted to tset < fpprimitivesceneinfo * > removedprimitivesceneinfo

 

③ Each frame of the game thread passes through the macro ENQUEUE_RENDER_COMMAND adds an updateseceneprimitives task to the rendering thread

When the rendering thread executes the updateseceneprimitives task, it will call the fscene:: updateallprimitivesceneinfo function to collect the tset < fprimitivesceneinfo * > deletedsceneinfo to be deleted from the current frame

And execute delete at the end of fscene:: updateallprimitivesceneinfo function to complete the recycling of FPrimitiveSceneProxy object

 

For LightComponent, the specific implementation logic is roughly as follows:

① When the UActorComponent is de registered (such as during map switching or Level unloading), call ULightComponent::DestroyRenderState_Concurrent to execute the FScene::RemoveLight function

Through the macro ENQUEUE_RENDER_COMMAND adds a FRemoveLightCommand task to the rendering thread

 

② The rendering thread executes the FRemoveLightCommand task in fscene:: removelightsceneinfo_ delete is executed at the end of the renderthread function to complete the recycling of the FLightSceneProxy object

 

reference resources

Analysis of unreal rendering system (02) - multithreaded rendering

Analysis of illusory rendering system (03) - rendering mechanism

Rendering thread (ue4 official documentation)

 

Topics: UE4