// 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)