diff --git a/InstancedMeshLines/Content/ExampleMap.umap b/InstancedMeshLines/Content/ExampleMap.umap index 432ab86d6b1b7f29d12017cdcfe98c67dc89a309..95c2a6584c8cadc2e3dced6bdfb3f89e2c0ffc12 100644 Binary files a/InstancedMeshLines/Content/ExampleMap.umap and b/InstancedMeshLines/Content/ExampleMap.umap differ diff --git a/InstancedMeshLines/Content/ExampleMap_BuiltData.uasset b/InstancedMeshLines/Content/ExampleMap_BuiltData.uasset index ca6d2ada6d90f40b3ee5eb8f1b6fb10f9cbe9c22..cd3ac19609c97112d2228248cc47404c009daf8c 100644 Binary files a/InstancedMeshLines/Content/ExampleMap_BuiltData.uasset and b/InstancedMeshLines/Content/ExampleMap_BuiltData.uasset differ diff --git a/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Content/line.uasset b/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Content/line.uasset index 6d82e1b00a139e365bbc2282a44f289b77812335..e53730b60727f78b76728d091c3f56474d3dc599 100644 Binary files a/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Content/line.uasset and b/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Content/line.uasset differ diff --git a/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Private/GPUInstancedLineComponent.cpp b/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Private/GPUInstancedLineComponent.cpp index 9801ba51ff71d3c2ca72e17c346ac93d40735600..b4cf26ce4e07ac0970e24f74d786224b52b5c905 100644 --- a/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Private/GPUInstancedLineComponent.cpp +++ b/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Private/GPUInstancedLineComponent.cpp @@ -9,6 +9,8 @@ UGPUInstancedLineComponent::UGPUInstancedLineComponent(const FObjectInitializer& SetCastShadow(false); SetCollisionEnabled(ECollisionEnabled::NoCollision); SetGenerateOverlapEvents(false); + + SetCanEverAffectNavigation(false); static ConstructorHelpers::FObjectFinder<UStaticMesh>LineMeshAsset(TEXT("StaticMesh'/InstancedMeshLineRendering/line.line'")); static ConstructorHelpers::FObjectFinder<UMaterial>LineMaterialAsset(TEXT("Material'/InstancedMeshLineRendering/DynamicLineMaterial.DynamicLineMaterial'")); @@ -27,8 +29,8 @@ UGPUInstancedLineComponent::UGPUInstancedLineComponent(const FObjectInitializer& TextureWidth = 2048; TextureHeight = 2048; - DynamicLineMaterial = nullptr; - PositionTexture = nullptr; + //DynamicLineMaterial = nullptr; + //PositionTexture = nullptr; } UGPUInstancedLineComponent::~UGPUInstancedLineComponent() @@ -76,7 +78,13 @@ void UGPUInstancedLineComponent::Init() PositionTexture->UpdateResource(); UE_LOG(LogTemp, Display, TEXT("UGPUInstancedLineComponent::Init Creating MID")); + DynamicLineMaterial = CreateAndSetMaterialInstanceDynamic(0);// UMaterialInstanceDynamic::Create(LineMat, this, "DynamicLineMaterialMID"); + if (DynamicLineMaterial == nullptr) // FOR WHATEVER REASON I HAVE NO IDEA + { + DynamicLineMaterial = UMaterialInstanceDynamic::Create(GetStaticMesh()->GetMaterial(0), this);// UMaterialInstanceDynamic::Create(LineMat, this, "DynamicLineMaterialMID"); + SetMaterial(0, DynamicLineMaterial); + } if(DynamicLineMaterial) { DynamicLineMaterial->SetTextureParameterValue("PositionTexture", PositionTexture); @@ -193,6 +201,7 @@ void UGPUInstancedLineComponent::PostEditChangeChainProperty(FPropertyChangedCha { const int32 AddedAtIndex = PropertyChangedEvent.GetArrayIndex(PropertyChangedEvent.Property->GetFName().ToString()); check(AddedAtIndex != INDEX_NONE); + AddLineFromEditorData(EditorLines[AddedAtIndex]); } else if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayRemove) { @@ -270,6 +279,61 @@ void UGPUInstancedLineComponent::ReserveMemory(int32 NumberOfSegments, int32 Num } +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(LogTemp, Fatal, TEXT("UGPUInstancedLineComponent::InitializeLinesInBulk : Inconsistent number of points and lines!")); + } + + LinearLineData = MoveTemp(Points); + UpdateWholeTexture(); + + // 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]; + + GPULineArray& NewLineArray = LineMap.Add(NextFreeId, GPULineArray()); + NewLineArray.Reserve(NumberOfSegmentsPerLine + 1); + + for (int32 PointIndex = 0; PointIndex < NumberOfSegmentsPerLine; ++PointIndex) + { + // Add with a dummy transform + 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, false); + SetCustomDataValue(InstanceId, 4, static_cast<float>(CurrentTextureIndex), false); // Segment Start + NewLineArray.Add(TPair<int32, int32>{InstanceId, LinearLineData.Num() + PointIndex}); + CurrentTextureIndex++; + + MoveTextureMarker(); + } + NewLineArray.Add(TPair<int32, int32>{-1, LinearLineData.Num() + NumberOfSegmentsPerLine}); + MoveTextureMarker(); + CurrentTextureIndex++; + NextFreeId++; + } +} int32 UGPUInstancedLineComponent::AddLine(const TArray<FVector>& Line, FLinearColor Color, float Width) @@ -293,7 +357,7 @@ int32 UGPUInstancedLineComponent::AddLine(const TArray<FVector>& Line, FLinearCo for (int32 PointIndex = 0; PointIndex < NumberSegments; ++PointIndex) { // Add with a dummy transform - int32 InstanceId = AddInstanceWorldSpace(FTransform::Identity); + int32 InstanceId = AddInstance(FTransform::Identity); SetCustomDataValue(InstanceId, 0, Color.R, false); SetCustomDataValue(InstanceId, 1, Color.G, false); SetCustomDataValue(InstanceId, 2, Color.B, false); @@ -827,6 +891,29 @@ bool UGPUInstancedLineComponent::UpdateLinesDataDirectly(int32 LineIdStart, TArr return true; } +bool UGPUInstancedLineComponent::DrawLinesDirectly(int32 LineIdStart, TArray<FVector4>& Points) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("UGPUInstancedLineComponent::DrawLinesDirectly")) + + 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)); + } + int32 NumberOfRegions = 0; + FUpdateTextureRegion2D* Regions = CalculateTextureRegions(FIntPoint(X, Y), TextureData->Num(), NumberOfRegions); + PositionTexture->UpdateTextureRegions(0, NumberOfRegions, Regions, 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::UpdatePoint(int32 LineId, int32 PointId, const FVector& Point) { diff --git a/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Public/GPUInstancedLineComponent.h b/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Public/GPUInstancedLineComponent.h index 1f2c98f84507f3eddc99f5616b9f7efb4c7a0f5a..30ba7d9ae429c65497f7483cabeaaf608a04a410 100644 --- a/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Public/GPUInstancedLineComponent.h +++ b/InstancedMeshLines/Plugins/InstancedMeshLineRendering/Source/InstancedMeshLineRendering/Public/GPUInstancedLineComponent.h @@ -128,6 +128,25 @@ public: UFUNCTION(BlueprintCallable, Category = "Components|InstancedLineComponent") void ReserveMemory(int32 NumberOfSegments, int32 NumberOfLines); + + /** + * Initializes the component with a number of lines with the same length. Existing data will be cleared. The data with which to initialize the texture needs to be already in a consistent format and + * will be moved (not copied) directly. This is more performant than adding the lines one by one, especially if lines are made up of very few segments. + * Color and width is only set per line, not per segment for now. if the Colors and Widths arrays are of size 1, the respective value will be used for all lines. + * + * It is recommended to call ReserveMemory before! + * + * @param NumberOfLines [in] The number of total lines to add. + * @param NumberOfSegmentsPerLine [in] The number of segments each line consists of. + * @param Points [in] The points with which to initialize the texture. The TArray will be MOVED, so it is empty on return. + * @param Colors [in] The colors of the lines. If it has size 1, this color will be used for all lines. + * @param Widths [in] The widths of the lines. If it has size 1, this width will be used for all lines. + * + * @return int32 Id of the line, can be used to access it later on. + */ + UFUNCTION(BlueprintCallable, Category = "Components|InstancedLineComponent") + void InitializeLinesInBulk(int32 NumberOfLines, int32 NumberOfSegmentsPerLine, TArray<FVector4>& Points, const TArray<FLinearColor>& Colors, const TArray<float>& Widths); + /** * Adds a line and returns the respective ID of the line, which can be used to identify it for updating, modifying and removing. * @@ -240,6 +259,21 @@ public: */ bool UpdateLinesDataDirectly(int32 LineIdStart, TArray<FVector4>& Points); + + /** + * Fast function to draw the specified lines directly WITHOUT UPDATING THE INTERNAL LINE STATE. + * This is the fastest way to draw lines, as no memory needs to be copied, but the internal linear line data array will not be updated, + * possibly leading to data inconsitency. Only the texture used to render the points is updated. + * + * + * @param LineIdStart [in] The index into the line data texture at which to start directly updating the points. + * @param Points [in] The array of points to be used for the texture update. + * No formatting is done, so the w value of the points should already be correctly set, if needed. + * The array will be MOVED, not copied, so Points will be empty on return. + * @return bool Returns true on successful update. + */ + bool DrawLinesDirectly(int32 LineIdStart, TArray<FVector4>& Points); + /** * This function updates the location of a given point in an existing line. * @@ -290,7 +324,7 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere) float SegmentLengthScale = 100; - UPROPERTY(BlueprintReadOnly, VisibleAnywhere, SkipSerialization) + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UTexture2D* PositionTexture; UPROPERTY(BlueprintReadWrite, EditAnywhere) @@ -299,7 +333,7 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere) int32 TextureHeight; - UPROPERTY(BlueprintReadOnly, VisibleAnywhere, SkipSerialization) + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UMaterialInstanceDynamic* DynamicLineMaterial; UPROPERTY(EditAnywhere, DisplayName = "Lines", meta = (MakeEditWidget = true, EditFixedOrder)) diff --git a/InstancedMeshLines/Source/InstancedMeshLines/Private/GPULineActor.cpp b/InstancedMeshLines/Source/InstancedMeshLines/Private/GPULineActor.cpp index c8f000b85ea68f0a44184c8893257d1ef55e0d20..869e9eb862eeb618bd37ed2011767993b9acc200 100644 --- a/InstancedMeshLines/Source/InstancedMeshLines/Private/GPULineActor.cpp +++ b/InstancedMeshLines/Source/InstancedMeshLines/Private/GPULineActor.cpp @@ -25,25 +25,37 @@ AGPULineActor::AGPULineActor() // Called when the game starts or when spawned void AGPULineActor::BeginPlay() { - GPUInstancedLineComponent->ReserveMemory(Segments, Lines); + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("ABasicLineActor::BeginPlay")) Super::BeginPlay(); + + GPUInstancedLineComponent->ReserveMemory(Segments, Lines); const uint32 MaxLines = Lines; const uint32 MaxSegments = Segments; - TArray<FVector> Points; - Points.SetNumUninitialized(MaxSegments + 1); + TArray<FVector4> Points; + Points.SetNumUninitialized(MaxLines * (MaxSegments + 1)); + + TArray<FLinearColor> Colors; + Colors.SetNumUninitialized(MaxLines); + TArray<float> Widths; + Widths.SetNumUninitialized(MaxLines); for(uint32 i = 0; i < MaxLines; ++i) { const float PosX = i * 100; + float W = 0; for (uint32 j = 0; j < MaxSegments; ++j) { const float PosZ = j * 100; - Points[j] = FVector(PosX, 0, PosZ); + Points[i * j] = FVector4(PosX, 0, PosZ, W); + W = 1.0; } - Points[MaxSegments] = Points[MaxSegments - 1] + FVector(0, 0, 100); - - GPUInstancedLineComponent->AddLine(Points, FVector(FMath::FRandRange(0.0, 1.0), FMath::FRandRange(0.0, 1.0), FMath::FRandRange(0.0, 1.0)), FMath::FRandRange(0.1, 10.0)); + Points[i * MaxSegments] = Points[i * (MaxSegments - 1)] + FVector4(PosX, 0, MaxSegments * 100, -1); + Colors[i] = FLinearColor::MakeRandomColor(); + Widths[i] = FMath::FRandRange(0.1, 10.0); } + + GPUInstancedLineComponent->InitializeLinesInBulk(MaxLines, MaxSegments, Points, Colors, Widths); + } // Called every frame