Could I get some help with CanvasRenderTarget2D?

Hi all! Just got the new preview code for 4.2 and got it compiled to try the new UCanvasRenderTarget2D. But I can’t make it work.

My class’ header :

#pragma once

#include "GameFramework/Actor.h"
#include "TestTarget.generated.h"

UCLASS()
class ATestTarget : public AActor
{
	GENERATED_UCLASS_BODY()
public:
	UFUNCTION()
	virtual void OnReceiveUpdate(class UCanvas* Canvas, int32 Width, int32 Height);

	UPROPERTY(BlueprintReadOnly, Category = TestTarget)
	class UCanvasRenderTarget2D* CanvasTarget;

	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = TestTarget)
	UTexture* TestTile;
};

And cpp file :

#include "MyFlightSim.h" // Placeholder game name
#include "TestTarget.h"

#include "Engine/CanvasRenderTarget2D.h"

ATestTarget::ATestTarget(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
	CanvasTarget = UCanvasRenderTarget2D::CreateCanvasRenderTarget2D(UCanvasRenderTarget2D::StaticClass(), 1024, 1024);
	CanvasTarget->OnCanvasRenderTargetUpdate.AddDynamic(this, &ATestTarget::OnReceiveUpdate);
	CanvasTarget->ClearColor = FLinearColor::Black;
}

void ATestTarget::OnReceiveUpdate(class UCanvas* Canvas, int32 Width, int32 Height)
{
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("TestTarget Received Update!"));
	Canvas->SetDrawColor(0, 255, 0);
	Canvas->DrawTile(TestTile, 0, 0, Width, Height, 0, 0, TestTile->GetSurfaceWidth(), TestTile->GetSurfaceHeight());
}

But the message is never shown! I inherit from this Actor in Blueprint with a root StaticMesh component (Cube Shape) with a simple instance from a material which has a single Texture2D parameter. In the Blueprint’s construction graph, I assign to this parameter the property CanvasTarget. The ClearColor is not even updated!

Please help!

I’ve notified the author of this update about your concern.

It might take a few days but he should get back to you

Rama

You should not be using the Actor’s C++ constructor to create the canvas render target.

Try overriding AActor::BeginPlay() and putting it in there instead.

Thanks! Now the event is called but there is still nothing to see…Could anyone post a quick Canvas draw listing for a test, please?

Tried it on DrawTexture, but is DrawTile better?

Pretty sure DrawTile now uses normalized coordinates for UV.

The UpdateResource was called from the Blueprint in a BeginPlay event, but I moved it to the class’ BeginPlay function. I assign a texture to TestTile in the Blueprint’s Defaults.

Hmm, I’m looking at your code and what is it that calls UCanvasRenderTarget2D::UpdateResource()? I also assume that TestTile is also valid?

I haven’t yet synched with 4.2 preview yet, but I’ll check it out when it has been released. However, when I was developing this feature it was working for me for both C++ and Blueprints.

I made some changes to my code and the Blueprint now does nothing except assigning default values, setting the root component to be a Cube and assigning the target to my material instance.

The new class cpp :
UCanvasRenderTarget2D* ATestTarget::ConstructTarget(int32 Width, int32 Height)
{
if (Width > 0 && Height > 0)
{
UCanvasRenderTarget2D* NewCanvasRenderTarget = ConstructObject(UCanvasRenderTarget2D::StaticClass(), GetTransientPackage());
if (NewCanvasRenderTarget)
{
NewCanvasRenderTarget->InitAutoFormat(Width, Height);
return NewCanvasRenderTarget;
}
}

	return nullptr;
}

ATestTarget::ATestTarget(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{
}

void ATestTarget::OnReceiveUpdate(class UCanvas* Canvas, int32 Width, int32 Height)
{
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("TestTarget Received Update!"));
}

void ATestTarget::BeginPlay()
{
	Super::BeginPlay();
	CanvasTarget = ConstructTarget(1024, 1024);
	CanvasTarget->OnCanvasRenderTargetUpdate.AddDynamic(this, &ATestTarget::OnReceiveUpdate);
	CanvasTarget->ClearColor = FLinearColor::Black;
	CanvasTarget->UpdateResource();

	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("TestTarget Received BeginPlay!"));
}

