GRAFIKA V LFB PRO PASCAL
{**********************************************************}
{*                                                        *}
{*           POPIS JAK NASTAVIT LFB                       *}
{*           ======================                       *}
{*    Vytvořil    : BENN                                  *}
{*    E-mail      : benn2@seznam.cz                       *}
{*                                                        *}
{**********************************************************}

Ozývají se hlasy prahnoucích po informacích o LFB. Trochu jsem do LFB zabrousil, tak něco málo prozradím. Nebude to zrovna lehké čtení, budu omílat VESA funkce v chráněném prostředí. Pokusím se minimálně používat assembler a využiju hotových procedur (bude-li nutné, tak je v asm rozepíšu).

Tak od začátku. V realném prostředí se používají 16b registry, což umožňuje adresovat paměť v 64kB blocích. To postihlo i paměť videokarty a 64kB bloky videopaměti jsou označovány jako banky. Takže k přístupu do různých části videopaměti je potřeba banky přepínat. S nástupem 32b registrů se zavedlo 32b prostředí a bloky paměti se zvětšily na 4GB. Takže dostatečná velikost bloku paměti měla za následek vytvoření jediného souvislého banku videopaměti označeného jako LFB (Linear Frame Buffering).

Je jasné, že je nutno pracovat v 32b prostředí. K tomu nám dobře poslouží překladače TMT Pascal nebo FreePascal. U BP lze inicializovat grafiku s LFB, ale bude to k ničemu, protože naadresujeme prvních 64kB videopaměti. BP se může rozloučit s LFB. Dále je nutné mít 32b DPMI a VESA BIOS verze 2.0 a vyšší pro snadné získání grafického režimu s LFB.

Obecný postup k nastavení LFB
==============================
1) Načíst informace o VESA (funkce 4F00h), kde je uložena VELIKOST videopaměti.
2) Načíst informace o požadovaném módu (funkce 4F01h) k získání FYZICKÉ ADRESY začátku videopaměti v chráněném režimu.
3) Namapovat fyzickou adresu videopaměti s příslušnou velikostí (DPMI funkce 800h), výsledkem mapování je LINEÁRNÍ ADRESA. (v TMT již lze přes ní přistupovat).
4) U FPC je potřeba dále lineární adresu upravit vzhledem k bázové adrese datového deskriptoru a také upravit limit tohoto deskriptoru podle velikosti a umístění videopaměti.
5) Nastavit požadovaný mód s LFB.

--------------------------------------------------------------------------------
1) VELIKOST VIDEOPAMĚTI:
--------------------------------------------------------------------------------
K určení velikosti paměti je nejvhodnější použít VESA funkce 4F00h. Je vráceno větší množství informací a jeden parametr určuje počet banků (64kB) videopaměti. Informace jsou vráceny na zadanou adresu ES:DI. Kompletní vrácená struktura informací je následující:

Type  TVESAInfoBlok = Record
      VESASignatura             :Array[1..4] of Char;
      VESAVerze                 :Word;
      OEMStringPtr              :^String;
      Vlastnosti                :Longint;
      VideoModPtr               :Pointer;
      CelkovaPamet              :Word;    {Velikost videopaměti: CelkovaPamet*65536 bytů}
      OEMSWRevision             :Word;
      OEMVendorNamePtr          :^String;
      OEMProductNamePtr         :^String;
      OEMProductRevPtr          :^String;
      Reserved                  :Array[1..222] of Byte;
   End;


Vstup:
  AX    = 4F00h
  ES:DI = segment:ofset, kam chceme informace přenést
  INT 10h
Výstup:
  AX    = 004Fh, úspěch

A zde nastavá problém, protože v 32b prostředí nejsou přímo přístupné funkce DOSu a BIOSu (ti kdo pracují v 32b prostředí to určitě znají). K tomu je ještě nutný 16b segment ES a 16b offset DI, takže základní paměť. Naštěstí tvůrci 32b překladačů předpokládali, že budeme chtít pracovat i s těmito funkcemi, tak přidali funkce a procedury, které nám to ulehčí. Teď je tedy potřeba je nalézt. Především to je funkce, která nám alokuje/dealokuje paměť v základní části paměti a funkce, která vykoná přerušení DOSu a BIOSu (pro nás je důležité INT 10h) jako v reálném režimu. Tyto funkce bývají většinou součástí nějaké knihovny např. DPMI (pro TMT) nebo Go32 (pro FPC).

1.1) Alokace zakladní paměti
----------------------------
Funkce pro alokaci základní paměti jsou většinou označeny jako DosMemoryAlloc (pro TMT), GlobalDosAlloc (pro BP) nebo Global_Dos_Alloc (pro FPC), nebo podobné jména. Pro všechny překladače je neznám, smůla. Vstupním parametrem je velikost požadované paměti (pro nás stačí 256B) a výstupem je vždy segment k paměti a někdy může být navíc vrácen i selektor. Selektor nás moc nezajímá, někdy bývá potřebný při uvolnění paměti (to je případ u FPC). Důležitý je segment, takže víme co do ES. Ofset je vždy nulový, DI=0. (Pro assembleristy: Při alokaci je využita DPMI funkce 100h, podrobnosti viz např. ATHelp).

Př.1(TMT Pascal):
****************
Uses DPMI;
Var  DosSegment  :Word;
..........
  DosSegment:=DosMemoryAlloc(256);
..........

Př.1(FPC):
*********
Uses Go32;
Var DosAddr     :Longint;
    DosSegment  :Word;
    DosSelector :Word;
..........
  DosAddr:=Global_Dos_Alloc(256);
  DosSegment := Word(DosAddr shr 16);
  DosSelector:= Word(DosAddr);
..........


1.2) Real-Mode přerušení
------------------------
Už víme kam chceme informace přenést, nyní potřebujeme vykonat INT 10h s AX=4F00h. Přímo to nejde, ale jde to přes DPMI funkci 300h. V TMT je označena jako RealModeInt. Pro FPC RealIntr. Vstupem je číslo přerušení, které chceme vykonat (my chceme 10h) a hodnoty registrů jaké mají být během přerušení. Hodnoty registrů se nezapisují přímo do registrů procesoru, ale ukládají se do datové struktury, která je společně definovaná s funkcí RealModeInt. U TMT je struktura označena jako TRMRegs (u FPC TRealRegs), takže stačí definovat proměnou třeba RMReg:TRMRegs(TRealRegs). Navíc bývá definována i procedura, která nám tyto registry vynuluje (v TMT - ClearRMRegs). Nulování je důležité, protože nepotřebné registry mohou způsobovat chyby.

Př.2(TMT Pascal):
****************
Uses DPMI;
Var  RMReg  :TRMRegs;
..........
  ClearRMRegs(RMReg);
  RMReg.AX:=$4F00;
  RMReg.ES:=DosSegment   {viz Př.1}
  RMReg.DI:=0
  RealModeInt($10,RMReg);
..........

Př.2(FPC):
****************
Uses DPMI;
Var  RMReg  :TRealRegs;
..........
  FillChar(RMReg,SizeOf(RMReg),0);
  RMReg.AX:=$4F00;
  RMReg.ES:=DosSegment   {viz Př.1}
  RMReg.DI:=0
  RealIntr($10,RMReg);
..........

Pro úplnost dodávám, že funkce RealModeInt (RealIntr) vraci hodnotu True (bez chyby) nebo False (chyba). V příkladu to nekontroluji, abych to nekomplikoval (problémy by zde neměli nastat). Potřeba je kontrolovat registr RMReg.AX, protože určuje úspěšnost či neúspěšnost VESA funkce 4F00h. Je-li RMReg.AX=$004F, tak proběhlo vše v pořádku a informace jsou v naší paměti. Pokud je tam jiná hodnota, tak v paměti jsou vráceny nesmysly, což taky znamená, že VESA není přítomna.

Nyní stačí vylovit z naší alokované paměti, kde jsou informace o VESA, kolik té videopaměti máme. Jak bylo uvedeno, tak parametrů je mnoho a zatím chybí struktura, která by to rozlišila. Možností je mnoho. Jednou možností je, že definujem proměnou s typem odpovídající struktuře VESA informací (zde označeno jako TVESAInfoBlok) a do této proměné zkopírujem získané data.

Př.3(TMT Pascal):
****************
..........
Var VESAInfo       :TVESAInfoBlok;
    VelikostVideoRAM :Longint;
..........
  Move(Mem[DosSegment*16],VESAInfo,SizeOf(VESAInfo));
  VelikostVideoRAM := VESAInfo.CelkovaPamet*65536;  {Prepocet na byty}
..........


Př.3(FPC):
****************
..........
Var VESAInfo       :TVESAInfoBlok;
    VelikostVideoRAM :Longint;
..........
  DosMemGet(DosSegment,00,VESAInfo,SizeOf(VESAInfo));
  VelikostVideoRAM := VESAInfo.CelkovaPamet*65536;  {Prepocet na byty}
..........

Je to trochu komplikované dostat číselnou hodnotu velikosti videopaměti, ale co bysme neudělali pro správnou funkci programu.

--------------------------------------------------------------------------------
2) FYZICKÁ ADRESA VIDEOPAMĚTI:
--------------------------------------------------------------------------------
Postup je obdobný jako při zjišťování velikosti videopaměti s tím rozdílem, že se budou používat jiné proměnné a jiné číselné hodnoty funkcí. Zde k získání počátku fyzické adresy videopaměti využijeme VESA funkci 4F01h, která nám vrací informace o video módu. Navíc je také nutné zadat pro jaký grafický mód se to bude týkat. Informace jsou vráceny na adresu ES:DI se strukturou informací:

Type  TVESAInfoMode = Record
      Atributy                  :Word;
      AtribA,AtribB             :Byte;
      Granularita               :Word;   {KB}
      VelikostOkna              :Word;   {KB}
      PocSegmentA,PocSegmentB   :Word;
      FARAdresa                 :Pointer;
      ScanovyRadek              :Word;   {Pixely}
      Sirka,Vyska               :Word;   {Pixely}
      SirkaZnaku,VyskaZnaku     :Byte;   {Pixely}
      PocetRovin                :Byte;
      BityNaPixel               :Byte;
      PocetBanku                :Byte;
      TypPametovehoModelu       :Byte;
      VelikostBanku             :Byte;   {KB}
      PocetObrazovychStranek    :Byte;
      Rezerv                    :Byte;
      VelMaskyR,PoziceMaskyR    :Byte;
      VelMaskyG,PoziceMaskyG    :Byte;
      VelMaskyB,PoziceMaskyB    :Byte;
      VelMaskyRez,PoziceMaskyRez:Byte;
      DirectScreen              :Byte;
      FyzickaAdresa             :Longint; {!!Hledáno!!}
      Zbytek                    :Array[1..256-41] of Byte;
   End;

Vstup:
  AX    = 4F01h
  CX    = požadovaný mód
  ES:DI = segment:ofset, kam chceme informace přenést
  INT 10h
Výstup:
  AX    = 004Fh, úspěch

Jako u zjišťování velkosti videopaměti, tak i v tomto případě se informace vracejí do základní paměti a pro vykonání přerušení opět použít DPMI funkce 300h. Alokovat základní paměť nemusíme to už máme, takže ES a DI známe. Nyní stačí vykonat přerušení INT 10h přes DMPI s příslušnými hodnotami registrů:

Př.4(TMT Pascal):
****************
..........
  ClearRMRegs(RMReg);
  RMReg.AX:=$4F01;
  RMReg.CX:=$103         {třeba 800x600x256, požadovaný mód}
  RMReg.ES:=DosSegment   {viz Př.1}
  RMReg.DI:=0
  RealModeInt($10,RMReg);
