How to write to UCanvasRenderTarget2D in C++

I have a pretty simple setup for a RenderCanvasTarget2D in Blueprint which I am trying to recreate in C++, but it’s not working for some reason.

Here is the BP version:

In a BP derived from RenderCanvasTarget2D, this is the ‘Event Receive Update’ delegate function:

And then in a BP actor, I have this in BeginPlay:

This successfully creates a CanvasRenderTarget2D of 128x128 pixels, writes a diagonal line across it, and saves it to the hard disk. All working perfectly.

In the C++ version, I am doing everything in a single actor called ‘HDRWriter’. Here is the full relevant code:

HDRWriter.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Runtime/Engine/Classes/Engine/CanvasRenderTarget2D.h"
#include "Runtime/Core/Public/Delegates/Delegate.h"
#include "Runtime/Engine/Classes/Engine/Canvas.h"
#include "Runtime/Engine/Classes/Kismet/KismetRenderingLibrary.h"
#include "HDRWriter.generated.h"

UCLASS()
class DRAWLINEBP_API AHDRWriter : public AActor
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    AHDRWriter();

    // The Canvas Render Target 2D
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UCanvasRenderTarget2D* CanvasRenderTarget;

    // Update Function
    UFUNCTION()
    void WhenCanvasUpdated(UCanvas* Canvas, int32 Width, int32 Height);

    UFUNCTION(BlueprintCallable)
    void CallUpdate();

    UFUNCTION(BlueprintCallable)
    void SetupCanvas();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

};

HDRWriter.cpp

#include "HDRWriter.h"

// Sets default values
AHDRWriter::AHDRWriter()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = false;
}

void AHDRWriter::WhenCanvasUpdated(UCanvas* Canvas, int32 Width, int32 Height)
{
    Canvas->K2_DrawLine(FVector2D(0.0f, 0.0f), FVector2D(Width, Height));

    UKismetRenderingLibrary::ExportRenderTarget(GEngine->GetWorld(), CanvasRenderTarget, "D:\\", "CPPLine.hdr");
}

void AHDRWriter::CallUpdate()
{
    CanvasRenderTarget->UpdateResource();
}

void AHDRWriter::SetupCanvas()
{
    CanvasRenderTarget = UCanvasRenderTarget2D::CreateCanvasRenderTarget2D(GEngine->GetWorld(), UCanvasRenderTarget2D::StaticClass(), 128, 128);

    CanvasRenderTarget->OnCanvasRenderTargetUpdate.AddDynamic(this, &AHDRWriter::WhenCanvasUpdated);
}

// Called when the game starts or when spawned
void AHDRWriter::BeginPlay()
{
    Super::BeginPlay();
}

// Called every frame
void AHDRWriter::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

So I run SetupCanvas to create the CanvasRenderTarget2D and bind a function (WhenCanvasUpdated) to the delegate OnCanvasRenderTargetUpdate. When I then call CallUpdate() it calls UpdateResource() on the CanvasRenderTarget.

When the function WhenCanvasUpdated() is then run, it should draw a line and save the file to the hard disk, as with the Blueprint version.

However, a file is indeed saved, but it is totally black - no line is drawn. I feel like I’m missing some important connection between the RenderCanvasTarget and the UCanvas that is passed in the delegate function, but I can’t see how they should be connected.

I tried nativizing the BP into C++ code, but found it to be completely illegible.

Does anyone know what I’m doing wrong here?

Thanks!

Got the same problem as you! Did you find a solution?

Have you tried calling UpdateResources() after K2_DrawLine() before you ExportRenderTarget?

That will force the canvas to redraw.

Is there a reason you’re using OnCanvasRenderTargetUpdate rather than manually controlling the drawing yourself?
I’m using a CanvasRenderTarget2D successfully in C++, but my flow looks like:

UCanvas * canvas;
FVector2D canvasSize;
FDrawToRenderTargetContext context;

FVector location = GetActorLocation();

UKismetRenderingLibrary::BeginDrawCanvasToRenderTarget(this, MiniMapCanvasRenderTarget, canvas, canvasSize, context);
FVector2D lineLocStart = FVector2D(location.X / 300 * 16, location.Y / 300 * 16);
FVector2D lineLocEnd = lineLocStart + FVector2D(15, 0);
canvas->K2_DrawLine(lineLocStart, lineLocEnd, 2.0f, FLinearColor::Yellow);
UKismetRenderingLibrary::EndDrawCanvasToRenderTarget(this, context);

Granted, I’m not using ExportRenderTarget, but rather generating a dynamic material instance with the contents, but still, it’s properly updating the canvas.

1 Like