Whats the most efficient way to draw a bunch of lines?

Hello, I’m currently using Draw Debug Line in a for loop to draw a few line graphs on the wall in my level with roughly 10k points between all the lines. I’m sure this function was never really intended for this and its dropping my frame rate by about 10-20 FPS. My data comes from arrays and I’ll soon need to render more of these. Is there a more efficient way to draw a series of lines without a bunch of debug line calls?

Whats the purpose of the lines?

I’m currently plotting audio analysis data for a plugin i’m working on. so some lines are for wave data, spectrum data, octave data, and some plot debug info over time (ex: rhythm analysis data) etc.

long term though I hope to include lines in texture generation and other visualization content. short term, it would be nice to fill my “debug room” with usable content and not drop down to 5FPS.

Does this usage info help? I’m not sure if the FPS bottleneck is in the number of draw calls (dx9 used to have that issue, dont think dx11 does) or in the way that debugLine generates verts, or perhaps I just have too many? Alternatives would be nice.

Hi,

There is functionality to draw a ‘batch’ of lines, but it’s not exposed directly at the moment.

If you look at the implementation of DrawDebugBox (in DrawDebugHelpers.cpp), you can see that it uses ULineBatchComponent::DrawLines. Adding a wrapper function that takes an arbitrary set of lines and calls this function should be fairly trivial (using DrawDebugBox as an example).

As an added advantage you can probably pre-allocate your FBatchedLine array as from your use case it sounds like you know how many lines you want to draw in advance.

Thanks! It works perfectly and is in fact MUCH faster! :slight_smile:

Here is what I am calling from blueprints, I hope it helps someone. Tweak to your needs.

header:

UFUNCTION(BlueprintCallable, Category = "Nebula_Render_Helper")
		void RenderDataArrayAsLineBatch(const TArray<float> & LineData, // Data
		const FVector & Start, const FVector & End, // location
		int32 StartIndex = 0, int32 EndIndex = -1, // Data range Index(-1 means till end)
		const FVector Up = FVector::UpVector, float LineHeightScale = 1, // rotation
		const FLinearColor StartColor = FLinearColor::White, const FLinearColor EndColor = FLinearColor::White, // colors
		uint8 DepthPriority = 0, const float Thickness = 0, const float LifeTime = 0);// misc.

And the CPP portion:

void UNebulaAudioAnalyzer::RenderDataArrayAsLineBatch(const TArray<float> & LineData, 
    	const FVector & Start, const FVector & End, 
    	int32 StartIndex, int32 EndIndex,
    	const FVector Up, float LineHeightScale, 
    	const FLinearColor StartColor, const FLinearColor EndColor, 
    	uint8 DepthPriority, const float Thickness, const float LifeTime)
    {
    	ULineBatchComponent* const LineBatcher = GetWorld()->ForegroundLineBatcher;
    
    	if (LineBatcher != nullptr)
    	{
    		StartIndex = fmaxf(1, StartIndex); // ensure minimum of 0, adjust it to one, this is a special starting point.
    		
    		// check special case, if end if less than start, make it remaining length.
    		if (EndIndex < StartIndex)
    			EndIndex = LineData.Num();
    
    		float len = fmaxf(1, EndIndex - StartIndex);
    
    		for (int i = StartIndex; i < EndIndex; i++)
    		{
    			float lerpVal = (i - StartIndex) / len;
    			float PrevlerpVal = ((i - 1) - StartIndex) / len;
    
    			FLinearColor LineColor = FMath::Lerp(StartColor, EndColor, lerpVal);
    			FVector LineStart = FMath::Lerp(Start, End, PrevlerpVal);
    			FVector LineEnd = FMath::Lerp(Start, End, lerpVal);
    			LineStart += Up * LineData[i - 1] * LineHeightScale;
    			LineEnd += Up * LineData[i] * LineHeightScale;
    
    			LineBatcher->DrawLine(LineStart, LineEnd, LineColor, DepthPriority, Thickness, LifeTime);
    		}
    	}
    }