OpenGL 301: AGP'de Veri Tutmak

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




Bu haberin geldigi yer: oyunyapimi.org
http://www.oyunyapimi.org

Bu haber icin adres:
http://www.oyunyapimi.org/modules.php?name=Sections&op=viewarticle&artid=42