Kirjautuminen

Haku

Tehtävät

Opasarkisto: SDL ja OpenGL yhdessä: Osa 2 - Tekstuurien ja funktioiden lataus ajon aikana

  1. Osa 1 - Yksinkertainen alustus ja käyttö
  2. Osa 2 - Tekstuurien ja funktioiden lataus ajon aikana

Kirjoittaja: Metabolix (2009).

SDL ja sen lisäkirjastot tarjoavat valmiita latausfunktioita monille kuvaformaateille. Mikäpä olisikaan käytännöllisempää kuin käyttää niitä omien tekstuurien lataamiseen? Hienoissa peliefekteissä tarvitaan myös monia OpenGL:n lisäominaisuuksia, jotka täytyy erikseen ladata ohjelman suorituksen aikana, ja joskus on tarpeen ladata jopa koko OpenGL vasta ohjelman ollessa käynnissä – esimerkiksi silloin, kun ohjelmassa on jokin vaihtoehtoinen järjestelmä niitä varten, joilla ei ole OpenGL:ää. Tässä oppaassa paneudutaan näihin seikkoihin.

Tekstuurien lataaminen

OpenGL itsessään ei sisällä tukea millekään erityiselle tiedostomuodolle tekstuureja varten. Sen sijaan se tarjoaa funktion, joka osaa käyttää valmiiksi ladattua, pakkaamatonta tekstuuria, kunhan parametreissa kerrotaan, millaisessa muodossa data on.

Yleisimmin kuvissa on kolmea väriä: punaista, vihreää ja sinistä. Tietyn värin määrä kuvapisteessä kerrotaan usein asteikolla 0–255, tämän laajempaa asteikkoa ei esimerkiksi SDL tue. Lisäksi kuvassa voi olla neljäskin värikanava, alfakanava, joka kertoo, kuinka paljon pikselistä näkyy läpi. Eri kuvaformaateissa on erilaisia käytäntöjä sen suhteen, missä järjestyksessä kanavat tallennetaan, ja alfakanavaa ei ole läheskään kaikissa tiedostomuodoissa. Lisäksi monet kuvat käyttävät pienempää väriskaalaa tai muutaman värin palettia, jolloin tiedostoista tulee tiiviimpiä. Tämä kaikki kuitenkin hankaloittaa lataamista tekstuuriksi, koska SDL yleensä säilyttää muistissakin kuvan alkuperäisen muodon eikä OpenGL ymmärrä läheskään kaikkea.

Mutta ei hätä ole tämän näköinen; hätä on pieni, punainen ja pyöreä. Kaikeksi onneksi SDL tarjoaa kaksi oivallista ominaisuutta: pintoja voi luoda haluamassaan muodossa, ja erilaisilta pinnoilta voi piirtää toisilleen – SDL hoitaa tarvittavat muunnokset. Helppo tapa tekstuurien kopiointiin onkin luoda pinta, jonka dataa OpenGL osaa käsitellä, ja piirtää tekstuuri väliaikaisesti tälle pinnalle.

Sopivan pinnan voi luoda funktiolla SDL_CreateRGBSurface, jolle annetaan pinnan koko ja tietoja datan halutusta muodosta. Piirtäminen pinnalta toiselle onnistuu tavallisella SDL_BlitSurface-funktiolla, ja OpenGL:lle normaalit 2-ulotteiset tekstuurit välitetään glTexImage2D-funktiolla. Tiedon säilymisen kannalta on käytännöllistä valita kuvalle muoto, jossa on neljä komponenttia (RGBA), joista kullekin on varattu tavu pikseliä kohden eli mainittu skaala 0–255 on käytössä.

Lataamiseen tarvittavasta funktiosta on jo julkaistu koodivinkki, joka on tarkasti kommentoitu ja jonka yhteydessä on myös käyttöesimerkki. Samaa funktiota käytetään myös tämän oppaan esimerkkiohjelmassa.

OpenGL:n funktioiden lataus ajon aikana

OpenGL sisältää paljon lisäosia, jotka eivät välttämättä ole käytettävissä kaikissa OpenGL-toteutuksissa tai kaikilla näytönohjaimilla. Nämä täytyy siis ladata vasta ohjelman aikana, jotta ohjelma toimisi edes jollain tavalla myös niillä, joilla näitä lisäosia ei ole. Myös kaikki tavalliset OpenGL-funktiot on mahdollista ladata kesken ohjelman, jolloin voi halutessaan tehdä ohjelman niin, että se toimii myös ilman OpenGL:ää. Kumpaankin tarkoitukseen käytettävä funktio on sama: SDL_GL_GetProcAddress. Funktioiden käyttämistä varten täytyy tietää niiden tyyppi eli parametrien tyypit. Seuraavassa esimerkissä haetaan funktio glClear funktio-osoittimeen.

