Using Slate to draw a Grid Pattern to Screen - Any tips for faster line rendering?

Okay so, update time. Here’s the latest iteration. I’m still following the line route with Slate because going down the mesh route is a going to be a very hard process. This is without any culling still for the radar ‘range’, so every point is still submitted to be drawn and in this test case, there’s a total of 4,225 points in this test level with 65 rows and 65 columns - so still not a lot of points in the grand scheme of things.

e50d7fea1f3f33bd527e86a77b1a0837683e62ff.jpeg

Here’s the code now translated into C++. There are a few issues, for one thing NativePaint() is const, so I can’t create a member variable TArray<FVector2D> and just change the values (this just in, apparently I can make a ‘mutable’ member variable which will allow the changes). - so atm it creates and allocates the array many times per-frame. So, changes so far:

  • Moved Paint function to C++
  • Changed all Get functions for the Vertex Buffer to be FORCEINLINE
  • Overwrote DrawLines() and created new inline version, which doesn’t call InContext.MaxLayer++ each time it’s called
  • Turned off Anti-Aliasing

I tried to go into FSlateDrawElements::MakeLines() to actually copy the code that’s there to prevent function call overhead, but unfortunately ‘Init’ is a private member of that class for some reason (annoying) - so I cannae do that. Definitely room for speed improvements here.

BZGame_TopoRadar.h



UCLASS()
class BZGAME_API UBZGame_TopoRadar : public UBZGame_BaseWidget
{
	GENERATED_BODY()
	
public:
	UBZGame_TopoRadar(const FObjectInitializer& ObjectInitializer);

	virtual void NativeConstruct() override;
	virtual void NativePaint(FPaintContext& InContext) const override;

protected:

	int32 NumRows, NumColumns;

// Can't modify because OnPaint() is const :(
// 	TArray<FVector2D> DrawPoints;
// 
// 	FORCEINLINE void AddItemToPoints(const FVector2D& Point)
// 	{
// 		DrawPoints.Add(Point);
// 	}

	FORCEINLINE void Fast_DrawGrid(FPaintContext& InContext, const TArray<FVector2D>& InPoints) const
	{
		// Ideally want to be able to do whatever this does right here, but lots of FSlateDrawElement is private -.-
		FSlateDrawElement::MakeLines(
			InContext.OutDrawElements,
			InContext.MaxLayer,
			InContext.AllottedGeometry.ToPaintGeometry(),
			InPoints,
			InContext.MyClippingRect,
			ESlateDrawEffect::None,
			FLinearColor::Blue,
			false);
	}

// We want to do this, but Init() is private :(
// 		FPaintGeometry PaintGeo = InContext.AllottedGeometry.ToPaintGeometry();
// 		PaintGeo.CommitTransformsIfUsingLegacyConstructor();
// 		FSlateDrawElement& DrawElt = InContext.OutDrawElements.AddUninitialized();
// 		DrawElt.Init(0, PaintGeo, InContext.MyClippingRect, ESlateDrawEffect::None);
// 		DrawElt.ElementType = EElementType::ET_Line;
// 		DrawElt.DataPayload.SetLinesPayloadProperties(DrawPoints, FLinearColor::Blue, false, ESlateLineJoinType::Sharp);
	//}
};


BZGame_TopoRadar.cpp



DECLARE_STATS_GROUP(TEXT("Radar"), STATGROUP_Radar, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("BZ ~ Draw Radar Grid"), STAT_DrawRadar, STATGROUP_Radar);

void UBZGame_TopoRadar::NativeConstruct()
{
	Super::NativeConstruct();

	if (OwningBZHud) { OwningBZHud->SetRadarWidget(this); }

	// Cached Rows/Columns
	// Get the Game Instance. Again, can probably be cached or at least inlined
	UBZGame_GameInstance* BZGI = UBZGame_GameInstance::GetInstance(this);
	ASSERTV(BZGI != nullptr, TEXT("BZGame Instance Is Nullptr"));

	BZGI->GetRadarGridSize(NumRows, NumColumns);
}

void UBZGame_TopoRadar::NativePaint(FPaintContext& InContext) const
{
	Super::NativePaint(InContext);

	SCOPE_CYCLE_COUNTER(STAT_DrawRadar);

	// Get the Game Instance. Again, can probably be cached or at least inlined
	UBZGame_GameInstance* BZGI = UBZGame_GameInstance::GetInstance(GetWorld());
	ASSERTV(BZGI != nullptr, TEXT("BZGame Instance Is Nullptr"));

	TArray<FVector2D> DPArray;

	// Just submit all the points for now. We need to do some 'clipping' here to only get points in radar-range of the player.
	for (int32 CIdx = 0; CIdx < NumColumns; CIdx++)
	{
		for (int32 RIdx = 0; RIdx < NumRows; RIdx++)
		{
			const FVector WorldPos = BZGI->Fast_GetRadarPosAtGrid(CIdx, RIdx);
			const FVector2D ScreenP = ABZGame_InGameHUD::WorldToTopographicalRadar(WorldPos);

			DPArray.Add(ScreenP);
		}

		Fast_DrawGrid(InContext, DPArray);
		DPArray.Empty();
	}

	for (int32 RIdx = 0; RIdx < NumRows; RIdx++)
	{
		for (int32 CIdx = 0; CIdx < NumColumns; CIdx++)
		{
			const FVector WorldPos = BZGI->Fast_GetRadarPosAtGrid(CIdx, RIdx);
			FVector2D ScreenP = ABZGame_InGameHUD::WorldToTopographicalRadar(WorldPos);

			DPArray.Add(ScreenP);
		}

		Fast_DrawGrid(InContext, DPArray);
		DPArray.Empty();
	}
}


Another thing I’m doing is getting and transforming all of the points twice… If I can figure out how to draw the grid without that, that’ll save some performance too, and cut the transform cost in half.