int21h

SDL místo 0013h

Úvod

V jakémsi dílu seriálu Martin Lux řekl, že by si i on rád přečetl o SDL. Já teď sice o SDL něco napíšu, ale jsem si jistý, že to není to, co hledal. Tohle je totiž SDL pro c, protože s JediSDL zkušenosti nemám a s freepascalem už vůbec ne. Neodsuzuji sice freepascal a jsem si jistý, že jde o velice výkonný jazyk, ale pokud jde o mě a mou lásku k unixu, tak c je pro mě jasná volba. (To už je jak reklama na jihlavanku:)

Jediné co mě poslední dobou drželo u DOSu bylo to strašně jednoduchý ovládání grafických módu. Vždyť stačilo jen zapnout režim přes int10h a byl pokoj. Pak už stačilo jen zapisovat do paměti byty.

Co mě ale strašně štvalo bylo to, že jsem si byl vědom, že můj program nespustím nikde jinde než na DOSu - dokonce i na Windows to někdy dělalo problémy. To bylo pro mě, nadšeného linuxáře, opravdu dilema.

Vůbec jsem neměl ponětí o tom, jak dělat pokročilejší grafiku, protože opengl je 3d grafika a SDL - jak jsem dříve myslel, jenom pokládání obrázků (jak jsem usoudil ze SDL her).
Nedávno jsem ale přišel na metodu, jak přistupovat rovnou k lineárnímu poli, kam se zapisují přímo pixely!

V tomto článku nechci popisovat práci v SDL (to někdy příště), ale metody jak uplatnit demařské zkušenosti na jiných platformách.

Všechno o čem budu psát se týká čistého c - takže ne c++

Theory

Základní teorie SDL je v tom, že se vše skládá z tzv. Surfaces - což jsou objekty, ve kterých jsou uložena pixelová data a nějaké informace jako rozměry a tak.
Do surface se dá uložit celý obrázek přes funkci SDL_LoadBMP, ale pokud chceme přistupovat k pixelům, tak musíme surface odemknout.

Inicializace

První, co musíme udělat je inicializování videomódu. To se musí provést ve dvou krocích.
SDL_Surface *scr;/*obrazovka*/
if (SDL_Init(SDL_INIT_VIDEO[|flags])<0) {
	fprintf(stderr,"SDL_Init error: %s",SDL_GetError());
	return 1;
}
if ((scr=SDL_SetVideoMode(WIDTH,HEIGHT,DEPTH,SDL_SURFACE[|flags]))==NULL) {
        fprintf(stderr,"SDL_SetVideoMode error: %s",SDL_GetError());
        return 1;
}
...
...
/*SDL_FreeSurface(scr); - to neni nutne*/
SDL_Quit();  

Plotting

[z]/plotting
Tím jsme zpřístupnili pole, které je na adrese scr->pixels. Takže už můžeme klidně napsat putpixel:
int putpixel(SDL_Surface *scr,int x, int y, int c) {
	((unsigned int*)scr->pixels)[y*scr->w+x]=c;
	return 0;
}  
Obdobný by byl getpixel.
Před přímým zápisem do pixelů musíme surface zamknout (pokud je to třeba):
if (SDL_MUSTLOCK(scr)) {
	if (SDL_LockSurface(scr)<0) {
		return 0;
	}
}  
A po dokončení přímého přístupu zase odemknout.
if (SDL_MUSTLOCK(scr)) {
	SDL_UnlockSurface(scr);
}  

Pokud budete pracovat v barevné hloubce 8 (256 barev), tak pole pixelů musíte přetypovat na bytové:
((unsigned char *)scr->pixels)[y*scr->w+x]=c;  

Pokud máte putpixel, tak už můžete dělat prakticky všechno co dokáží grafické knihovny pro MCGA
Ovšem proč psát něco, co už udělali z úspěchem jiní? Skvělá knihovna, která se podobá právě těm dosařským se jmenuje SDL_Draw a umí všechno přes čáry až po vyplněné polygony.

Kreslení bychom měli - a teď další dosová záležitost - paleta.

Palety

[z]/paleta
Knihovna SDL má sice přímo strukturu paleta SDL_Palette, ale skoro nikde jsem si neviděl používat.
Paleta se dá ovšem používat jen pro 8-bitový režim.
Daleko víc se používá přímo pole barev:
SDL_Color pal[256];  
Pak už můžeme paletu naplnit klasicky přes r g b
for (i=0;i<256;i++) {
	pal[i].r=i/5;
	pal[i].g=i/4;
	pal[i].b=i/6;
}
/* A pak celou paletu použijeme */
SDL_SetPalette(scr,SDL_LOGPAL|SDL_PHYSPAL,0,256);  