..........


Př.4(FPC):
****************
..........
  FillChar(RMReg,SizeOf(RMReg),0);
  RMReg.AX:=$4F01;
  RMReg.CX:=$103         {třeba 800x600x256, požadovaný mód}
  RMReg.ES:=DosSegment   {viz Př.1}
  RMReg.DI:=0
  RealIntr($10,RMReg);
..........
Původní informace v alokované základní paměti se přepíší. Opět zkontrolujeme, zda je RMReg.AX=$004F a není-li, tak toto rozlišení není podporováno. Jako v předešlém případě vytáhneme hledaný parametr (teď to bude FyzickaAdresa).
Př.5(TMT Pascal):
****************
..........
Var ModeInfo            :TVESAInfoMode;
    FyzickaAdresaVideoRAM :Longint;
..........
  Move(Mem[DosSegment*16],ModeInfo,SizeOf(ModeInfo));
  FyzickaAdresaVideoRAM := ModeInfo.FyzickaAdresa;
..........


Př.5(FPC):
****************
..........
Var ModeInfo            :TVESAInfoMode;
    FyzickaAdresaVideoRAM :Longint;
..........
  DosMemGet(DosSegment,00,ModeInfo,SizeOf(ModeInfo));
  FyzickaAdresaVideoRAM := ModeInfo.FyzickaAdresa;
..........

Nyní již alokovanou paměť v základní části nepotřebujeme a můžeme ji uvolnit funkcemi DosMemoryFree (pro TMT), GlobalDosFree (pro BP), Global_Dos_Free (pro FPC) atd. Velmi důležité je také zkontrolovat hodnotu fyzické adresy. Pokud se nám vrátila hodnta fyzické adresy rovna 0, tak to jsme skončili (GAME OVER). Né všechny podporované režimy VESY umožňují pracovat s LFB, většinou se jedná o režimy s 16ti barvami.

--------------------------------------------------------------------------------
3) MAPOVÁNÍ VIDEOPAMĚTI:
--------------------------------------------------------------------------------
Známe-li fyzickou adresu začátku videopaměti a jeho velikost, tak tuhle paměť můžeme namapovat pomocí DPMI funkce 800h. Výsledkem mapování je lineární adresa, přes kterou můžeme zapisovat nebo číst. V TMT Pascalu je funkce pro mapování označena jako MapPhysicalToLinear (u FPC Get_Linear_Addr). Vstupem je fyzická adresa a velikost paměti a výstupem je lineární adresa.

Př.6(TMT Pascal):
****************
..........
Var LinearniAdresaVideoRAM:Longint;
..........
   LinearniAdresaVideoRAM:=MapPhysicalToLinear(FyzickaAdresaVideoRAM,VelikostVideoRAM);
..........

Př.6(FPC):
****************
..........
Var LinearniAdresaVideoRAM:Longint;
..........
   LinearniAdresaVideoRAM:=Get_Linear_Addr(FyzickaAdresaVideoRAM,VelikostVideoRAM);
..........

--------------------------------------------------------------------------------
4) ÚPRAVA LINEÁRNÍ ADRESY A LIMITU DATOVÉHO DESKRIPTORU (Plati pouze pro FPC):
--------------------------------------------------------------------------------
Aby šlo adresovat videopaměť v FPC je nutné provést ještě 2 věci. První věcí je upravit namapovanou Lineární adresu podle nastaveného datového deskritoru. Jde o to, že při jakékoliv adresaci se k naší adrese automaticky přičte i bázová adresa LDT deskriptou pro DS a jsme mimo videopaměť. Takže stačí od lineární adresy odečíst tuto bázovou adresu. Druhou věcí je zvětšení limitu datového deskriptoru, protože videpaměť může být uložena hodně "vysoko" (klidně to může odpovídat adrese, kde leží 1GB). Takže zvětšíme limit na lineární adresa + velikost videopaměti a zahrneme tím videopaměť do našich dat. TMT Pascal to má odpočátku nastaveno na maximum 4GB (dokonce chybí i funkce, které by to zjišťovaly - byly by zbytečny). U FPC je funkce pro zjištění bázové adresy označena jako Get_Segment_Base_Address, kde vstupem je selector. Datový selector je uložen v proměnné Get_DS (nebo v registru DS). Funkce pro nastavení limitu je v FPC pod jménem Set_Segment_Limit se vstupem selector a rozsahem, Get_Segment_Limit - zjišťování limitu. Úprava je tedy následující:

Př.7(FPC):
****************
..........
Var LFBPointer:Pointer;
..........
  LFBPointer:=Pointer(LinearniAdresaVideoRAM-Get_Segment_Base_Address(Get_DS));

   {Je-li limit menší, tak znovu nastavit}
  If DWord(LFBPointer)+VelikostVideoRAM-1 > DWord(Get_Segment_Limit(Get_DS)) then
       Set_Segment_Limit(Get_DS,DWord(LFBPointer)+VelikostVideoRAM-1);
..........

Další možností je vytvořit si vlastní datový deskriptor (funkce Allocate_LDT_Descriptors) a vyplnit bázovou adresu lineární adresou videopaměti a s limitem odpovídajícím velikosti videopaměti. Při adresaci je nutno vždy pamatovat na jiný datový selector mezi daty v deklaraci var a naší videopamětí.
--------------------------------------------------------------------------------
5) NASTAVENÍ GRAFICKÉHO REŽIMU S LFB:
--------------------------------------------------------------------------------
Poslední operací je nastavit grafický režim. K nastavení nám poslouží VESA funkce 4F02h. Přehled grafických módů je následující:

 Módy s 16 barvami:
 *******************
   102h : 800x600
   104h : 1024x768
   106h : 1280x1024

 Módy s 256 barvami:
 *******************
   12Eh : 320x200
   131h : 320x240
   141h : 400x300
   151h : 512x384
   100h : 640x400
   101h : 640x480
   103h : 800x600
   105h : 1024x768
   161h : 1152x864
   107h : 1280x1024
   120h : 1600x1200

 Módy s 32k barvami:
 *******************
   10Dh : 320x200
   132h : 320x240
   142h : 400x300
   152h : 512x384
   11Ch : 640x400
   110h : 640x480
   113h : 800x600
   116h : 1024x768
   162h : 1152x864
   119h : 1280x1024
   121h : 1600x1200

 Módy s 64k barvami:
 *******************
   10Eh : 320x200
   133h : 320x240
   143h : 400x300
   153h : 512x384
   11Dh : 640x400
   111h : 640x480
   114h : 800x600
   117h : 1024x768
   163h : 1152x864
   11Ah : 1280x1024
   122h : 1600x1200

 Módy s 16M barvami:
 *******************
   10Fh : 320x200
   134h : 320x240
   144h : 400x300
   154h : 512x384
   11Eh : 640x400
   112h : 640x480
   115h : 800x600
   118h : 1024x768
   164h : 1152x864

Tak to by byl drobný přehled grafických módů a záleží na grafické kartě, jestli jsou všechny podporovány. Tyto módy nastaví požadovaný grafický režim, ale nebude v LFB (zase bank switching). LFB se nastavuje 14. bitem, to znamená, že číslo módu stačí orovat hodnotou 4000h. Pokud chceme třeba rozlišení 800x600 s 256 barvami v LFB, tak číslu módu odpovídá hodnota 4103h.

Př.8A(TMT Pascal):
****************
..........
Const  PozadovanyMod:Word = $103; {800x600x256, požadovaný mód}
..........
  ClearRMRegs(RMReg);
  RMReg.AX:=$4F02;
  RMReg.BX:=PozadovanyMod OR $4000;
  RealModeInt($10,RMReg);
