Description

SaveGeneratedTexture2DAsset can crash for a variety of reasons when overwriting an existing texture asset. When no existing asset exists, the function simply moves the provided transient Texture asset to the specified non-transient package. However, in the case the asset already exists, the function tries to do a complex texture to texture copy from the source to destination asset. Unfortunately there are a number of edge cases which are not handled properly, causing a number of crash or corrupt texture output scenarios. Note that in order to save an existing non-transient texture using this function, the UTexture can be duplicated to a transient package with DuplicateObject(SourceTexture, GetTransientPackage()). The following are a number of specific scenarios that will cause a crash, all source assets in these examples were duplicated to a transient package as outlined previously:

  • Save a world partition minimap texture to the same asset path twice
  • Save a texture whose source texture is not a power of two, but has the Pad to Power of Two setting, to the same asset path twice
  • Save a texture with compression set to a compressed format (for instance DXT1) to the same asset path twice

Other non crashing cases include:

  • Save an uncompressed (VectorDisplacementmap) virtual texture asset to the same asset path twice:
    On the first save, the texture is an exact copy of the virtual texture. However, after the second save (and overwrite), the texture asset becomes non-virtual.

This way of trying to copy the texture data is very error prone. A much simpler solution would be to duplicate the source texture object over top of the existing one. This is both faster and much more reliable. This can be done with something like the following code:

if (UObject* ExistingAsset = StaticFindObject(nullptr, UsePackage, *NewObjectName, false))
{
    UTexture2D* ExistingTexture2D = Cast<UTexture2D>(ExistingAsset);
    if (ExistingTexture2D && Options.bOverwriteIfExists)
   {

        if (GeneratedTexture = DuplicateObject(SourceTexture, ExistingTexture->GetOuter(), ExistingTexture->GetFName()))       

       

{             GeneratedTexture->ClearFlags(RF_Transient); GeneratedTexture->SetFlags(RF_Public | RF_Standalone | RF_Transactional);         }

    }

}

Steps to Reproduce
  • Using a source texture with one of the above example properties (WP minimap, DXT1, or padded non-power of two texture), duplicate this source texture to a non-transient package using DuplicateObject(SourceTexture, GetTransientPackage())
  • Setup the UE::AssetUtils::FTexture2DAssetOptions struct with the NewAssetPath and set the bOverwriteIfExists field to true.
  • Save the duplicated texture object, with the same settings, twice, using UE::AssetUtils::SaveGeneratedTexture2DAsset
  • Observe a crash

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-355953 in the post.

0
Login to Vote

Unresolved
ComponentUE - Texture
Affects Versions5.35.45.55.65.75.8
CreatedNov 28, 2025
UpdatedDec 1, 2025
View Jira Issue