Poglavlje 49: CPU Optimizacija
U ovom poglavlju ulazimo duboko u svet CPU optimizacije u Unreal Engine 5. Naucicete kako da identifikujete, razumete i resite svaki znacajan CPU bottleneck -- od game thread-a koji gusi logiku igre, preko render thread-a koji ne stize da pripremi draw call-ove, do skrivenih ubica kao sto su Garbage Collection pauze, Blueprint overhead i previse aktora koji tikuju svaki frejm. Ovo nije poglavlje o teoriji -- ovo je poglavlje koje ce vam sacuvati milisekunde, a svaka milisekunda se racuna.
Uvod: CPU -- Nevidljivi Dirigent
Zamislite orkestar. GPU je zvucnik -- on proizvodi finalnu sliku, piksele na ekranu. Ali CPU? CPU je dirigent. On odlucuje sta se svira, kada se svira, i kojim redosledom se svira. Ako dirigent ne stize -- ako zamuckuje, kasni sa uputstvima, ili se zagubi u notama -- ceo orkestar staje. Nije bitno koliko je zvucnik mocan ako nema sta da pusti.
U Unreal Engine 5, CPU radi ogromnu kolicinu posla koja je potpuno nevidljiva igracu:
- Izvrsava svu logiku igre (Blueprint i C++ Tick funkcije)
- Racuna fiziku, AI, navigaciju
- Evaluira animacije
- Priprema scene podatke za rendering
- Odlucuje sta je vidljivo a sta ne (visibility i occlusion culling)
- Pakuje draw call-ove i salje ih GPU-u
- Upravlja memorijom, ucitava asset-e, strimuje nivoe
- Pokrece Garbage Collector
Sve ovo mora da se zavrsi unutar jednog frame-a. Na 60 FPS, to je 16.67 milisekundi. Na 30 FPS -- 33.33 milisekundi. Zvuci puno? Verujte mi, nije.
Ako ste procitali poglavlje 40 (UE5 Profiling Alati), vec znate kako da identifikujete da li je CPU vas bottleneck -- stat unit komanda jasno pokazuje Game thread, Draw (Render) thread i GPU vremena. Ako ste procitali poglavlje 39 (Optimizacija -- Filozofija), znate da nikada ne treba optimizovati naslepo. U ovom poglavlju pretpostavljamo da ste uradili profajliranje, ustanovili da je CPU problem, i sada trazite konkretna resenja.
Krenimo od temelja.
49.1 Game Thread vs Render Thread: Dva Mozga Jednog Engine-a
49.1.1 Sta radi Game Thread?
Game thread (ponekad nazvan "main thread" ili prikazan kao "Game" u stat unit) je nit koja izvrsava svu logiku vase igre. Svaki frejm, game thread radi sledece, ovim pribliznim redosledom:
- Input processing -- cita ulaz sa tastature, misa, gamepad-a
- Actor Tick -- poziva
Tick()funkciju na svakom aktoru koji ima omogucen ticking - Component Tick -- poziva
Tick()na svakoj komponenti koja ima omogucen ticking - Physics simulation -- pokrece Chaos physics solver
- Animation evaluation -- evaluira AnimGraph, skeletal mesh-ove, blend space-ove
- AI update -- Behavior Tree-ovi, EQS upiti, AI Perception
- Navigation -- NavMesh upiti, path finding
- Gameplay framework -- GameMode, GameState, PlayerController logika
- Blueprint execution -- sav Blueprint kod koji se izvrsava ovog frejma
- Garbage Collection -- periodocno ciscenje memorije
- Async task completion -- zavrsavanje asinhronih operacija
Sve ovo je sekvencijalno unutar game thread-a (sa nekim izuzecima gde engine koristi paralelizaciju, o cemu cemo pricati u sekciji 49.7). Ako imate aktor sa skupim Tick-om koji traje 5 ms, i jos 200 aktora koji tikuju po 0.05 ms svaki -- to je 5 + 10 = 15 ms samo na ticking. Na 60 FPS, skoro vas ceo budzet je potrosen samo na tikovanje aktora.
49.1.2 Sta radi Render Thread?
Render thread (prikazan kao "Draw" u stat unit) je potpuno odvojena nit od game thread-a. Njen posao je da pripremi sve sto GPU treba da nacrta:
- Scene proxy update -- azurira render-side predstave (proxy-je) objekata na osnovu promena iz game thread-a
- Visibility determination -- prolazi kroz scenu i odlucuje koji objekti su potencijalno vidljivi
- Occlusion culling -- preciznije testira da li su objekti zaista vidljivi ili ih nesto zaklanja
- Draw call preparation -- za svaki vidljivi objekat, priprema mesh draw command sa svim potrebnim state-ovima
- Sorting -- sortira transparentne objekte po dubini, sortira opaque objekte po materijalu za bolji batching
- Light setup -- priprema podatke o svetlima koja uticu na scenu
- Shadow setup -- odlucuje koji objekti bacaju senke i u koje shadow mape
- Command list building -- pakuje sve komande u command buffer koji ce GPU izvrsiti
Kljucna stvar: render thread ne crta nista na ekranu. On samo priprema instrukcije za GPU. Ali ta priprema moze biti vrlo skupa -- pogotovo ako imate mnogo objekata, mnogo svetala, ili kompleksnu scenu.
49.1.3 Kako rade paralelno -- Pipeline Model
Ovo je mozda najvazniji koncept za razumevanje CPU performansi u UE5. Game thread i render thread rade paralelno, ali sa jednim frame-om razmaka:
Game Thread: [Frame N+1][Frame N+2][Frame N+3][Frame N+4] ...
Render Thread: [ ][Frame N ][Frame N+1][Frame N+2] ...
GPU: [ ][ ][Frame N ][Frame N+1] ...
Vreme ---->
Dok game thread racuna logiku za frame N+1, render thread priprema rendering za frame N (koji je game thread vec zavrsio), a GPU renderuje frame N-1. Ovo je pipelined execution -- svaka faza radi na razlicitom frame-u istovremeno.
Zasto je ovo vazno? Zato sto se frame time odredjuje najsporijim od ova tri, a ne njihovim zbirom. Ako game thread traje 8 ms, render thread 6 ms, a GPU 12 ms, vas frame time ce biti ~12 ms (plus mali overhead za sinhronizaciju), a ne 26 ms.
Ali ovo takodje znaci da ako je jedan thread dramaticno sporiji od ostalih, ostali ce cekati:
Scenario: Game Thread je bottleneck
Game Thread: [████████████████████████] 20 ms
Render Thread: [████████░░░░░░░░░░░░░░░░] 8 ms (ceka game thread)
GPU: [██████████░░░░░░░░░░░░░░] 10 ms (ceka render thread)
Frame Time = 20 ms (50 FPS umesto 60 FPS)
Scenario: Render Thread je bottleneck
Game Thread: [██████░░░░░░░░░░░░░░░░░░] 6 ms (zavrsava brzo, ali...)
Render Thread: [████████████████████████] 24 ms (spora priprema scene)
GPU: [████████████░░░░░░░░░░░░] 12 ms (ceka render thread)
Frame Time = 24 ms (~41 FPS)
49.1.4 Game Thread moze da blokira Render Thread (i obrnuto)
Pored osnovnog pipeline modela, postoje situacije gde jedan thread eksplicitno ceka drugi:
Game thread ceka render thread:
- Kada game thread zavrsi frame N+1 brze nego sto render thread zavrsi frame N, game thread mora da ceka pre nego sto moze da nastavi sa frame N+2. Ovo je zato sto bi inace game thread "pobegao" predaleko ispred, sto bi izazvalo probleme sa konzistentnoszcu podataka.
- U profajleru, ovo se vidi kao
WaitForRenderThreadiliGameThreadWaitForRenderThreadmarker.
Render thread ceka game thread:
- Obrnuto -- ako je game thread sporiji, render thread mora da ceka podatke za sledeci frame.
- Vidite
RenderThreadIdlemarker u profajleru.
FlushRenderingCommands:
- Ovo je najgori scenario. Poziv
FlushRenderingCommands()iz game thread-a forsira game thread da ceka dok render thread ne zavrsi sve pending komande. Ovo efektivno ukida paralelizam izmedju ta dva thread-a. - Neke operacije ovo rade implicitno (npr. sinhrone promene RHI resursa).
- U profajleru, ovo se vidi kao
FlushRenderingCommandsblok.
// LOSE: Ovo ubija paralelizam
FlushRenderingCommands();
// Game thread je pauziran dok render thread ne zavrsi SVE
// BOLJE: Koristite async verzije operacija kad god je moguce
// Vecina UE5 operacija ima async alternativu
49.1.5 RHI Thread -- Treci Igrac
Pored game i render thread-a, UE5 ima i RHI (Rendering Hardware Interface) thread. Ovaj thread je posrednik izmedju render thread-a i grafickog API-ja (DirectX 12, Vulkan). Njegov posao je da prevodi engine-level rendering komande u API-specificne pozive.
RHI thread je retko bottleneck u modernim projektima, ali moze postati problem ako imate:
- Ogroman broj state change-ova
- Mnogo malih draw call-ova koji konstantno menjaju pipeline state
- Probleme sa drajverom (stariji drajveri mogu imati skup RHI overhead)
U stat unit, RHI thread se prikazuje kao RHIT. Ako vidite da je RHIT veci od Draw i Game, imate problem na RHI nivou -- sto je neobicno i obicno ukazuje na nesto fundamentalno pogresno sa rendering pipeline-om ili drajverom.
49.2 Tick Budgeting: Kontrola Nad Kostom Svakog Frejma
49.2.1 Problem: Svaki Tick Kosta
Svaki aktor i svaka komponenta u UE5 ima mogucnost da "tikuje" -- da izvrsava kod svaki frejm. Ovo je osnova gameplay programiranja: proveravate input, pomerate aktore, proveravate uslove, azurirate stanja.
Ali hajde da napravimo racunicu:
Scena sa 500 aktora, svaki tikuje
Prosecna cena jednog Tick-a: 0.02 ms
Ukupno: 500 * 0.02 ms = 10 ms
Na 60 FPS (budzet 16.67 ms), potrosili ste 60% budzeta
samo na tikovanje -- pre fizike, animacija, AI-a, renderinga...
Zvuci malo, 0.02 ms po aktoru? Da, svaki individualni tick je jeftin. Ali skaliranje je ubica. Ovo je jedan od najcescih CPU bottleneck-ova u UE5 projektima, i jedan od najlaksih za resavanje.
49.2.2 Smanjenje frekvencije tikovanja
Prva i najjednostavnija optimizacija: ne mora svaki aktor da tikuje svaki frejm.
Tick Interval:
// U konstruktoru aktora
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 0.1f; // Tikuj samo svakih 100ms (10 puta u sekundi)
}
// Ili dinamicki, u runtime-u
SetActorTickInterval(0.5f); // Tikuj dva puta u sekundi
U Blueprintu, ovo mozete podesiti u Class Defaults:
Actor Tick > Tick Interval: 0.1
Kada koristiti smanjenu frekvenciju:
| Tip logike | Preporuceni interval |
|---|---|
| Movement (igrac, neprijatelji) | Svaki frejm (0.0) |
| AI odlucivanje | 0.1 - 0.5 sekundi |
| UI update | 0.05 - 0.2 sekundi |
| Proveravanje uslova (da li je igrac blizu?) | 0.2 - 1.0 sekundi |
| Ambient efekti (rotirajuci objekat) | 0.033 (30 Hz) |
| Proveravanje inventara | 0.5 - 2.0 sekundi |
| Daleki NPC-ovi | 0.5 - 1.0 sekundi |
Vazno: Kada koristite Tick Interval, vasa Tick funkcija prima DeltaTime koji odrazava stvarno proteklo vreme od poslednjeg tick-a tog aktora -- ne globalni frame delta. To znaci da vam logika zasnovana na DeltaTime i dalje radi korektno, samo se izvrsava redje.
49.2.3 Potpuno iskljucivanje tick-a
Najbolji tick je onaj koji ne postoji:
// U konstruktoru -- nikada ne tikuj
AMyActor::AMyActor()
{
PrimaryActorTick.bCanEverTick = false;
}
Mnogi aktori uopste ne treba da tikuju. Primeri:
- Staticni dekorativni objekti (stolovi, stolice, zidovi)
- Trigger volume-i (koriste overlap event-e, ne tick)
- Pickup-ovi koji samo cekaju da ih igrac pokupii (koriste overlap)
- Audio emitteri (MetaSounds ih upravlja interno)
- Light source aktori (staticni ili Lumen-upravljani)
Pravilo palca: Ako vas aktor ne radi nista aktivno svaki frejm, iskljucite mu tick. Koristite event-driven pristup umesto toga -- overlap events, delegate, timer-e.
49.2.4 Dinamicko ukljucivanje/iskljucivanje tick-a
Cesto zelite da aktor tikuje samo kada je relevantan -- na primer, kada je igrac blizu:
// Iskljuci tick (runtime)
SetActorTickEnabled(false);
// Ukljuci tick (runtime)
SetActorTickEnabled(true);
Prakticni pattern za distance-based tick management:
void AMyActor::CheckShouldTick()
{
APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(this, 0);
if (!PlayerPawn) return;
float Distance = FVector::Dist(GetActorLocation(), PlayerPawn->GetActorLocation());
if (Distance < ActivationDistance && !IsActorTickEnabled())
{
SetActorTickEnabled(true);
}
else if (Distance > DeactivationDistance && IsActorTickEnabled())
{
SetActorTickEnabled(false);
}
}
Ali cekajte -- ako ste iskljucili tick, kako cete proveravati distancu? Resenje:
- Timer-based provera: Koristite
SetTimerda povremeno proveravate distancu - Significance Manager: UE5 ima ugradjeni sistem (vise o njemu uskoro)
- Centralizovani manager: Jedan "manager" aktor tikuje i proverava distance za sve aktore
49.2.5 Timer-Based Updates umesto Tick-a
Timer-i su cesto bolji od Tick-a jer:
- Mozete ih podesiti na proizvoljan interval
- Lako ih ukljucujete i iskljucujete
- Nisu vezani za frame rate
- Imaju manji overhead od registrovanog Tick-a koji je samo iskljucen
// Postavi timer koji poziva funkciju svakih 0.5 sekundi
GetWorldTimerManager().SetTimer(
MyTimerHandle, // Handle za kasniju kontrolu
this, // Objekat
&AMyActor::DoUpdate, // Funkcija koja se poziva
0.5f, // Interval u sekundama
true // Da li se ponavlja
);
// Zaustavi timer
GetWorldTimerManager().ClearTimer(MyTimerHandle);
U Blueprintu:
Set Timer by Function Name
Function Name: "DoUpdate"
Time: 0.5
Looping: true
Timer vs Tick -- Kada koristiti koji:
| Situacija | Preporuka |
|---|---|
| Mora da se izvrsava svaki frejm (movement, input) | Tick |
| Mora da se izvrsava sa fiksnim intervalom | Timer |
| Povremena provera stanja | Timer |
| Event koji se desava jednom nakon delay-a | Timer (non-looping) |
| Potrebna precizna sinhronizacija sa rendering-om | Tick |
49.2.6 Significance Manager
UE5 ima ugradjeni Significance Manager sistem koji automatski upravlja znacajnoscu objekata na osnovu njihove udaljenosti od kamere i drugih faktora.
// Registrujte aktor sa Significance Manager-om
USignificanceManager* SM = FSignificanceManagerModule::Get(GetWorld());
if (SM)
{
SM->RegisterObject(
this, // Objekat
TEXT("MyActorTag"), // Tag
[](USignificanceManager::FManagedObjectInfo* Info,
const FTransform& ViewPoint) -> float
{
// Funkcija koja racuna "znacajnost" -- veca vrednost = bitniji objekat
float Distance = FVector::Dist(
Info->GetObject()->GetActorLocation(),
ViewPoint.GetLocation()
);
// Blize = znacajnije (inverzan odnos sa distancom)
return FMath::Max(0.f, 1.f - Distance / 10000.f);
},
USignificanceManager::EPostSignificanceType::Sequential
);
}
Na osnovu vraceene znacajnosti, mozete prilagoditi ponasanje aktora:
void AMyActor::OnSignificanceChanged(float OldSignificance, float NewSignificance)
{
if (NewSignificance > 0.8f)
{
// Blizu igraca -- pun kvalitet
SetActorTickInterval(0.0f); // Svaki frejm
SkeletalMesh->SetAnimationMode(EAnimationMode::AnimationBlueprint);
}
else if (NewSignificance > 0.3f)
{
// Srednja udaljenost -- smanjen kvalitet
SetActorTickInterval(0.1f); // 10 Hz
SkeletalMesh->SetAnimationMode(EAnimationMode::AnimationBlueprint);
}
else
{
// Daleko -- minimalan kvalitet
SetActorTickInterval(0.5f); // 2 Hz
SkeletalMesh->SetAnimationMode(EAnimationMode::AnimationCustomMode);
// Ili cak potpuno zaustavite animacije
}
}
Fortnite intenzivno koristi Significance Manager za upravljanje stotinama igraca i NPC-ova. Aktori koji su daleko od kamere dobijaju smanjenu frekvenciju tick-a, simplifikovane animacije, i manje preciznu fiziku.
49.2.7 Tick Groups i Tick Dependencies
UE5 ima koncept tick grupa koje odredjuju redosled izvrsavanja tick-ova:
TG_PrePhysics -- Pre fizicke simulacije
TG_StartPhysics -- Pocetak fizike
TG_DuringPhysics -- Tokom fizike (paralelno sa fizikom!)
TG_EndPhysics -- Kraj fizike
TG_PostPhysics -- Posle fizicke simulacije
TG_PostUpdateWork -- Posle svega
Ovo je vazno za optimizaciju jer mozete:
- Staviti tick u
TG_DuringPhysicsda bi se izvrsavao paralelno sa fizickom simulacijom (ako ne zavisi od rezultata fizike) - Pravilno podesiti zavisnosti da izbegnete nepotrebno cekanje
// Stavite tick u grupu koja se izvrsava tokom fizike
PrimaryActorTick.TickGroup = TG_DuringPhysics;
Upozorenje: Budite pazljivi sa TG_DuringPhysics -- ako vas tick pristupa fizickim podacima koji se upravo azuriraju, dobicete nedeterministicko ponasanje (race conditions).
49.3 Component Optimization: Manje je Vise
49.3.1 Svaka Komponenta Kosta
Aktori u UE5 su kontejneri za komponente. Svaka komponenta koju dodate na aktor ima svoju cenu:
- Registracija: Kada se komponenta registruje na scenu, engine azurira interne strukture podataka
- Tick: Mnoge komponente imaju svoj tick (odvojen od tick-a aktora)
- Memorija: Svaka komponenta zauzima memoriju za svoje podatke
- Scene traversal: Render-related komponente se prolaze pri visibility check-ovima
Hajde da pogledamo tipicne cene nekih komponenti:
| Komponenta | Tick cena | Memorija | Rendering overhead |
|---|---|---|---|
StaticMeshComponent |
Nizak (ne tikuje podrazumevano) | Srednja | Draw call |
SkeletalMeshComponent |
Visok (evaluira animacije) | Velika | Draw call + bone transforms |
CharacterMovementComponent |
Visok | Srednja | Nikakav |
AudioComponent |
Srednji | Mala | Nikakav |
ParticleSystemComponent |
Visok | Velika | Visestruki draw calls |
BoxCollisionComponent |
Nizak | Mala | Nikakav (debug only) |
AIPerceptionComponent |
Srednji-Visok | Srednja | Nikakav |
WidgetComponent |
Visok (UI update) | Velika | Draw call |
SplineComponent |
Nizak | Srednja | Debug only |
SceneComponent |
Minimalan | Mala | Nikakav |
49.3.2 Deaktivacija nekoristenih komponenti
Ne morate unistiti komponentu da biste eliminisali njen overhead -- mozete je samo deaktivirati:
// Deaktiviraj komponentu -- zaustavlja tick, uklanja iz scene queries
MyComponent->Deactivate();
// Reaktiviraj kada bude potrebna
MyComponent->Activate();
Za rendering komponente (mesh-eve), koristite visibility:
// Sakrij mesh -- uklanja ga iz rendering-a ali komponenta ostaje
MyMeshComponent->SetVisibility(false);
// Ili jos bolje, potpuno ga ukloni iz scene:
MyMeshComponent->SetHiddenInGame(true);
Razlika izmedju SetVisibility(false) i Deactivate():
SetVisibility(false)samo sakriva vizuelni prikaz, ali komponenta i dalje tikuje, obraduje collision, i trosi CPUDeactivate()potpuno zaustavlja komponentu -- nema tick-a, nema scene queries, minimalan overhead
Za Skeletal Mesh komponente, imate dodatne opcije:
// Zaustavi evaluaciju animacija (ali komponenta ostaje aktivna)
SkeletalMeshComp->SetComponentTickEnabled(false);
// Ili koristi URO (Update Rate Optimization) -- vise o ovome uskoro
SkeletalMeshComp->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
49.3.3 Biranje laksih alternativa
Ponekad pravi odgovor nije optimizacija postojece komponente vec zamena sa laksom alternativom:
Static Mesh umesto Skeletal Mesh: Ako objekat nema animacije, koristite Static Mesh. Skeletal Mesh evaluira bone transformacije svaki frejm cak i bez AnimBP-a, sto je nepotreban overhead. Izuzetak je ako vam treba morphing ili proceduralna animacija -- ali i tada razmislite da li Static Mesh sa Material-based animacijom moze da posluzi (npr. World Position Offset u materijalu za vetar koji duva travu).
InstancedStaticMeshComponent umesto mnogo StaticMeshComponent-i:
Ako imate 100 istih mesh-eva (npr. drve u sumi), jedan InstancedStaticMeshComponent (ili HierarchicalInstancedStaticMeshComponent) je drasticno jeftiniji od 100 odvojenih StaticMeshComponent-i.
// LOSE: 100 odvojenih aktora sa StaticMeshComponent
for (int i = 0; i < 100; i++)
{
SpawnActor<ATreeActor>(Location);
// 100 aktora, 100 komponenti, 100 tick registracija, 100 draw call-ova
}
// BOLJE: Jedan aktor sa HISM komponentom
UHISM = CreateDefaultSubobject<UHierarchicalInstancedStaticMeshComponent>(TEXT("Trees"));
for (int i = 0; i < 100; i++)
{
HISM->AddInstance(TransformForTree[i]);
// 1 aktor, 1 komponenta, 1 tick registracija, MNOGO manje draw call-ova
}
Billboard umesto mesh-a za daleke objekte: Za objekte koji su daleko i koji nikada nece biti blizu kamere, razmislite o Billboard komponenti ili imposter sprite-ovima.
49.3.4 Component Tick optimizacija
Svaka komponenta ima nezavisnu kontrolu nad ticking-om:
// Iskljuci tick na komponenti (nezavisno od aktora)
MyComponent->SetComponentTickEnabled(false);
// Postavi tick interval na komponenti
MyComponent->SetComponentTickInterval(0.5f);
Cesta greska: ostavljanje tick-a ukljucenog na komponentama koje ne rade nista u Tick-u. UE5 ce i dalje trositi overhead na pozivanje prazne Tick funkcije, registrovanje u tick sistemu, i pracenje tick zavisnosti.
Auditiranje tick-ova u vasem projektu:
Koristite stat dumpticks konzolnu komandu da vidite sve aktore i komponente koje tikuju:
dumpticks // Stampa sve tickove u log
stat tickgroups // Prikazuje statistiku po tick grupama
Ovo je jedan od najkorisnijih debugging alata za CPU optimizaciju. Cesto cete otkriti da imate stotine aktora koji tikuju a da vi niste ni svesni -- jer su spawn-ovani od strane nekog sistema, ili jer neka komponenta ima tick ukljucen podrazumevano.
49.4 Actor Lifecycle Management: Spawn, Destroy, Pool
49.4.1 Cena Spawn-ovanja i Destroy-ovanja
Svaki put kada pozovete SpawnActor(), UE5 radi sledece:
- Alocira memoriju za novi UObject
- Konstruise objekat -- poziva konstruktor, inicijalizuje default vrednosti
- Registruje na scenu -- dodaje aktor u scene octree, registruje komponente
- Poziva BeginPlay -- izvrsava svu inicijalizacionu logiku
- Registruje za ticking -- dodaje aktor u tick listu (ako ima tick)
- Registruje za repliciranje -- ako je multiplayer, registruje za mrenu repliciju
Svaki put kada pozovete DestroyActor():
- Poziva EndPlay -- izvrsava cleanup logiku
- Deregistruje sa scene -- uklanja iz octree-a, deregistruje komponente
- Deregistruje tick -- uklanja iz tick liste
- Markira za Garbage Collection -- objekat se ne brise odmah, vec se markira za GC
Ove operacije nisu besplatne. Za jednostavan aktor, SpawnActor moze da traje 0.05-0.2 ms. Za kompleksan aktor sa mnogo komponenti, moze da traje 1-5 ms. DestroyActor je obicno brzi, ali Garbage Collection koja sledi moze da izazove nepredvidive pauze.
Kada ovo postaje problem?
Zamislite pucacinu. Svaki put kada igrac puca, spawn-ujete metak (Actor), spawn-ujete particle efekt za bljesak (Actor), i spawn-ujete zvuk (Actor). Igrac puca 10 puta u sekundi. To je 30 spawn-ova i 30 destroy-a u sekundi. Za jednog igraca. U multiplayer-u sa 8 igraca? To je potencijalno 240 spawn-ova i 240 destroy-a u sekundi. CPU plače.
49.4.2 Object Pooling -- Reciklirajte Umesto Da Bacate
Object pooling je tehnika gde unapred kreirate grupu objekata, i umesto da ih unistavate kada vise nisu potrebni, "vracate" ih u bazen (pool) za ponovnu upotrebu.
// Jednostavan Object Pool
UCLASS()
class AProjectilePool : public AActor
{
GENERATED_BODY()
public:
void InitializePool(TSubclassOf<AProjectile> ProjectileClass, int32 PoolSize);
AProjectile* GetProjectile();
void ReturnProjectile(AProjectile* Projectile);
private:
UPROPERTY()
TArray<AProjectile*> AvailableProjectiles;
UPROPERTY()
TArray<AProjectile*> ActiveProjectiles;
};
void AProjectilePool::InitializePool(TSubclassOf<AProjectile> ProjectileClass, int32 PoolSize)
{
for (int32 i = 0; i < PoolSize; i++)
{
AProjectile* Proj = GetWorld()->SpawnActor<AProjectile>(ProjectileClass);
Proj->SetActorHiddenInGame(true);
Proj->SetActorEnableCollision(false);
Proj->SetActorTickEnabled(false);
AvailableProjectiles.Add(Proj);
}
}
AProjectile* AProjectilePool::GetProjectile()
{
if (AvailableProjectiles.Num() == 0)
{
UE_LOG(LogTemp, Warning, TEXT("Pool is empty! Consider increasing pool size."));
return nullptr; // Ili dinamicki kreiraj jos jedan
}
AProjectile* Proj = AvailableProjectiles.Pop();
Proj->SetActorHiddenInGame(false);
Proj->SetActorEnableCollision(true);
Proj->SetActorTickEnabled(true);
Proj->ResetProjectile(); // Resetujte stanje na default
ActiveProjectiles.Add(Proj);
return Proj;
}
void AProjectilePool::ReturnProjectile(AProjectile* Projectile)
{
Projectile->SetActorHiddenInGame(true);
Projectile->SetActorEnableCollision(false);
Projectile->SetActorTickEnabled(false);
ActiveProjectiles.Remove(Projectile);
AvailableProjectiles.Add(Projectile);
}
Prednosti pooling-a:
- Eliminise cost spawn-ovanja i destroy-ovanja u runtime-u
- Eliminise Garbage Collection pritisak (objekti nikada nisu unistavani)
- Predvidljiva memorijska upotreba (znate tacno koliko objekata postoji)
- Eliminise hitching od GC-a
Mane pooling-a:
- Veca bazna memorijska upotreba (svi objekti su alocirani od starta)
- Kompleksnost koda (morate upravljati stanjem pool-a)
- Morate pazljivo resetovati objekat kada se "reciklira" (zaboravljeni state iz prethodnog zivota moze izazvati bagove)
Sta je vredno pooling-a:
| Objekat | Pooling? | Razlog |
|---|---|---|
| Projektili | Da | Cesto spawn/destroy, kratkog zivota |
| Particle efekti | Delimicno | UE5 Niagara vec ima interni pooling |
| Audio instance | Delimicno | UE5 audio ima vlastiti pooling |
| NPC neprijatelji | Zavisno | Ako se cesto spawn-uju u talasima |
| Pickup-ovi | Da | Cesto spawn/destroy |
| Decali (krv, rupe od metaka) | Da | Mnogo njih, kratkog zivota |
| Glavni likovi | Ne | Retko se spawn-uju/destroy-uju |
| Level geometry | Ne | Staticki, nikada se ne destroy-uju |
49.4.3 Garbage Collection -- Tihi Ubica Performansi
UE5 koristi Garbage Collector (GC) za automatsko upravljanje memorijom UObject-a. GC periodocno skenira sve UObject-e, identifikuje one koji niko vise ne referencira, i brise ih.
Problem? GC se izvrsava na game thread-u i moze izazvati značajne pauze:
Tipicne GC pauze:
Mali projekat (malo UObject-a): 0.5 - 2 ms
Srednji projekat: 2 - 10 ms
Veliki projekat (mnogo UObject-a): 10 - 50+ ms
Na 60 FPS (budzet 16.67 ms), GC pauza od 15 ms znaci da cete propustiti barem jedan frame. Igrac to vidi kao "stucanje" ili micro-freeze.
Kako kontrolisati GC:
-
Smanjite broj UObject-a koji se kreiraju i unistavaju: Ovo je najefektivnija strategija. Manje smeća = manje posla za GC.
-
Podesavanje GC parametara:
gc.MaxObjectsNotConsideredByGC=1 // Minimalan broj objekata koji GC ne razmatra gc.TimeBetweenPurgingPendingKillObjects=60 // Interval izmedju GC ciklusa gc.NumRetriesBeforeForcingGC=5 // Broj pokusaja pre forsiranog GC-a -
Inkrementalni GC: UE5 podrzava inkrementalni GC koji deli posao na vise frejm-ova:
gc.IncrementalBeginDestroyEnabled=1 // Ukljuci inkrementalno unistavanie gc.MultithreadedDestructionEnabled=1 // Koristi vise niti za destrukciju -
Forsirani GC u kontrolisanim trenucima:
// Forsirajte GC kada znate da igrac nece primetiti (loading screen, pauza) GEngine->ForceGarbageCollection(true); -
Koristite non-UObject tipove kada je moguce:
FVector,FTransform,TArray-- ovi tipovi nisu UObject-i i ne prolaze kroz GC. Koristite ih za podatke koji ne trebaju UObject infrastrukturu (repliciranje, editovanje u editoru, itd.).
49.4.4 Cluster-based GC
UE5 ima koncept GC Clustering-a gde se grupa povezanih objekata tretira kao jedan "cluster" za potrebe GC-a. Umesto da GC skenira svaki objekat pojedinacno, skenira cluster kao celinu -- ako je root cluster-a ziv, svi objekti u cluster-u su zivi.
Ovo je posebno korisno za asset-e: StaticMesh sa svojim materijalima i teksturama formira cluster. Umesto da GC proverava svaki od tih objekata, proverava samo StaticMesh root.
// U Project Settings ili DefaultEngine.ini
[/Script/Engine.GarbageCollectionSettings]
gc.CreateGCClusters=True
gc.MinGCClusterSize=5
49.5 Async Loading: Eliminisanje Hitching-a
49.5.1 Sinhrono vs Asinhrono ucitavanje
Jedna od najcescih gresakaka u UE5 projektima je sinhrono ucitavanje asset-a. Evo sta se desava:
// LOSE: Sinhrono ucitavanje
UStaticMesh* Mesh = LoadObject<UStaticMesh>(
nullptr,
TEXT("/Game/Meshes/BigTree.BigTree")
);
// Game thread je BLOKIRAN dok se ceo mesh ne ucita sa diska
// Ako mesh ima 50 MB, ovo moze da traje 100+ ms
// Igrac vidi freeze
Sinhrono ucitavanje blokira game thread dok se ceo asset ne ucita u memoriju. Za male asset-e, ovo moze proci neprimeceno. Ali za velike mesh-eve, teksture, ili Blueprint klase, mozete dobiti pauze od 50-200+ ms -- katastrofalni freeze koji igrac odmah primeti.
49.5.2 Soft References -- Kljuc Asinhronog Pristupa
Razlika izmedju hard reference-a i soft reference-a:
Hard Reference (UStaticMesh* ili TSubclassOf<AActor>):
- Asset se ucitava automatski kada se ucita objekat koji ga referencira
- Garantuje da je asset dostupan, ali moze uzrokovati ogromne lance ucitavanja
- Ceo dependency tree mora da se ucita pre nego sto vas objekat moze da postoji
Primer lanca ucitavanja sa hard reference-om:
MojBP_Neprijatelj (1 MB)
-> Mesh_Neprijatelj (15 MB)
-> Material_Koža (2 MB)
-> Texture_Diffuse (8 MB)
-> Texture_Normal (8 MB)
-> AnimBP (3 MB)
-> Montage_Napad (500 KB)
-> Sound_Napad (2 MB)
-> BP_Projektil (500 KB)
-> Mesh_Projektil (1 MB)
-> Particle_Eksplozija (5 MB)
-> ...
Ukupno za jednog neprijatelja: ~46 MB
Sve se ucitava ODJEDNOM, SINHRONO
Soft Reference (TSoftObjectPtr<UStaticMesh> ili TSoftClassPtr<AActor>):
- Cuva samo putanju (path) do asset-a, ne sam asset
- Asset se ucitava tek kada ga eksplicitno zatrazite
- Mozete ga ucitati asinhrono, u pozadini, bez blokiranja game thread-a
// Deklaracija soft reference-a
UPROPERTY(EditDefaultsOnly, Category = "Assets")
TSoftObjectPtr<UStaticMesh> TreeMeshSoft;
// Asinhrono ucitavanje
void AMyActor::LoadTreeMesh()
{
if (TreeMeshSoft.IsNull()) return;
// Proveri da li je vec ucitano
if (TreeMeshSoft.IsValid())
{
// Vec je u memoriji, koristi odmah
MeshComp->SetStaticMesh(TreeMeshSoft.Get());
return;
}
// Ucitaj asinhrono
FStreamableManager& StreamableManager =
UAssetManager::GetStreamableManager();
StreamableManager.RequestAsyncLoad(
TreeMeshSoft.ToSoftObjectPath(),
FStreamableDelegate::CreateUObject(
this,
&AMyActor::OnTreeMeshLoaded
)
);
}
void AMyActor::OnTreeMeshLoaded()
{
if (TreeMeshSoft.IsValid())
{
MeshComp->SetStaticMesh(TreeMeshSoft.Get());
}
}
49.5.3 StreamableManager i Asset Manager
StreamableManager je UE5 sistem za asinhrono ucitavanje asset-a. Pruza vise mogucnosti od prostog RequestAsyncLoad:
// Ucitaj grupu asset-a odjednom
TArray<FSoftObjectPath> AssetsToLoad;
AssetsToLoad.Add(MeshSoft.ToSoftObjectPath());
AssetsToLoad.Add(MaterialSoft.ToSoftObjectPath());
AssetsToLoad.Add(SoundSoft.ToSoftObjectPath());
StreamableHandle = StreamableManager.RequestAsyncLoad(
AssetsToLoad,
FStreamableDelegate::CreateLambda([this]()
{
// Svi asset-i su ucitani
OnAllAssetsLoaded();
}),
FStreamableManager::DefaultAsyncLoadPriority,
false // bManageActiveHandle -- da li StreamableManager upravlja handle-om
);
Asset Manager je visi nivo apstrakcije nad StreamableManager-om. Omogucava vam da definisete "primarne asset tipove" i upravljate njihovim ucitavanjem:
// U AssetManager subclass-u
void UMyAssetManager::StartInitialLoading()
{
Super::StartInitialLoading();
// Registruj primarne asset tipove
// Ovo odredjuje koje asset-e engine "zna" i moze da ucita na zahtev
}
// Ucitavanje primarnog asset-a
FPrimaryAssetId WeaponAssetId = FPrimaryAssetId("Weapon", "Rifle_AK47");
UAssetManager::GetIfValid()->LoadPrimaryAsset(
WeaponAssetId,
TArray<FName>(), // Bundle-ovi koji se ucitavaju
FStreamableDelegate::CreateUObject(this, &AMyActor::OnWeaponLoaded)
);
49.5.4 Preloading -- Ucitajte Pre Nego Sto Zatreba
Najbolji nacin da izbegnete hitching je da ucitate asset pre nego sto vam zatreba:
// Na pocetku nivoa, preload-ujte asset-e koji ce vam trebati
void AMyGameMode::BeginPlay()
{
Super::BeginPlay();
// Preload neprijatelje koji ce se pojaviti u ovom nivou
for (const TSoftClassPtr<AEnemy>& EnemyClass : EnemiesInThisLevel)
{
StreamableManager.RequestAsyncLoad(
EnemyClass.ToSoftObjectPath(),
FStreamableDelegate() // Nema callback -- samo ucitaj u pozadini
);
}
}
Strategije preloading-a:
- Loading screen preload: Ucitajte sve asset-e dok je loading screen aktivan
- Proximity preload: Kada se igrac priblizi nekom delu nivoa, pocnite asinhrono ucitavanje asset-a za taj deo
- Anticipatory preload: Na osnovu gameplay-a, predvidite sta ce igracu trebati (npr. ucitajte boss mesh pre nego sto igrac udje u boss sobu)
- Bundle preload: Grupisite povezane asset-e u "bundle-ove" i ucitajte ih zajedno
49.5.5 Merenje hitching-a od ucitavanja
Koristite sledece alate da detektujete hitching od sinhronog ucitavanja:
stat asyncload // Statistika asinhronog ucitavanja
stat streaming // Streaming statistika
stat levels // Level streaming statistika
U Unreal Insights-u, potrazite:
LoadObjectmarker-e na game thread-u -- svaki od ovih je sinhrono ucitavanjeFlushAsyncLoading-- forsirano cekanje na asinhrona ucitavanja (takodje lose)- Dugacke pauze na game thread-u sa
Serializeu call stack-u
49.6 Level Streaming Optimization
49.6.1 Zasto je Level Streaming vazan?
Ni jedna igra ne moze da drzi ceo svet u memoriji odjednom. Otvoreni svetovi sa desetinama kvadratnih kilometara terena, hiljadama zgrada, i milionima objekata bi zahtevali stotine gigabajta RAM-a. Resenje je level streaming -- ucitavanje i oslobadjanje delova sveta dinamicki, na osnovu pozicije igraca.
UE5 nudi dva glavna sistema za ovo:
- World Partition (moderni pristup, pokriven u poglavlju 32) -- automatski deli svet na celije i strimuje ih
- Level Streaming (klasicni pristup) -- rucno kreirate sub-level-e i kontrolisete njihovo ucitavanje
49.6.2 Streaming Volumes
Level Streaming Volumes su 3D volumeni u svetu koji kontrolisu ucitavanje i oslobadjanje sub-level-a:
Kada igrac UDJE u volume -> Ucitaj sub-level
Kada igrac IZADJE iz volumena -> Oslobodi sub-level
Optimizacijske strategije za volume-e:
- Preload volume-i: Napravite vece volume-e koji se prostiru izvan vidljivosti igraca. Kada igrac udje u preload volume, pocnite ucitavanje. Kada udje u main volume, level je vec ucitan.
[ Preload Volume (veci) ]
[ ]
[ [ Main Volume (manji) ] ]
[ [ ] ]
[ [ Level Content ] ]
[ [ ] ]
[ [______________________] ]
[ ]
[______________________________]
-
Hysteresis: Dodajte razmak izmedju ucitavanja i oslobadjanja da izbegnete "thrashing" (konstantno ucitavanje i oslobadjanje kada je igrac na granici volumena).
-
Prioritizacija: Ne ucitavajte sve level-e sa istim prioritetom. Level koji je ispred igraca treba da se ucita pre level-a koji je iza njega.
49.6.3 Programsko kontrolisanje streaming-a
Ponekad volume-i nisu dovoljni. Mozete kontrolisati streaming programski:
// Ucitaj sub-level asinhrono
UGameplayStatics::LoadStreamLevel(
this,
FName("SubLevel_CityBlock_3"),
true, // bMakeVisibleAfterLoad
false, // bShouldBlockOnLoad -- NIKADA ne stavljajte true osim za loading screen
LatentInfo
);
// Oslobodi sub-level
UGameplayStatics::UnloadStreamLevel(
this,
FName("SubLevel_CityBlock_3"),
LatentInfo,
false // bShouldBlockOnUnload
);
49.6.4 Loading Screens kao optimizacijski alat
Loading screen-ovi nisu samo kozmeticki -- oni su legitimno optimizacijski alat:
- Sinhrono ucitavanje tokom loading screen-a: Mozete koristiti sinhrono ucitavanje bez brige za hitching jer igrac ne vidi gameplay
- GC cleanup: Pokrenite potpuni GC ciklus tokom loading screen-a
- Precomputation: Izracunajte sve sto vam treba (NavMesh, AI patrolne rute, itd.)
void AMyGameMode::TransitionToLevel(FName LevelName)
{
// Pokazi loading screen
ShowLoadingScreen();
// Sada mozemo raditi skupe stvari bez brige
// 1. Ocisti prethodni level
UnloadCurrentLevel();
// 2. Forsiraj GC (bezbedno jer je loading screen aktivan)
GEngine->ForceGarbageCollection(true);
// 3. Ucitaj novi level (moze i sinhrono jer igrac ceka)
LoadStreamLevel(LevelName, /*bShouldBlockOnLoad=*/true);
// 4. Preload-uj asset-e za taj level
PreloadAssetsForLevel(LevelName);
// 5. Sakrij loading screen
HideLoadingScreen();
}
49.6.5 Async Loading Priority
UE5 ima sistem prioriteta za asinhrono ucitavanje. Mozete kontrolisati koji asset-i imaju prednost:
// Visi prioritet = ucitava se ranije
static const int32 HighPriority = 100;
static const int32 NormalPriority = 50;
static const int32 LowPriority = 10;
StreamableManager.RequestAsyncLoad(
CriticalAsset.ToSoftObjectPath(),
Callback,
HighPriority // Ovaj asset ima prednost nad drugima
);
Prakticna pravila za prioritizaciju:
| Tip asset-a | Prioritet | Razlog |
|---|---|---|
| Level geometry (mesh-evi, kolizija) | Visok | Igrac ne sme da padne kroz pod |
| Teksture | Srednji | Streaming vec upravlja mipmap-ovima |
| Zvukovi | Nizak | Mogu da kasne par frejmova bez problema |
| Animacije | Srednji | NPC-ovi mogu koristiti placeholder animaciju |
| Blueprint klase | Visok (pre spawn-a) | Moraju biti ucitane pre spawn-ovanja |
| Particle efekti | Nizak | Mogu da se ucitaju "lazy" |
49.6.6 World Partition optimizacione napomene
Ako koristite World Partition (sto bi trebalo za otvorene svetove u UE5), evo kljucnih optimizacionih tacaka:
- Cell size: Manji cell-ovi = cesce ucitavanje/oslobadjanje ali manja memorija. Veci cell-ovi = redje ucitavanje ali veca memorija. Tipicne vrednosti su 12800 - 25600 cm (128m - 256m).
- Loading range: Odredjuje koliko daleko od igraca se cell-ovi ucitavaju. Manji range = manje memorije i brze ucitavanje.
- Data Layers: Grupisite objekte u data layer-e i kontrolisite koji se ucitavaju (npr. samo gameplay objekti za server, gameplay + vizuelni za klijenta).
- HLOD: Hierarchical Level of Detail zamenjuje daleke cell-ove simplifikovanim proxy mesh-evima (pogledajte poglavlje 32 za detalje).
49.7 Multithreading Awareness: Iskoristite Sve Jezgre
49.7.1 UE5 Threading Model
Moderni CPU-ji imaju 8, 12, 16 ili vise jezgara. Ali vecina koda u UE5 (Blueprint Tick, gameplay logika) se izvrsava na jednoj niti -- game thread-u. Ovo znaci da ako imate 16-core procesor, 15 jezgara potencijalno sede besposlen dok game thread gusi jedno jezgro.
UE5 vec koristi vise niti za neke sisteme:
Game Thread -- Logika igre, Blueprint, ticking
Render Thread -- Priprema rendering komandi
RHI Thread -- Graficki API pozivi
Physics Thread(s) -- Chaos fizicka simulacija
Audio Thread -- Audio mixing i processing
Animation Thread(s) -- Bone evaluation (paralelno za vise skeletal mesheva)
Async Loading Thread -- Ucitavanje asset-a sa diska
Task Threads -- Pool niti za razne zadatke (Task Graph)
Ali vasa gameplay logika (Tick, Blueprint) je na game thread-u. Da biste iskoristili ostala jezgra, morate eksplicitno koristiti multithreading.
49.7.2 Task Graph System
UE5 Task Graph je sistem za rasporedjivanje zadataka (tasks) na worker niti. Umesto da sami kreirate i upravljate nitima (sto je opasno i komplikovano), definisete "zadatke" i Task Graph ih rasporeduje na dostupne worker niti.
// Primer: Procesiranje velikog niza u pozadinskom thread-u
class FMyProcessingTask : public FNonAbandonableTask
{
friend class FAutoDeleteAsyncTask<FMyProcessingTask>;
TArray<FVector> DataToProcess;
TArray<FVector> Results;
FMyProcessingTask(const TArray<FVector>& InData)
: DataToProcess(InData)
{
}
void DoWork()
{
// Ovo se izvrsava na worker thread-u, NE na game thread-u
Results.Reserve(DataToProcess.Num());
for (const FVector& V : DataToProcess)
{
// Neki skupi proracun
Results.Add(V.GetSafeNormal() * FMath::Sin(V.Size()));
}
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMyProcessingTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
// Pokretanje task-a
void AMyActor::StartHeavyComputation()
{
(new FAutoDeleteAsyncTask<FMyProcessingTask>(MyLargeDataArray))->StartBackgroundTask();
}
49.7.3 ParallelFor -- Paralelizacija Petlji
ParallelFor je najjednostavniji nacin da paralelizujete "for" petlju:
// SEKVENCIJALNO (jednonit):
for (int32 i = 0; i < BigArray.Num(); i++)
{
BigArray[i] = ExpensiveComputation(BigArray[i]);
}
// PARALELNO (multi-nit):
ParallelFor(BigArray.Num(), [&](int32 Index)
{
BigArray[i] = ExpensiveComputation(BigArray[Index]);
});
ParallelFor automatski deli posao na chunck-ove i rasporeduje ih na worker niti. Ako imate 8 jezgara i 1000 elemenata, svako jezgro obradjuje ~125 elemenata.
Kada koristiti ParallelFor:
| Situacija | ParallelFor? | Razlog |
|---|---|---|
| 10 elemenata, svaki je jeftin | Ne | Overhead thread-ova je veci od ustede |
| 1000 elemenata, svaki je jeftin | Mozda | Zavisi od overhead-a; testirajte |
| 100 elemenata, svaki je skup (1+ ms) | Da | Znacajna usteda |
| Elementi zavise jedni od drugih | Ne | Race conditions! |
| Elementi pristupaju deljenim podacima za pisanje | Ne | Race conditions! |
| Elementi samo citaju deljene podatke | Da | Citanje je thread-safe |
Kriticno upozorenje: ParallelFor radi na worker nitima, ali NE na game thread-u. To znaci:
- NE pozivajte UE5 API-je koji nisu thread-safe (vecina gameplay API-ja)
- NE modifikujte UObject-e iz worker niti
- NE spawn-ujte ili destroy-ujte aktore
- NE pristupajte World-u za modifikaciju
Koristite ParallelFor za "ciste" proracune -- matematiku, fiziku, path finding proracune, AI evaluaciju -- gde radite sa kopijama podataka, a rezultate aplicirate na game thread-u.
49.7.4 Async Tasks
Za dugotrajne operacije koje ne moraju da se zavrse u istom frame-u, koristite AsyncTask:
// Pokretanje async task-a koji se izvrsava na background thread-u
AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this]()
{
// Skup proracun na background thread-u
TArray<FPathResult> Paths = ComputePatrolPaths(AllWaypoints);
// Kada zavrsimo, vracamo rezultat na game thread
AsyncTask(ENamedThreads::GameThread, [this, Paths = MoveTemp(Paths)]()
{
// Ovo se izvrsava na game thread-u -- bezbedno je pristupati UE5 API-jima
ApplyPatrolPaths(Paths);
});
});
Pattern za dvosmerni async rad:
- Pripremite podatke na game thread-u
- Pokrenite async task na background thread-u
- U async task-u, radite skup proracun
- Kada zavrsiste, koristite
AsyncTask(GameThread, ...)da biste primenili rezultate na game thread-u
49.7.5 Kada NE koristiti multithreading
Multithreading nije uvek odgovor:
- Overhead: Kreiranje i sinhronizacija niti ima overhead. Ako je posao manji od ~0.1 ms, overhead moze biti veci od ustede.
- Kompleksnost: Multithreaded kod je tezi za debugging, testiranje i odrzavanje. Race conditions su jedni od najtezih bagova za pronalazenje.
- UE5 ogranicenja: Vecina UE5 gameplay API-ja nije thread-safe. Ne mozete bezbedno pristupati World-u, spawn-ovati aktore, ili modifikovati UObject-e sa background niti.
- Latencija: Async task daje rezultat u buducem frame-u. Ako vam rezultat treba odmah u ovom frame-u, async ne pomaze.
Pravilo palca: Koristite multithreading za "batch" proracune koji traju vise od 1 ms i ciji rezultati ne moraju da budu dostupni u istom frame-u.
49.8 Render Thread Optimization: Smanjite CPU Overhead Renderinga
49.8.1 CPU strana renderinga -- Podsecanje
Poglavlje 42 (Draw Calls i Batching) detaljno pokriva ovu temu. Ovde cemo se fokusirati na CPU-specificne aspekte render thread-a koje nismo pokrili tamo.
Render thread je odgovoran za pripremu svega sto GPU treba. Njegov CPU overhead zavisi od:
- Broja objekata u sceni -- svaki objekat mora da prodje kroz visibility test
- Broja vidljivih objekata -- svaki vidljivi objekat generise draw call(ove)
- Broja svetala -- svako svetlo utice na pripremu shadow pass-eva i light pass-eva
- Kompleksnosti scene hijerarhije -- dublja hijerarhija = vise scene traversal-a
- Broja materijala -- vise unikatnih materijala = vise state change-ova
49.8.2 Visibility i Culling -- CPU perspektiva
Pre nego sto GPU nacrta ista, CPU mora da odluci sta da nacrta. Ovaj proces -- visibility determination -- moze biti veoma skup za velike scene:
Frustum Culling: CPU proverava bounding box svakog objekta u sceni protiv frustum-a kamere. Objekti izvan frustum-a se preskazu. Ovo je O(N) operacija gde je N broj objekata u sceni.
Za scenu sa 50,000 objekata, frustum culling moze da traje 1-3 ms. Zvuci malo, ali zapamtite da je ovo samo prvi korak.
Occlusion Culling: Posle frustum culling-a, CPU proverava da li su preostali objekti zaklanjeni drugim objektima. UE5 koristi hardware occlusion queries (salje upite GPU-u iz prethodnog frejma) i software occlusion (rasterizira occlusion mesh-eve na CPU-u).
Optimizacije za visibility/culling:
1. Smanjite ukupan broj objekata u sceni
- Koristite Instanced Static Mesh za ponavljajuce objekte
- Merge-ujte male objekte u vece mesh-eve
- Koristite HLOD za zamenu grupa udaljenih objekata
2. Poboljsajte bounding box preciznost
- Lose fitovani bounding box-ovi uzrokuju lazne pozitive u frustum culling-u
- Custom occlusion geometry moze pomoci za kompleksne objekte
3. Koristite Precomputed Visibility Volumes
- Za zatvorene prostore (enterijeri, tuneli) gde staticka vidljivost moze da se pre-izracuna
- Drasticno smanjuje CPU posao za visibility
4. Podesavajte cull distance
- MinDrawDistance i MaxDrawDistance na mesh-evima
- Cull Distance Volumes za automatsko skrivanje malih objekata na daljini
// Podesavanje max draw distance na mesh komponenti
MyMeshComponent->SetCullDistance(10000.0f); // Ne crtaj dalje od 100m
// Ili na nivou aktora
MyActor->SetActorHiddenInGame(bShouldHide);
49.8.3 Batching -- Pomozite Render Thread-u da Vam Pomogne
Render thread pokusava da "batch-uje" (grupise) draw call-ove sa slicnim stanjem. Sto su vam objekti slicniji (isti mesh, isti materijal), render thread efikasnije grupe:
Sta pomaze batching-u:
- Koristite Material Instances umesto unikatnih materijala (deo isti shader, deo samo razliciti parametri)
- Koristite Texture Atlase da kombinujete teksture u jednu veliku
- Koristite Instancing za identicne objekte (HISM, ISM, foliage)
- Minimizujte broj unikatnih mesh+material kombinacija
Sta ubija batching:
- Svaki objekat ima unikatni materijal
- Previse mesh varijacija za isti tip objekta
- Razliciti render state-ovi (opaque vs translucent mesani blizu)
- Per-object custom depth/stencil koji razbija batch
49.8.4 Scene Complexity i Render Thread
Render thread mora da prodje kroz celu scenu svaki frejm. Sto je scena kompleksnija (vise objekata, dublja hijerarhija, vise overlapping svetala), to render thread duze radi.
Prakticne mere za smanjenje render thread pritiska:
-
Smanjite broj primitiva u sceni: Koristite
stat scenerenderingda vidite koliko primitiva imate:stat scenerenderingPotrazite
Mesh Draw CallsiVisible Static Mesh Elements. Cilj je da drzite ove brojeve sto nizim. -
Smanjite broj svetala: Svako non-Nanite svetlo dodaje overhead-a render thread-u. Koristite Lumen gde mozete. Za staticna svetla, koristite baked lighting. Ogranicite point/spot light-ove sa
Attenuation Radius. -
Izbjegavajte dinamicke promene scene: Pomeranje, rotiranje, ili skaliranje staticnih objekata u runtime-u prisiljava render thread da azurira scene proxy-je. Ako objekat ne treba da se menja, oznacite ga kao statican (Mobility: Static).
-
Koristite Nanite: Nanite prebacuje ogromnu kolicinu visibility/culling posla sa CPU-a na GPU. Za Nanite mesh-eve, render thread radi dramaticno manje posla jer ne mora da priprema individualne draw call-ove za svaki mesh.
49.9 Cesti CPU Bottleneck-ovi u UE5
49.9.1 Blueprint Overhead
Blueprint-i su fantastican alat za rapid prototyping i gameplay programiranje. Ali imaju izmerljiv CPU overhead u poredjenju sa ekvivalentnim C++ kodom.
Zasto su Blueprint-i sporiji od C++-a?
Blueprint kod se izvrsava kroz Blueprint Virtual Machine (VM) -- interpreter koji cita Blueprint bytecode i izvrsava ga korak po korak. Svaka Blueprint noda prolazi kroz:
- Interpretation overhead -- VM mora da dekodira instrukciju
- Type checking -- proverava tipove na runtime-u
- Indirection -- pristupa podacima kroz pointere na UProperty, ne direktno
- Nema optimizacija kompajlera -- C++ kompajler agresivno optimizuje (inlining, vectorization, loop unrolling), Blueprint VM to ne radi
Koliki je overhead? Zavisi od tipa operacije:
Relativni overhead Blueprint vs C++ (priblizno):
Matematicke operacije (Add, Multiply): 4-8x sporije
Property pristup (Get/Set varijable): 3-5x sporije
Function poziv: 10-20x sporije
Array operacije (Add, Find, ForEach): 5-15x sporije
String operacije: 2-5x sporije
UObject Cast: 2-3x sporije
NAPOMENA: Ovi brojevi su PRIBLIZNI i variraju od verzije do verzije UE5.
Pojedinacna noda je i dalje VEOMA brza u apsolutnim brojevima.
Kada Blueprint overhead postaje problem:
Problem nastaje kada imate:
- Tick sa kompleksnom logikom u Blueprint-u koji se poziva svaki frejm
- Stotine aktora koji svi tikuju sa Blueprint logikom
- Teske petlje u Blueprint-u (ForEach nad velikim nizovima)
- Ceste pozive skupih Blueprint operacija (npr. GetAllActorsOfClass svaki frejm)
Prakticna resenja:
-
Prebacite "hot path" na C++: Ne morate ceo projekat da pisete u C++. Identifikujte funkcije koje se pozivaju najcesce i koje su najskuplje, i samo njih prebacite na C++.
-
Nativize Blueprint-e: UE5 moze da konvertuje Blueprint u C++ kod tokom build-a (Blueprint Nativization). Ovo eliminise VM overhead ali dodaje kompleksnost build sistemu.
-
Izbegavajte skupe Blueprint cvorove u Tick-u:
LOSE (svaki frejm): Event Tick -> Get All Actors of Class -> ForEach Loop -> Line Trace -> Branch BOLJE: Event Tick -> (samo proveri cached rezultat) Timer (svakih 0.5s) -> Get All Actors of Class -> cache rezultat -
Koristite C++ za matematiku i petlje: Blueprint je sjajan za logiku toka programa (if/else, state machines), ali za matematicke proracune i iteraciju nad velikim skupovima podataka, C++ je dramaticno brzi.
-
Profilisanje Blueprint koda: Koristite
stat gamei Blueprint Profiler (Window > Developer Tools > Blueprint Debugger/Profiler) da identifikujete skupe Blueprint cvorove.
49.9.2 Excesivni Collision Queries
Collision query-ji (line trace, sweep, overlap) su jedni od najcescih "skrivenih" CPU bottleneck-ova:
// LOSE: Line trace svaki frejm od svakog NPC-a ka svakom drugom NPC-u
void ANPC::Tick(float DeltaTime)
{
for (ANPC* OtherNPC : AllNPCs)
{
FHitResult Hit;
GetWorld()->LineTraceSingleByChannel(
Hit,
GetActorLocation(),
OtherNPC->GetActorLocation(),
ECC_Visibility
);
// ...
}
}
// 100 NPC-ova = 10,000 line trace-ova PO FREJMU
Zasto su collision query-ji skupi?
Svaki line trace ili overlap test mora da:
- Prodje kroz physics broadphase (AABB tree traversal)
- Izvrsi narrowphase test (geometrijski test protiv precizne geometrije)
- Sortira rezultate po udaljenosti (za multi-hit)
- Filtrira po channelu, profilu, ignore listi
Za jedan line trace, ovo traje ~0.005-0.05 ms. Ali pomnozite sa 1000 trace-ova po frejmu i imate 5-50 ms samo na collision query-jima.
Optimizacije:
-
Smanjite frekvenciju: Ne radite trace svaki frejm. Koristite timer ili smanjeni tick interval.
-
Koristite async trace: UE5 podrzava asinhrone trace-ove koji se izvrsavaju u sledecim frejmovima:
FTraceHandle Handle = GetWorld()->AsyncLineTraceByChannel( EAsyncTraceType::Single, Start, End, ECC_Visibility ); // Rezultat ce biti dostupan u narednim frejmovima // Proverite sa: GetWorld()->QueryTraceData(Handle, Result) -
Koristite jednostavniju geometriju za koliziju: Collision mesh ne mora da bude identican vizuelnom mesh-u. Koristite simple collision (kutije, sfere, kapsule) umesto complex collision.
-
Smanjite trace duzinu: Kraci trace = manje objekata za testiranje.
-
Koristite channel-e pravilno: Filtrirajte trace na specificne kanale da smanjite broj objekata za testiranje.
-
Batch-ujte trace-ove: Umesto mnogo pojedinacnih trace-ova, koristite
MultiLineTraceili overlap sa vecim volumenom.
49.9.3 Navigation Mesh Updates
NavMesh je struktura podataka koju AI koristi za navigaciju. Azuriranje NavMesh-a je CPU-intenzivan proces:
Kada se NavMesh azurira?
- Kada se pomeri dinamicki objekat koji utice na navigaciju
- Kada se doda ili ukloni NavMesh modifier
- Kada se ucita novi sub-level sa navigacionom geometrijom
Zasto je skupo? NavMesh regeneracija ukljucuje:
- Voxelizaciju scene geometrije
- Generisanje navigacionih poligona
- Kreiranje povezanosti (graph edges)
- Azuriranje tile-ova koji su pogojeni promenom
Tipicni NavMesh azuriranje cost:
Jedan mali objekat se pomeri: 0.1 - 0.5 ms
Veci objekat se pomeri: 0.5 - 2 ms
Mnogo objekata se pomeri: 2 - 20+ ms
Puna regeneracija velikog nivoa: 100+ ms (samo tokom build-a ili loading-a)
Optimizacije:
-
Minimizujte dinamicke NavMesh modifikatore: Ako objekat ne utice na navigaciju, iskljucite mu
bAffectsNavigation:MyMeshComponent->SetCanEverAffectNavigation(false); -
Koristite Navigation Invokers: Umesto da NavMesh pokriva ceo svet, generisite ga samo oko igraca:
Project Settings > Navigation System > Runtime Generation: Dynamic Supported Agents > Navigation Data Gathering Mode: Lazy -
Podesavajte tile size: Manji tile-ovi znace manje regeneracije po promeni, ali vise tile-ova ukupno.
-
Koristite NavMesh Bounds Volume: Ogranicite gde se NavMesh uopste generise.
-
Izbegavajte ceste promene: Ako imate dinamicke prepreke (npr. vrata), koristite NavLink-ove umesto regeneracije NavMesh-a.
49.9.4 Animation Evaluation
Skeletal animation evaluation moze biti jedan od najskupljih CPU procesa, posebno sa mnogo animiranih likova:
Sta kosta u animation evaluation?
- AnimGraph evaluacija -- prolazak kroz AnimBP cvorove
- Blend evaluacija -- blendovanje izmedju vise animacija
- IK (Inverse Kinematics) -- posebno Full Body IK
- Bone transform computation -- racunanje finalne pozicije svakog bone-a
- Physics asset update -- za ragdoll i cloth
- Curve evaluation -- animacione krive (morph targets, custom curves)
Tipicni animation cost po liku:
Jednostavan AnimBP (idle/walk/run): 0.05 - 0.15 ms
Kompleksan AnimBP sa IK: 0.2 - 0.5 ms
Full Body IK + fizika: 0.5 - 2 ms
Ragdoll: 0.3 - 1 ms
Za 50 NPC-ova sa kompleksnom animacijom, to moze biti 10-25 ms -- daleko vise od celog frame budzeta na 60 FPS.
Optimizacije:
-
URO (Update Rate Optimization): UE5 moze da smanji frekvenciju evaluacije animacija na osnovu udaljenosti od kamere:
// Na SkeletalMeshComponent SkeletalMeshComp->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;Opcije:
AlwaysTickPoseAndRefreshBones-- Uvek evaluira (default, najskuplje)AlwaysTickPose-- Uvek evaluira pozu ali refresh-uje bone-ove samo kada je rendovanOnlyTickMontagesWhenNotRendered-- Tikuje samo montaze kada nije vidljivOnlyTickPoseWhenRendered-- Evaluira animaciju samo kada je vidljiv (najjeftinije)
-
Anim Budget Allocator: UE5 plugin koji automatski upravlja animacionim budzetom -- ogranicava koliko ukupnog CPU vremena animacije mogu da potrose:
Plugins > Animation Budget AllocatorOvaj sistem automatski smanjuje kvalitet animacija (skipuje frame-ove evaluacije) za manje vazne likove da bi odrzao ukupan budzet.
-
Smanjite broj bone-ova: Manje bone-ova = brza evaluacija. LOD skeletal mesh-evi mogu imati redukovane skeleton-e.
-
Simplificirajte AnimBP za daleke likove: Koristite AnimBP LOD sistem -- razlicite AnimBP grafove za razlicite LOD nivoe:
// Registrujte LOD za AnimInstance // LOD 0: Pun AnimBP (blizu kamere) // LOD 1: Samo locomotion blend space // LOD 2: Samo idle animacija, nema IK -
Koristite Distance Matching umesto Root Motion za daleke likove: Root Motion je skuplji jer zahteva preciznu sinhronizaciju sa animacijom.
49.9.5 AI Perception
AI Perception sistem u UE5 moze biti znacajan CPU trosak:
Sta radi AI Perception?
- Sight sense -- radi visibility proveru (ray cast ili frustum test) za svaki AI aktor prema svim potencijalnim target-ima
- Hearing sense -- proverava da li zvukovi dosegu AI aktore
- Damage sense -- reaguje na primljenu stetu
- Custom sense -- korisnicki definisani sense-ovi
Zasto je skupo?
Sight sense je obicno najskuplji jer za svaki AI aktor mora da:
- Pronadje sve potencijalne targete u range-u
- Za svaki target, proveri da li je u FOV (field of view)
- Za svaki target u FOV-u, uradi line trace da proveri vidljivost (line of sight)
Za 50 AI aktora sa 10 potencijalnih targeta svaki, to je potencijalno 500 line trace-ova po update-u.
Optimizacije:
-
Smanjite Sense Update Interval:
// Podrazumevani interval je 0.5 sekunde -- mozda mozete povecati PerceptionComponent->SetSenseUpdateInterval(1.0f); // Azuriraj samo jednom u sekundi -
Ogranicite Max Age: Sense stimuli koji su stariji od Max Age se brisu, smanjujuci memoriju i CPU za procesiranje.
-
Smanjite Sight Range: Ne treba svakom AI aktoru sight range od 5000 jedinica. Razmislite koliko daleko AI zaista treba da vidi za gameplay koji imate.
-
Koristite Affiliation filtering: Konfiguriste detection po "afiliaciji" (friendly, neutral, hostile) da smanjite broj proveravanih targeta.
-
Koristite Team-based filtere: Implementirajte
IGenericTeamAgentInterfaceda biste automatski filtrirali nebitne targete. -
Alternativa -- Custom Query umesto Perception: Za jednostavne slucajeve, razmislite o sopstvenoj, laksoj implementaciji:
// Umesto AI Perception sistema sa svim njegovim overhead-om, // jednostavna sferna provera + jedan line trace moze biti dovoljna void ASimpleAI::CheckForPlayer() { float DistSq = FVector::DistSquared(GetActorLocation(), PlayerLocation); if (DistSq < SightRangeSq) { FHitResult Hit; if (!GetWorld()->LineTraceSingleByChannel(Hit, GetActorLocation(), PlayerLocation, ECC_Visibility)) { // Vidimo igraca OnPlayerSeen(); } } } // Pozivajte ovo sa timerom svakih 0.3-1.0 sekundi
49.10 Profiling CPU sa Unreal Insights i Stat Komandama
49.10.1 Podsecanje na Osnovne Alate
Poglavlje 40 detaljno pokriva sve profiling alate. Ovde cemo se fokusirati na one koji su najkorisniji specificno za CPU analizu.
stat unit -- Identifikacija bottleneck thread-a:
stat unit
Ovo je UVEK prvi korak. Gledajte Game vs Draw vs GPU vrednosti:
Game: 12 ms <-- Ako je ovo najvece, problem je na game thread-u
Draw: 8 ms <-- Ako je ovo najvece, problem je na render thread-u
GPU: 6 ms <-- Ako je ovo najvece, problem je na GPU-u
49.10.2 Game Thread Profiling
Za dublju analizu game thread-a:
stat game:
stat game
Prikazuje vreme potroseno na razlicite sisteme unutar game thread-a:
- Tick time
- Blueprint time
- Input processing
- World tick time
stat anim:
stat anim
Detaljne statistike animacionog sistema:
- Evaluacija AnimBP-ova
- Bone transform computation
- Blend evaluacija
- URO statistika
stat ai:
stat ai
AI sistem statistike:
- Behavior Tree evaluacija
- EQS query vreme
- Perception update vreme
stat physics:
stat physics
Fizicki sistem:
- Broadphase vreme
- Narrowphase vreme
- Solver vreme
- Contact generation
stat collision:
stat collision
Collision query statistike -- posebno korisno za identifikaciju excesivnih trace-ova.
stat navmesh:
stat navmesh
Navigation mesh statistike:
- Tile regeneracija
- Path finding upiti
- NavMesh velicina
stat dumpticks:
dumpticks
Stampa SVAKI aktor i komponentu koja tikuje, sa frekvencijom. Neophodno za identifikaciju nepotrebnog ticking-a.
49.10.3 Render Thread Profiling
Za analizu render thread bottleneck-a:
stat scenerendering:
stat scenerendering
Prikazuje:
- Mesh draw calls
- Visible mesh elements
- Culling statistika (frustum culled, occluded)
- Static mesh draw calls vs dynamic
stat initviews:
stat initviews
Specijalizovano za visibility/culling:
- Frustum cull time
- Occlusion cull time
- Processed primitives
- Visible primitives
Ako je stat initviews vreme veliko, imate previse objekata u sceni za visibility sistem.
49.10.4 Unreal Insights -- Duboka CPU Analiza
Unreal Insights (pokriven u poglavlju 40) je najmoconiji alat za CPU analizu. Evo specificnog workflow-a za CPU debugging:
Korak 1: Pokrenite snimanje
// Pokrenite igru sa trace-om
UnrealEditor.exe MyProject.uproject -trace=cpu,frame,bookmark
Ili iz editora:
Tools > Run Unreal Insights > Start Trace
Korak 2: Reprodukujte problem
Igrajte igru i reprodukujte tacnu situaciju gde imate CPU problem (npr. udjite u scenu sa mnogo NPC-ova).
Korak 3: Analizirajte trace u Insights-u
Otvorite trace file u Unreal Insights. Gledajte:
- Timing view -- gornji deo ekrana pokazuje sve niti i koliko vremena svaki blok trosi
- Game thread -- potrazite najduze blokove:
TickActors-- koliko ukupno trosi ticking?BlueprintVM-- koliko trosi Blueprint izvrsavanje?Physics-- koliko trosi fizicka simulacija?AnimGameThread-- koliko trose animacije?GarbageCollection-- da li GC izaziva spike-ove?
- Render thread -- potrazite:
InitViews-- visibility/cullingBasePass-- draw call pripremaShadowDepths-- shadow priprema
Korak 4: Drill Down
Kliknite na blok da vidite children -- koji pod-sistem, koji aktor, koja funkcija trosi najvise vremena. Insights vam omogucava da precizno identifikujete "koji aktor sa kojom funkcijom trosi 5 ms po frejmu" -- sto je nemoguce sa stat komandama.
49.10.5 Custom Stat Markeri
Za vas sopstveni kod, dodajte custom stat markere da biste mogli da ih vidite u profajleru:
// Deklarisite stat grupu (u .cpp fajlu)
DECLARE_STATS_GROUP(TEXT("MyGame"), STATGROUP_MyGame, STATCAT_Advanced);
// Deklarisite individualni stat
DECLARE_CYCLE_STAT(TEXT("AI Update"), STAT_AIUpdate, STATGROUP_MyGame);
// Koristite u kodu
void AMyAIController::UpdateAI()
{
SCOPE_CYCLE_COUNTER(STAT_AIUpdate);
// Vas kod ovde...
// Vreme unutar ovog scope-a ce se pojaviti u profajleru
// kao "AI Update" unutar grupe "MyGame"
}
Sada mozete videti vas custom stat sa:
stat MyGame
I u Unreal Insights-u ce se pojaviti kao marker na game thread-u.
Savet: Dodajte stat markere na SVAKI vazan sistem u vasoj igri. To je mali overhead u runtime-u (~0.001 ms po markeru) ali ogromna pomoc pri debugovanju.
49.10.6 Prakticni CPU Profiling Workflow
Evo kompletnog workflow-a za resavanje CPU bottleneck-a:
1. stat unit
-> Koji thread je problem? Game ili Draw?
2a. Ako je GAME thread:
-> stat game (opsti pregled)
-> stat physics (da li je fizika kriva?)
-> stat anim (da li su animacije krive?)
-> stat ai (da li je AI kriv?)
-> stat collision (da li su collision queries krivi?)
-> dumpticks (da li tikuje previse aktora?)
-> Unreal Insights (precizna identifikacija funkcije)
2b. Ako je DRAW (render) thread:
-> stat scenerendering (koliko draw call-ova, primitiva?)
-> stat initviews (koliko visibility/culling trosi?)
-> stat RHI (koliko RHI overhead?)
-> Unreal Insights (precizna identifikacija)
-> Pogledajte poglavlje 42 za draw call optimizacije
3. Identifikujte TOP 3 najskuplje stavke
4. Primenite optimizacije (iz ovog poglavlja)
5. Merite ponovo
6. Ponovite dok ne postignete target
49.11 Kompletni Primer: Od Problema do Resenja
Hajde da prodjemo kroz realan primer optimizacije CPU performansi.
Scenario
Igrate otvoreni svet RPG. Imate selo sa 200 NPC-ova. Kada igrac udje u selo, FPS padne sa 60 na 28.
Korak 1: Identifikacija
stat unit:
Frame: 35.7 ms
Game: 31.2 ms <-- PROBLEM
Draw: 8.4 ms
GPU: 14.1 ms
Game thread je jasno bottleneck.
Korak 2: Dijagnoza
stat game:
Tick Time: 22.4 ms <-- Ogromno!
stat anim:
AnimEval: 8.2 ms <-- Skupo
stat ai:
Perception: 4.1 ms <-- Znacajno
BehaviorTree: 3.8 ms <-- Znacajno
dumpticks:
200 NPC aktora tikuje (svaki frejm)
200 SkeletalMeshComponent tikuje
200 AIPerceptionComponent tikuje
200 CharacterMovementComponent tikuje
Korak 3: Plan optimizacije
Na osnovu profajliranja, identifikujemo tri glavna problema:
- Svih 200 NPC-ova tikuje svaki frejm
- Svih 200 animacija se evaluira svaki frejm
- AI Perception radi proveravanja za svih 200 NPC-ova
Korak 4: Implementacija
Optimizacija 1: Significance-based Ticking
// NPC-ovi dalji od 30m tikuju sa smanjenom frekvencijom
// NPC-ovi dalji od 100m ne tikuju uopste
void ANPC::UpdateSignificance(float NewSignificance)
{
if (NewSignificance > 0.7f) // Blizu igraca
{
SetActorTickInterval(0.0f);
SetActorTickEnabled(true);
}
else if (NewSignificance > 0.3f) // Srednja udaljenost
{
SetActorTickInterval(0.2f);
SetActorTickEnabled(true);
}
else // Daleko
{
SetActorTickEnabled(false);
}
}
Optimizacija 2: Animation LOD
// Daleki NPC-ovi koriste simplifikovane animacije
SkeletalMeshComp->VisibilityBasedAnimTickOption =
EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
Optimizacija 3: AI Perception tuning
// Smanjite frekvenciju perception update-a za daleke NPC-ove
// Bliski: 0.5s, srednji: 1.0s, daleki: potpuno iskljucen
PerceptionComponent->SetSenseUpdateInterval(UpdateInterval);
Korak 5: Rezultati
stat unit (posle optimizacije):
Frame: 14.8 ms
Game: 9.6 ms <-- Sa 31.2 na 9.6 ms!
Draw: 8.2 ms
GPU: 14.0 ms
Od 28 FPS na preko 60 FPS. Bez ikakvih vizuelnih promena za igraca -- daleki NPC-ovi i dalje izgledaju zivo jer tikuju sa smanjenom frekvencijom (dovoljno za njihovu locomotion animaciju), a bliski NPC-ovi rade punom brzinom.
49.12 Sveobuhvatna Checklista za CPU Optimizaciju
Pre nego sto zavrsimo ovo poglavlje, evo kompletne checkliste koju mozete koristiti kao referentni vodic:
Game Thread
- Da li svi aktori koji tikuju zaista moraju da tikuju?
- Da li aktori koji tikuju to rade sa odgovarajucom frekvencijom?
- Da li daleki/nevidljivi aktori imaju iskljucen tick?
- Da li koristite timer-e umesto tick-a gde je moguce?
- Da li vas Blueprint Tick sadrzi skupe operacije (GetAllActorsOfClass, ForEach nad velikim nizovima)?
- Da li imate object pooling za cesto kreirane/unistene objekte?
- Da li kontrolisete GC pauze?
- Da li koristite async loading umesto sinhronog ucitavanja?
Render Thread
- Koliko ukupno draw call-ova imate (stat scenerendering)?
- Da li koristite instancing za ponavljajuce objekte?
- Da li imate previse unikatnih materijala?
- Da li koristite cull distance za male objekte?
- Da li koristite Nanite za staticnu geometriju (prebacuje posao sa CPU na GPU)?
- Da li koristite HLOD za daleke grupe objekata?
Specifcni Sistemi
- Animacije: Da li koristite URO i animation LOD?
- AI: Da li je perception update interval razuman?
- Fizika: Da li je collision complexity odgovarajuca?
- Navigation: Da li je NavMesh regeneracija pod kontrolom?
- Audio: Da li je voice count pod kontrolom?
Opste
- Da li ste profajlirali pre optimizacije? (Poglavlje 39, nikada naslepo!)
- Da li ste profajlirali posle svake promene?
- Da li koristite custom stat markere u vasem kodu?
- Da li imate performance budget za svaki sistem?
Rezime Poglavlja
Ovo poglavlje je pokrilo sve kljucne aspekte CPU optimizacije u Unreal Engine 5. Hajde da sumiramo najbitnije lekcije:
-
CPU ima dva glavna thread-a: Game thread (logika igre) i Render thread (priprema renderinga). Rade paralelno, ali jedan moze da blokira drugi.
stat unitvam odmah kaze koji je bottleneck. -
Tick budgeting je kriticno: Svaki aktor i komponenta koja tikuje trosi CPU vreme. Smanjite frekvenciju tikovanja, iskljucite tick za neaktivne aktore, koristite timer-e umesto tick-a, i implementirajte Significance Manager za automatsko upravljanje.
-
Komponente treba pazljivo birati i upravljati njima: Deaktivirajte nekoristene komponente, birajte lakse alternative (Static vs Skeletal Mesh, HISM vs mnogo odvojenih mesh-eva), i kontrolisite component tick nezavisno od actor tick-a.
-
Object pooling eliminise spawn/destroy overhead: Za objekte kratkog zivota (projektili, efekti, pickup-ovi), reciklirajte umesto da kreirate i unistavate. Ovo takodje smanjuje GC pritisak.
-
Asinhrono ucitavanje je obavezno: Sinhrono ucitavanje blokira game thread i izaziva vidljivo stucanje. Koristite Soft Reference-e, StreamableManager i Asset Manager za asinhrono ucitavanje. Preload-ujte asset-e pre nego sto vam zatrebaju.
-
Level streaming zahteva pazljivo planiranje: Koristite preload volume-e, podesavajte prioritete ucitavanja, i koristite loading screen-ove kao priliku za tezak posao (GC, preloading, precomputation).
-
Multithreading moze pomoci ali ima ogranicenja: ParallelFor i AsyncTask mogu da prebace tezak posao na worker niti, ali vecina UE5 API-ja nije thread-safe. Koristite multithreading za "ciste" proracune i aplicirajte rezultate na game thread-u.
-
Render thread se optimizuje smanjivanjem scene kompleksnosti sa strane CPU-a: Manje objekata, bolji batching, Nanite za staticnu geometriju, HLOD za daleke grupe, i pravilne cull distance.
-
Pet najcescih CPU ubica: Blueprint overhead u tick-u, excesivni collision query-ji, NavMesh regeneracija, animaciona evaluacija na stotinama likova, i AI Perception na previise NPC-ova.
-
Profajliranje je temelj svega:
stat unitza identifikaciju thread-a, specificnestatkomande za sistem,dumpticksza tick auditing, Unreal Insights za preciznu analizu, i custom stat markeri za vas kod.
Kljucni Pojmovi
| Termin | Objasnjenje |
|---|---|
| Game Thread | Glavna nit engine-a koja izvrsava svu logiku igre: Tick, Blueprint, fiziku, AI, navigaciju |
| Render Thread | Nit koja priprema rendering komande za GPU: visibility, culling, draw call priprema, sorting |
| RHI Thread | Nit koja prevodi engine rendering komande u graficki API pozive (DX12, Vulkan) |
| Tick | Funkcija koja se poziva na aktoru ili komponenti svaki frejm (ili sa podesenim intervalom) |
| Tick Interval | Minimalno vreme izmedju dva poziva Tick funkcije; 0 = svaki frejm |
| Tick Group | Kategorija koja odredjuje kada u frejmu se tick izvrsava (PrePhysics, DuringPhysics, PostPhysics, itd.) |
| Significance Manager | UE5 sistem za upravljanje "znacajnoscu" objekata na osnovu udaljenosti i drugih faktora |
| Object Pooling | Tehnika gde se objekti recikliraju umesto da se kreiraju i unistavaju |
| Garbage Collection (GC) | Automatsko ciscenje memorije nekoriscenih UObject-a; moze izazvati pauze |
| GC Clustering | Grupisanje povezanih UObject-a u klastere za efikasniji GC |
| Soft Reference | Referenca na asset koja cuva samo putanju, ne ucitava asset dok se eksplicitno ne zatrazi |
| Hard Reference | Referenca na asset koja forsira ucitavanje asset-a cim se ucita objekat koji ga referencira |
| StreamableManager | UE5 sistem za asinhrono ucitavanje asset-a |
| Asset Manager | Visi nivo apstrakcije nad StreamableManager-om za upravljanje primarnim asset tipovima |
| Level Streaming | Dinamicko ucitavanje i oslobadjanje delova sveta (sub-level-a) na osnovu pozicije igraca |
| World Partition | Moderni UE5 sistem koji automatski deli svet na celije i strimuje ih |
| Task Graph | UE5 sistem za rasporedjivanje zadataka na worker niti |
| ParallelFor | Funkcija za paralelizaciju for petlji na vise niti |
| AsyncTask | Mehanizam za pokretanje zadataka na pozadinskim nitima |
| Blueprint VM | Virtual Machine koja interpretira Blueprint bytecode; sporija od nativnog C++ koda |
| URO (Update Rate Optimization) | Sistem za smanjenje frekvencije evaluacije animacija na osnovu vidljivosti i udaljenosti |
| Anim Budget Allocator | Plugin koji automatski upravlja ukupnim CPU budzetom za animacije |
| AI Perception | UE5 sistem koji omogucava AI aktorima da "osecaju" svet (vid, sluh, steta, itd.) |
| NavMesh | Navigation Mesh -- struktura podataka za AI navigaciju u svetu |
| FlushRenderingCommands | Funkcija koja blokira game thread dok render thread ne zavrsi sav pending posao; ubija paralelizam |
| Collision Query | Line trace, sweep ili overlap test protiv fizicke geometrije scene |
| Precomputed Visibility | System koji pre-racunava vidljivost objekata za zatvorene prostore, smanjujuci runtime CPU cost |
| Cull Distance | Maksimalna udaljenost na kojoj se objekat crta; objekti dalje od ove udaljenosti se preskazu |
| HLOD | Hierarchical Level of Detail -- zamenjuje grupe udaljenih objekata jednim simplifikovanim proxy mesh-om |
Veze sa Drugim Poglavljima
-
Poglavlje 36 (Audio, Physics i Gameplay Performance) -- Detaljno pokriva audio overhead, physics simulation cost, i tick rate management koji su direktno povezani sa game thread optimizacijom iz ovog poglavlja. Pogledajte sekcije o tick rate-ovima, actor lifecycle-u i Blueprint vs C++ overhead-u za komplementarnu perspektivu.
-
Poglavlje 39 (Optimizacija -- Filozofija i Pristup) -- Fundamentalna filozofija "profiling-first" pristupa. Pre nego sto primenite bilo koju tehniku iz ovog poglavlja, procitajte poglavlje 39 da biste razumeli zasto nikada ne treba optimizovati naslepo i kako pravilno pristupiti performansnim problemima naucnim metodom.
-
Poglavlje 40 (UE5 Profiling Alati) -- Kompletni vodic za sve profiling alate ukljucujuci
statkomande, Unreal Insights, GPU Visualizer, i Frame Profiler. Ovo poglavlje (49) koristi alate iz poglavlja 40 ali se fokusira na CPU-specificne workflow-e i interpretaciju. -
Poglavlje 42 (Draw Calls i Batching) -- Detaljno pokriva CPU cost draw call-ova, state change-ova, instancing-a, mesh merging-a, i auto-batching-a. Sekcija 49.8 ovog poglavlja daje pregled render thread optimizacije, ali poglavlje 42 ide mnogo dublje u mehaniku draw call-ova i svih tehnika za njihovo smanjivanje.
-
Poglavlje 30 (Nanite) -- Nanite fundamentalno menja CPU overhead renderinga tako sto prebacuje visibility i culling posao na GPU. Razumevanje Nanite-a je kljucno za razumevanje zasto render thread moze biti dramaticno jeftiniji za Nanite mesh-eve.
-
Poglavlje 32 (World Partition) -- Moderni pristup level streaming-u u UE5, ukljucujuci automatsku podelu sveta na celije, HLOD sistem, i Data Layers. Direktno povezano sa sekcijom 49.6 ovog poglavlja.
-
Poglavlje 35 (Niagara) -- Particle sistemi mogu biti znacajan CPU trosak. Niagara podrzava GPU simulaciju koja premesta posao sa CPU-a, sto je relevantno za CPU optimizaciju.
Preporuceno Citanje i Resursi
Zvanicna dokumentacija
-
Performance and Profiling Overview -- Glavni hub za sve performansne teme u UE5:
docs.unrealengine.com/5.0/en-US/performance-and-profiling-in-unreal-engine/ -
CPU Profiling -- Specificna dokumentacija za CPU profiling sa
statkomandama i Unreal Insights:docs.unrealengine.com/5.0/en-US/cpu-profiling-in-unreal-engine/ -
Actor Ticking -- Dokumentacija o tick sistemu, tick grupama i tick zavisnostima:
docs.unrealengine.com/5.0/en-US/actor-ticking-in-unreal-engine/ -
Asynchronous Asset Loading -- Vodic za asinhrono ucitavanje koristeci StreamableManager i Soft Reference:
docs.unrealengine.com/5.0/en-US/asynchronous-asset-loading-in-unreal-engine/ -
Level Streaming -- Kompletna dokumentacija za level streaming, streaming volumes i World Partition:
docs.unrealengine.com/5.0/en-US/level-streaming-in-unreal-engine/ -
Animation Optimization -- URO, Anim Budget Allocator, i AnimBP LOD:
docs.unrealengine.com/5.0/en-US/animation-optimization-in-unreal-engine/
Epic Games prezentacije i resursi
-
"Large Worlds in UE5: A Whole New (Open) World" -- GDC prezentacija o World Partition, level streaming optimizaciji i CPU performansama za otvorene svetove
-
"Performance Budget in Fortnite" -- Inside Unreal prezentacija o tome kako Epic upravlja performance budget-om u Fortnite-u, ukljucujuci Significance Manager, tick budgeting i animation LOD
-
"Maximizing Your Game's Performance in UE5" -- Sveobuhvatna prezentacija o profiling workflow-u od
stat unitdo Unreal Insights, sa prakticnim primerima CPU optimizacije -
"Threading and Parallelism in UE5" -- Tehnicka prezentacija o Task Graph sistemu, ParallelFor, i best practices za multithreading u gameplay kodu
Knjige i eksterni resursi
-
"Game Programming Patterns" (Robert Nystrom) -- Poglavlje o Object Pooling i Component pattern-u. Besplatno dostupno online na
gameprogrammingpatterns.com -
"Optimizing C++ Code" (Kurt Guntheroth) -- Odlicna knjiga o C++ optimizaciji koja je direktno primenljiva na UE5 game thread kod
-
"C++ Concurrency in Action" (Anthony Williams) -- Za dublje razumevanje multithreading koncepata koji stoje iza UE5 Task Graph sistema
-
Unreal Engine Community Wiki -- Pretrazite "CPU optimization", "tick optimization", "object pooling UE5" za community-written vodicre sa prakticnim primerima
-
Unreal Slackers Discord -- Aktivna zajednica sa kanalima za performanse i optimizaciju gde mozete postaviti specificna pitanja
Video resursi
-
Pretrazite "UE5 CPU optimization", "UE5 tick optimization", i "Unreal Insights CPU profiling" na YouTube-u za video tutorijale sa prakticnim demonstracijama
-
Epic Games YouTube kanal redovno objavljuje Inside Unreal i Tech Talk videe o performansnim temama