2
\$\begingroup\$

I've been trying to implement something that will update textures on the render thread from a background thread. And from what I've read of the unreal engine documentation you should never access a descendant of UObject from the render thread since the game thread could deallocate it at any time. Epic describes an example of this situation in their documentation

Here is a simple example of a race condition / threading bug:

/** FStaticMeshSceneProxy Actor is called on the game thread when a component is registered to the scene. */
FStaticMeshSceneProxy::FStaticMeshSceneProxy(UStaticMeshComponent* InComponent):
    FPrimitiveSceneProxy(...),
    Owner(InComponent->GetOwner()) <======== Note: AActor pointer is cached 
    ...

   /** DrawDynamicElements is called on the rendering thread when the renderer is doing a pass over the scene. */
    void FStaticMeshSceneProxy::DrawDynamicElements(...)
    {
        if (Owner->AnyProperty) <========== Race condition!  The game thread owns all AActor / UObject state, 
            // and may be writing to it at any time.  The UObject may even have been garbage collected, causing a crash.
            // This could have been done safely by mirroring the value of AnyProperty in this proxy.
    }

However the actual code they write violates this rule all of the time. There are numerous examples in FTexture2DResource accessing its Owner property which is a UTexture2D* from the render thread. Just one is

/**
 * Called when the resource is initialized. This is only called by the rendering thread.
 */
void FTexture2DResource::InitRHI()
{
  FTexture2DScopedDebugInfo ScopedDebugInfo(Owner);
  INC_DWORD_STAT_BY( STAT_TextureMemory, TextureSize );
  INC_DWORD_STAT_FNAME_BY( LODGroupStatName, TextureSize );

#if STATS
  if (Owner->LODGroup == TEXTUREGROUP_UI) <========== Accessing LODGroup from owner should be unsafe
    {
        GUITextureMemory += TextureSize;
    }
...
}

This seems to directly contradict the documentation given by Epic even though this is commonplace in their source code.

From the source it doesn't look like FTexture2DResource or any of its ancestors perform any smart pointer magic or add the UTexture2D object to the root set to prevent GC and even then race conditions would still apply.

I'll probably end up answering this one myself, but it would be great if someone happened to know this.

\$\endgroup\$
2
  • \$\begingroup\$ Not familiar with Unreal, but in general if the documentation says to not do something, don't do it. Maybe the engine allows it for now, but in a later update this might not be the case. \$\endgroup\$ – TomTsagk May 27 '20 at 13:00
  • \$\begingroup\$ @TomTsagk That's basically what I'm following for now, since I'm patching a bug related to the error the documentation says I would have. However I think there is probably a good insight to be had so I'll keep looking, and document it here when I find it. \$\endgroup\$ – J. Rehbein May 28 '20 at 0:18
2
\$\begingroup\$

I think I've figured out how it works. I'll edit this answer if I find a definitive answer.

It was actually simpler than I was thinking. FRenderResource instances are issued commands solely on behalf of some UObject descendant that owns it. So when that UObject is marked to be destroyed a final command to destroy the FRenderResource it owns is issued to the Render Queue by the UObject. Since its a queue the last command issued that uses the FRenderResource instance will be the one that destroys it. The UObject waits for the destruction of the FRenderResource before releasing itself. In specific case UTexture has a ReleaseFence property of type FRenderCommandFence that allows it to check when the command it issued to destroy its FRenderResource has been processed.

As for race conditions I think those variables are probably just private and not changed internally after initialization of the UObject, but this is just conjecture I haven't actually checked.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the info ‘! \$\endgroup\$ – ColdSteel May 29 '20 at 1:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.