int21h

Zajímavá rozšíření ve Free Pascalu

V tomto článku trochu mimo pořadí (posouvá zvukové karty až na dobu poté) se zaměříme na pár věcí, které dělají FP tím, čím je, tedy lepším než Turbo Pascal 7, alespoň co se týče možností pro programátory (současná stabilní (?) verze je 2.0.2 jak pro DOS, Windows tak i Linux, pro ostatní zatím jen 2.0.0). Pokud chcete, aby Vaše aplikace běžely co nejrychleji, tak kromě triků, které jsme už zmiňovali, je vhodné, abyste se naučili i následující věci (jde mi samozřejmě o rychlost, takže něco jako zpracování výjimek nepřipadá v úvahu):

Moderní instrukce koprocesorů

Turbo Pascal 7 (TP7) dokáže využívat pouze koprocesory řady 8087 až 80287. To je sice užitečné v případě, kdy potřebujete počítat např. s reálnými čísly, a ač v dnešních počítačích fungují tyto instrukce také (jen jsou zbytečně pomalejší, neboť nejsou 32 bitové), může jejich využití vadit, pokud se rozhodnete využívat např. MMX. Důvod je jasný: MMX a FPU totiž používají stejné registry, takže pokud chcete něco dělat s MMX a pak s FPU, musíte před první instrukcí FPU vždy použít instrukci "emms", která trvá ovšem dost dlouho, takže je vhodné sloučit co nejvíce podobných bloků, aby jste její použití minimalizovali (samozřejmě, pokud pouze přesouváte data pomocí koprocesoru, nemusíte jí volat - to je nutné pouze, pokud chcete počítat pomocí FPU). A pokud Váš program využívá koprocesor, a je určený pro 386 a níže, které ho většinou nemají, musíte k němu přiložit soubor WMEMU387.DXE a použít jednotku EMU387 (jedná se pouze o DOSové programy, ostatní OS mají případnou emulaci už v sobě).

Free Pascal (FP) naštěstí podporuje i vyšší koprocesory, resp. instrukce SSE, které FPU nahradí a tedy nebude nutné jej používat. Tím odpadá povinnost volat "emms" po každém bloku MMX, po kterém počítáte s reálnými čísly. K tomuto slouží direktiva $FPUTYPE, kde namísto X87 zadáte SSE (pro které potřebujete minimálně PentiumIII nebo AthlonXP) nebo SSE2 (od Pentium4 nebo AthlonXP64). Na 64 bitových CPU můžete ještě využít i SSE64 (mluvím jen o Intelu, jiné CPU mají jiné typy FPU). FP použije dané instrukce automaticky na výpočty s daty typu single (nebo i double u SSE2). I kdybyste nepoužívali MMX, tímto se Vaše operace zrychlí (musíte ovšem mít daný procesor).

Práce s pamětí (stručně)

Pokud přecházíte z TP7, jistě částečně narazíte na pár odlišností, které se týkají správy paměti (pokud chcete, aby Vaše programy fungovaly i pod DOSem, a nemáte žádného správce PM kompatibilního s FP (EMM386 může ale nemusí být), musíte přiložit k Vaší aplikaci ještě soubor CWSDPMI.EXE - kdybyste psali Vaši aplikaci např. ve FASM pro chráněný mód, můžete používat i jiné extendery, např. DOS4GW nebo DOS32A). FP se naštěstí snaží být tak kompatibilní, jak jen to jde. Pokud používáte pouze proměnné přes VAR, které poté adresujete pomocí jejich jmen, popř. je přesouváte pomocí MOVE, nemusíte se o nic starat, Váš program bude fungovat stejně.