The ConstructTarget is a static function that is copied from the UCanvasRenderTarget2D::CreateCanvasRenderTarget2D because, for unknown reasons, the parameters are garbage (used VS2013 debugger to find out , i.e. Width = 44 & Height = -12754). My debug messages are shown but the material is not black (it is brownish). Made some checks using the debugger and the UCanvasRenderTarget2D functions don’t seem to be called properly (null variables when a value is expected, weird parameters). I’m completely lost but there is progress.

Here is a minimum test case example that I quickly whipped up to test.

BlankCPPHUD.h:

#pragma once

#include "RenderUtils.h"
#include "Engine/CanvasRenderTarget2D.h"
#include "GameFramework/HUD.h"
#include "BlankCPPHUD.generated.h"

/**
 * 
 */
UCLASS()
class ABlankCPPHUD : public AHUD
{
	GENERATED_UCLASS_BODY()

public:
	/** PostRender is the main draw loop. */
	virtual void PostRender() OVERRIDE;

	/** Delegate bound to the canvas render target to draw onto it. */
	UFUNCTION()
	virtual void DrawCanvasRenderTarget(class UCanvas* RenderTargetCanvas, int32 Width, int32 Height);

	/** Pointer to the canvas render target. */
	UPROPERTY()
	class UCanvasRenderTarget2D* MyCanvasRenderTarget;
};

BlankCPPHUD.cpp:

#include "BlankCPP.h"
#include "BlankCPPHUD.h"

ABlankCPPHUD::ABlankCPPHUD(const class FPostConstructInitializeProperties& PCIP)
: 
Super(PCIP)
{
	MyCanvasRenderTarget = nullptr;
}

void ABlankCPPHUD::PostRender()
{
	Super::PostRender();

	if (MyCanvasRenderTarget == nullptr)
	{
		MyCanvasRenderTarget = UCanvasRenderTarget2D::CreateCanvasRenderTarget2D(UCanvasRenderTarget2D::StaticClass(), 256, 256);

		if (MyCanvasRenderTarget != nullptr)
		{
			MyCanvasRenderTarget->OnCanvasRenderTargetUpdate.AddDynamic(this, &ABlankCPPHUD::DrawCanvasRenderTarget);
			MyCanvasRenderTarget->UpdateResource();
		}
	}
	
	if (MyCanvasRenderTarget != nullptr)
	{
		Canvas->SetDrawColor(255, 255, 255);
		Canvas->DrawTile(MyCanvasRenderTarget, 0.0f, 0.0f, MyCanvasRenderTarget->GetSurfaceWidth(), MyCanvasRenderTarget->GetSurfaceHeight(), 0.0f, 0.0f, MyCanvasRenderTarget->GetSurfaceWidth(), MyCanvasRenderTarget->GetSurfaceHeight(), BLEND_Opaque);
	}
}

void ABlankCPPHUD::DrawCanvasRenderTarget(class UCanvas* RenderTargetCanvas, int32 Width, int32 Height)
{
	if (RenderTargetCanvas != nullptr)
	{
		FCanvasTileItem WhiteBox = FCanvasTileItem(FVector2D(0.0f, 0.0f), GWhiteTexture, FVector2D(Width, Height), FLinearColor(1.0f, 1.0f, 1.0f));
		RenderTargetCanvas->DrawItem(WhiteBox);

		FCanvasTileItem RedBox = FCanvasTileItem(FVector2D(Width * 0.1f, Height * 0.1f), GWhiteTexture, FVector2D(Width * 0.8f, Height * 0.8f), FLinearColor(1.0f, 0.0f, 0.0f));
		RenderTargetCanvas->DrawItem(RedBox);

		FCanvasTileItem GreenBox = FCanvasTileItem(FVector2D(Width * 0.2f, Height * 0.2f), GWhiteTexture, FVector2D(Width * 0.6f, Height * 0.6f), FLinearColor(0.0f, 1.0f, 0.0f));
		RenderTargetCanvas->DrawItem(GreenBox);

		FCanvasTileItem BlueBox = FCanvasTileItem(FVector2D(Width * 0.3f, Height * 0.3f), GWhiteTexture, FVector2D(Width * 0.4f, Height * 0.4f), FLinearColor(0.0f, 0.0f, 1.0f));
		RenderTargetCanvas->DrawItem(BlueBox);
	}
}

If you wish to copy this code into your project, make sure to add RenderCore to PrivateDependencyModuleNames array in your project’s build configuration. ClearColor is also only an inherited variable from Render Targets and are not used in Canvas Render Target.

I get the feeling that it may be due to the mixture of Blueprints and C++ causing the problems as I only tested either a full Blueprint method or a full C++ method. However, I can’t imagine that it would cause too many issues if the order of operations is done correctly.

Lastly remember when you debug things to ensure you are running in Debug Editor. Debugging in Development Editor usually leads to Visual Studio reporting strange values for variables.

Hopefully this code examples helps you to solve the problem on your end.

Cool! This is working! Now, how do I assign this material to an in-game Actor?

I found a way to make it work, thanks to @Solid-Snake! Here is my Actor’s C++ code for future reference.

TestTarget.h :

#pragma once

#include "GameFramework/Actor.h"
#include "TestTarget.generated.h"

UCLASS()
class ATestTarget : public AActor
{
	GENERATED_UCLASS_BODY()
public:
	UFUNCTION()
	virtual void OnReceiveUpdate(class UCanvas* Canvas, int32 Width, int32 Height);

	virtual void BeginPlay() override;

	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = Target)
	int32 SurfaceWidth;

	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Category = Target)
	int32 SurfaceHeight;

	UPROPERTY(BlueprintReadWrite, Category = Target)
	class UMaterialInstanceDynamic* MaterialInstance;

	UPROPERTY(EditDefaultsOnly, Category = Target)
	FName ParameterName;

protected:
	UPROPERTY()
	class UCanvasRenderTarget2D* CanvasTarget;
};

TestTarget.cpp :

#include "MyFlightSim.h" // Placeholder Game Name
#include "TestTarget.h"

#include "Engine/CanvasRenderTarget2D.h"
#include "RenderUtils.h"

ATestTarget::ATestTarget(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP), SurfaceWidth(512), SurfaceHeight(512), MaterialInstance(nullptr), ParameterName(TEXT("Texture"))
{
}

void ATestTarget::BeginPlay()
{
	Super::BeginPlay();
	CanvasTarget = UCanvasRenderTarget2D::CreateCanvasRenderTarget2D(UCanvasRenderTarget2D::StaticClass(), SurfaceWidth, SurfaceHeight);
	CanvasTarget->OnCanvasRenderTargetUpdate.AddDynamic(this, &ATestTarget::OnReceiveUpdate);
	CanvasTarget->UpdateResource();

	if (MaterialInstance != nullptr)
		MaterialInstance->SetTextureParameterValue(ParameterName, CanvasTarget);

	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("TestTarget Received BeginPlay!"));
}

void ATestTarget::OnReceiveUpdate(class UCanvas* Canvas, int32 Width, int32 Height)
{
	FCanvasTileItem WhiteBox = FCanvasTileItem(FVector2D(0.0f, 0.0f), GWhiteTexture, FVector2D(Width, Height), FLinearColor(1.0f, 1.0f, 1.0f));
	Canvas->DrawItem(WhiteBox);

	FCanvasTileItem RedBox = FCanvasTileItem(FVector2D(Width * 0.1f, Height * 0.1f), GWhiteTexture, FVector2D(Width * 0.8f, Height * 0.8f), FLinearColor(1.0f, 0.0f, 0.0f));
	Canvas->DrawItem(RedBox);

	FCanvasTileItem GreenBox = FCanvasTileItem(FVector2D(Width * 0.2f, Height * 0.2f), GWhiteTexture, FVector2D(Width * 0.6f, Height * 0.6f), FLinearColor(0.0f, 1.0f, 0.0f));
	Canvas->DrawItem(GreenBox);

	FCanvasTileItem BlueBox = FCanvasTileItem(FVector2D(Width * 0.3f, Height * 0.3f), GWhiteTexture, FVector2D(Width * 0.4f, Height * 0.4f), FLinearColor(0.0f, 0.0f, 1.0f));
	Canvas->DrawItem(BlueBox);

	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("TestTarget Received Update!"));
}

To make it work, create a material with a named Texture2DParameter, assign it to an Actor Blueprint subclassing this class, adjust the defaults (surface size and parameter name). In the Construction Script, create a DynamicMaterialInstance and assign it to the property MaterialInstance.

I might be doing a Wiki page on this later. Thanks, awesome community!