SceneCapture2D - ConstructTexture2D crashes engine

Hi,

I am currently trying to implement a photo-feature for my game. I would like to capture an image using the USceneCaptureComponent2D component and get it as a UTexture2D to display in the UI. I have looked through the blueprint-office example and searched the answerhub but I can’t seem to find out how to correctly use USceneCaptureComponent2D and UTextureRenderTarget2D. Whenever I try to construct the UTexture2D using ConstructTexture2D my entire engine crashes due to illegal memory access.

The capture component is part of my character class which is based upon the FPS-example.

My header declarations:

UCLASS(config = Game)
class AKTUCharacter : public ACharacter
{
	GENERATED_BODY()

	/** First person camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FirstPersonCameraComponent;

	/** Scene Capture Component*/
	UPROPERTY(VisibleAnywhere, Category = Camera)
	class USceneCaptureComponent2D* sceneCapture;

	UTexture2D* lastPhoto;

	UTextureRenderTarget2D* renderTarget;

public:
        AKTUCharacter(const FObjectInitializer& ObjectInitializer);

protected:
	void OnPhoto();
}

Constructor assignment:

AKTUCharacter::AKTUCharacter(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)

        /**
           FPS-Camera and value initialisation
        */

	renderTarget = NewObject<UTextureRenderTarget2D>();
	renderTarget->InitAutoFormat(512, 512);
	renderTarget->UpdateResourceImmediate();

	sceneCapture = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("SceneCapture"));
	sceneCapture->AttachParent = FirstPersonCameraComponent;
	sceneCapture->CaptureSource = SCS_FinalColorLDR;
	sceneCapture->TextureTarget = renderTarget;

}

Taking the photo:

void AKTUCharacter::OnPhoto()
{ 
		sceneCapture->FOVAngle = FirstPersonCameraComponent->FieldOfView;
		sceneCapture->UpdateContent();
		lastPhoto = sceneCapture->TextureTarget->ConstructTexture2D(this, "Photo", EObjectFlags::RF_NoFlags, CTF_DeferCompression); // <- It crashes here
}

Does anyone know what I did wrong?

Thanks in advance,
Todd

Some info on the crash would be useful (actual output). Can you step into the function?

The illegal memory access is caused by the texture target is nor valid when OnPhoto is called.

There are three major faults in your code:

The first one is, sceneCapture is a default sub object but it is not exported, this will make the various property modifications that you have made in the constructor are reverted back to the default values in PostLoad.

The second one is, even if you set sceneCapture as export-able, the render target that you use as its TextureTarget is not export-able in USceneCaptureComponent2D :

/** Temporary render target that can be used by the editor. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=SceneCapture)
class UTextureRenderTarget2D* TextureTarget;

The last one is the texture 2d variable that you are using to restore the captured result, lastPhoto, is neither an unreal property nor part of a root set, and this will make it illegible to be garbage collected by the engine and then when it does get collected (as soon as the engine decides to do GC) your pointer will point to an invalid area of the memory (a dangling pointer).

There are several solution to this problem, but this is one that I prefer for this kind of feature:

 UCLASS(config = Game)
 class AKTUCharacter : public ACharacter
 {
     GENERATED_BODY()
 
     /** First person camera */
     UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Export, Category = Camera, meta = (AllowPrivateAccess = "true"))
     class UCameraComponent* FirstPersonCameraComponent;
 
     /** Scene Capture Component*/
     UPROPERTY(VisibleAnywhere, Export, Category = Camera)
     class USceneCaptureComponent2D* sceneCapture;

     UPROPERTY(EditAnywhere, Category = Camera)
     UTextureRenderTarget2D* renderTarget;

     UPROPERTY(Transient)
     UTexture2D* lastPhoto;
 
     AKTUCharacter(const FObjectInitializer& ObjectInitializer)
          : Super(ObjectInitializer)
     {
          ...

          sceneCapture = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("SceneCapture"));
          sceneCapture->AttachParent = FirstPersonCameraComponent;
          sceneCapture->CaptureSource = SCS_FinalColorLDR;
     }
 
     void OnPhoto()
     {
          if ( renderTarget == nullptr ) 
          {
               UE_LOG(LogTemp, Warning, TEXT("A render target has not been specified, creating a temporary 512x512 render target"));
               renderTarget = NewObject<UTextureRenderTarget2D>();
               renderTarget->InitAutoFormat(512, 512);
               renderTarget->UpdateResourceImmediate();          }
          }
         sceneCapture->FOVAngle = FirstPersonCameraComponent->FieldOfView;
         sceneCapture->TextureTarget = renderTarget;
         sceneCapture->UpdateContent();
         lastPhoto = sceneCapture->TextureTarget->ConstructTexture2D(this, "Photo", EObjectFlags::RF_NoFlags, CTF_DeferCompression);
     }
 };

Note: I haven’t tried to compile my code snippet, so i don’t guarantee that it will work, but this is roughly what I would do to address the problem.

Hi heapcleaner - trying to understand unreal’s object model better here: I saw that renderTarget was not a UPROPERTY (my first guess at the problem) but figured this would be okay because it was a UPROPERTY in USceneCaptureComponent2D and that would hang onto a reference and prevent garbage collection - is that not the case here?

yes, just like what you said, renderTarget will not be garbage collected because it is referenced in USceneCaptureComponent2D, and I didn’t mentioned that as a flaw because it isn’t, what I explained to you is that the USceneCaptureComponent2D::TextureTarget is not export-able.

By default reference to an object is serialized as a string containing the path to that object. But if there is an RF_Export flag in that object (which we can specify it by adding Export or Instanced property specifier) then the whole object is serialized instead of just the paths.

What had happened in your code was:

When the engine save the actor, the TextureTarget is serialized as a string to a transient render target that you have created in the constructor, this one:

     renderTarget = NewObject<UTextureRenderTarget2D>();
     renderTarget->InitAutoFormat(512, 512);
     renderTarget->UpdateResourceImmediate();

NewObject with no params means that you are using a transient package as its outermost, and as you have probably know, transient package is not saved.

And then, when the engine load the actor, it will load the TextureTarget path successfully but the path refers to a non existing object in the transient package, so it will revert the value to the USceneCaptureComponent2D CDO (thus overriding the changes that you’ve made in the constructor)

Not my code - I just came across is when looking for clues to a problem I’m having with texture creation on iOS and got curious!

Thanks for going into more detail. Guess I don’t understand why exporting matters in this case, but could understand that it might.

For the original poster - you can put a data breakpoint on the pointer to see where its getting set, warning though - the object initialization code is long and complex taking into account serialized data, contructor etc… interesting read.