Pokud používáte funkce GETMEM nebo FREEMEM, FP bude také fungovat stejně. Jen je nutné mít na paměti, že veškerá paměť neleží pod 1.MB (DOS), ale nad ním (Get a Free si de/alokují prostor na haldě, která ovšem nyní leží jinde). Tedy nejste omezeni 64kB, můžete využít tolik MB, kolik MB RAM má Váš počítač, popř. jak velký je swap soubor na disku (DOS, Windows, Linux). I zde se používá práce s ukazateli, které jsou 32 bitové (nebo 64 bitové na novějších CPU). Vtip je tom, že se nyní nepoužívá segment, takže pokud použijete funkce SEG() a OFS(), dostanete v prvním případě 0, ve druhém 32 bitový offset v DS segmentu. FP to ale nijak nevadí, např. Funkce PTR, která složí segment (tady selektor) a offset jednoduše zase vrátí 32/64 bitový pointer. Až potud by vše fungovalo tak, jak by mělo.

Potíže nastanou, pokud pro přístup paměti alokované přes ukazatele nepoužíváte PTR, ale MEM (např. i v instrukcích MOVE). FP totiž v rámci toho, aby se přes MEM dalo přistupovat do DOSové paměti (a např. aby fungoval zápis na $a000:0, což je video paměť), ponechal tuto funkci nezměněnou. Tedy, pokud alokujete něco přes GETMEM, nemůžete k tomu přistupovat pomocí pole MEM, ale jedině přes PTR. Tedy tyto odkazy bude nutné přepsat na PTR (více méně to zůstane stejné, jen slovo MEM nahradíte na PTR a hranaté závorky změníte na kulaté, a přidáte na konec stříšku^).

Dalším problémem může být, pokud jste chtěli přes GETMEM vyčlenit DOSovou paměť pro zvukovou kartu či ovladač XMS, nebo jste pomocí MOVE přesouvali data do konvenční paměti (a nutně jste je tam potřebovali mít). Tady musíte přepsat skoro všechno. Funkce GETMEM a FREEMEM musíte nahradit funkcemi (které navíc ještě vyžadují jednotku GO32) global_dos_alloc a global_dos_free. Pak data mezi VAR a mezi DOSovou pamětí (získanou přes SEG z vráceného LONGINTu, a za OFS dosaďte standardně 0, pokud chcete začátek) kopírujete funkcemi dpmi_dosmemget a dpmi_dosmemput. Funkce FILLx musíte nahradit buď dvojicí, kdy vymažete nějaký buffer ve VAR a pak ho do 1.MB zkopírujete, nebo funkcí dpmi_dosmemfillword či dpmi_dosmemfillchar. Samozřejmě, pokud jste toto potřebovali jen pro XMS, s klidným svědomím můžete GetMem, atd. nechat, MEM přepsat na PTR, a ve Vašich XMS funkcích pouze změnit obsah tak, aby funkce, která alokuje blok v XMS využila zase jen GetMem (a všechno související se skutečnou XMS vyhodit), a ostatní, které přesouvají data mezi konvenční a XMS pamětí, změnit tak, aby kopírovaly data mezi jedním a druhým prostorem od Getmem. Sice to bude zbytečně pomalejší, ale zase toho nebudete muset tolik přepisovat (pokud převádíte velký program).

Vícevláknové procesy

Moderní CPU podporují dvě a více jader (a moderní desky ještě navíc dva a více CPU), takže umožňují skutečně spustit dva a více programů současně. Resp. pokud se jedná o více jaderné CPU, běží na nich tzv. vlákna, která si buď řídí sama aplikace (v ideálním případě), nebo se o ně stará OS (operační systém). Prakticky tedy platí, že na to, abyste mohli používat vlákna (hodí se např. pro ACCEPT/CONNECT při využití SOCKETS) nemusíte mít procesor, který má dvě jádra (Pentium IV, Athlon, atd.), ale alespoň operační systém (a CPU min. 386), který podporuje multitasking (Windows 9X a vyšší, Linux, a další). Funguje to vlastně tak, že Váš program si vytvoří vlákno, které se spustí paralelně k němu a k případným ostatním vláknům. Pokud např. máte tabulky, kde potřebujete sečíst vždy 2 čísla na stejných pozicích (A[1]+B[1]), můžete použít dvě vlákna, z nichž jedno bude sčítat liché řádky a to druhé sudé. Nemáte ale zaručeno, které z nich doběhne první (ve vetšině případů to ani tak moc nevadí).

FP podporuje spoustu funkcí pro práci s vlákny, my se ovšem podíváme jen na ty nejzákladnější. Každému vláknu můžete předat parametr, jako kdyby to byla funkce. Ovšem zde máte pouze na výběr, zda mu předáte ukazatel nebo nic (ten bude v tom případě roven NIL). Parametr se stejně jako u funkce předává při spuštění vlákna:

function BeginThread(Thread : tthreadfunc) : dword;
function BeginThread(Thread : tthreadfunc; p : pointer) : dword;

Typ DWORD je neznaménkový WORD, ale pokud potřebujete extra rychlé výpočty, doporučuji na 95% nevyužívat tento typ, ale spíše LONGINT (záleží na situaci). Jen tak pro ukázku Vám ukážu, jak dopadne program tohoto typu:

var _word : word;
    _dword : dword;
    _longint : longint;
begin
 _word := _word*2;
 _word := _word*3;
 _word := _word*4;
 _word := _word*10;
 _dword := _dword*2;
 _dword := _dword*3;
 _dword := _dword*4;
 _dword := _dword*10;
 _longint := _longint*2;
 _longint := _longint*3;
 _longint := _longint*4;
 _longint := _longint*10;
end.

který přeložíte pro různé typy procesorů:

386/486

# [5] _word := _word*2;
	movzwl	%di,%eax
	addl	%eax,%eax
	movw	%ax,%di
# [6] _word := _word*3;
	movzwl	%di,%eax
	leal	(%eax,%eax,2),%eax
	movw	%ax,%di
# [7] _word := _word*4;
	movzwl	%di,%eax
	leal	(,%eax,4),%eax
	movw	%ax,%di
# [8] _word := _word*10;
	movzwl	%di,%eax
	leal	(%eax,%eax,4),%eax
	addl	%eax,%eax
	movw	%ax,%di
# [9] _dword := _dword*2;
	addl	%ebx,%ebx
# [10] _dword := _dword*3;
	movl	%ebx,%edx
	movl	$3,%eax
	mull	%edx
# [11] _dword := _dword*4;
	leal	(,%eax,4),%eax
	movl	%eax,%ebx
# [12] _dword := _dword*10;
	movl	%ebx,%edx
	movl	$10,%eax
	mull	%edx
	movl	%eax,%ebx
# [13] _longint := _longint*2;
	addl	%esi,%esi
# [14] _longint := _longint*3;
	leal	(%esi,%esi,2),%esi
# [15] _longint := _longint*4;
	leal	(,%esi,4),%esi
# [16] _longint := _longint*10;
	leal	(%esi,%esi,4),%esi
	addl	%esi,%esi
	movl	%esi,%eax

Pentium/MMX

# [5] _word := _word*2;
	movzwl	%di,%eax
	addl	%eax,%eax
	movw	%ax,%di
# [6] _word := _word*3;
	movzwl	%di,%eax
	leal	(%eax,%eax,2),%eax
	movw	%ax,%di
# [7] _word := _word*4;
	movzwl	%di,%eax
	leal	(,%eax,4),%eax
	movw	%ax,%di
# [8] _word := _word*10;
	movzwl	%di,%eax
	imull	$10,%eax
	movw	%ax,%di
# [9] _dword := _dword*2;
	addl	%ebx,%ebx
# [10] _dword := _dword*3;
	movl	%ebx,%edx
	movl	$3,%eax
	mull	%edx
# [11] _dword := _dword*4;
	leal	(,%eax,4),%eax
	movl	%eax,%ebx
# [12] _dword := _dword*10;
	movl	%ebx,%edx
	movl	$10,%eax
	mull	%edx
	movl	%eax,%ebx
# [13] _longint := _longint*2;
	addl	%esi,%esi
# [14] _longint := _longint*3;
	leal	(%esi,%esi,2),%esi
# [15] _longint := _longint*4;
	leal	(,%esi,4),%esi
# [16] _longint := _longint*10;
	imull	$10,%esi
	movl	%esi,%eax

Pentium2+

# [5] _word := _word*2;
	movzwl	%di,%eax
	shll	$1,%eax
	movw	%ax,%di
# [6] _word := _word*3;
	movzwl	%di,%eax
	imull	$3,%eax
	movw	%ax,%di
# [7] _word := _word*4;
	movzwl	%di,%eax
	shll	$2,%eax
	movw	%ax,%di
# [8] _word := _word*10;
	movzwl	%di,%eax
	imull	$10,%eax
	movw	%ax,%di
# [9] _dword := _dword*2;
	shll	$1,%ebx
# [10] _dword := _dword*3;
	movl	%ebx,%edx
	movl	$3,%eax
	mull	%edx
# [11] _dword := _dword*4;
	shll	$2,%eax
	movl	%eax,%ebx
# [12] _dword := _dword*10;
	movl	%ebx,%edx
	movl	$10,%eax
	mull	%edx
	movl	%eax,%ebx
# [13] _longint := _longint*2;
	shll	$1,%esi
# [14] _longint := _longint*3;
	imull	$3,%esi
# [15] _longint := _longint*4;
	shll	$2,%esi
# [16] _longint := _longint*10;
	imull	$10,%esi
	movl	%esi,%eax

Free Pascal dovolí takto generovaný kód na disku ponechat, takže se to hodí pro zjištění, jak moc efektní je kód, který byl vygenerován. Pokud zjisíte, že se tam např. zbytečně používá nějaké to dělení, zkuste Váš kód přepsat tak, aby se tam už neobjevilo. Samozřejmě, výše uvedené výstupy nejsou moc přesné, protože kód na dalších řádcích závisí na těch předchozích a FP se toho snaží z této situace co nejvíce vytěžit.

Ale zpět k našim vláknům. Dané funkce spustí vlákno, které jim předáte jako parametr, což není nic jiného, než ukazatel na funkci. Navíc může vracet i parametr (longint), ale zatím nevím, k čemu je dobrý (pravděpodobně slouží ale k předávání chybového kódu):

type TThreadFunc = function(parametr : pointer) : longint;

Funkce BEGINthread vrátí 0, pokud start vlákna proběhl dobře. Každé vlákno se ukončí po doběhnutí na konec, protože se vlastně jedná jen o obyčejnou funkci. Nebo jej můžete ukončit dříve sami, ale ne pomocí funkce Exit, ale pomocí podobné (pokud zadáte chybový kód, můžete jej získat později pomocí funkce WaitForThreadTerminate; vlákno můžete také "zabít" i z nadřazeného procesu pomocí příkazu KillThread, kde musíte zadat ID vlákna, které Vám vrátí 3. varianta funkce BeginThread, kterou jsem zde ovšem neuvedl - viz. dokumentace FP a jednotky SYSTEM):

procedure EndThread(ExitCode : DWord);
procedure EndThread;

Nyní je nutné uvědomit si, jak vlákna fungují. Každé vlákno pracuje samostatně, ale sdílí kód. Na tom není nic špatného, protože to vůbec nevadí. Horší je to s daty. Všechny proměnné, které definujete uvnitř vlákna, jsou jeho lokální a tedy k nim má přístup jen on. Ostatní proměnné (globální) jsou sdílené všemi vlákny. Tedy, pokud jedno vlákno zapíše do proměnné 0, druhé si ji tam může přečíst. Výjimkou jsou proměnné, které nadefinujete ne pomocí VAR, ale pomocí THREADVAR. V tom případě se pak pro každé vlákno vytvoří jedna kopie dané proměnné, takže neovlivní její hodnotu pro ty ostatní (poznámka: každé vlákno dostane na začátku hodnotu, která byla na začátku programu, nikoliv hodnotu, kterou tam zanechalo předchozí vlákno!). Lepší je takové proměnné dávat přes VAR přímo do dané funkce. Pokud chcete ale používat ve více vláknech stejnou globální proměnnou, neměli byste k ní přistupovat pomocí standardních funkcí, ale pomocí chráněných:

InterLockedIncrement	(* INC *)
InterlockedDecrement	(* DEC *)
InterlockedExchange	(* XCHG *)

Uvedeme si krátký příklad, jak používat vlákna (jedná se o úpravu příkladu v nápovědě FP, kdybyste ji nepochopili):

{$mode objfpc}  
uses sysutils {$ifdef unix},cthreads{$endif};
const	PocetVlaken = 100;
	DelkaRetezce = 10000;  
var	Dokonceno : longint;  
 
threadvar	thri : longint;  
 
function Funkce(p : pointer) : longint;
var s : ansistring;
begin
 Writeln('Vlákno ',longint(p),' bylo spuštěno');
 thri := 0;
 while thri < DelkaRetezce do
 begin
  s := s + '1';
  inc(thri);
 end;
 Writeln('Vlákno ',longint(p),' bylo dokončeno');
 InterLockedIncrement(Dokonceno);
 Funkce := 0;
end;
 
var Cislo : longint;  
 
begin  
 Dokonceno:=0;  
 for i := Cislo to PocetVlaken do  
  BeginThread(@Funkce,pointer(Cislo));  
 while Dokonceno <= PocetVlakem do Writeln(finished);  
end.

Je to víceméně dost jednoduchá záležitost. Vyzkoušejte si ji, a pokud budete programovat např. hru, kde máte více se pohybujících objektů nezávislých v některých věcech na sobě, nebo píšete server pro více klientů, jistě budete vlákna dost využívat. Existuje spousta dalších funkcí, které umožňují např. i vlákno uspat nebo nastavit jeho prioritu, takže si můžete napsat i svůj vlatní OS :-)

Technologie MMX

Poslední věcí, na kterou se nyní podíváme, je využití MMX. Kdysi to byla velká novinka, dneska tyto instrukce podporuje kde co. Při jejich použití Vám může několika násobně stoupnout rychlost Vašich operací s multimédii (i když se dá obecně použít i na libovolná data). FP podporuje základní operace s MMX, na ty ostatní už musíte sáhnout po assembleru (FP podporuje kompletní sadu nejnovějších CPU). Protože MMX umí zpracovat až 8 bytů současně (pro informaci, SSE umí až 16!), můžete s ním např. přesouvat až 8 bytů současně (MOVSD umí jen 4). Ideálně je přesouvat rovnou 16, kde můžete použít offset +8 u druhého přesunu (tj. [PAM]=>MMX0, [PAM+8]=>MMX1, MMX0=>[CIL], MMX1=>[CIL+8]), takže ušetříte jeden pár ADD CIL,8 a ADD MEM,8 instrukcí. Pokud např. zpracováváte 8 bitovou grafiku nebo mono zvuky, stoupne Vám rychlost teoreticky až 8x. U 16 bitových 4x, u 32 bitových 2x. MMX samozřejmě nebude vždy tak rychlé, ale určitě bude rychlejší než normální instrukce.

Pro použití MMX v FP musíte použít MMX jednotku. Navíc je nutné v těch částech programu, kde chcete, aby FP vygeneroval MMX instrukce, použít direktivu $MMX+ a na konci daného bloku $MMX- (máte tak jistotu, že víte, kde případně nesmíte použít FPU instrukce). FP použije v takovém případě MMX instrukce vždy tam, kde je velikost dat násobkem 8. FP Vám umožní použít MMX na tyto operace: sčítání (+), odčítání (-), celočíselné násobení (*), a logické XOR, OR a AND. Je nutné podotknout, že DIV zde chybí úplně a NOT je pouze emulované, takže je pomalé! FP také doporučuje, abyste MMX používali pouze tam, kde nevoláte žádnou další proceduru nebo funkci, neboť v tomto případě se totiž ukládají registry FPU (a tedy i MMX), takže to bude velmi pomalé! MMX používá 8 registrů mm# (64 bitů, 0-7), stejně jako FPU má 8 registrů (80 bitů). Registry MMX mohou být děleny na QWORD (8 bytů), DWORDy (2x4 byty), WORDy (4x2 byty) nebo BYTY (8x1 byte).

Free Pascal také podporuje saturaci. To je tzv. kontrola přetečení (výhoda MMX). Pokud např. potřebujete mixovat dva zvuky, tak to se dělá tak, že podle fyzikálních zákonů jejich vlny (tedy každý byte/word) prostě sečtete. Ovšem zde narazíte na to, že pokud sečtete např. 2 BYTY, z nichž jeden má hodnotu 165 a druhý 92, byte Vám přeteče a dostanete pouze 1, což je nesmysl. Dříve se to řešilo takto: buď se autoři programu domluvili s autory zvuků, že hlasitost zvuku nepřekročí určitou hranici. Na to se ale nelze vždy spolehnout, zvláště, pokud píšete nějaký univerzální přehrávač. Pokud přehráváte jen 1 zvuk současně, tak toto neřešíme. Pokud ale děláme hru, kdy je potřeba přehrát např. 2 nebo 4 zvuky současně (viz. Doom), už nás to trápit bude. Pak máme více méně tři řešení (a z nich odvozené):

a) Všechny zvuky po načtení do paměti zeslabíme tolikrát, kolik zvuků současně budeme chtít přehrát (pro 2 současně zeslabíme každý 2x). Zároveň bych měli tolikrát zesílit hlasitost na zvukové kartě. Řešení je dobré v tom, že se nám nestane, že by při namixování i dvou (čtyř, osmi) maximálně hlasitých zvuků došlo k přetečení. Nevýhoda: zvuky budou tišší, ztratí trochu dynamiku (zvláště u 4 až 8 zvuků současně).

b) Druhé řešení je, že počítáme s tím, že zvuky jsou většinou normalizovány na -6 dB, tedy na 50% hlasitost a jen občas vyletí výše. Takže pro 2 zvuky současně nebudeme zlutmovat nic, pro 4 jen 2x, atd. Zde se spoléháme na to, že i kdyby se sešly 2 zvuky současně, je malá pravděpodobnost, že se sejdou 2 vlny se stejným znaménkem, navíc max. hlasité. Ale vyloučit se to nedá a proto i tady hrozí přetečení.

c) Kombinace A+B, popř. ještě s tím, že zvuky nemusíme ztlumovat vůbec (pokud jsou normalizovány), využívá to, že použijeme kontrolu přetečení. Toto je ideální způsob, jak s pomocí B udržet ještě dobrou kvalitu a hlasitost (zvuková karta nemusí umět vždy až 8x zesílit hlasitost - řešení je zesílit ještě výsledný zmixovaný vzorek zase tolikrát, kolikrát jsme zeslabovali zvuky, ať už s kontrolou přetečení (raději ano), či nikoliv, ale to nás zase zpomaluje). Mix dvou bytů či wordů se pak provádí následovně:

const	Max = 32767;
var	A,B,C : integer;
begin
 C := A+B;
 if (longint(A)+longint(B) > C then C := Max else
  if (longint(A)+longint(B) < C then C := -Max;
end;

Nebo lépe a přehledněji s využitím většího rozsahu (můžeme samozřejmě, pokud mixujeme až 4 věci současně ještě u A a B provést SHR 1, pro 8 to bude SHR 2):

const	Max = 32767;
var	A,B,C : integer;
	X : longint;
begin
 X := A+B;
 if X < -Max then X := -Max else
  if X > Max then X := Max;
 C := integer(X);
end;

MMX nás této pohromy zbaví. Dokáže totiž podle typu (znaménko či ne) zajistit, aby při přetečení/podtečení zůstalo v registru buď minimum (0 nebo -MIN) nebo maximum ($ff, $ffff, atd.). Nyní se tedy podíváme na nějaký ten program. Jsou to sice sloučené a upravené příklady z WWW stránek FP, ale vzhledem k tomu, že je v nich obsaženo téměř všechno, myslím, že není na škodu to zde uvést (MMX jednotka definuje pár nových datových typů, které nejsou nic jiného, než ARRAY o celkové velikosti 8 bytů):

uses mmx;
var	d : double;
	a : array[0..10000] of double;
	i : longint;
	audio1 : tmmxinteger;
	audio2 : tmmxinteger;
	j : smallint;
	w3 : tmmxword;
const	helpdata1 : tmmx = ($c000,$c000,$c000,$c000);
	helpdata2 : tmmxword = ($8000,$8000,$8000,$8000);
begin
 d := 1.0;		(* přiřadíme do D nějaké reálné číslo *)
 d := a[0] * 1.777;	(* provedeme nějaké výpočty *)
 if not is_mmx_cpu then	(* pokud nemáme MMX procesor *)
 begin
  for i := 0 to 3 do	(* musíme počítat "ručně" *)
   w3[i] := w1[i] + w2[i];
  Exit;
 end;			(* jinak to necháme na něm *)
{$mmx+}			(* zapneme tedy podporu MMX *)
 w3 := w1 + w2;
			(* teď sice použijeme FPU registry
			   ale neděláme žádné výpočty! *)
 for i:= 0 to 10000 do
  a[i] := d2;		(* jsou použity 64 bitové přesuny *)
{$saturation+}		(* sečteme 2x4 vzorky s kontrolou přetečení *)
 audio1 := audio1+audio2;
{$saturation-}
 for i := 0 to 3 do	(* vydělíme znaménková čísla bez MMX *)
  audio1 := audio1 div 2;
{$mmx-}
 emms;			(* vyčistíme FPU *)
 d := a[0] / 0.6;	(* teď můžeme počítat s reálnými čísly *)
end.

Jednotka MMX obsahuje spoustu typů, např. tmmxbyte, tmmxcardinal, tmmxinteger, tmmxlongint, tmmxshortint, tmmxsingle, tmmxword, kde se může (ale nemusí) uplatnit operace se znaménky. Nevím, zda to byla chyba mého CPU, aktuální verze FP (ale podle vygenerovaného kódu to zřejmě mělo fungovat) či aktuální konstalace Windows, ale jak v tomto příkladu, tak v příkladu na saturaci z WWW stránek FP mi práce se zápornými čísly, kde byla zapnutá saturace, nefungovala dobře (ať už došlo k přetečení či nikoliv). Pokud jsem ji vypnul, tak se vypočetly dobře jen záporná čísla X+Y < Max, jinak žádné záporné číslo (kladná čísla fungují dobře). Snad budete mít více štěstí než já :-) Pokud je chyba v mém CPU (že by? Pentium II?), tak na stránkách FP najdete i příklad, který zvyšuje hlasitost zvuku o 50% pomocí neznaménkových operací na znaménkových datech v neznaménkových typech (!) s využitím MMX, což se Vám může hodit, až budeme někdy příště (po skriptech) probírat zvuky a zvukové karty.

Jednotka MMX ale obsahuje některé dost důležité konstanty (které se nastavují samy při startu), které se hodí třeba i pro využití lepších koprocesorů (např. SSE). Umožní nám totiž zjistit podle bitů CPU, jaké vlastnosti náš procesor umí (pro AMD procesory se nepoužívá instrukce "emms", ale "femms"):

is_mmx_cpu	- zjistí, zda umí MMX
is_sse_cpu	- zjistí, jestli umí SSE
is_sse2_cpu	- nebo dokonce SSE2
is_amd_3d_cpu	- zjistí, zda podporuje 3D Now instrukce, atd.:
is_amd_3d_dsp_cpu
is_amd_3d_mmx_cpu : boolean

A to je pro dnešní díl všechno. Příště se pokusíme podívat na to, jak udělat hru více variabilnější, tedy, nebudeme kódovat chování postav, levelů a prostředí do našeho EXE programu, ale umožníme tvůrcům levelů využívat skripty. A pak už snad konečně ty zvuky. Kdyby Vás zatím zajímalo, jak moc jde urychlit různý kód (hlavně v assembleru), zavítejte opět na mé stránky do sekce programování.

2006-11-30 | Martin Lux
Reklamy: