OpenGL 301: AGP'de veri tutmak (VBO)
Nedir?
Yeni
çıkan OpenGL 1.5 versiyonunda gelen eklentilerden ARB_vertex_buffer_object (VBO)
eklentisini ve kullanımını bu yazıda açıklamaya çalışacağım. Daha önce şuradaki
makalede, OpenGL kullanırken vertex verisini en hızlı şekilde vertex array'ler (VA)
ile karta gönderebileceğimizi, çizim yaparken en önemli darboğazın da verileri
aktarma işlemi olduğundan bahsetmiştik. VA fonksiyonları, 1.1 sürümünden beri
OpenGL standardı içinde. Her ne kadar hızlı olsa da, VA kullanırken vertexleri her
çizim adımında (frame) sistem hafiza alanından karta tekrar tekrar yolluyoruz. Oysa
örneğin bir araba yarışı oyununda, kullandığımız arabayı ekran kartının çok
daha hızlı erişebildiği AGP hafizasında tutabilmek çok daha mantıklı. Böylelikle,
her çizim adımında tekrar tekrar araba modelini, hiç değişmediği halde, karta
yollamak ile uğraşmazdık. Bu nVidia ve ATI'nin kartlarında bulunan iki ayrı eklenti
ile mümkün idi şimdiye kadar (NV_vertex_array_range ve NV_vertex_array_range2, diğer
adıyla VAR ve ATI_vertex_array_object). İşte ARB_vertex_buffer_object (VBO) eklentisi,
farklı üreticilerin çıkardığı ve karmaşaya yol açan bu gerekli yöntemi, OpenGL
ARB standardı içine aldı.
Kısaca
VBO ile vertex verisini AGP hafiza bölgesinde tutabiliyoruz. Konu ile ilgili döküman
aradığımda bulabildiğim tek şey eklentinin kendi açiklama sayfası idi. Çevrede
ufak tefek örnekler ve bilgiler var tabi ama kullanmak için yapılacak tek şey http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt
adresindeki OpenGL eklenti spesifikasyonunu okumakmış. Zor gibi gözükse de ilk başta,
çok da karışık değil aslında.
Nasıl?
VBO'yu
farklı şekillerde kullanmak mümkün. Verileri bir kez AGP'ye atıp sonra oradan çizim
için kullanabileceğiniz gibi, verinin tümünü veya bir kısmını istediğiniz zaman
tekrar geri alıp (veya map'leyip), değiştirmeniz de mümkün. Ancak AGB teknolojisinin
doğası gereği (unbuffered) bu pek de mantıklı değil. Tekrar tekrar AGP'ye atılmış
veriyle oynamak duruma göre performansınızı VA'lerin de altına çekebilir. Bu
yüzden, en mantıklısı, sahnenizdeki geometrisi değişmeyen ve çok gözüken
nesneleri AGP'ye atmak. Ben de burada bu kullanıma örnek vereceğim.
İlk
olarak VBO ile gelen yeni fonksiyonlara bakalım. 11 adet yeni fonksiyon var. Daha önce şurada
eklentilerin nasıl yüklendiğini görmüştük.
const unsigned char* ext = glGetString (GL_EXTENSIONS);
std::string ext_str = (char*)ext;
// GL_ARB_vertex_buffer_object
if(std::string::npos != ext_str.find("GL_ARB_vertex_buffer_object") )
{
glBindBufferARB = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB");
glDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB");
glGenBuffersARB = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB");
glIsBufferARB = (PFNGLISBUFFERARBPROC)wglGetProcAddress("glIsBufferARB");
glBufferDataARB = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB");
glBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC)wglGetProcAddress("glBufferSubDataARB");
glGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC)wglGetProcAddress("glGetBufferSubDataARB");
glMapBufferARB = (PFNGLMAPBUFFERARBPROC)wglGetProcAddress("glMapBufferARB");
glUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC)wglGetProcAddress("glUnmapBufferARB");
glGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC)wglGetProcAddress("glGetBufferParameterivARB");
glGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC)wglGetProcAddress("glGetBufferPointervARB");
}
Fonksiyonlara
pointer'larımızı aldıktan sonra, artık verileri AGP'ye yollamayı deneyebiliriz. Bu
işlemi sadece bir kere yapacağız programın (veya sahne yukleme işleminin) başında.
// VBO için biraz data hazırlayalım
int sz = pMesh->getVertexCount();
int nArrayObjectSize = (2* sizeof(Vertex3D) + sizeof(Vertex2D)) * sz;
char *pData = new char[nArrayObjectSize];
// ve model içinden datayı kopyalayalım (sıra önemli olacak ileride)
memcpy(pData, pMesh->getVertices(), sizeof(Vertex3D)*sz); // once vertexler (x, y, z)
memcpy(&pData[sizeof(Vertex3D)*sz], pMesh->getNormals(), sizeof(Vertex3D)*sz); // normaller (nx, ny, nz)
memcpy(&pData[2*sizeof(Vertex3D)*sz], pMesh->getTexCoords(), sizeof(Vertex2D)*sz); // doku (s, t)
Yukarıdaki bölümde Vertex3D ve Vertex2D içinde float
vertex verileri var. Mümkün olduğu kadar float veri tipini kullanmak daha iyi. pMesh
içinde ise tüm model ile ilgili bilgiler duruyor. Bu kadarını bilmek yeterli.
VBO için DisplayList'lerde oldugu gibi, her buffer nesnesi
için bir ID yaratmamız gerekli önce. Bu ID'ler her zaman için 1'den başlıyor.
// Bir adet buffer
nesnesi yaratalım
unsigned int id = 0;
glGenBuffersARB( 1, &id );
pMesh->setBufferID(id);
// Bu yeni buffer nesnesinde
vertex verilerini tutacagiz
glBindBufferARB( GL_ARRAY_BUFFER_ARB, id );
// Ve veri statik olarak hep
AGP'de kalacak, biz bir daha erismeye calismayacagiz (onemli: son parametre)
glBufferDataARB( GL_ARRAY_BUFFER_ARB, nArrayObjectSize, pData, GL_STATIC_DRAW_ARB
);
// Bakalim basarabildik mi?
int
nParam_ArrayObjectSize = 0;
glGetBufferParameterivARB( GL_ARRAY_BUFFER_ARB, GL_BUFFER_SIZE_ARB,
&nParam_ArrayObjectSize );
if(
nParam_ArrayObjectSize <= 0 )
return;//
basaramazsak geri donelim
glBindBufferARB( GL_ARRAY_BUFFER_ARB, NULL );
Bu
noktadan itibaren, hem pMesh içindeki hem de pData içindeki vertex verilerini
silebiliriz. Artık veri AGP hafızasında, boşu boşuna (gerekmiyorsa) aynı verinin bir
kopyasını da sistem hafıza alanında (RAM) tutmak gereksiz. Sırada indexlerimizi
VBO'ya atmak var, hemen hemen aynı kodu tekrarlıyoruz.
nArrayObjectSize = sizeof(unsigned short) * pMesh->getIndexCount();
nParam_ArrayObjectSize = 0;
// yeni bir buffer açalım.
glGenBuffersARB( 1, &id );
pMesh->setIndexBufferID(id);
// tek fark burada, GL_ELEMENT_ARRAY_BUFFER_ARB parametre bu sefer..
glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, id );
glBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, nArrayObjectSize, pMesh->getIndices(), GL_STATIC_DRAW_ARB );
glGetBufferParameterivARB( GL_ELEMENT_ARRAY_BUFFER_ARB, GL_BUFFER_SIZE_ARB, &nParam_ArrayObjectSize );
if( nParam_ArrayObjectSize <= 0 )
return;
glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, NULL );
İndeksler de artık AGP'de.. Çizim esnasında ne yaptığımıza gelelim..
if(pMesh->getBufferID() > 0)
{
// kullanacağımız buffer'ları aktive edelim.
glBindBufferARB( GL_ARRAY_BUFFER_ARB, pMesh->getBufferID() );
glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pMesh->getIndexBufferID());
// sıraya ve BUFFER_OFFSET makrosuna dikkat, normal VA'dan tek fark bu.
long offset = sizeof(Vertex3D)*pMesh->getVertexCount();
glVertexPointer( 3, GL_FLOAT, 0, BUFFER_OFFSET(0) );
glNormalPointer( GL_FLOAT, 0, BUFFER_OFFSET(offset));
glTexCoordPointer( 2, GL_FLOAT, 0, BUFFER_OFFSET(2*offset));
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_NORMAL_ARRAY );
glEnableClientState( GL_TEXTURE_COORD_ARRAY);
// indeksler için de tek fark yine BUFFER_OFFSET makrosu
glDrawElements( GL_TRIANGLES, pMesh->getIndexCount(), GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
glDisableClientState( GL_VERTEX_ARRAY );
glDisableClientState( GL_NORMAL_ARRAY );
glDisableClientState( GL_TEXTURE_COORD_ARRAY);
// buffer'ları kullandık, deaktive edelim.
glBindBufferARB( GL_ARRAY_BUFFER_ARB, NULL );
glBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, NULL );
}
BUFFER_OFFSET makrosu ile, AGP'deki verimizin adresini belirliyoruz. Makronun tanımı
şöyle;
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
Evet,
hepsi bu kadar. Son olarak da, programdan çıkarken AGP hafızasını nasıl
temizlediğimizi görelim.
// id, kullanımda olan bir buffer'ın ID'si
if( glIsBufferARB(id) )
glDeleteBuffersARB( 1, &id );
En basit kullanımı ile VBO bu kadar. Eklenti ile gelen diğer fonksiyonları
kullanarak AGP'deki veriye farklı şekillerde erişebilirsiniz. Son olarak, bir de örnek
verelim, 12000 üçgenden oluşan, tek ışık, tek kaplamalı bir model için, GF4 bir
kartta %300'luk bir performans artışı gördüm ben. Tahminen bu maximum kazanç, sahne
kompleksleştikçe kazanç da düşecektir. Ama bence yine de hiç fena değil...
Bu yazı ile ilgili, hatalar, görüşleriniz, eleştirileriniz
için mentat@cfxweb.net adresini kullanabilirsiniz.
Ve tabi www.oyunyapimi.org forumlarını. Umarım
yazdıklarım işinize yarar.
OpenGL eklentileri bilgileri: http://oss.sgi.com/projects/ogl-sample/registry/
mentat :: 19.08.2003 :: www.oyunyapimi.org