Podle mě jsou s 8-bitovými režimy jen potíže a proč se omezovat 8 bity?
Já to řeším tak, že si definuji jak velkou chci paletu (SDL_Color pal[n]) a když chci vykreslit třeba padesátou barvu v paletě, tak předám putpixelu pal[50].r*65536+pal[50].g*256+pal[50].b (je dobré si to uzavřít do funkce).

Pro tento účel jsem napsal i knihovnu sdlpal, která načte paletu ze souboru. (je dole v balíku)

Sprites

[z]/pictures
Tak tady je to jednoduché.
Načtení obrázku do Surface se dělá přes funkci SDL_LoadBMP(char *path).
Při tomhle musí být surface, do kterého zapisujeme odemknuto.
|pozn. jestli se chcete zajímat dál, tak se podívejte na knihovnu SDL_Image, která má funkce na načtení jakéhokoli formátu|
Tak, měli by jsme obrázková data, ale co dál? Super, můžeme číst data a vykreslovat je po pixelu - a to se fakt někdy používá, například při deformacích a efektech s obrázky, ale pokud chceme rychle vykreslit obrázek, tak musíme poznat další prvek - SDL_Rect
SDL_Rect je vlastně jenom prvek definující obdélníkovou oblast pixelů. Pixely ovšem neobsahuje. Obsahuje pouze souřadnice a rozměry.
Pro vložení jednoho surface do druhého je funkce SDL_BlitSurface.
Takže kostra:

SDL_Surface *img;
img=SDL_LoadBMP("obr.bmp");
SDL_SetColorKey(img,SDL_SRCCOLORKEY,0);/*nastavi prvni barvu jako pruhlednou*/


SDL_Rect pos;
pos.x=10;
pos.y=10;
pos.w=img->w;
pos.h=img->h;


SDL_UnlockSurface(scr);
SDL_BlitSurface(img,NULL,scr,&rct);
SDL_LockSurface(scr);


SDL_UpdateRect(img,0,0,img->w,img->h);
...
...
SDL_FreeSurface(img);/*mela by se uvolnit pamet*/  

Fonty

Jak psát na obrazovku?
Obrázkové fonty[z]/bmpfont
Nejčastější možnost je ta, že celý font je uložen v obrázku a program si ho pak rozseká do jednotlivých znaků.
Všechny SDL fonty přeskakují prvních 33 znaků(i s mezerou).
Pokud máme font neproporciální, tak není problém (když známe šířku), ale neproporcionální fonty jsou dost nevzhledné.
Větší problém je ale s proporciálními fonty, kde má každý znak svou šířku. To se v SDL hrách řeší tím, že mají všechny fonty jednotný formát:
Na prním řádku (y=0) je pixelová fialová(0xff00ff) čára, která určuje šířku fontu. V místech pod fialovou čárou není žádný znak a mezi filaovýma čarama je tedy font.
Dále - pixel v levém dolním rohu je barva, která se má chápat jako průhledná.
Nevím, jestli je pro takový formát nějaké knihovna, nebo si ji každý musí napsat sám, ale pokud nějaká knihovna je, tak je určitě pro c++.
Tak jsem si procedury na zpracování tohoto formátu musel napsat sám (jsou zase v balíku)
Algoritmus:
/*sfont = obrazek s fontem
  font->charpos = pole o alespon 200 prvcich*/
while (xw) {
	if (getpixel(sfont,x,0)==0xff00ff) {
		font->charpos[i++]=x;
        	while ((xw) && (getpixel(sfont,x,0)==0xff00ff))
			x++;
		font->charpos[i++]=x;
	}
	x++;
}  
Vektorové fonty[z]/ttffont
Pokud vám stačí normální jednobarevný font je dobré využít knihovnu SDL_ttf, která umí načítat fonty ve formátu TrueTypeFont(TTF). Opět není těžké používat.
TTF_Font *font;
if (TTF_Init()) {
	printf("TTF_Init error: %s",TTF_GetError());
	return 1;
}
if ((font=TTF_OpenFont("fontname.ttf",32))==NULL) { /*druhy parametr je velikost*/
	printf("TTF_OpenFont error: %s",TTF_GetError());
	return 1;
}
...
...
TTF_CloseFont(font);
TTF_Quit();  
A ještě nějakou write funkci
int SDLwrite(SDL_Surface *scr, TTF_Font *font, int x, int y,char *chr, int c) {
	SDL_Surface *surf;
        SDL_Rect rect;
        SDL_Color col;
        col.r=(c/65536)%256;
        col.g=(c/256)%256;
        col.b=c%256;
        surf=TTF_RenderText_Solid(font,chr,col);
        rect.x=x;
        rect.y=y;
        rect.w=surf->w;
        rect.h=surf->h;
        SDL_BlitSurface(surf,NULL,scr,&rect);
        SDL_FreeSurface(surf);
	return 0;
}  
Existují tři renderovací metody - Solid, Blended a Shaded(té ale musíte předat dvě barvy - fg a bg).
Barvu v jednom čísle mám jenom kvůli kompatibilitě s mým putpixelem.

