Spawned actors rendering bug

Branch: Source 4.2

Build version: 4.2.1 (Win64 Development Editor)

I have a strange problem when spawning custom Actors.

I created ASimpleActor class which have box StaticMeshComponent, and also ASimpleActrorSpawner which spawn ASimpleActor-s to the pool (they are hidden on the start). After that I switch their visibility/collision over time (showing them in the scene). This is needed for optimization on mobile devices (exclude spawn function in the middle of gameplay).

If spawn ~1500 actors and visualize them (no matter how many), everything looks good:

But if I spawn something about >2000 actors then strange graphics artifacts apear with some actors overtime:

Looks like there is a problem with the end part of the pool. Screenshots are captured on PC, but I have also tested this code on iOS and there is the same problem, but objects are not colored and simply black. Also this issue cause a very significant draw time growth. (x10 times).

SimpleActor.h:

#pragma once

#include "GameFramework/Actor.h"
#include "SimpleActor.generated.h"

UCLASS()
class ASimpleActor : public AActor
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(Category = SimpleActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|StaticMesh"))
	TSubobjectPtr<class UStaticMeshComponent> StaticMeshComponent;	

	virtual void Tick(float DeltaSeconds) OVERRIDE;

	UFUNCTION()
	void Show();

	UFUNCTION()
	void Hide();

	uint32 Counter;
};

SimpleActor.cpp:

#include "Idol.h"
#include "SimpleActor.h"


ASimpleActor::ASimpleActor(const class FPostConstructInitializeProperties& PCIP)
	: Super(PCIP)
{	
	static ConstructorHelpers::FObjectFinder<UStaticMesh> SM_Box(TEXT("StaticMesh'/Game/Common/Meshes/SM_Box_1.SM_Box_1'"));

	bWantsInitialize = false;
	Counter = 0;

	StaticMeshComponent = PCIP.CreateDefaultSubobject<UStaticMeshComponent>(this, TEXT("StaticMeshComponent"));
	RootComponent = StaticMeshComponent;

	if (SM_Box.Object)
	{
		StaticMeshComponent->SetStaticMesh(SM_Box.Object);
	}

	StaticMeshComponent->CreateAndSetMaterialInstanceDynamic(0);	

	StaticMeshComponent->SetRelativeScale3D(FVector(0.2, 0.2, 0.2));
	
	StaticMeshComponent->Mobility = EComponentMobility::Movable;
	StaticMeshComponent->SetCollisionProfileName(UCollisionProfile::BlockAllDynamic_ProfileName);
}
// End

void ASimpleActor::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	Counter++;
}
// End

void ASimpleActor::Show()
{
	PrimaryActorTick.bCanEverTick = true;
	this->SetActorTickEnabled(true);
	this->SetActorHiddenInGame(false);	
	this->SetActorEnableCollision(true);
}
// End

void ASimpleActor::Hide()
{
	PrimaryActorTick.bCanEverTick = false;
	this->SetActorTickEnabled(false);
	this->SetActorHiddenInGame(true);
	this->SetActorEnableCollision(false);
}
// End

SimpleActorSpawner.h:

#pragma once

#include "GameFramework/Actor.h"

#include "SimpleActor.h"

#include "SimpleActorSpawner.generated.h"

UCLASS()
class ASimpleActorSpawner : public AActor
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SimpleActorSpawner)
	int32 PoolSize;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SimpleActorSpawner)
	int32 VisibleAmount;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SimpleActorSpawner)
	int32 ChangesPerTick;

	UPROPERTY()
	TArray<ASimpleActor*> Pool;

	UPROPERTY()
	TArray<ASimpleActor*> Visible;

	virtual void BeginPlay() OVERRIDE;

	virtual void Tick(float DeltaSeconds) OVERRIDE;

	UFUNCTION()
	void FillPool();
};

SimpleActorSpawner.cpp:

#include "Idol.h"
#include "SimpleActorSpawner.h"


ASimpleActorSpawner::ASimpleActorSpawner(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
	PrimaryActorTick.bCanEverTick = true;
	this->SetActorTickEnabled(true);

	PoolSize = 3000;
	VisibleAmount = 400;
	ChangesPerTick = 5;
}
// End

void ASimpleActorSpawner::BeginPlay()
{
	Super::BeginPlay();		

	FillPool();
}
// End

void ASimpleActorSpawner::FillPool()
{
	ASimpleActor* SimpleActor;
	for (int32 i = 0; i < PoolSize; i++)
	{
		SimpleActor = GWorld->SpawnActor<ASimpleActor>(FVector(0, 0, 0), FRotator(0, 0, 0));
		if (SimpleActor && SimpleActor->IsValidLowLevel())
		{
			SimpleActor->Hide();
			Pool.Add(SimpleActor);
		}
	}
}

void ASimpleActorSpawner::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	ASimpleActor* SimpleActor;
	int32 i;

	for (i = 0; i < ChangesPerTick && Pool.Num() + Visible.Num() > 0; i++)
	{
		SimpleActor = Pool[0];
		Pool.RemoveAt(0);

		FVector Loc = FVector(FMath::SRand() - 0.5, FMath::SRand() - 0.5, FMath::SRand()*0.5);
		Loc.Normalize();
		SimpleActor->SetActorLocation(Loc * 500);
		SimpleActor->Show();

		Visible.Add(SimpleActor);
	}
	
	while (Visible.Num() > VisibleAmount)
	{
		SimpleActor = Visible[0];
		Visible.RemoveAt(0);

		SimpleActor->Hide();

		Pool.Add(SimpleActor);
	}
}
// End

Hi Xegby -

I don’t think there is anything wrong with your code, but I wanted to check on your CPUs specification. You also may want to look into using an Instanced Static Mesh instead of just spawning with new static meshes which will help with some of the draw call bottlenecks as well.

Thank You

Eric Ketchum

Hi Eric.

PCs specifications on which I tested: PC1, PC2

I also tested on iPad3 (iOS 6.1.3) and can make a tests on iPhone 4S and iPad Mini 2, but only tomorrow can post results.
Note: I rebuilt engine (and project of course) with Win64 Shipping configuration but nothing has changed.

PS Thank you for your advice about Instanced Static Meshes, I will try it tomorow.

I tried using InstancedStaticMeshComponent, and it have good perfomance.
But, unfortunately, instanced meshes did not work on mobile =)

Hi Xegby -

Unfortunately, the errors you are having are based on lack of memory in the CPU/GPU to render the total number of draw calls you have. The way you are spawning the actors each actor is a separate draw call and it is maxing out. Does your intended use require the player’s camera to spin around the spawned objects? It might be possible to use a Particle System to simulate the effect you are looking for but only render camera facing planes as opposed to full cubes.

Thank You -

Eric Ketchum

Hi Eric,

This scene is made for test purpose: check UE4 ability to spawn and manage many dynamic objects - we need this for dynamicaly generating way in a runner like game (for example Temple Run). As for an optimization, we made a pool to hide and store already spawned objects in it and then show them back when needed, instead of destroy them and spawn again over time (because of significant glitches on garbage collection moment).

In this scene we have 2000 actors in pool and 400 actors showing (Hidden false). But that’s no matter. If we show 100 actors artifacts are still there. Or maybe setting actors Hidden still causing them to draw? Anyway it’s still max 2000 draw calls. Can’t believe that my PC and iPad have simillar capability in terms of CPU/GPU memory or that is UE4 can draw only ~1500 dynamic objects at all. Maybe we need to hide or turn off objects in any other way? Would you have any suggestions on that?

Thank You,
Roman Kalinin

Eric,

We also found this topic:

There are about ~10 000 spawned meshes and still everything works fine. Can’t see the difference.

Thank You,
Roman Kalinin

Hi Roman -

The issue is in draw calls not vertex generation. In an instanced static mesh there is a shared material and one draw call, so you may be generating 10,000 meshes or nearly 80,000 vertices (if their cubes) but you only have 1 draw call. Therefore a fast performance, unfortunately this method rendering is not supported in OpenGL infrastructure used in most mobile devices.

I believe this issue you guys are exactly experiencing is a matter of rendering lightmaps over top of one another. So, basically you substantiate the objects and set them to hidden, but according to the engine they exist which is taking into account their placement and rendering lights one on top of another (or in your case shadows). It is this computation which is maxing out the render engine.

Try to spawn your actors without overlap, so in a straight line and see if the glitch remains and you get black faces.

Thank You

Eric Ketchum

Hi Eric

Nope, we have tried this today, and it didn’t work out.

Thank You

Aleksandr Chupin