I have a ‘FCanvasDrawer’ abstract class that takes care of pushing canvas size to the rendertarget (the class being a shameless copy of one of the UDK classes!)
Warning notice: I am not sure this is actually worth the effort. The rendering quality of FCanvas is really low, lines and polygons are not antialiased making the whole solution a bit useless to draw a HUD!!
#pragma once
typedef TSharedPtr<class FCanvasDrawer, ESPMode::ThreadSafe> FThreadSafeFCanvasDrawerPtr;
/**
*
*/
class FCanvasDrawer : public ICustomSlateElement
{
public:
FCanvasDrawer();
~FCanvasDrawer();
/** Debug - Frame counter */
int Frames;
/**
* Sets up the canvas for rendering
*/
bool BeginRenderingCanvas(const FIntRect& InCanvasRect, const FIntRect& InClippingRect, bool bInIsRealtime);
/**
* Delegates rendering method
*/
virtual void Draw(FCanvas& Canvas, const FVector2D& Size) = 0;
private:
/**
* ICustomSlateElement interface
*/
virtual void DrawRenderThread(FRHICommandListImmediate& RHICmdList, const void* InWindowBackBuffer) override;
private:
/** Render target that the canvas renders to */
class FSlateCanvasRenderTarget* RenderTarget;
/** Whether preview is using realtime values */
bool bIsRealtime;
};
CPP file:
#include "FCanvasDrawer.h"
/**
* Simple representation of the backbuffer
* This class may only be accessed from the render thread
*/
class FSlateCanvasRenderTarget : public FRenderTarget
{
public:
/** FRenderTarget interface */
virtual FIntPoint GetSizeXY() const
{
return ClippingRect.Size();
}
/** Sets the texture that this target renders to */
void SetRenderTargetTexture(FTexture2DRHIRef& InRHIRef)
{
RenderTargetTextureRHI = InRHIRef;
}
/** Clears the render target texture */
void ClearRenderTargetTexture()
{
RenderTargetTextureRHI.SafeRelease();
}
/** Sets the viewport rect for the render target */
void SetViewRect(const FIntRect& InViewRect)
{
ViewRect = InViewRect;
}
/** Gets the viewport rect for the render target */
const FIntRect& GetViewRect() const
{
return ViewRect;
}
/** Sets the clipping rect for the render target */
void SetClippingRect(const FIntRect& InClippingRect)
{
ClippingRect = InClippingRect;
}
/** Gets the clipping rect for the render target */
const FIntRect& GetClippingRect() const
{
return ClippingRect;
}
private:
FIntRect ViewRect;
FIntRect ClippingRect;
};
/* --------------------FCanvasDrawer-------------------------- */
FCanvasDrawer::FCanvasDrawer()
: RenderTarget(new FSlateCanvasRenderTarget)
, bIsRealtime(false)
, Frames(0)
{
}
FCanvasDrawer::~FCanvasDrawer()
{
delete RenderTarget;
}
bool FCanvasDrawer::BeginRenderingCanvas(const FIntRect& InCanvasRect, const FIntRect& InClippingRect, bool bInIsRealtime)
{
if (InCanvasRect.Size().X > 0 && InCanvasRect.Size().Y > 0 && InClippingRect.Size().X > 0 && InClippingRect.Size().Y > 0)
{
/**
* Struct to contain all info that needs to be passed to the render thread
*/
struct FRenderInfo
{
/** Size of the Canvas tile */
FIntRect CanvasRect;
/** How to clip the canvas tile */
FIntRect ClippingRect;
/** Whether preview is using realtime values */
bool bIsRealtime;
};
FRenderInfo RenderInfo;
RenderInfo.CanvasRect = InCanvasRect;
RenderInfo.ClippingRect = InClippingRect;
RenderInfo.bIsRealtime = bInIsRealtime;
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER
(
BeginRenderingSlateCanvas,
FCanvasDrawer*, CanvasDrawer, this,
FRenderInfo, InRenderInfo, RenderInfo,
{
CanvasDrawer->RenderTarget->SetViewRect(InRenderInfo.CanvasRect);
CanvasDrawer->RenderTarget->SetClippingRect(InRenderInfo.ClippingRect);
CanvasDrawer->bIsRealtime = InRenderInfo.bIsRealtime;
}
);
return true;
}
return false;
}
void FCanvasDrawer::DrawRenderThread(FRHICommandListImmediate& RHICmdList, const void* InWindowBackBuffer)
{
Frames++;
// Clip the canvas to avoid having to set UV values
FIntRect ClippingRect = RenderTarget->GetClippingRect();
RHICmdList.SetScissorRect(true,
ClippingRect.Min.X,
ClippingRect.Min.Y,
ClippingRect.Max.X,
ClippingRect.Max.Y);
RenderTarget->SetRenderTargetTexture(*(FTexture2DRHIRef*)InWindowBackBuffer);
{
// Check realtime mode for whether to pass current time to canvas
float CurrentTime = bIsRealtime ? (FApp::GetCurrentTime() - GStartTime) : 0.0f;
float DeltaTime = bIsRealtime ? FApp::GetDeltaTime() : 0.0f;
FCanvas Canvas(RenderTarget, NULL, CurrentTime, CurrentTime, DeltaTime, GMaxRHIFeatureLevel);
{
Canvas.SetAllowedModes(0);
Canvas.SetRenderTargetRect(RenderTarget->GetViewRect());
// Delegate drawing to child classes...
Draw(Canvas, RenderTarget->GetSizeXY());
}
Canvas.Flush_RenderThread(RHICmdList, true);
}
RenderTarget->ClearRenderTargetTexture();
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
}
Once you’ve got that, create a inherited class to actually draw something (in my case, a radar):
class FRadarDrawer : public FCanvasDrawer
{
public:
FRadarDrawer() {}
~FRadarDrawer() {}
private:
void DrawCircle(FCanvas& Canvas, const FVector2D& Position, const float& Radius)
{
const int N = 24;
float angleInc = PI * 2 / (float)N;
float angle = 0;
for (int i = 0; i < N; i++)
{
FVector2D start(Radius * cos(angle), Radius * sin(angle));
FVector2D end(Radius * cos(angle + angleInc), Radius * sin(angle + angleInc));
FCanvasLineItem lineItem(Position + start, Position + end);
lineItem.LineThickness = 1;
lineItem.SetColor(FLinearColor::Green);
Canvas.DrawItem(lineItem);
angle += angleInc;
}
}
/**
* ICustomSlateElement interface
*/
virtual void Draw(FCanvas& Canvas, const FVector2D& Size) override
{
if (Style != NULL)
{
const FVector2D Center(Size.X * 0.5f, Size.Y * 0.5f);
DrawCircle(Canvas, Center, 0.5f * Size.X);
DrawCircle(Canvas, Center, 0.4f * Size.X);
DrawCircle(Canvas, Center, 0.3f * Size.X);
DrawCircle(Canvas, Center, 0.2f * Size.X);
}
}
};
Drawing is done, now we need to include that ‘radar’ in a Slate element to get nice UI layout management done for us!
#pragma once
typedef TSharedPtr<class FRadarDrawer, ESPMode::ThreadSafe> FThreadSafeFRadarDrawerPtr;
/**
*
*/
class SRadarWidget : public SLeafWidget
{
public:
SRadarWidget();
~SRadarWidget();
SLATE_BEGIN_ARGS(SRadarWidget)
{}
SLATE_END_ARGS()
/** Constructs this widget with InArgs */
void Construct(const FArguments& InArgs);
private:
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
virtual FVector2D ComputeDesiredSize() const override
{
return FVector2D(512,512);
}
FThreadSafeFRadarDrawerPtr Drawer;
};
and cpp:
#include "FRadarDrawer.h" // could even be inlined in this class...
#include "SRadarWidget.h"
SRadarWidget::SRadarWidget()
: Drawer(new FRadarDrawer())
{
}
SRadarWidget::~SRadarWidget()
{
// Pass the preview element to the render thread so that it's deleted after it's shown for the last time
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER
(
SafeDeletePreviewElement,
FThreadSafeFCanvasDrawerPtr, DrawerPtr, Drawer,
{
DrawerPtr.Reset();
}
);
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SRadarWidget::Construct(const FArguments& InArgs)
{
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
int32 SRadarWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
FSlateRect SlateCanvasRect = AllottedGeometry.GetClippingRect();
FSlateRect ClippedCanvasRect = SlateCanvasRect.IntersectionWith(MyClippingRect);
FIntRect CanvasRect(
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Left)),
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Top)),
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Right)),
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Bottom)));
FIntRect ClippingRect(
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Left)),
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Top)),
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Right)),
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Bottom)));
if (Drawer->BeginRenderingCanvas(CanvasRect, ClippingRect, true))
{
// Draw above everything else (not needed - mostly for debugging)
uint32 TopLayer = LayerId + 1;
FSlateDrawElement::MakeCustom(OutDrawElements, TopLayer, Drawer);
}
}
return LayerId;
}