#include <SDL.h>
#include <SDL_opengl.h>

/* Funktion tyyppi; rivi vastaa muuten funktion määrittelyä, mutta alussa on typedef ja nimeen on lisätty _t. */
typedef void glClear_t(GLbitfield mask);

/* Funktio-osoitin eli osoitin äsken määriteltyyn tyyppiin */
glClear_t *glClear_f;

/* Haetaan funktio. Standardi kieltää muunnoksen objektiosoitinten ja funktio-osoitinten välillä, täytyy siis turvautua kepulikonsteihin:
&glClear_f on glClear_t**, tehdään siitä void**, josta saadaan void*.
*/
*(void**)&glClear_f = SDL_GL_GetProcAddress("glClear");

/* Virheet on tietenkin hyvä tarkistaa, vaikkei niitä pitäisikään tulla. */
if (!glClear_f) {
	printf("Virhe funktion hakemisessa!\n");
	exit(1);
}

/* Funktiota käytetään ainakin C:ssä täysin normaalisti. */
glClear_f(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Funktioita on paljon ja niiden lataaminen itse on työlästä. Siksi voi olla hyvä harkita tarkoitukseen tehtyä apukirjastoa nimeltä GLEW. Se toimii oikein hyvin SDL:nkin kanssa.

Oman OpenGL-kirjastotiedoston käyttö

Jos järjestelmään asennettu OpenGL-versio ei jostain syystä miellytä, niin toki omankin voi pakata ohjelman mukaan. SDL:ää tästä muutoksesta voi informoida funktiolla SDL_GL_LoadLibrary, joka siis lataa kirjaston halutusta tiedostosta. Tätä tulee kutsua ennen SDL_SetVideoMode-kutsua.

#ifdef WINDOWS
const char *kirjasto = "opengl.oma.dll";
#else
const char *kirjasto = "./opengl.oma.so";
#endif

if (SDL_GL_LoadLibrary(kirjasto) == 0) {
	printf("Ladattiin '%s'\n", kirjasto);
} else {
	printf("Kirjaston '%s' lataus ei onnistunut: %s\n", kirjasto, SDL_GetError());
	printf("Kokeillaan oletusvaihtoehtoa...\n");

	if (SDL_GL_LoadLibrary(0) == 0) {
		printf("Oletuskirjasto ladattu!\n");
	} else {
		printf("Oletuskirjaston lataus ei onnistunut: %s\n", SDL_GetError());
		printf("Sulkeudutaan...\n");
		exit(1);
	}
}

Esimerkkiohjelma

Asiaa on vähän eikä se ole kovin mutkikasta. Puisevaa tekstiä paremmin sitä kuitenkin esittelee esimerkkiohjelma, jonka voi ladata C-kielisenä lähdekoodina.

Ohjelmassa ladataan OpenGL:n funktiot dynaamisesti, jolloin ohjelman käynnistyminen ei vaadi OpenGL:ää. Näin ohjelma voi tarvittaessa siirtyä käyttämään piirtämiseen pelkkää SDL:ää. Lisäksi ohjelmassa luodaan SDL-kuvapinta ja ladataan se OpenGL-tekstuuriksi.

Ohjelmalle voi myös antaa komentoriviparametrin SDL tai OGL sen mukaan, kumpaa piirtotapaa haluaa sen käyttävän. Ilman parametria yritetään ensin käyttää OpenGL:ää, ja jos se ei toimi, vaihdetaan pelkkään SDL:ään. Ohjelma toimii muuten samalla tavalla kummassakin tapauksessa, mutta piirtoon käytetään kirjastosta riippuen eri koodia.

Toiminnassa taideteos voisi näyttää vaikkapa tältä:

Lauri Kenttä, 3.2.2009


Kommentit

BlueByte [06.01.2007 16:15:15]

#

OMG LOL XDDDDD
OON EKA !!1 :-DDD

ankzilla [17.02.2009 20:48:02]

#

Loistava opas! :)

vehkis91 [18.02.2009 19:13:15]

#

:D

Jtm [10.03.2009 00:46:48]

#

Jostakin syystä alusta_OGL() kutsuminen kaataa ohjelman ja palauttaa 3. Tarkistin alusta_OGL()-funktion suorituksen ja se onnistui palauttaen 0 (kuten pitääkin). Mutta ohjelma ei jatka ensimmäisestä if-lauseesta eteenpäin, vaan kaatuu heti palauttaen 3. Miksi? Ja miksi 3?

