Vissa kanske undrar hur mycket resurser som lagts på att reda ut om det vore möjligt att hantera ray tracing på ett väldigt effektivt sätt m.h.a. av existerande GPU-HW.
Nvidia hävdade vid lansering av RTX-korten att de jobbat med tekniken i ca 10 år. Finns en hel del som pekar på att det ligger en del sanning i påståendet, går att hitta massor med forskningspapper åren mellan 2000-2010 som presenterar olika idéer kring hur man effektivt skulle kunna hantera ray tracing med SIMD/SIMT paradigmen (den som GPU primärt utnyttjar för sin parallellism).
Det man i slutändan konstaterade var att primärstrålarna kan typiskt hanteras relativt effektivt strålar i närliggande pixels ofta träffar samma eller i alla fall närliggande trianglar.
Problemet är att redan SSE/AVX är en utmaning så fort man ger sig på sekundärstrålar. För att förstå problematiken på GPUer är det viktigt att ha i bakhuvudet att SSE är rätt mycket som en GPU med en "warp" bredd på 4 medan AVX har en bredd på 8 "CUDA-kärnor". Nvidias GPUer har en bredd på 32 "CUDA-kärnor" och AMDs GCN har en bredd på 64 "CUDA-kärnor" (om vi håller oss till Nvidias nomenklatur, AMD kallar en "warp" för en "wave-front" men är samma sak).
Vad är då exakt problemet här?
Har man denna kod
void foo(float *a, float *b) {
int simdLane = threadId.x; // threadId.x ger CUDA-kärnan för detta anrop
if (a[simdLane] > 0) {
bar(a, b);
} else {
baz(a, b);
}
}
GPUer har sedan de fick "programmera shaders" för över tio år sedan varit kapabla att hantera att olika CUDA-kärnor tar lite olika väg genom programkoden.
Men då det bara finns en enda programräknare för alla trådar som tillhör samma "warp" finns två viktiga egenskaper som måste uppfyllas för att en GPU ska hantera koden effektivt:
I de flesta fall måste alla trådar i samma warp ta samma väg vid villkorad körning, d.v.s. endera "if" eller "else" delen
om vissa trådar tar en annan väg måste mängden kod som skiljer sig vara så kort som möjligt
Om vi antar att funktionerna bar() och baz() är relativt dyra (som mycket väl kan vara fallet för sekundärtrådar om de får studsa många gånger) blir ju effekten av att minst en tråd tar en annan väg att effektiviteten minskar rejält.
Som exempel, antag att kostnaden för bar() och baz() är ungefär densamma, i det läget kapas effektiv prestanda i hälften då en GPU (eller CPU som använder SIMD) måste göra beräkningen på följande sätt
först körs bar(), alla trådar som inte tar den vägen markeras sin inaktiva -> inget händer med data för de inaktiva trådarna
sedan körs baz(), nu med de trådar som körde bar() inaktiva
Om alla trådar tar samma väg kommer den funktion ingen anropade naturligt vis inte heller köras, då det fallet är effektivt.
Då ray tracing är rekursiv (följ primärstrålarna, för de strålar som träffar en yta generera sekundärstrålar och följ dessa etc.) är det tyvärr rätt sannolikt att CUDA-trådar som väl divergerat inte kommer ta samma väg förrän alla sekundär, tertiär etc trådar för en viss primärstråle är färdighanterad.
För en bredd på 4 som för SSE verkar man ändå hittat varianter av ray tracing där SIMD ger ett klart mervärde. Men redan vid en bredd på 8 (AVX) börjar man se en rätt stor variation mellan olika scentyper. GPUer har som sagt 32/64 trådar, det går inte att utnyttja på ett bra sätt för ray tracing vilket är orsaken att trots enorm beräkningskapacitet (typiskt >x10 över HEDT CPU) blir det inte jätteeffektivt, behövs något som inte är SIMD/SIMT för hantering av strålarna.
Några axplock av olika försök att använda SIMD/SIMT för ray tracing
Detta sammanfattar problematiken bra
Nu blir ändå GPUer allt bättre på att hantera villkorad körning, GPUer är numera snabbar eoch mer energieffektiva på ray tracing jämfört med CPUer även utan RT-kärnor. Men RTX-serien ger ändå en vink om att dedicerat kisel för att hantera stråle/triangel träffar ger 4-6 heltalsfaktorer till över Turing-kärnorna (som i sin tur ger 30-50 % bättre GPGPU prestanda jämfört med Pascal).