A program ebben az esetben is a main függvényben indul.
A korábbiakhoz képest ez a program már komplexebbnek tűnhet. Ennek fő oka, hogy számos előkészítő lépést tartalmaz, amik igazából nem bonyolultak, csak időigényel a leírásuk.
A textúrák használatához először is még a globális scope-ban definiálnunk kell a textúra leírókat.
texture<float4, cudaTextureType2D, cudaReadModeElementType> tex_in;
texture<float4, cudaTextureType2D, cudaReadModeElementType> tex_out;
Erre azért van szükség, mert a CUDA-ban egyszerűen így van definiálva a textúrák előkészítése. A textúrázó egy generikus objektum, aminek három paraméter kell megadnunk.
- Az bemeneti képen a pixelek intenzitások típusát;
- A textúra típusát (1-, 2-, vagy 3-D);
- Illetve az olvasás közben megadott típus konverziót.
Következőnek nézzük meg a main függvényt!
Az első rész, ami korábbi példáktól eltérő lehet, az az adatok előkészítése. Ezt több lépésben kell megoldanunk, miközben két új függvényt használunk.
unsigned char* image;
int h, w;
readRGBImageFromFile("Lena.png", image, w, h);
// tömbök a GPU-nál
unsigned char* dev_byte_ptr;
float* dev_src;
float* dev_dst;
// grafikus memóriaterületek foglalása
cudaMalloc((void**)&dev_byte_ptr, 4 * h * w * sizeof(unsigned char));
cudaMalloc((void**)&dev_src, 4 * h * w * sizeof(float));
cudaMalloc((void**)&dev_dst, 4 * h * w * sizeof(float));
// kiinduló kép másolás a grafikus memóriába
cudaMemcpy(dev_byte_ptr, image, 4 * h * w * sizeof(unsigned char), cudaMemcpyHostToDevice);
// kép konvertálás float formátumra
convByte2FloatGPU(dev_src, dev_byte_ptr, 4*h*w);
Az adatok előkészítésétben először beolvassuk a képet a readRGBImageFromFile() függvénnyel.
A képen viszont az adatok byte-os formában vannak amit float-á kell konvertálni. Itt jön a képbe a GPU, mivel ha belegondolunk a konverziót el tudjuk végezni a GPU-n is. A byte-os pixel intenzitásokat átmásoljuk a GPU-ra, majd a convByte2FloatGPU(...) függvényt használva átkonvertáljuk azokat float típusúvá. Erről a függvényről még később részletesebben írunk.
Az adatok előkészítése után a következő lépés a textúrázó előkészítése. Ez viszonylag egyszerűen megy, csak létre kell hoznunk egy csatornaleírót, ami a textúrázó paramétereit adja meg, és hozzá kell kötnünk a globális scope-ban definiált textúrázókhoz a egfelelő memóriaterületeket.
cudaChannelFormatDesc chDesc = cudaCreateChannelDesc();
cudaBindTexture2D(NULL, tex_in, dev_src, chDesc, w, h, 4*w*sizeof(float));
cudaBindTexture2D(NULL, tex_out, dev_dst, chDesc, w, h, 4*w*sizeof(float));
Majd később látjuk, hogy miért is, de ezen a ponton két textúrázót készítünk. Egyet a bemeneti képhez, a másikat pedig a kimeneti képhez kötjük hozzá.
Következőnek jöhet maga aszámítás.
for(i=0; i<ITERATION_COUNT; i++) {
if(i%2) {
// ping
// páratlan cilkusban a 2. textúrából olvasunk és az eredményt az 1.-be írjuk
smoothKernel<<<grid, block>>>((float4*)dev_src, w, h, 1);
} else {
// pong
// páros cilkusban az 1. textúrából olvasunk és az eredményt a 2.-ba írjuk
smoothKernel<<<grid, block>>>((float4*)dev_dst, w, h, 0);
}
}
A számítás iteratívan meg végbe többször, amit a smoothKernel<<<grid, block>>>(...) függvény segítségével végzünk. Itt észre kell venni, hogy minden konvolúciónak két memóriája van egy kimenet és egy bemenet. A következő iterációban viszont az előző interció kimenetén kell tovább dolgozni, és kelleni kell egy újabb kimenet, ami lehet az előző iteráció bemeneti memóriája.
Az interációban tehát folyamatosan cserégetjük a bemenetet és a kimenetet, és a konvolúciós kernelt éis kétféleképpen használjuk. Az egyik iterációban az első memóriából olvasul az első textúrázón keresztül, és követlenül a második memóriába írunk. A másik iterációban pedig fordítva a második memóriából olvasunk a második textúrázón keresztül, és közvetlenül az első memóriába írunk.
Ez nem okoz gondot, mert - bár a textúrázó csak olvasható, rajta keresztül nem tudunk írni a memóriába - a textúrázó alatti memória közvetlenül még mindig írható.
A kernel ennek megfelelően megkapja a cél memória pointerét, a kép szélességét, és magasságát, illetve egy 0 vagy 1-es paramétert attól függően, hogy melyik textúrázóból olvas.
Végül a számítás után az eredményben a pixelek típusát visszaalakítjuk byte-á; az eredményt pedig visszamásoljuk a központi memóriába és kiírjuk a lemezre.
convFloat2ByteGPU(dev_byte_ptr, dev_src, 4*w*h);
cudaMemcpy(image, dev_byte_ptr, 4*w*h*sizeof(unsigned char), cudaMemcpyDeviceToHost);
writeRGBImageToFile("out.png", image, w, h);