..........

Př.8A(FPC):
****************
..........
Const  PozadovanyMod:Word = $103; {800x600x256, požadovaný mód}
..........
  FillChar(RMReg,SizeOf(RMReg),0);
  RMReg.AX:=$4F02;
  RMReg.BX:=PozadovanyMod OR $4000;
  RealIntr($10,RMReg);
..........


 Nebo po assemblerovsku (v tomto případě můžeme Int 10h vykonat přímo):

Př.8B(TMT Pascal):
****************
..........
Const  PozadovanyMod:Word = $103; {800x600x256, požadovaný mód}
..........
  Asm
   MOV AX,4F02h
   MOV BX,PozadovanyMod
   OR  BX,4000h
   INT 10h
  End;
..........


Př.8B(FPC):
****************
{$ASMMODE Intel}
..........
Const  PozadovanyMod:Word = $103; {800x600x256, požadovaný mód}
..........
  Asm
   MOV AX,4F02h
   MOV BX,PozadovanyMod
   OR  BX,4000h
   INT 10h
  End;
..........

Na závěr zkontrolujeme, zda v registru AX (RMReg.AX) je hodnota 004Fh a vše proběhlo v pořádku. Pokud tam je jiná hodnota, tak máme smůlu a s tímto režimem se můžem rozloučit.

V tuto chvíli je vše připraveno k zápisu do videopaměti. Jak bylo řečeno stačí nám k tomu lieární adresa videopaměti. Už je jenom na nás jak s touto adresou naložíme. Například budem mít rozlišení 800x600 s 256 barvami a přímo adresovat videopaměť. Adresa bodu se v tomto případě určí jako LinearniAdresaVideoRAM + Y*800 + X (FPC: LFBPointer + Y*800 + X).

Př.9A(TMT Pascal):
****************
..........
Const  X = 100;
       Y = 100;
..........
  Byte(Pointer(LinearniAdresaVideoRAM + Y*800 + X)^):=White;
  { nebo Mem[LinearniAdresaVideoRAM + Y*800 + X]:=White}
..........
  Asm
   MOV EDI,LinearniAdresaVideoRAM
   MOV AL,Red

   MOV [EDI+500*800+500],AL  {Bod na pozici 500,500}
  End;
..........


Př.9A(FPC):
****************
..........
Const  X = 100;
       Y = 100;
..........
  pByte(LFBPointer + Y*800 + X)^:=White;
..........
  Asm
   MOV EDI,LFBPointer
   MOV AL,Red

   MOV [EDI+500*800+500],AL  {Bod na pozici 500,500}
  End;
..........

Nebo lineární adresu zapasovat do struktury (například do pole) a tím nám odpadne výpočet adresy ve videopaměti. Jednoduchý příklad pro rozlišení 800x600 a 256 barev.

Př.10B(TMT Pascal):
****************
..........
Type   TPole = Array[0..599,0..799] of Byte;
Var    Pole  :^TPole;
Const  X = 100;
       Y = 100;
..........
  Pole:=Pointer(LinearniAdresaVideoRAM);
  Pole^[X,Y]:=White;
  Pole^[500,500]:=Red;
..........


Př.10B(FPC):
****************
..........
Type   TPole = Array[0..599,0..799] of Byte;
Var    Pole  :^TPole;
Const  X = 100;
       Y = 100;
..........
  Pole:=LFBPointer;
  Pole^[X,Y]:=White;
  Pole^[500,500]:=Red;
..........

Další možností je vytvořit si vlastní LDT deskriptor, vyplnit jej lineární adresou a limitem (velikostí videopaměti). Potom se s tím lépe pracuje, hlavně v assembleru. Bez příkladu.

Celkově to vypadá následovně:

viz soubory:
FPC_LFB.pas
TMT_LFB.pas


P.S.: Zná někdo Hardware Triple Buffering !!!???