Audio

Na začátek. Do SDL_Init musíte zařadit flag SDL_INIT_AUDIO (doufám, že o flazích už něco víte)
Pokud budete chtít pracovat s audio na trošku větší úrovni s trošku menší složitostí, určitě includujte SDL_mixer.h
WAV VOC[z]/audio
Datový typ je Mix_Chunk. Do tétu struktury můžete nahrát wav nebo voc soubory přes Mix_LoadWAV(), no nevím jestli je to dělané přímo pro tohle (spíš je to pro nahrávání samplů do modulů, ale to je jedno).
Pak už stačí přehrát přes Mix_PlayChannel()
Mix_Chunk wave;
if (Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 2048) == -1) {
        printf("Mix_OpenAudio() error: %s", Mix_GetError());
}
Mix_AllocateChannels(1);
if ((wave=Mix_LoadWAV("sample.wav")) == NULL) {
        printf("Music loading error : %s", Mix_GetError());
}
Mix_PlayChannel(0,wave,-1);
...
...
Mix_FreeChunk(wave);
Mix_CloseAudio();  
Ten poslední parametr v playchannel je looping. Znamená kolikrát opakovat a -1 je pořád dočtverečka(dokolečka je už ohraný)
MOD S3M IT XM[z]/modules
Tak tohle je opravdu lahůdka pro demomakery, protože přehrávání modulů bylo na DOSu vždycky nejobtížnější (ani nevím jaký RAINy, nebo MIDASe k tomu byly zapotřebí)
Datový typ je Mix_Music. Nejdřív inicializujeme zvukový driver přes Mix_OpenAudio(), a pak už můžeme otevřít nějaký modul přes Mix_LoadMUS("sample.mod")
Mix_Music *music;
if (Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 2048) == -1) {
	printf("Mix_OpenAudio() error: %s", Mix_GetError());
}
Mix_AllocateChannels(1);
if ((music = Mix_LoadMUS("sample.mod")) == NULL) {
	printf("Music loading error : %s", Mix_GetError());
}
Mix_PlayMusic(music, -1); 
...
...
Mix_FreeMusic(music);
Mix_CloseAudio();  
Pro přehrávání se asi použije nové vlákno, protože hraje nezávisle na tom, co se děje v programu - naštěstí to dělá sám a o nějaký thread se nemusíme starat.

Závěr

Myslím, že SDL se opravdu hodí na tvoření dem, her, ... to je tak všechno:) Ale přesně tohle se dělalo s grafikou na DOSu.
Vůbec jsem se tu nezmiňoval o GL (možná příště), ale to už je úplně jiné odvětví, ve kterém demařské zkušenosti asi neuplatníte. Teď se hry píšou jen pro OpenGL a samotné SDL upadá. I když pořád se ještě najdou krásné SDL hry, které mě osobně připomínají staré časy víc než nějaká OpenGL střílečka.

Mluvím o tom, že se do SDL se už skoro hromadně portují staré dosové hry jako Nebulus, Jump'n'Bump, Lemmings, Scavenger a další. Je to super, protože si můžete zahrát svou oblíbenou hru na všech platformách XFree, Windows, nebo i MacOSX a když napíšete program v SDL, tak už vás nemusí zajímat na jakém počítači bude zkompilován.

Zdroje
SDL Library Documentation
Man pages
Zdrojáky různých programů

MultiPack

Moje články nejsou moc dlouhé, protože se snažím hlavně o praxi a myslím, že ze zdrojáků (pokud jsou dobře napsané) jde vyčíst víc než z mých blbých keců.
Stačí to rozbalit přes tar a gz a pak spustit configure a make (pokud jste na unixu, pokud jste na win tak vám neporadím, protože s tím nenám zkušenosti, ale mělo by to fakčit)
[z]/xxx znamená, že ve složce xxx je příklad k danému tématu.
]sdltries-0.91.tar.gz[ MD5-SUM:b19aecb0731d6d74cdac824270dc278e
2006-12-01 | BOby
Reklamy:
„Vhodnou základnou pro manželství je oboustranné nepochopení.“ Oscar Wilde