1/6

Open GL öğreniyoruz

Engin Cilasun 3.06.2010 - 12:31
Dinc Interactive'in programcısı Engin Cilasun sizler için hazırlamaya başladı

ÖNEMLİ NOT

Tüm bu seri boyunca okuyucunun C/C++ bildiği varsayılacak ve teknik terimlerin açıklamaları düzenli bir şekilde verilecektir. Tüm kod örnekleri Courier fontu ile yazılacak ve aksi belirtilmedikçe C++ olacaktır. Okuyucunun Visual C++ 6 (ya da üzeri / uyumlu bir C++ derleyicisi) kullandığı varsayılacaktır.

Bölüm 1: Sistem Altyapısı

A- SIMD mimarisi hakkında ön bilgi

Bu yazı grafik programlama ile ilgili olmasına rağmen, ilk önce elimizdeki sistemi tanıyarak ve bazı kurallar koyarak başlayacağız.

Bilgisayarlar 1983'den bu yana oldukça fazla gelişmelerine rağmen aynı taban üzerinde ayakta kalmaktalar. Süreklilik gerektiren işlerde oldukça yetenekliler, fakat düzensiz işlemlerde, örneğin değişken bir üç boyutlu sahneyi işlemekte, oldukça yavaşlar. Oyunlarda sıkça yapılan şeylerden birisi bu şekildeki verinin işlenmesi oldugu için, bu seride ilk yapacağımız şey, bilgisayarların ne olduğunu (ve olmadığını) öğrenmek ve bu kısıtlamanın üstesinden nasıl gelineceğini öğrenmek olacak.

Bir günümüz bilgisayarı:

1- Doğrusal ve uzun bloklar halindeki işlemleri daha hızlı yapabilir.

2- Ne kadar az hafıza erişimi yaparsa o kadar hızlı çalışır.

3- Elimizdeki "düzensiz" veri miktarı arttıkça adresleme hataları da o denli artar.

Tüm bunlar, Intel'in ve diğer firmaların şu anda kullandığı SIMD (Single Instruction Multiple Data = Tek komutta birden çok veri) tarzı işletim ile neredeyse tamamen örtüşen bir durum oluşturuyor. İlk kısıtlamamiz, bize uzun blokların tek seferde daha hızlı işlendiğini söylüyor, bu da tam olarak SIMD'nin yaptığı şey, uzun blokları tek seferde işlemek. Diğer iki kısıtlama bize adres çözme işleminin problemlerini, yani "cache miss" ve "misalign" sorunlarını anlatıyor. "Cache miss" ile kastedilen, bir adres çözme işlemi sonucu erişilecek verinin kaşe bellekte bulunmadığının anlaşılıp yeniden bir adres çözme işlemine yol açması. "Misalign" ise çözülen adresin 8 (ya da şimdilerde 16 byte)'ın katlarına denk gelmemesi, örneğin hafızanın 3. byte'ında bulunması sonucu gereksiz miktarda verinin birden çok kez okunmasına yol açmasına verilen isim.

Şimdi bu ufak tablodaki kısıtlamalardan kurtulmak için bu seri boyunca uyacağımız kurallara bir göz atalım:

1- SIMD ve/veya SIMD2 işlemlerine ağırlık vermek ya da bunlara dönüşebilir şekilde kod yazmak.

2- Veriyi daima "hizalanmış" hafıza adreslerinden başlatmak.

3- Veriyi daima 16 byte'ın katları olarak tutmaya çalışmak, mümkünse {X,Y,Z} gibi sırasız veri akışını {X[],Y[],Z[]} haline getirmek.

Üçüncü madde biraz karışık gelebilir. Tipik bir programda sakladığımız veri eğer

class nokta

{

      float x, y, z;

};

nokta benimNoktalarım[1024];

ise, bu SIMD için daha uygun olan:

class yogunNokta

{

      float x[1024], y[1024], z[1024];

};

yogunNokta benimNoktalarim;  

şeklinde yeniden düzenlenmelidir. Tabii bu şekilde statik veri ayrılması pek önerilen birşey olmadığı için bunu sadece konuyu açıklamak için verdiğimiz bir örnek olarak yorumlamanız gerekiyor, pratikte:

class yogunNokta

{

      float *x,* y, *z;

};

yogunNokta benimNoktalarim;

x=(float*) aligned_malloc( 1024 * sizeof(float), 16);

y=(float*) aligned_malloc( 1024 * sizeof(float), 16);

z=(float*) aligned_malloc( 1024 * sizeof(float), 16);

daha doğru bir yaklaşım. Buradaki aligned_malloc komutu 16 byte'ın katı olan bir adresten başlayacak şekilde bellek ayırmamızı sağlıyor.

B- Optimizasyon

Kullandığımız sistem, herşeyden önce, saf C/C++ kodunun daima en basit hedef işlemciye göre (zorlanmadıkça) derlendiği bir sistemdir. Yani yazdığımız aşağıdakine benzer bir satır, eğer biz derleyiciyi buna zorlamazsak, 80386'da hızlı çalışacak şekilde derlenir:

float fArray[16] = {1.0f, 4.6f, 12.0f, 43.0f, 61.0f, 52.0f, 2.0f, 1.0f, 3.0f, 4.0f, 6.0f, 13.0f, 421.0f, 54.0f, 654.0f, 1.0f};

for(unsigned long i=0; i<16; i++)

{

      fArray[i] *= 2.49f;

}