Kommentoimalla ongelmallinen koodipätkä seuraavasti, ohjelma toimii mainiosti SDL-hyödyntäen.

// if (valittu == VALITTU_SDL || alusta_OGL() != 0) {
	if (valittu == VALITTU_OGL || alusta_SDL() != 0) {
		printf("Alustus ei onnistunut!");
		exit(1);
	}
// }

Mistä syystä ohjelma kaatuu palauttaen 3, vaikka alusta_OGL()-kutsu palauttaa 0? Käänsin ohjelman Code Blocksilla, Windows Vista 32 bit käyttöjärjestelmässä.

Metabolix [10.03.2009 12:35:30]

#

Jtm kirjoitti:

Ohjelma ei jatka ensimmäisestä if-lauseesta eteenpäin, vaan kaatuu heti palauttaen 3. Miksi? Ja miksi 3?

Voit käynnistää ohjelman parametrilla "SDL", jolloin et tarvitse esittämääsi kommenttia vaan ohjelma käyttää suoraan SDL:ää. Itse ongelmaan en osaa ottaa kantaa, minulla ohjelma toimii aivan hyvin. Pystytkö selvittämään debuggerilla, missä kohti se tarkalleen kaatuu, tai saatko kaatumisen syystä muuta tietoa?

Jtm [11.03.2009 01:13:52]

#

Aikaisemmin "debuggasin" koodia käyttäen cstdlid-kirjaston exit()-funktiota. (Jostakin syystä exit()-funktio toimii alusta_OGL() loppuun saakka.)

Nyt selvitin tarkemmin alusta_OGL() toimintaa käyttäen "goto virhe;"-määrittelyä. Sain selville, että alusta_OGL()-funktio toimii normaalisti ennen riviä:

glGenTextures_f(1, &gl_kuva);

Tämän rivin jälkeiset "goto virhe;" kutsut eivät enää mahdollista alusta_SDL()-funktiokutsua, vaikka alusta_OGL() pitäisi palauttaa -1. Ohjelma kaatuu palauttaen: 0xC0000005
Koetin myös "goto virhe;" sijasta "return -1", niin ohjelma kaatuu palauttaen: 0x3

PS. Testasin oppaan 1. osan koodia ja se toimii! :)

Jtm [11.03.2009 01:37:10]

#

Kannettavani ATI Mobility Radeon HD 2600 -näytönohjaimesta löytyy tuki OpenGL 2.0 saakka.

Tarkistin OpenGL-tuen ohjelmalla:
http://www.realtech-vr.com/glview/download.html

Metabolix [11.03.2009 02:16:10]

#

Tuo kuulostaisi siltä, että ongelma on haettujen funktioiden kutsumisessa. Funktioiden tyypit on siis ehkä määritelty käyttöjärjestelmään nähden väärin. Voi olla, että Windowsissa (tai erityisesti Vistassa) myös OpenGL käyttää stdcall-kutsutapaa. Funktioiden oikeat määrittelyt löytyvät tietenkin jostain otsikkotiedostosta, joten voit tarkistaa asian sieltä.

Kokeilepa muuttaa kaikki GL-funktioiden tyypit näin:

typedef void glClear_t(GLbitfield mask);
// =>
typedef GLAPI void APIENTRY glClear_t(GLbitfield mask);

Jos tämä ratkaisee ongelman, teen muutoksen myös oppaan koodiin. Teknisistä syistä en ole itse testannut oppaan sisältöä Windowsissa. :)

Jtm [11.03.2009 17:27:45]

#

Se ratkaisi ongelman -- nyt toimii! Kiitos paljon! :)

rautamiekka [19.03.2009 00:00:38]

#

Harmi kun opas on C:tä, VB6:lla tulisi käyttöä myöhemmin.

Dzarg [08.07.2009 18:01:54]

#

Itselläni tämä ja vastaavat rivit:

typedef GLAPI void GLAPIENTRY glClear_t(GLbitfield mask);

toimivat macrojenkin kanssa vain ilman alun GLAPI, eli muodossa:

typedef void GLAPIENTRY glClear_t(GLbitfield mask);

(tai vaihtoehtoisesti suoraan GLAPIENTRY -> APIENTRY)

Visual Studio 2005 ja Windows XP käytössä.

Jos nyt sattumalta joku muu kohtaa saman ongelman.

Edit: Tosiaan "more than one storage class specified" on viesti mitä visual studio noista riveistä sanoo käännettäessä jo virheinä. Saattaa olla jokin ihan perusjuttu kielessäkin vain. Itse pääasiassa Javaa olen tehnyt ja melko vähän vasta C/C++.

Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta