Práce s grafikou 640x480@16bit v Pascalu. (2/4)
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu
Reklama

Práce s grafikou 640x480@16bit v Pascalu. (2/4)Práce s grafikou 640x480@16bit v Pascalu. (2/4)

 

Práce s grafikou 640x480@16bit v Pascalu. (2/4)

Google       Google       3. 2. 2006       9 685×

Popis grafické unity pro práci v grafickém módu 111h, (640x480x16bit) využívající virtuální obrazovku v XMS paměti. Obsahuje procedury pro práci s XMS, kreslení základních geometrických tvarů, grafický OutText a načítání obrázků ze souborů PCX (256barev).
Část 2. Detekce přítomnosti XMS a práce s virtuální obrazovkou v XMS.

Reklama
Reklama

V minulém díle jsme si ukázali jak zapnout grafický režim 111h (640x480x16bit), umístit bod na obrazovku a vykreslit základní geometrické tvary. Při snaze obrazovku trochu rozpohybovat se ale stává, že vykreslování není moc rychlé a obraz problikává. Tento problém se řeší tzv. virtuální obrazovkou, kdy je obraz vykreslován nejdříve do RAM paměti a po jeho úplném vykreslení se rychle přenese do paměti grafické karty.

Při rozlišení 640x480 se 16bitovými barvami je pro takovou virtuální obrazovku potřeba 600kB paměti. Vzhledem k tomu že v dosu je přímo adresovatelných pouze 640kB (a z toho bývá asi jen polovina volná) musíme pro naši virtuální obrazovku využít XMS nebo EMS paměť.

V klasickém Dosu (Windows 95,98, ...) je pro použití XMS či EMS nutné mít nainstalován (spuštěn souborem konfig.sys) některý z ovladačů rozšířené paměti. Nejznámější ovladač je Himem.sys od Microsoftu. Ve Windows XP je paměť XMS a EMS emulována automaticky, musí být ale pro každý program povolena. (Ve vlastnostech programu klikněte na kartu paměť, a zvolte automatické přidělování všech pamětí.)

Jako první ze standardů rozšířené paměti jsem pro virtuální obrazovku vyzkoušel EMS, protože tato paměť je použitelná přes standardní pascalovskou jednotku Object. Při testech této virtuální obrazovky jsem ale s hrůzou zjistil že maximální dosažitelná obnovovací frekvence je zhruba 15fps, což bylo ještě horší, než vykreslovat přímo do paměti grafické karty. Obsluha paměti XMS je na první pohled o něco složitější, rychlost je ale o poznání lepší než u EMS.

Práce s XMS

K tomu abychom zjistily zda vůbec máme v počítači nainstalován ovladač XMS nám slouží multiplexní přerušení DOSu 2Fh.


function detekujXms:word;
{ Vrati 0, pokud je ovladac XMS nainstalovany.}
{ zmeni global. prom. xmsAdr, xmsVersion, ovlXmsVersion a hmaDetect}
var vrat:word;
Begin
  asm
    mov vrat,1    { predpokladame chybu }
    Mov ax,4300h  { sluzba 4300h - XMS, zjisti pritomnost XMS}
    Int 2Fh       { multiplexni preruseni (preruseni nainstalovanych ovladacu)}
    Cmp al,80h    { (pokud mame v al 80h je XMS) }
    Jnz @konec    { ...XMS neni, tak skocime na konec... }
    mov vrat,0    { XMS je!!! }

Samotné ovládání funkcí XMS probíhá voláním vzdáleného programu (ovladače). Adresu pro jeho volání zjistíme vykonáním služby 4310h.


    mov ax,4310h  { sluzba 4310h, vrat adresu obsluzneho programu }
    int 2Fh
    mov word ptr [xmsadr],bx    { do xmsAdr si dame adresu kde mame ovladac ridit }
    mov word ptr [xmsadr+2],es

    mov ah,00h           {vrat stav XMS}
    call [xmsadr]
    mov [xmsVersion],ax    { verze xms v BCD }
    mov [ovlXmsVersion],bx { verze ovladace. Opet v BCD }
    mov [hmaDetect],dx     { true/false }

   @konec:
  end;
  detekujXms:=vrat
end;

Adresu volání ovladače XMS umístí funkce detekujXMS do proměnné XMSAdr. Tato proměnná se nesmí po dobu práce s XMS měnit, jinak dojde k pádu DOSu! (Při pokusu o použití XMS.)

Když již víme že máme XMS a známe adresu vzdálené funkce kterou jí ovládáme, musíme si zjistit kolik kB si můžeme v paměti alokovat.


procedure getSizeXms(var celkem,blok:word);
{ V celkem vraci celkovou (volnou?) velikost Xms a v blok kolik si maximalne muzeme alokovat pro 1 blok. }
{ Vetsinou jsou obe velikosti shodne. }
var celk,blk:word;
Begin
  asm
    mov ah,08h  {zjisti velikost XMS}
    call [xmsadr]
    mov [chyba],bl    { chyba? }
    mov [celk],dx
    mov [blk],dx
  end;
  celkem:=celk;
  blok:=blk;
end;

Pokud máme v jednom bloku k dispozici dostatek volného místa, můžeme si blok paměti alokovat.


function alokujXms(kolik:word):word;
{ Kolik je v Kb. Vraci handle (rukojet) alokovaneho bloku. }
var vrat:word;
Begin
  asm
    mov ah,09h          {alokuj XMS}
    mov dx,[kolik]
    call [xmsadr]
    mov [vrat],dx
    mov [chyba],bl      { chyba? }
  end;
  alokujXms:=vrat;
end;

Pokud se po provedení funkce proměnná chyba nerovná 0, tak se alokace bloku nezdařila. (Vis. funkce errorXMS) Pokud se alokace bloku zdařila, vrací funkce alokujXMS rukojeť právě alokovaného bloku. Ta se používá při přesunech mezi konvenční pamětí a XMS a při uvolnění bloku. Každý alokovaný blok musí být před ukončením programu uvolněn, jinak zůstane až do restartu systému nepoužitelný pro jiné aplikace. (Ve Windows XP se paměť uvolní při ukončení příkazové řádky.)


procedure uvolniXms(handle:word);
{ Vsechny bloky, ktere jsme si alokovali bychom meli uvolni. }
{ Jinak budou az do restartu systemu nepouzitelne pro jine aplikace. }
Begin
  asm
    mov ah,0ah
    mov dx,[handle]
    call [xmsadr]
    mov [chyba],bl    { chyba? }
  end;
end;

Do XMS se nedá zapisovat ani číst běžným způsobem. K zápisu či čtení z XMS musíte mít v konvenční paměti alokován buffer (jakoukoliv proměnnou či pole) do kterého se data kopírují. Kolik bajtů, odkud a kam chceme data kopírovat musíme zapsat na nějaké místo v paměti (do proměnné) a ovladači XMS tento záznam předat. Ten pak již požadovaný přesun dat vykoná. K tomuto účelu jsem definoval záznam TstrukturaPresunu do kterého se uloží požadavek o přesun (kopírování) a ten je poté předán ovladači realizující vlastní přesun.


Type
 TstrukturaPresunu=record  {Temito daty ridim ovladac XMS,
                            kdyz chci presunout blok pameti}
   velikost  : longint;    {kolik budeme kopirovat}
   zdRukojet : word;    {pokud zdroj ci cil je v konvencni pameti
                                je: rukojet=0}
   zdOffset  : longint; {       offset ((segment shl 16) and offset)}
   clRukojet : word;    {pokud zdroj ci cil je v XMS je:
                                rukojet=rukojet vracena pri alokaci}
   clOffset  : longint; {       offset=pocatek kopirovanych dat }
 end;

var defPresunu:TstrukturaPresunu;

Do záznamu velikost vložíme kolik bajtů budeme chtít přesunout. ZdRukojet a zdOffset udávají odkud mají být data kopírována a clRukojet a clOffset udávají kam mají být data kopírována. Pokud má být zdroj nebo cíl kopírování v konvenční paměti, vložte do (zd/cl) rukojet 0 a do (zd/cl) offset vložte pár segment:offset bufferu v konvenční paměti. (segment horní 2 byty a offset dolní 2 byty)

Pro snažší pochopení příklad: Mám definovanou proměnnou buffer jako array[0..127] of byte a obsah tohoto pole chci zkopírovat do bloku v XMS který jsem si předtím alokoval a jeho rukojeť mám v handle.


{  var buffer:array[0..127]of byte; }
{        bufferAdr:longint;         }

    BufferAdr:=seg(Buffer);
    BufferAdr:=BufferAdr shl 16;
    BufferAdr:=BufferAdr or ofs(Buffer);

    defPresunu.velikost  := SizeOf(Buffer); {zkopirujeme cely buffer}
    defPresunu.zdRukojet := 0;         {zdrojovy buffer je v konvencni pameti}
    defPresunu.zdOffset  := BufferAdr;
    defPresunu.clRukojet := handle;    {rukojet bloku dat v XMS}
    defPresunu.clOffset  := 0;      {kam do bloku v XMS budeme data kopirovat}

    presunXMS( defPresunu );

Konečně procedura obstarávající přesun (spíš jeho zprostředkování ovladačem):


procedure presunXms( defPresunu:TstrukturaPresunu );assembler;
{ Pokud mame v ax 1h je vse OK, pokud ne mame v ax cislo chyby vis. errorXMS }
asm
 push ds
 mov ah,0bh
 xor bl,bl
 lds si,defPresunu
 call [xmsadr]
 pop ds
end;

Pokud po vykonání této procedury máme v registru ax hodnotu 1h, tak je všechno OK a přesun dat proběhl úspěšně. Pokud ne, tak v ax je uložen kód chyby XMS. Popis chyby získáme voláním funkce errorXMS:


function errorXms(chyba:word):string;
{ vraci textovy popis chyby XMS }
begin
 case chyba of
    $80 : errorXms:='funkce neni implementovana';
    $81 : errorXms:='zjisteno zarizeni VDISK';
    $82 : errorXms:='nastala chyba A20';
    $8e : errorXms:='obecná chyba ovladace';
    $8f : errorXms:='nezotavitelna chyba ovladace';
    $90 : errorXms:='HMA neexistuje';
    $91 : errorXms:='HMA je jiz pouzivana';
    $92 : errorXms:='parametr DX je mensi nez HMAMIN';
    $93 : errorXms:='HMA neni pridelena';
    $94 : errorXms:='hardware A20 je dosud zapnuto';
    $a0 : errorXms:='vsechna rozsirena pamet je alokovana';
    $a1 : errorXms:='vsechny rukojeti rozsirene pameti jsou jiz pouzivany';
    $a2 : errorXms:='rukojet je neplatna';
    $a3 : errorXms:='zdrojova rukojet je neplatna';
    $a4 : errorXms:='zdrojovy offset je neplatny';
    $a5 : errorXms:='cilova rukojet je neplatna';
    $a6 : errorXms:='zdrojovy offset je neplatny';
    $a7 : errorXms:='delka je neplatna';
    $a8 : errorXms:='presunovane oblasti maji nepovoleny presah';
    $a9 : errorXms:='nastala chyba parity';
    $aa : errorXms:='blok neni uzamcen';
    $ab : errorXms:='blok je uzamcen';
    $ac : errorXms:='preteceni maximalniho poctu zamku bloku';
    $ad : errorXms:='uzamceni neuspesne';
    $b0 : errorXms:='je-li k dispozici mensi blok horni pameti (UMB)';
    $b1 : errorXms:='nejsou dalsi bloky horni pameti';
    $b2 : errorXms:='cislo segmentu UMB je neplatne';
 else
    errorXms:='nedokumentovana chyba XMS';
 end;
end;

Jak jsem ale zjistil, ovladač XMS (Win. XP) občas vrátí chybu ($a4) jen tak i přes to, že přesun dat vykoná správně. Zákonitost tohoto chování jsem neodhalil, proto ve svých programech chybu po přesunu dat netestuji.

Ovladač rozšířené paměti nám toho nabízí mnohem více, (vis. Sysman.) popsané funkce nám ale pro práci s virtuální obrazovkou a i pro spoustu dalších operací bohatě postačí.

Virtuální obrazovka

Pro virtuální obrazovku v módu 111h by sice stačilo "pouze" 600kB (640*480*2 = 600*1024), při přesunech by se ale musela dělat zvláštní podmínka pro desátý bank videopaměti který není v módu 111h využit celý. (Jeden bank videopaměti má totiž 64kB. => 600 / 64 = 9,4) To by prodloužilo zápis funkcí a nepatrně i dobu vykonávání. Proto je pro virtuální obrazovku alokováno v XMS 640kB. (Celých 10 banků videopaměti) Alokování bloku XMS pro virtuální obrazovku a bufferu pro přesuny je v grafické unitě gr16b obstaráno ve funkci init.

Velikost bufferu pro přesuny jsem zvolil na 32kB. Tak je celá obrazovka pomyslně rozdělena na 20 částí, z kterých je vždy jedna zrcadlena v bufferu. Pokud vykreslujeme pixely do virtuální obrazovky na místo které je zrcadleno v bufferu je pixel umístěn do tohoto bufferu. Pokud se pixel nachází v jiné části (než která je zrcadlena) je buffer zkopírován do XMS (na místo které zrcadlil) a zpět do bufferu je vložena část kam má být umístěn pixel. Může to znít trochu složitě, je to ale podobné jako přepínaní banků videopaměti. Číslo části (od shora dolů) která je zrcadlena v bufferu je v proměnné zrcadlenaCast.

Procedura pro umístění pixelu do virtuální obrazovky:


procedure Pixel(x,y:longint;barva:word);
var cast,off:longint;
Begin
  if (x<0) or (y<0) or (x>639) or (y>479) then exit;

  x:=x shl 1;
  cast:=(y*radek+x) shr 15;

  off:=y*1280;
  off:=off+x;
  off:=off-(Cast shl 15);

  if zrcadlenaCast<>Cast then Begin
    defPresunu.velikost  := 32768;
    defPresunu.zdRukojet := 0;
    defPresunu.zdOffset  := BufferAdr;
    defPresunu.clRukojet := handle;
    defPresunu.clOffset  := zrcadlenaCast shl 15;

    presunXms( defPresunu );

    defPresunu.velikost  := 32768;
    defPresunu.zdRukojet := handle;
    defPresunu.zdOffset  := Cast shl 15;
    defPresunu.clRukojet := 0;
    defPresunu.clOffset  := BufferAdr;

    presunXms( defPresunu );
  end;

  zrcadlenaCast:=Cast;

  memW[ seg(Buffer^) : ofs(Buffer^)+off ] :=barva;
end;

Přesun virtuální obrazovky z XMS do paměti grafické paměti.

Jak již bylo řečeno, v grafickém módu 111h je ve videopaměti využito (necelých) deset banků (64kB velkých). Data se musí kopírovat do těchto banků postupně, protože se vždy musí nastavit určitý bank, který je pak na adrese videopaměti viditelný. Před kopírováním virtuální obrazovky do paměti grafické karty se ještě musí do XMS zkopírovat část zrcadlená v bufferu, a na chvíli zakázat zobrazování ukazatel myši (o té v příštím díle).


procedure presunVirtObr;
var bank:longint;
    wbank:word;
    bylaMys:boolean;
    ukazatel:pointer;
Begin
  mys.jeZobrazena:=false; {protoze ji ted prekreslime}
  bylaMys:=jeMys;
  jeMys:=false;           {aby nam preruseni mysi neprepinalo banky}
  { nejdrive si presunu prave zrcadlenou cast (buffer) do xms}
    defPresunu.velikost  := 32768;
    defPresunu.zdRukojet := 0;
    defPresunu.zdOffset  := BufferAdr;
    defPresunu.clRukojet := handle;
    defPresunu.clOffset  := zrcadlenaCast shl 15;

    presunXMS(defpresunu);

  { a ted jdu rvat virt obr z xms do vram...}
  bank:=0;
  repeat

    wbank:=bank;
    asm
       xor bx, bx
       mov dx, [wBANK]  { Nastav Bank }
       mov ax, 4f05h   { Vesa funkce pro praci s okny }
       int 10h
    end;

    defPresunu.velikost:=65536;
    defPresunu.zdRukojet:=handle;
    defPresunu.zdOffset:=bank shl 16;
    defPresunu.clRukojet:=0;
    defPresunu.clOffset:= $A000 shl 16;

    presunXMS(defpresunu);

    bank:=bank+1;
  until bank=10;

  {jdem znova vykreslit mysicku....}
  predchozi:=bank;
  asm
    mov ax,$3
    int $33
    mov [akx],cx
    mov [aky],dx
  end;

  jeMys:=bylaMys;
  if jeMys then zobrazMysProc;
end;

Aby se stoprocentně zabránilo blikání obrazu, měl by před přesunem dat do VRAM být kód čekající na návrat vykreslovacího paprsku monitoru do horní části obrazovky.

Kód čekající na návrat paprsku:


  asm
    mov dx,03dah
    @@1: in al, dx
    test al,8
    jz @@1
  end;

Při testech jsem ale zjistil, že procedura presunVirtObr přesouvá data natolik rychle, že problikávání je nepostřehnutelné. Navíc čekání na paprsek zbrzdí běh procedury natolik že maximální dosažitelná obnovovací frekvence je asi 60fps. Bez tohoto kódu se dá dosáhnout až 80fps.

V příštím díle něco o hlodavcích a jak s nimi pracovat v vyšších grafických módech.


×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

3 názory  —  3 nové  
Hlasování bylo ukončeno    
0 hlasů
Google
Autor se zajímá o grafiku v Pascalu, také programuje v Pascalu, Delphi, PHP a Assembleru.

Nové články

Obrázek ke článku Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres HCPP16 pořádá od 30. září do 2. října nezisková organizace Paralelní Polis již potřetí, a to ve stejnojmenném bitcoinovém prostoru v pražských Holešovicích. Letos přiveze na třídenní konferenci přes 40 většinou zahraničních speakerů – lídrů z oblastí technologií, decentralizované ekonomiky, politických umění a aktivismu. Náměty jejich přednášek budou také hacking, kryptoměny, věda, svoboda nebo kryptoanarchie.

Reklama
Reklama
Obrázek ke článku ICT PRO školení zaměřené nejenom na ICT

ICT PRO školení zaměřené nejenom na ICT

Dovolte, abychom se představili. Jsme zaměstnanci společnosti ICT Pro, profesionálové v oblasti poskytování komplexních ICT služeb. Neboli služeb spojených s informačními a komunikačními technologiemi, které dnes - ve 21. století - tvoří  nedílnou součást běžného provozu všech moderních firem.

loadingtransparent (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })();
Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý