Blurry RVT Tiles After Load or Teleport

Hi everyone,

I’m currently using Unreal Engine 5.6.1 and encountering an issue where Runtime Virtual Texture (RVT) tiles appear blurry after loading the game or teleporting the player. This happens in both PIE and packaged builds, and the blur disappears either when moving the character or after executing “r.VT.Flush”.

What I’m Seeing

  • Some RVT tiles are blurry after loading or teleport. The tiles stay blurry until I move the character far enough to evict them from the cache, or explicitly call “r.VT.Flush”
  • “r.VT.RVT.MipColor 1” confirms that the tiles are using the correct mip level.
  • “r.VT.Residency.Show 1” shows that VT pools are large enough and no bias is applied.
  • The issue only occurs when rendering a specific asset to the RVT, which uses regular streamed textures (not virtual). If I hide the asset or set its input textures to “NeverStream=True”, the issue disappears.

Why I Think This Happens

It seems that RVT tiles are rendered using streamed textures that haven’t fully loaded into memory. Once the texture data is available, flushing the VT cache regenerates the tiles correctly. This issue has been reported before:

  • [Landscape RVT showing artifacts on level [Content removed]
  • [Blurry [Content removed]

There was also a bug tracker associated to the issue, UE-309761, which is now set to WNF :worried:

Workarounds I’ve Found

  1. Set input textures to non-streaming using “NeverStream=True”. This works but increases memory usage. The cost is significant with very large worlds, which is why I’m looking for alternatives.
  2. Explicitly call “r.VT.Flush” a few seconds after a teleport. The tiles are correctly regenerated, however there is a clear drop in quality for a split second after the flush. Also it’s difficult to time it correctly since there is no notification when textures are fully streamed in (AFAIK).
  3. Enable continuous update on the RVT. This ensures tiles are eventually redrawn at full quality, but it takes a long time (20 to 30 seconds with default settings). I found that the following cvars can speed up the generation time, at the price of some CPU/GPU time.
r.VT.MaxContinuousUpdatesPerFrame  
r.VT.MaxTilesProducedPerFrame  
r.VT.MaxUploadsPerFrame

Questions

  • Is there another (smarter) way to fix blurry RVT tiles?
  • Is there a way to somehow preload the textures by the RVT?

Thanks in advance for any insights or suggestions!

Hello,

After discussing with a colleague - enabling continuous updates on the RVT is the usual way to handle this issue, especially if you instantaneously teleport and have no warm up frames before hand like you typically would in a cinematic. Once we’ve already teleported the most effective streaming is view feedback based (because that prioritizes what we see). You’re using the right CVARs to adjust the RVT update. You should be able to temporarily increase them during load/teleport so you aren’t waiting so long to update the RVT. Have you already tried this and were not getting good results?

There was also a bug tracker associated to the issue,UE-309761, which is now set to WNF :worried:

The reason given for this was that this is an inherent limitation of the RVT system and prestreaming must be used to work around it.

Pre-streaming is typically used when you have something like a cinematic where you have 10-15 frames to prepare before teleporting. The cinematic pre-streaming plugin does this by playing back part of the upcoming sequence, loading the assets used and recording virtual texture pages to preload. An alternative approach for preloading RVT pages that we support in 5.6 is to use the URuntimeVirtualTextureComponent::RequestPreload() function. We used this in the UnrealFest Witcher demo to warm up RVT before some camera cuts. It’s not as “automated” to set up as the cinematic pre-streaming, and care must be taken not to try to force too fine a mip level, or for too large an area, to avoid over-subscription. The typical technique would be to have sequencer call this function for 10-15 frames before camera cuts, with specific volume areas that need preloading to avoid texture pop-in.

/** 
     * Request preload of an area of the runtime virtual texture at a given mip level. 
     * @param WorldBounds : The world space bounds of the pages to preload.
     * @param Level : The mip map level to preload.
     */
    UFUNCTION(BlueprintCallable, Category = VirtualTexture)
    ENGINE_API void RequestPreload(FBoxSphereBounds const& WorldBounds, int32 Level);

However, because you’re RVT is blurry due to an asset that hasn’t finished streaming in yet this likely won’t work reliably unless regular pre-streaming is scheduled before you call RequestPreload for the RVT.

The issue only occurs when rendering a specific asset to the RVT, which uses regular streamed textures (not virtual). If I hide the asset or set its input textures to “NeverStream=True”, the issue disappears.

If you have warm up frames, you may be able to use UMeshComponent::PrestreamTextures or AActor::PrestreamTextures but that’s assuming the mesh is already loaded. If there is some kind of portal you are going through you may need to add that location to the streaming manager to start streaming in those textures with IStreamingManager::Get().AddViewLocation();

/**
     * IStreamingManager::Get().AddViewLocation()
     * Queue up view locations to the streaming system. These locations will be added properly at the next call to AddViewInformation,
     * re-using the screensize and FOV settings.
     *
     * @param Location              World-space view origin
     * @param BoostFactor           A factor that affects all streaming distances for this location. 1.0f is default. Higher means higher-resolution textures and vice versa.
     * [Content removed] which forces the streaming system to ignore all other locations
     * [Content removed] in seconds. 0 means just for the next Tick.
     */
    ENGINE_API void AddViewLocation(const FVector& Location, float BoostFactor = 1.0f, bool bOverrideLocation = false, float Duration = 0.0f);

I hope this helps shed light on what’s going on and some of the available options.

Thanks Alex for the detailed reply!

I’ve settled with using Continuous Update for now, and I added a subsystem to temporarily increase the MaxContinuousUpdatesPerFrame and MaxUploadsPerFrame when we teleport the camera. It works well enough for now :slight_smile:

I am however very interested about pre-streaming. I haven’t been able to find documentation on the subject however. Do you know of some documents, or source code, that I should look into to better understand how pre-streaming works in the engine, and how it ties into RVT?

For the record, I did attempt to call AddViewLocation() as soon as I knew where the player was going to be teleported to (when in a loading screen), but it did not have any impact on the blurry RVT tiles.

Apologies for the delay. Pre-streaming is usually handled by adding the new location via IStreamingManager::Get().AddViewLocation() and letting the streaming manager take care of it. I suspect this wasn’t working in your use-case because the RVT needed to be updated after the items at that location had finished streaming in. I’d be interested to know if calling URuntimeVirtualTextureComponent::RequestPreload() after giving the streaming manager a chance to stream in those assets has an effect.

If you have specific items you want to pre-stream, as in the case of a character customization system, that’s when you call PrestreamTextures() on the component or AActor::PrestreamTextures for a full actor. Fortnite does this for previewing items but also has a more invasive version that forces specific mips to be loaded where it gathers the Actors, then gathers the UTextures from the Material::GetUsedTextures and UStreamableRenderAssets, calls UStreamableRenderAssets::SetForceMipLevelsToBeResident and RegisterMipLevelChangeCallback to register a callback so they can track when all the render assets are finished loading before continuing.