Something went wrong on our end
Select Git revision
GPUInstancedLineComponent.cpp
-
David Gilbert authoredDavid Gilbert authored
GPUInstancedLineComponent.cpp 53.10 KiB
// Fill out your copyright notice in the Description page of Project Settings.
#include "GPUInstancedLineComponent.h"
#include "InstancedMeshLineRendering.h" // Only needed for logging
//#pragma optimize("", on)
UGPUInstancedLineComponent::UGPUInstancedLineComponent(const FObjectInitializer& ObjectInitializer)
{
NumCustomDataFloats = 5;
CurrentTextureMarker = FIntPoint(0, 0);
CurrentTextureIndex = 0,
//NextFreeId = 0;
TextureWidth = 2048;
TextureHeight = 2048;
SetWorldTransform(FTransform::Identity);
SetUsingAbsoluteLocation(true);
SetCastShadow(false);
SetCollisionEnabled(ECollisionEnabled::NoCollision);
SetGenerateOverlapEvents(false);
SetCanEverAffectNavigation(false);
static ConstructorHelpers::FObjectFinder<UStaticMesh>LineMeshAsset(TEXT("StaticMesh'/InstancedMeshLineRendering/line_subdivided5.line_subdivided5'"));
static ConstructorHelpers::FObjectFinder<UMaterial>LineMaterialAsset(TEXT("Material'/InstancedMeshLineRendering/DynamicLineMaterial.DynamicLineMaterial'"));
UStaticMesh* LineAsset = LineMeshAsset.Object;
UMaterial* LineMaterial = LineMaterialAsset.Object;
SetStaticMesh(LineAsset);
SetMaterial(0, LineMaterial);
LineMaterialInterface = LoadObject<UMaterialInterface>(NULL, TEXT("/InstancedMeshLineRendering/DynamicLineMaterial.DynamicLineMaterial"), NULL, LOAD_None, NULL);
//DynamicLineMaterial = UMaterialInstanceDynamic::Create(LineMaterialInterface, GetTransientPackage());
SetMaterial(0, DynamicLineMaterial);
//SetMobility(EComponentMobility::Static);
#if WITH_EDITOR
bAutoActivate = true;
PrimaryComponentTick.bCanEverTick = true;
bTickInEditor = true;
SetComponentTickEnabled(true);
#else
PrimaryComponentTick.bCanEverTick = false;
bTickInEditor = false;
SetComponentTickEnabled(false);
#endif
}
UGPUInstancedLineComponent::~UGPUInstancedLineComponent()
{
ReleaseData();
}
// Not implemented yet because the data structure isn't clear yet.
void UGPUInstancedLineComponent::ReleaseData()
{
}
void UGPUInstancedLineComponent::UpdateWholeTexture() const
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::UpdateWholeTexture"))
// todo optimize height computation
FUpdateTextureRegion2D* Region = new FUpdateTextureRegion2D(
0,
0,
0,
0, FMath::Min(LinearLineData.Num(), TextureWidth), FMath::CeilToInt(static_cast<float>(LinearLineData.Num()) / static_cast<float>(TextureWidth)));
TArray<FVector4>* TextureData = new TArray<FVector4>(LinearLineData);
// Needed for packaging - works without this in the editor but crashes when packaging.
TextureData->SetNumUninitialized(TextureWidth * TextureHeight, false);
PositionTexture->UpdateTextureRegions(0, 1, Region, TextureWidth * sizeof(FVector4), sizeof(FVector4), (uint8*)TextureData->GetData(),
[](auto InTextureData, auto InRegions)
{
delete InTextureData;
delete InRegions;
});
}
FLinearColor UGPUInstancedLineComponent::GetLineColor(int32 LineId)
{
const float R = PerInstanceSMCustomData[LineMap[LineId].IndexArray[0].InstanceIndex * NumCustomDataFloats];
const float G = PerInstanceSMCustomData[LineMap[LineId].IndexArray[0].InstanceIndex * NumCustomDataFloats + 1];
const float B = PerInstanceSMCustomData[LineMap[LineId].IndexArray[0].InstanceIndex * NumCustomDataFloats + 2];
return { R, G, B };
}
int32 UGPUInstancedLineComponent::AddNewSegmentInstance(const FLinearColor& Color, float Width, int32 Index)
{
// Add with a dummy transform
const int32 InstanceId = AddInstance(FTransform::Identity);
SetCustomDataValue(InstanceId, 0, Color.R, false);
SetCustomDataValue(InstanceId, 1, Color.G, false);
SetCustomDataValue(InstanceId, 2, Color.B, false);
SetCustomDataValue(InstanceId, 3, Width / 2.0f, false);
SetCustomDataValue(InstanceId, 4, static_cast<float>(Index), false); // Segment Start
return InstanceId;
}
void UGPUInstancedLineComponent::UpdateTexture(const FIntPoint& StartIndex, int32 NumberOfPoints, uint8* SrcData, bool bMarkRenderStateDirty)
{
int32 NumRegions = 0;
FUpdateTextureRegion2D* Regions = CalculateTextureRegions(StartIndex, NumberOfPoints, NumRegions);
const uint32 Pitch = TextureWidth;
PositionTexture->UpdateTextureRegions(0, NumRegions, Regions, Pitch * sizeof(FVector4), sizeof(FVector4), SrcData,
[](auto InTextureData, auto InRegions)
{
// Clean up the copied data
delete InTextureData;
delete InRegions;
});
// Probably not a good place to call this
if(bMarkRenderStateDirty)
MarkRenderStateDirty();
}
void UGPUInstancedLineComponent::Init()
{
if (PositionTexture == nullptr)
{
UE_LOG(LogLineRendering, Display, TEXT("UGPUInstancedLineComponent::Init Creating Texture"));
PositionTexture = UTexture2D::CreateTransient(TextureWidth, TextureHeight, PF_A32B32G32R32F);
//// Allocate the texture RHI
PositionTexture->UpdateResource();
UpdateWholeTexture();
}
if (DynamicLineMaterial == nullptr) // FOR WHATEVER REASON I HAVE NO IDEA
{
//UE_LOG(LogLineRendering, Display, TEXT("UGPUInstancedLineComponent::Init MID was nullptr!"));
DynamicLineMaterial = UMaterialInstanceDynamic::Create(LineMaterialInterface, GetTransientPackage());
SetMaterial(0, DynamicLineMaterial);
}
DynamicLineMaterial->SetTextureParameterValue("PositionTexture", PositionTexture);
DynamicLineMaterial->SetScalarParameterValue("TextureWidth", TextureWidth);
//UpdateWholeTexture();
bIsInitialized = true;
}
void UGPUInstancedLineComponent::UpdateAllEditorLines()
{
for (const FEditorLineData& EditorLine : EditorLines)
{
if (EditorLine.RespectiveLineId != -1)
{
UpdateLineFromEditorData(EditorLine);
}
else
{
UE_LOG(LogLineRendering, Error, TEXT("UGPUInstancedLineComponent::UpdateAllEditorLines : An editor line wasn't registered before updating it, ignoring!"));
}
}
}
FUpdateTextureRegion2D* UGPUInstancedLineComponent::CalculateTextureRegions(const FIntPoint& StartIndex,
int32 NumberOfPoints, int32& NumberOfRegionsOut)
{
// Calculate the first consecutive region:
int32 RemainingPoints = NumberOfPoints;
const uint32 TextureWidthU = static_cast<uint32>(TextureWidth);
TArray<FUpdateTextureRegion2D>* Regions = new TArray<FUpdateTextureRegion2D>();
Regions->AddZeroed(1);
(*Regions)[0].DestX = StartIndex.X;
(*Regions)[0].DestY = StartIndex.Y;
(*Regions)[0].SrcX = 0; //InitialTextureMarker.X;
(*Regions)[0].SrcY = 0; // InitialTextureMarker.Y;
(*Regions)[0].Width = FMath::Min(NumberOfPoints, TextureWidth - static_cast<int32>((*Regions)[0].DestX));
(*Regions)[0].Height = StartIndex.X == 0 ? (NumberOfPoints / TextureWidth) : 1;
checkf((*Regions)[0].DestX + (*Regions)[0].Width <= TextureWidthU, TEXT("Region[0] out of bounds on X. Texture: %i, %i, %i"), (*Regions)[0].DestX, (*Regions)[0].Width, PositionTexture->GetSizeX());
RemainingPoints -= (*Regions)[0].Height == 1 ? (*Regions)[0].Width : (*Regions)[0].Width * (*Regions)[0].Height;
if(RemainingPoints == 0)
{
NumberOfRegionsOut = 1;
return Regions->GetData();
}
Regions->AddZeroed(1);
(*Regions)[1].DestX = 0;
(*Regions)[1].DestY = (*Regions)[0].DestY + (*Regions)[0].Height;
(*Regions)[1].SrcX = NumberOfPoints - RemainingPoints; // InitialTextureMarker.Y;
(*Regions)[1].SrcY = 0; //InitialTextureMarker.X;
(*Regions)[1].Width = FMath::Min(RemainingPoints,TextureWidth - static_cast<int32>((*Regions)[1].DestX));
RemainingPoints -= (*Regions)[1].Width;
(*Regions)[1].Height = RemainingPoints == 0 ? 1 : (RemainingPoints / TextureWidth);
RemainingPoints -= (*Regions)[1].Width * ((*Regions)[1].Height - 1);
checkf((*Regions)[1].DestX + (*Regions)[1].Width <= TextureWidthU, TEXT("Region[1] out of bounds on X. Texture: %i, %i, %i"), (*Regions)[1].DestX, (*Regions)[1].Width, PositionTexture->GetSizeX());
if (RemainingPoints == 0)
{
NumberOfRegionsOut = 2;
return Regions->GetData();
}
else if (RemainingPoints >= TextureWidth)
UE_LOG(LogLineRendering, Fatal, TEXT("UGPUInstancedLineComponent::CalculateTextureRegions : Calculation went wrong on second region, fix code!"));
Regions->AddZeroed(1);
(*Regions)[2].DestX = 0;
(*Regions)[2].DestY = (*Regions)[1].DestY + (*Regions)[1].Height;
(*Regions)[2].SrcX = NumberOfPoints - RemainingPoints; // InitialTextureMarker.Y;
(*Regions)[2].SrcY = 0;
(*Regions)[2].Width = FMath::Min(RemainingPoints, TextureWidth - static_cast<int32>((*Regions)[2].DestX));
RemainingPoints -= (*Regions)[2].Width;
(*Regions)[2].Height = RemainingPoints == 0 ? 1 : (RemainingPoints / TextureWidth);
RemainingPoints -= (*Regions)[2].Width * ((*Regions)[1].Height - 1);
checkf((*Regions)[2].DestX + (*Regions)[2].Width <= TextureWidthU, TEXT("Region[2] out of bounds on X. Texture: %i, %i, %i"), (*Regions)[2].DestX, (*Regions)[2].Width, PositionTexture->GetSizeX());
if (RemainingPoints > 0)
UE_LOG(LogLineRendering, Fatal, TEXT("UGPUInstancedLineComponent::CalculateTextureRegions : Calculation went wrong on third region, fix code!"));
NumberOfRegionsOut = 3;
return Regions->GetData();
}
void UGPUInstancedLineComponent::BeginPlay()
{
Super::BeginPlay();
if(!PositionTexture)
{
UE_LOG(LogLineRendering, Warning, TEXT("UGPUInstancedLineComponent::BeginPlay: Component is not initialized on BeginPlay, Initializing..."));
Init();
}
}
#if WITH_EDITOR
void UGPUInstancedLineComponent::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
Init();
if (PropertyChangedEvent.Property != nullptr)
{
if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGPUInstancedLineComponent, EditorLines))
{
// A new entry has been added
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd)
{
const int32 AddedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString());
check(AddedAtIndex != INDEX_NONE);
AddLineFromEditorData(EditorLines[AddedAtIndex]);
}
// An entry has been duplicated
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::Duplicate)
{
const int32 AddedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString());
check(AddedAtIndex != INDEX_NONE);
AddLineFromEditorData(EditorLines[AddedAtIndex]);
}
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayRemove)
{
const int32 RemovedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString());
check(RemovedAtIndex != INDEX_NONE);
RemoveLineFromEditorData(EditorLineIds[RemovedAtIndex]);
//RemoveLine(EditorLines[RemovedAtIndex].RespectiveLineId);
// todo test me, depends on if the data was removed already or not - the data seems to be already removed
}
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayClear)
{
RemoveAllEditorLines();
}
}
else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FEditorLineData, Points))
{
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd)
{
const int32 AddedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString());
const int32 LineIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName().ToString());
check(AddedAtIndex != INDEX_NONE);
check(LineIndex != INDEX_NONE);
// Set the point to the previous one instead of defaulting to 0,0,0 - default to 0 if we insert it at the front
if (AddedAtIndex == 0)
EditorLines[LineIndex].Points[AddedAtIndex].Point = EditorLines[LineIndex].Points[AddedAtIndex + 1].Point;
else if(AddedAtIndex == EditorLines[LineIndex].Points.Num() - 1)
EditorLines[LineIndex].Points[AddedAtIndex].Point = EditorLines[LineIndex].Points[AddedAtIndex - 1].Point;
else
{
EditorLines[LineIndex].Points[AddedAtIndex].Point = 0.5 * EditorLines[LineIndex].Points[AddedAtIndex - 1].Point
+ 0.5 * EditorLines[LineIndex].Points[AddedAtIndex + 1].Point;
}
InsertPointWithSameColor(EditorLines[LineIndex].RespectiveLineId, AddedAtIndex, EditorLines[LineIndex].Points[AddedAtIndex].Point);
}
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::Duplicate)
{
const int32 AddedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString());
const int32 LineIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName().ToString());
check(AddedAtIndex != INDEX_NONE);
check(LineIndex != INDEX_NONE);
AddPoint(EditorLines[LineIndex].RespectiveLineId, EditorLines[LineIndex].Points[AddedAtIndex].Point);
}
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayRemove)
{
// Get Indices
const int32 RemovedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString());
const int32 EditorLineIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName().ToString());
check(RemovedAtIndex != INDEX_NONE);
check(EditorLineIndex != INDEX_NONE);
// This isn't allowed to happen, but cannot be explicitly forbidden in the engine. For now, just hack it by inserting a point at 0,0,0.
// Alternatively, just delete the line? TODO
if(EditorLines[EditorLineIndex].Points.Num() < 2)
{
UE_LOG(LogLineRendering, Error, TEXT("UGPUInstancedLineComponent: A Line needs to consist of at least two points, trying to remove one of them doesn't work. Defaulted point inserted again!"));
EditorLines[EditorLineIndex].Points.AddZeroed(1);
UpdatePoint(EditorLines[EditorLineIndex].RespectiveLineId, RemovedAtIndex, EditorLines[EditorLineIndex].Points[RemovedAtIndex].Point);
}
else
{
RemovePoint(EditorLines[EditorLineIndex].RespectiveLineId, RemovedAtIndex);
}
}
else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayClear) // Tricky one because we need at least 2 points for a valid line, else the backend breaks.
{
// GetArrayIndex somehow fails unfortunately...
//const int32 LineIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName().ToString());
//check(LineIndex != INDEX_NONE);
//
// For now, find the cleared array manually TODO
//
for (int32 EditorLineIndex = 0; EditorLineIndex < EditorLines.Num(); ++EditorLineIndex)
{
if(EditorLines[EditorLineIndex].Points.Num() == 0)
{
EditorLines[EditorLineIndex].Points.SetNumZeroed(2);
// For simplicity's sake: (TODO)
const int32 NumberOfPoints = GetNumberOfPointsInLine(EditorLines[EditorLineIndex].RespectiveLineId);
const int32 NumberOfPointsToRemove = FMath::Max(NumberOfPoints - 2, 0);
for(int32 PointToRemove = 0; PointToRemove < NumberOfPointsToRemove; PointToRemove++)
{
RemovePoint(EditorLines[EditorLineIndex].RespectiveLineId, 2); // just repeatedly delete point 2
}
TArray<FVector> Pts = { FVector(0, 0, 0), FVector(0, 0, 0) };
UpdatePoints(EditorLines[EditorLineIndex].RespectiveLineId, 0, Pts);
break;
}
}
}
}
else if (PropertyChangedEvent.Property->GetName() == "X" || PropertyChangedEvent.Property->GetName() == "Y" || PropertyChangedEvent.Property->GetName() == "Z")
{
if (PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode() != nullptr && PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName() == GET_MEMBER_NAME_CHECKED(FEditorPoint, Point))
{
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet || PropertyChangedEvent.ChangeType == EPropertyChangeType::Interactive)
{
// Find the correct updated point in editor line data and propagate the update.
TDoubleLinkedList<FProperty*>::TDoubleLinkedListNode* PointsNode = PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetPrevNode();
TDoubleLinkedList<FProperty*>::TDoubleLinkedListNode* LinesNode = PointsNode->GetPrevNode();
const int32 PointIndex = PropertyChangedEvent.GetArrayIndex(PointsNode->GetValue()->GetFName().ToString());
const int32 LineIndex = PropertyChangedEvent.GetArrayIndex(LinesNode->GetValue()->GetFName().ToString());
check(PointIndex != INDEX_NONE);
check(LineIndex != INDEX_NONE);
UpdatePoint(EditorLines[LineIndex].RespectiveLineId, PointIndex, EditorLines[LineIndex].Points[PointIndex].Point);
}
}
}
else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FEditorLineData, Color))
{
const int32 LineIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName().ToString());
SetLineColor(EditorLines[LineIndex].RespectiveLineId, EditorLines[LineIndex].Color);
}
else if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FEditorLineData, Width))
{
const int32 LineIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.PropertyChain.GetActiveNode()->GetPrevNode()->GetValue()->GetFName().ToString());
SetLineWidth(EditorLines[LineIndex].RespectiveLineId, EditorLines[LineIndex].Width);
}
else if (PropertyChangedEvent.Property->GetFName() == "TextureWidth" || PropertyChangedEvent.Property->GetFName() == "TextureHeight")
{
ResizeTexture(TextureWidth, TextureHeight);
}
}
Super::PostEditChangeChainProperty(PropertyChangedEvent);
}
void UGPUInstancedLineComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Init();
const FName PropertyName = PropertyChangedEvent.GetPropertyName();
if (PropertyChangedEvent.Property != nullptr)
{
if (PropertyName == GET_MEMBER_NAME_CHECKED(FEditorPoint, Point))
{
if(PropertyChangedEvent.ChangeType == EPropertyChangeType::Unspecified)
{
// Due to not having access to the chain whenever the widget is used, we need to just update ALL the editor lines every time
// the widget is dragged around. This is incredibly annoying but there doesn't seem to be another way, as the widget drag doesn't fire
// the PostEditChangeChainProperty callback.
UpdateAllEditorLines();
}
}
if (PropertyName == "LineMaterialInterface")
{
DynamicLineMaterial = UMaterialInstanceDynamic::Create(LineMaterialInterface, GetTransientPackage());
SetMaterial(0, DynamicLineMaterial);
DynamicLineMaterial->SetTextureParameterValue("PositionTexture", PositionTexture);
DynamicLineMaterial->SetScalarParameterValue("TextureWidth", TextureWidth);
}
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
void UGPUInstancedLineComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if(TickCount < MaxTicksUntilInit)
{
TickCount++;
}
else
{
Init();
bTickInEditor = false;
SetComponentTickEnabled(false);
}
}
#endif
void UGPUInstancedLineComponent::BeginDestroy()
{
Super::BeginDestroy();
ReleaseData();
}
void UGPUInstancedLineComponent::ReserveMemory(int32 NumberOfSegments, int32 NumberOfLines)
{
const int32 Total = NumberOfSegments * NumberOfLines;
PreAllocateInstancesMemory(Total);
LineMap.Reserve(Total);
LinearLineData.Reserve(NumberOfLines * (NumberOfSegments + 1));
}
void UGPUInstancedLineComponent::ReserveMemoryWithoutSegments(int32 NumberOfLines, int32 NumberOfTotalPoints)
{
PreAllocateInstancesMemory(NumberOfTotalPoints - NumberOfLines); // Allocates a bit too much
LineMap.Reserve(NumberOfTotalPoints - NumberOfLines);
LinearLineData.Reserve(NumberOfTotalPoints);
}
bool UGPUInstancedLineComponent::ResizeTexture(int32 Width, int32 Height)
{
if (LinearLineData.Num() > Width * Height)
return false;
// what basically needs to be done is a complete re-calculation of the texture indices
CurrentTextureMarker = { 0, 0 };
CurrentTextureIndex = 0;
TextureWidth = Width;
TextureHeight = Height;
//int32 TextureIndex = 0;
// Sequentially update the custom data and the line map.
// This might be a bit awkward as the line map is a *map*, and not just an array.
// Some lines might be re-ordered in the texture, but that should be fine.
for(int32 LineId = 0; LineId < LineMap.Num(); ++LineId)// TPair<int32, FGPULineArray>& Line : LineMap)
{
for (FGPULineIndices& LineIndices : LineMap[LineId].IndexArray)
{
// Key is instance id, value is texture index.
if(LineIndices.InstanceIndex >= 0)
SetCustomDataValue(LineIndices.InstanceIndex, 4, CurrentTextureIndex, false);
LineIndices.TextureIndex = CurrentTextureIndex;
CurrentTextureIndex++;
MoveTextureMarker();
}
}
// Recreate texture
UE_LOG(LogLineRendering, Display, TEXT("UGPUInstancedLineComponent::ResizeTexture - Creating New Texture"));
PositionTexture = UTexture2D::CreateTransient(TextureWidth, TextureHeight, PF_A32B32G32R32F);
// Allocate the texture RHI
PositionTexture->UpdateResource();
UpdateWholeTexture();
// Update material parameters and texture
DynamicLineMaterial->SetTextureParameterValue("PositionTexture", PositionTexture);
DynamicLineMaterial->SetScalarParameterValue("TextureWidth", TextureWidth);
return true;
}
void UGPUInstancedLineComponent::InitializeLinesInBulk(int32 NumberOfLines, int32 NumberOfSegmentsPerLine,
TArray<FVector4>& Points, const TArray<FLinearColor>& Colors, const TArray<float>& Widths)
{
if (LinearLineData.Num() != 0 || LineMap.Num() != 0 || GetInstanceCount() != 0)
{
LinearLineData.Empty(); // Reset
LineMap.Empty();
//NextFreeId = 0;
CurrentTextureIndex = 0;
CurrentTextureMarker = FIntPoint(0, 0);
ClearInstances();
}
if (Points.Num() != NumberOfLines * (NumberOfSegmentsPerLine + 1))
{
UE_LOG(LogLineRendering, Fatal, TEXT("UGPUInstancedLineComponent::InitializeLinesInBulk : Inconsistent number of points and lines!"));
}
LinearLineData = MoveTemp(Points);
// Add the number of instances - sadly this requires the for loop
const bool UniformColor = Colors.Num() == 1;
const bool UniformWidth = Widths.Num() == 1;
for (int32 LineIndex = 0; LineIndex < NumberOfLines; ++LineIndex)
{
const FLinearColor Color = UniformColor ? Colors[0] : Colors[LineIndex];
const float Width = UniformWidth ? Widths[0] : Widths[LineIndex];
const int32 Idx = LineMap.Add(FGPULineArray());
FGPULineArray& NewLineArray = LineMap[Idx];
NewLineArray.IndexArray.Reserve(NumberOfSegmentsPerLine + 1);
for (int32 PointIndex = 0; PointIndex < NumberOfSegmentsPerLine; ++PointIndex)
{
const int32 InstanceId = AddNewSegmentInstance(Color, Width, CurrentTextureIndex);
NewLineArray.IndexArray.Add({InstanceId, LineIndex * (NumberOfSegmentsPerLine + 1) + PointIndex});
CurrentTextureIndex++;
MoveTextureMarker();
}
NewLineArray.IndexArray.Add({-1, LineIndex * (NumberOfSegmentsPerLine + 1) + NumberOfSegmentsPerLine});
MoveTextureMarker();
CurrentTextureIndex++;
//NextFreeId++;
}
MarkRenderStateDirty();
UpdateWholeTexture();
}
void UGPUInstancedLineComponent::InitializeLinesInBulkTesselate(int32 NumberOfLines, int32 NumberOfSegmentsPerLine,
TArray<FVector4>& Points, const TArray<FLinearColor>& Colors, const TArray<float>& Widths)
{
// Just as a test
const int32 NumOriginalSegments = NumberOfSegmentsPerLine;
NumberOfSegmentsPerLine += 9;
LinearLineData.Reserve(NumberOfLines * (NumberOfSegmentsPerLine + 1));
if (LinearLineData.Num() != 0 || LineMap.Num() != 0 || GetInstanceCount() != 0)
{
LinearLineData.Empty(); // Reset
LineMap.Empty();
//NextFreeId = 0;
CurrentTextureIndex = 0;
CurrentTextureMarker = FIntPoint(0, 0);
ClearInstances();
}
LinearLineData = MoveTemp(Points);
// Add tesselated points from back to front
for (int LineIdx = NumberOfLines - 1; LineIdx >= 0; --LineIdx)
{
// Get start and end of line
const FVector4 Start = LinearLineData[LineIdx * (NumOriginalSegments + 1)];
const FVector4 End = LinearLineData[LineIdx * (NumOriginalSegments + 1) + 1];
const FVector4 Dist = End - Start;
// Tesselate
const FVector4 First = Start + 0.1 * Dist;
const FVector4 Second = Start + 0.2 * Dist;
const FVector4 Third = Start + 0.3 * Dist;
const FVector4 Fourth = Start + 0.4 * Dist;
const FVector4 Fith = Start + 0.5 * Dist;
const FVector4 Sixth = Start + 0.6 * Dist;
const FVector4 Seventh = Start + 0.7 * Dist;
const FVector4 Eigth = Start + 0.8 * Dist;
const FVector4 Nineth = Start + 0.9 * Dist;
LinearLineData.Insert({ First, Second, Third, Fourth, Fith, Sixth, Seventh, Eigth, Nineth }, LineIdx * (NumOriginalSegments + 1) + 1);
}
// Add the number of instances - sadly this requires the for loop
const bool UniformColor = Colors.Num() == 1;
const bool UniformWidth = Widths.Num() == 1;
for (int32 LineIndex = 0; LineIndex < NumberOfLines; ++LineIndex)
{
const FLinearColor Color = UniformColor ? Colors[0] : Colors[LineIndex];
const float Width = UniformWidth ? Widths[0] : Widths[LineIndex];
const int32 Idx = LineMap.Add(FGPULineArray());
FGPULineArray& NewLineArray = LineMap[Idx];
NewLineArray.IndexArray.Reserve(NumberOfSegmentsPerLine + 1);
for (int32 PointIndex = 0; PointIndex < NumberOfSegmentsPerLine; ++PointIndex)
{
const int32 InstanceId = AddNewSegmentInstance(Color, Width, CurrentTextureIndex);
NewLineArray.IndexArray.Add({ InstanceId, LineIndex * (NumberOfSegmentsPerLine + 1) + PointIndex });
CurrentTextureIndex++;
MoveTextureMarker();
}
NewLineArray.IndexArray.Add({ -1, LineIndex * (NumberOfSegmentsPerLine + 1) + NumberOfSegmentsPerLine });
MoveTextureMarker();
CurrentTextureIndex++;
//NextFreeId++;
}
MarkRenderStateDirty();
UpdateWholeTexture();
}
int32 UGPUInstancedLineComponent::AddLine(const TArray<FVector>& Line, FLinearColor Color, float Width)
{
if (Line.Num() < 2)
{
UE_LOG(LogLineRendering, Error, TEXT("UGPUInstancedLineComponent::AddLine : Can't add line with less than 2 points.")); // Actually we can!
return -1;
}
// 1.
const int32 LineId = LineMap.Add(FGPULineArray());
FGPULineArray& NewLineArray = LineMap[LineId];
NewLineArray.IndexArray.Reserve(Line.Num());
//const int32 LineId = NextFreeId;
//NextFreeId++; // TODO Fix Id system as this can possibly overflow.
const int32 NumberSegments = Line.Num() - 1;
const FIntPoint InitialTextureMarker = CurrentTextureMarker;
for (int32 PointIndex = 0; PointIndex < NumberSegments; ++PointIndex)
{
// Add with a dummy transform
const int32 InstanceId = AddNewSegmentInstance(Color, Width, CurrentTextureIndex);
NewLineArray.IndexArray.Add({InstanceId, LinearLineData.Num() + PointIndex});
CurrentTextureIndex++;
MoveTextureMarker();
}
// Add the last point which does not correspond to a transform/instance, but needs to be added to the texture still such that the second last instance can read it:
NewLineArray.IndexArray.Add({-1, LinearLineData.Num() + NumberSegments});
MoveTextureMarker();
CurrentTextureIndex++;
// Recreate the data on the heap to allow asynchronous texture update.
TArray<FVector4>* TextureData = new TArray<FVector4>(Line);
// "Mark" the first entry with w=0 as first segment of the line, and the last with w=-1
(*TextureData)[0].W = 0.0;
TextureData->Last().W = -1.0;
// Store the points in a linear array here
LinearLineData.Append(*TextureData);
UpdateTexture(InitialTextureMarker, Line.Num(), (uint8*)TextureData->GetData());
return LineId;
}
int32 UGPUInstancedLineComponent::AddLine(TArray<FVector4>& Line, FLinearColor Color, float Width, bool bMarkRenderStateDirty)
{
if (Line.Num() < 2)
{
UE_LOG(LogLineRendering, Error, TEXT("UGPUInstancedLineComponent::AddLine : Can't add line with less than 2 points.")); // Actually we can!
return -1;
}
// 1.
const int32 LineId = LineMap.Add(FGPULineArray());
FGPULineArray& NewLineArray = LineMap[LineId];
NewLineArray.IndexArray.Reserve(Line.Num());
const int32 NumberSegments = Line.Num() - 1;
const FIntPoint InitialTextureMarker = CurrentTextureMarker;
for (int32 PointIndex = 0; PointIndex < NumberSegments; ++PointIndex)
{
// Add with a dummy transform
const int32 InstanceId = AddNewSegmentInstance(Color, Width, CurrentTextureIndex);
NewLineArray.IndexArray.Add({ InstanceId, LinearLineData.Num() + PointIndex });
CurrentTextureIndex++;
MoveTextureMarker();
}
// Add the last point which does not correspond to a transform/instance, but needs to be added to the texture still such that the second last instance can read it:
NewLineArray.IndexArray.Add({ -1, LinearLineData.Num() + NumberSegments });
MoveTextureMarker();
CurrentTextureIndex++;
// Recreate the data on the heap to allow asynchronous texture update.
TArray<FVector4>* TextureData = new TArray<FVector4>(MoveTemp(Line));
// Store the points in a linear array here
LinearLineData.Append(*TextureData);
UpdateTexture(InitialTextureMarker, TextureData->Num(), (uint8*)TextureData->GetData(), bMarkRenderStateDirty);
return LineId;
}
int32 UGPUInstancedLineComponent::AddLineFromEditorData(FEditorLineData& LineData)
{
const TArray<FEditorPoint>* EditorLineDataPtr = &LineData.Points;
const TArray<FVector>* FVectorLineDataPtr = reinterpret_cast<const TArray<FVector>*>(EditorLineDataPtr);
LineData.RespectiveLineId = AddLine(*FVectorLineDataPtr, LineData.Color, LineData.Width);
EditorLineIds.Add(LineData.RespectiveLineId);
return LineData.RespectiveLineId;
}
// Add a point to the end of the specified line
bool UGPUInstancedLineComponent::AddPoint(int32 LineId, const FVector& Point)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::AddPoint"))
// Adding a point to the last line is easy. Adding it to a line the middle is awkward and requires an almost complete recalculation
// Get color and width of the line
FGPULineArray& Line = LineMap[LineId];
const float Width = PerInstanceSMCustomData[Line.IndexArray[0].InstanceIndex * NumCustomDataFloats + 3];
const FIntPoint InitialTextureMarker = CurrentTextureMarker;
// Check if it's the last line:
FGPULineIndices& Indices = Line.IndexArray.Last();
const int32 InstanceId = AddNewSegmentInstance(GetLineColor(LineId), Width, Indices.TextureIndex);
Line.IndexArray.Last().InstanceIndex = InstanceId;
FVector4& PreviousLastPoint = LinearLineData[Indices.TextureIndex];
PreviousLastPoint.W = 1.0; // This isn't the line end anymore.
if(LineId == LineMap.Num() - 1)
{
LinearLineData.Add(FVector4(Point, -1)); // This is the last point now.
// Add a new dummy entry for the last point
Line.IndexArray.Add(FGPULineIndices{ -1, LinearLineData.Num() - 1 });
CurrentTextureIndex++;
MoveTextureMarker();
// Update the new point
FUpdateTextureRegion2D* Region = new FUpdateTextureRegion2D(
InitialTextureMarker.X,
InitialTextureMarker.Y,
0,
0, 1, 1);
// Copy for now - no need to do that
TArray<FVector4>* TextureData = new TArray<FVector4>{LinearLineData.Last()};
PositionTexture->UpdateTextureRegions(0, 1, Region, TextureWidth * sizeof(FVector4), sizeof(FVector4), (uint8*)TextureData->GetData(),
[](auto InTextureData, auto InRegions)
{
delete InTextureData;
delete InRegions;
});
// Update old point
TArray<FVector4>* TextureData2 = new TArray<FVector4>{ PreviousLastPoint };
// Update the new point
int32 PrevX = InitialTextureMarker.X - 1;
int32 PrevY = InitialTextureMarker.Y;
if(PrevX < 0)
{
PrevX = TextureWidth - 1;
PrevY--;
}
FUpdateTextureRegion2D* Region2 = new FUpdateTextureRegion2D(
PrevX,
PrevY,
0,
0, 1, 1);
PositionTexture->UpdateTextureRegions(0, 1, Region2, TextureWidth * sizeof(FVector4), sizeof(FVector4), (uint8*)TextureData2->GetData(),
[](auto InTextureData, auto InRegions)
{
delete InTextureData;
delete InRegions;
});
MarkRenderStateDirty();
return true;
}
// Add point to a line in the middle
// This is slightly more complicated as all the indices after the added point need to be adjusted.
// Additionally, the point needs to be inserted into the texture, shifting the whole thing.
const int32 NewPointTextureIndex = Indices.TextureIndex + 1;
// Update all LineMap Indices that have a texture index larger than the newly inserted point.
//Todo: Make more efficient
for (FGPULineArray& LineArray : LineMap)
{
if (LineArray.IndexArray[0].TextureIndex >= NewPointTextureIndex)
{
// Need to increase all indices by 1 and adjust the custom data
for (int32 i = 0; i < LineArray.IndexArray.Num(); ++i)
{
LineArray.IndexArray[i].TextureIndex++;
SetCustomDataValue(LineArray.IndexArray[i].InstanceIndex, 4, LineArray.IndexArray[i].TextureIndex, false);
}
}
}
// Insert the point into the LinearData:
LinearLineData.Insert(FVector4(Point, -1), NewPointTextureIndex);
// Add it to the LineMap - this is the new dummy segment because we add it at the end of the line
Line.IndexArray.Add(FGPULineIndices{ -1, NewPointTextureIndex });
//SetCustomDataValue(InstanceId, 4, NewPointTextureIndex, true);
CurrentTextureIndex++;
MoveTextureMarker();
// For simplicities sake, update the whole texture:
UpdateWholeTexture();
MarkRenderStateDirty();
return true;
}
bool UGPUInstancedLineComponent::InsertPoint(int32 LineId, int32 SegmentId, const FVector& Point, const FLinearColor& Color)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::InsertPoint"))
FGPULineArray& Line = LineMap[LineId];
if (SegmentId == Line.IndexArray.Num())
{
return AddPoint(LineId, Point);
}
const float Width = PerInstanceSMCustomData[Line.IndexArray[0].InstanceIndex * NumCustomDataFloats + 3];
const int32 LinearDataIndex = Line.IndexArray[SegmentId].TextureIndex;
LinearLineData.Insert(FVector4(Point), LinearDataIndex);
if (SegmentId == 0)
{
// This is now the new start point
LinearLineData[LinearDataIndex].W = 0;
// This was the old start point
LinearLineData[LinearDataIndex + 1].W = 1;
}
// Add a new segment BEFORE the current one.
const int32 InstanceId = AddNewSegmentInstance(Color, Width, LinearDataIndex);
Line.IndexArray.Insert(FGPULineIndices{ InstanceId, LinearDataIndex }, SegmentId);
// Update the rest of the line
for(int32 i = SegmentId + 1; i < Line.IndexArray.Num(); ++i)
{
Line.IndexArray[i].TextureIndex++;
SetCustomDataValue(Line.IndexArray[i].InstanceIndex, 4, Line.IndexArray[i].TextureIndex);
}
// Update all following lines
for (FGPULineArray& LineArray : LineMap)
{
if (LineArray.IndexArray[0].TextureIndex > LinearDataIndex)
{
// Need to increase all indices by 1 and adjust the custom data
for (int32 i = 0; i < LineArray.IndexArray.Num(); ++i)
{
LineArray.IndexArray[i].TextureIndex++;
SetCustomDataValue(LineArray.IndexArray[i].InstanceIndex, 4, LineArray.IndexArray[i].TextureIndex, false);
}
}
}
CurrentTextureIndex++;
MoveTextureMarker();
// For simplicities sake, update the whole texture:
UpdateWholeTexture();
MarkRenderStateDirty();
return true;
}
bool UGPUInstancedLineComponent::InsertPointWithSameColor(int32 LineId, int32 SegmentId, const FVector& Point)
{
return InsertPoint(LineId, SegmentId, Point, GetLineColor(LineId));
}
bool UGPUInstancedLineComponent::InsertPointWithColor(int32 LineId, int32 SegmentId, const FVector& Point,
const FLinearColor& Color)
{
return InsertPoint(LineId, SegmentId, Point, Color);
}
bool UGPUInstancedLineComponent::AddPoints(int32 LineId, const TArray<FVector>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::AddPoint"))
// Adding a point to the last line is easy. Adding it to a line the middle is awkward and requires an almost complete recalculation
// Get color and width of the line
FGPULineArray& Line = LineMap[LineId];
const FLinearColor Color = GetLineColor(LineId);
const float Width = PerInstanceSMCustomData[Line.IndexArray[0].InstanceIndex * NumCustomDataFloats + 3];
const FIntPoint InitialTextureMarker = CurrentTextureMarker;
// Check if it's the last line:
FGPULineIndices& Indices = Line.IndexArray.Last();
const int32 PointTextureIndex = Indices.TextureIndex;
FVector4& PreviousLastPoint = LinearLineData[Indices.TextureIndex];
PreviousLastPoint.W = 1.0; // This isn't the line end anymore.
if (LineId == LineMap.Num() - 1)
{
for (int32 PointIndex = 0; PointIndex < Points.Num(); ++PointIndex)
{
const int32 InstanceId = AddNewSegmentInstance(Color, Width, PointTextureIndex + PointIndex);
// Update the latest dummy entry to point to the actual segment.
if (PointIndex == 0)
Line.IndexArray.Last().InstanceIndex = InstanceId;
else
Line.IndexArray.Add({InstanceId, LinearLineData.Num() - 1 + PointIndex});
CurrentTextureIndex++;
MoveTextureMarker();
}
LinearLineData.Append(Points);
LinearLineData.Last().W = -1;
// Add a new dummy entry for the last point
Line.IndexArray.Add({ -1, LinearLineData.Num() - 1 });
CurrentTextureIndex++;
MoveTextureMarker();
// Recreate the data on the heap to allow asynchronous texture update.
TArray<FVector4>* TextureData = new TArray<FVector4>(Points);
UpdateTexture(InitialTextureMarker, Points.Num(), (uint8*)TextureData->GetData());
//MarkRenderStateDirty(); called in UpdateTexture already
return true;
}
// Add points to a line in the middle
// This is slightly more complicated as all the indices after the added points need to be adjusted.
// Additionally, the points needs to be inserted into the texture, shifting the whole thing.
const int32 NewPointTextureIndex = Indices.TextureIndex + 1;
// Update all LineMap Indices that have a texture index larger than the newly inserted point. Todo: Make more efficient, maybe remove map and swap to array:
for (FGPULineArray& LineArray : LineMap)
{
if (LineArray.IndexArray[0].TextureIndex >= NewPointTextureIndex)
{
// Need to increase all indices by 1 and adjust the custom data
for (int32 i = 0; i < LineArray.IndexArray.Num(); ++i)
{
LineArray.IndexArray[i].TextureIndex += Points.Num();
SetCustomDataValue(LineArray.IndexArray[i].InstanceIndex, 4, LineArray.IndexArray[i].TextureIndex, false);
}
}
}
for (int32 PointIndex = 0; PointIndex < Points.Num(); ++PointIndex)
{
const int32 InstanceId = AddNewSegmentInstance(Color, Width, NewPointTextureIndex - 1 + PointIndex);
// Update the latest dummy entry to point to the actual segment.
if (PointIndex == 0)
Line.IndexArray.Last().InstanceIndex = InstanceId;
else
{
Line.IndexArray.Add({InstanceId, NewPointTextureIndex + PointIndex - 1});
CurrentTextureIndex++;
MoveTextureMarker();
}
}
// Insert the points into the LinearData:
TArray<FVector4> TextureData = TArray<FVector4>(Points);
TextureData.Last().W = -1;
LinearLineData.Insert(TextureData, NewPointTextureIndex);
// Add it to the LineMap - this is the new dummy segment because we add it at the end of the line
Line.IndexArray.Add({ -1, NewPointTextureIndex + Points.Num() - 1 });
//SetCustomDataValue(InstanceId, 4, NewPointTextureIndex, true);
CurrentTextureIndex++;
MoveTextureMarker();
// For simplicities sake, update the whole texture:
UpdateWholeTexture();
MarkRenderStateDirty();
return true;
}
bool UGPUInstancedLineComponent::InsertPoints(int32 LineId, int32 SegmentId, const TArray<FVector>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::InsertPoint"))
FGPULineArray& Line = LineMap[LineId];
if (SegmentId == Line.IndexArray.Num())
{
return AddPoints(LineId, Points);
}
const float Width = PerInstanceSMCustomData[Line.IndexArray[0].InstanceIndex * NumCustomDataFloats + 3];
const int32 LinearDataIndex = Line.IndexArray[SegmentId].TextureIndex;
LinearLineData.Insert(TArray<FVector4>(Points), LinearDataIndex);
if (SegmentId == 0)
{
// This is now the new start point
LinearLineData[LinearDataIndex].W = 0;
// This was the old start point
LinearLineData[LinearDataIndex + Points.Num()].W = 1;
}
for (int32 PointIndex = 0; PointIndex < Points.Num(); ++PointIndex)
{
const int32 InstanceId = AddNewSegmentInstance(GetLineColor(LineId), Width, LinearDataIndex + PointIndex);
Line.IndexArray.Insert({ InstanceId, LinearDataIndex + PointIndex }, SegmentId + PointIndex);
CurrentTextureIndex++;
MoveTextureMarker();
}
// Update the rest of the line
for (int32 i = SegmentId + Points.Num(); i < Line.IndexArray.Num(); ++i)
{
Line.IndexArray[i].TextureIndex += Points.Num();
SetCustomDataValue(Line.IndexArray[i].InstanceIndex, 4, Line.IndexArray[i].TextureIndex);
}
// Update all following lines
for (FGPULineArray& LineArray : LineMap)
{
//FGPULineArray& LineArray = Pair.Value;
if (LineArray.IndexArray[0].TextureIndex > LinearDataIndex)
{
// Need to increase all indices by 1 and adjust the custom data
for (int32 i = 0; i < LineArray.IndexArray.Num(); ++i)
{
LineArray.IndexArray[i].TextureIndex += Points.Num();
SetCustomDataValue(LineArray.IndexArray[i].InstanceIndex, 4, LineArray.IndexArray[i].TextureIndex, false);
}
}
}
// For simplicities sake, update the whole texture:
UpdateWholeTexture();
MarkRenderStateDirty();
return true;
}
// todo test this not sure if it actually works
bool UGPUInstancedLineComponent::UpdateLine(int32 LineId, TArray<FVector>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::UpdateLine"))
const FGPULineArray& Line = LineMap[LineId];
if(Line.IndexArray.Num() != Points.Num())
{
return false;
}
TArray<FVector4>* TextureData = new TArray<FVector4>(MoveTemp(Points));
TextureData->Last().W = -1;
(*TextureData)[0].W = 0;
const int32 TextureIndex = Line.IndexArray[0].TextureIndex;
const FIntPoint StartIndex(TextureIndex % TextureWidth, TextureIndex / TextureWidth);
FMemory::Memcpy(LinearLineData.GetData() + TextureIndex, TextureData->GetData(), TextureData->Num() * sizeof(FVector4));
UpdateTexture(StartIndex, TextureData->Num(), (uint8*)TextureData->GetData());
return true;
}
// Use the Points array to update the lines, assuming the array contains already well defined data.
bool UGPUInstancedLineComponent::UpdateLinesDataDirectly(int32 LineIdStart, TArray<FVector4>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::UpdateLines - AlignedMem"))
if (LineIdStart + Points.Num() > LinearLineData.Num())
return false;
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::UpdateLines - Memcopy LinearLineData"))
FMemory::Memcpy(LinearLineData.GetData() + LineIdStart, Points.GetData(), Points.Num() * sizeof(FVector4));
}
const int32 X = LineIdStart % TextureWidth;
const int32 Y = LineIdStart / TextureWidth;
TArray<FVector4>* TextureData;
{
// Copy for now
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::UpdateLines - MoveTemp Texture Data"))
TextureData = new TArray<FVector4>(MoveTemp(Points));
}
UpdateTexture(FIntPoint(X, Y), TextureData->Num(), (uint8*)TextureData->GetData());
return true;
}
bool UGPUInstancedLineComponent::UpdateLineFromEditorData(const FEditorLineData& LineData)
{
TArray<FVector> Points;
Points.SetNumUninitialized(LineData.Points.Num());
FMemory::Memcpy(Points.GetData(), LineData.Points.GetData(), sizeof(FVector) * LineData.Points.Num());
return UpdateLine(LineData.RespectiveLineId, Points);
}
bool UGPUInstancedLineComponent::DrawLinesDirectly(int32 LineIdStart, TArray<FVector4>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::DrawLinesDirectly - Move"))
const int32 X = LineIdStart % TextureWidth;
const int32 Y = LineIdStart / TextureWidth;
TArray<FVector4>* TextureData;
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::DrawLinesDirectly - MoveTemp Texture Data"))
TextureData = new TArray<FVector4>(MoveTemp(Points));
}
UpdateTexture(FIntPoint(X, Y), TextureData->Num(), (uint8*)TextureData->GetData());
return true;
}
bool UGPUInstancedLineComponent::DrawLinesDirectly(int32 LineIdStart, const TArray<FVector4>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::DrawLinesDirectly - Memcpy"))
TArray<FVector4> Cpy = Points;
return DrawLinesDirectly(LineIdStart, Cpy);
}
bool UGPUInstancedLineComponent::DrawLinesDirectly(int32 LineIdStart, TArray<FVector4>* Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::DrawLinesDirectly - Ptr"))
const int32 X = LineIdStart % TextureWidth;
const int32 Y = LineIdStart / TextureWidth;
UpdateTexture(FIntPoint(X, Y), Points->Num(), (uint8*)Points->GetData());
return true;
}
bool UGPUInstancedLineComponent::DrawLinesDirectly(int32 LineIdStart, uint8* SrcData, int32 Num)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::DrawLinesDirectly - Raw Ptr"))
const int32 X = LineIdStart % TextureWidth;
const int32 Y = LineIdStart / TextureWidth;
UpdateTexture(FIntPoint(X, Y), Num, SrcData);
return true;
}
// todo test this not sure if it actually works
bool UGPUInstancedLineComponent::UpdatePoint(int32 LineId, int32 PointId, const FVector& Point)
{
// Should check for validity
const FGPULineIndices& PointIndices = LineMap[LineId].IndexArray[PointId];
// Only need to update the linear data - don't overwrite W as the line start/end doesn't change
LinearLineData[PointIndices.TextureIndex].X = Point.X;
LinearLineData[PointIndices.TextureIndex].Y = Point.Y;
LinearLineData[PointIndices.TextureIndex].Z = Point.Z;
const int32 TextureIndex = PointIndices.TextureIndex;
const int32 X = TextureIndex % TextureWidth;
const int32 Y = TextureIndex / TextureWidth;
// Update the texture - need to get instance custom data
FUpdateTextureRegion2D* Region = new FUpdateTextureRegion2D(
X,
Y,
0,
0, 1, 1);
// Copy for now - no need to do that
TArray<FVector4>* TextureData = new TArray<FVector4>{ LinearLineData[PointIndices.TextureIndex] };
PositionTexture->UpdateTextureRegions(0, 1, Region, TextureWidth * sizeof(FVector4), sizeof(FVector4), (uint8*)TextureData->GetData(),
[](auto InTextureData, auto InRegions)
{
delete InTextureData;
delete InRegions;
});
return true;
}
// todo test this not sure if it actually works
bool UGPUInstancedLineComponent::UpdatePoints(int32 LineId, int32 StartingPointId, TArray<FVector>& Points)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::UpdatePoints"))
const FGPULineArray& Line = LineMap[LineId];
if (Line.IndexArray.Num() < Points.Num() || StartingPointId + Points.Num() > Line.IndexArray.Num())
{
return false;
}
if(Line.IndexArray.Num() == Points.Num() && StartingPointId == 0)
{
return UpdateLine(LineId, Points);
}
TArray<FVector4>* TextureData = new TArray<FVector4>(MoveTemp(Points));
if(StartingPointId == 0)
{
(*TextureData)[0].W = 0;
}
if (StartingPointId + TextureData->Num() == Line.IndexArray.Num())
{
TextureData->Last().W = -1;
}
const int32 StartTextureIndex = Line.IndexArray[StartingPointId].TextureIndex;
const FIntPoint StartIndex(StartTextureIndex % TextureWidth, StartTextureIndex / TextureWidth);
FMemory::Memcpy(LinearLineData.GetData() + StartTextureIndex, TextureData->GetData(), TextureData->Num() * sizeof(FVector4));
UpdateTexture(StartIndex, TextureData->Num(), (uint8*)TextureData->GetData());
return true;
}
bool UGPUInstancedLineComponent::RemoveLine(int32 LineId)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::RemoveLine"))
const FGPULineArray& Line = LineMap[LineId];
const int32 LineLength = Line.IndexArray.Num();
const int32 LineTextureIndex = Line.IndexArray[0].TextureIndex;
// Because ISM are so great, they reshuffle id's on removal....
// This means we can't just remove consecutive ids, but need to actually re-calculate them EVERY SINGLE TIME
// As instances of a line aren't guaranteed to be in order, collect and sort the indices first, then start top down.
//
TArray<int32> InstancesToRemove;
InstancesToRemove.Reserve(Line.IndexArray.Num());
for(int32 PointIndex = 0; PointIndex < Line.IndexArray.Num() - 1; PointIndex++)
{
InstancesToRemove.Add(Line.IndexArray[PointIndex].InstanceIndex);
}
InstancesToRemove.Sort();
for (int32 Index = 0; Index < InstancesToRemove.Num(); Index++)
{
RemoveInstance(InstancesToRemove[InstancesToRemove.Num() - 1 - Index]);
}
// Remove linear data:
LinearLineData.RemoveAt(LineTextureIndex, LineLength);
// Remove line from map, this invalidates above Line reference.
LineMap.RemoveAt(LineId);
// Update all following lines
for (FGPULineArray& LineArray : LineMap)
{
//FGPULineArray& LineArray = Pair.Value;
for (int32 i = 0; i < LineArray.IndexArray.Num(); ++i)
{
if (LineArray.IndexArray[i].InstanceIndex >= InstancesToRemove[0])
{
// Apparently unreal also shuffles the instance indices, this might bite us later on
// This is insanely slow and very ugly:
for(int32 Index = InstancesToRemove.Num(); Index > 0; --Index)
{
if (LineArray.IndexArray[i].InstanceIndex >= InstancesToRemove[Index - 1])
{
LineArray.IndexArray[i].InstanceIndex -= Index;
break;
}
}
}
if (LineArray.IndexArray[0].TextureIndex >= LineTextureIndex)
{
LineArray.IndexArray[i].TextureIndex -= LineLength;
SetCustomDataValue(LineArray.IndexArray[i].InstanceIndex, 4, LineArray.IndexArray[i].TextureIndex, false);
}
}
}
CurrentTextureIndex -= LineLength;
CurrentTextureMarker.X = CurrentTextureIndex % TextureWidth;
CurrentTextureMarker.Y = CurrentTextureIndex / TextureWidth;
UpdateWholeTexture();
return true;
}
bool UGPUInstancedLineComponent::RemoveLineFromEditorData(int32 LineId)
{
int32 Index = 0;
if (EditorLineIds.Find(LineId, Index))
{
RemoveLine(LineId);
EditorLineIds.RemoveAt(Index);
}
return false;
}
bool UGPUInstancedLineComponent::RemoveAllEditorLines()
{
for(const int32 Id : EditorLineIds)
{
RemoveLine(Id);
}
if(EditorLines.Num() != 0)
{
EditorLines.Empty();
UE_LOG(LogLineRendering, Display, TEXT("UGPUInstancedLineComponent::RemoveAllEditorLines - EditorLines wasn't empty already, clearing."));
}
EditorLineIds.Empty();
return true;
}
bool UGPUInstancedLineComponent::RemovePoint(int32 LineId, int32 PointId)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::RemovePoint"))
FGPULineArray& Line = LineMap[LineId];
const FGPULineIndices& Indices = Line.IndexArray[PointId];
const int32 LineTextureIndex = Indices.TextureIndex;
int32 RemovedInstanceId = Indices.InstanceIndex;
if (PointId == 0)
{
LinearLineData[LineTextureIndex + 1].W = 0;
}
LinearLineData.RemoveAt(LineTextureIndex);
// Last point is special
if(PointId == Line.IndexArray.Num() - 1)
{
LinearLineData[Line.IndexArray[PointId - 1].TextureIndex].W = -1;
RemovedInstanceId = Line.IndexArray[PointId - 1].InstanceIndex;
RemoveInstance(Line.IndexArray[PointId - 1].InstanceIndex);
Line.IndexArray[PointId - 1].InstanceIndex = -1;
}
else
{
// Update the following segments in this line:
for (int32 i = PointId + 1; i < Line.IndexArray.Num(); ++i)
{
Line.IndexArray[i].TextureIndex -= 1;
SetCustomDataValue(Line.IndexArray[i].InstanceIndex, 4, Line.IndexArray[i].TextureIndex, false);
}
//MarkRenderStateDirty();
RemoveInstance(RemovedInstanceId);
}
Line.IndexArray.RemoveAt(PointId);
// Update all following lines
for (FGPULineArray& LineArray : LineMap)
{
for (int32 i = 0; i < LineArray.IndexArray.Num(); ++i)
{
if (LineArray.IndexArray[i].InstanceIndex >= RemovedInstanceId)
{
// Apparently unreal also shuffles the instance indices, this might bite us later on
LineArray.IndexArray[i].InstanceIndex -= 1;
}
if (LineArray.IndexArray[0].TextureIndex > LineTextureIndex)
{
LineArray.IndexArray[i].TextureIndex -= 1;
SetCustomDataValue(LineArray.IndexArray[i].InstanceIndex, 4, LineArray.IndexArray[i].TextureIndex, false);
}
}
}
CurrentTextureIndex--;
CurrentTextureMarker.X = CurrentTextureIndex % TextureWidth;
CurrentTextureMarker.Y = CurrentTextureIndex / TextureWidth;
MarkRenderStateDirty();
// For sake of simplicity for now:
UpdateWholeTexture();
return true;
}
// todo
bool UGPUInstancedLineComponent::RemovePoints(int32 LineId, int32 StartingPointId, int32 NumberOfPoints)
{
TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::RemovePoints"))
return false;
}
bool UGPUInstancedLineComponent::SetLineColor(int32 LineId, const FLinearColor& Color)
{
const FGPULineArray& Line = LineMap[LineId];
for (const FGPULineIndices& LineIndices: Line.IndexArray)
{
const int32 InstanceId = LineIndices.InstanceIndex;
if (InstanceId >= 0)
{
SetCustomDataValue(InstanceId, 0, Color.R, false);
SetCustomDataValue(InstanceId, 1, Color.G, false);
SetCustomDataValue(InstanceId, 2, Color.B, false);
}
}
MarkRenderStateDirty();
return true;
}
bool UGPUInstancedLineComponent::SetSegmentColor(int32 LineId, int32 SegmentId, const FLinearColor& Color)
{
// Get Instance Id:
const int32 InstanceId = LineMap[LineId].IndexArray[SegmentId].InstanceIndex;
SetCustomDataValue(InstanceId, 0, Color.R, false);
SetCustomDataValue(InstanceId, 1, Color.G, false);
SetCustomDataValue(InstanceId, 2, Color.B, true);
return true;
}
bool UGPUInstancedLineComponent::SetLineWidth(int32 LineId, float Width)
{
const FGPULineArray& Line = LineMap[LineId];
for (const FGPULineIndices& LineIndices : Line.IndexArray)
{
const int32 InstanceId = LineIndices.InstanceIndex;
if (InstanceId >= 0)
{
SetCustomDataValue(InstanceId, 3, Width / 2.0f, false);
}
}
MarkRenderStateDirty();
return true;
}
bool UGPUInstancedLineComponent::SetSegmentWidth(int32 LineId, int32 SegmentId, float Width)
{
const int32 InstanceId = LineMap[LineId].IndexArray[SegmentId].InstanceIndex;
SetCustomDataValue(InstanceId, 3, Width / 2.0f, false);
return true;
}
//#pragma optimize("", on)