int21h
Efektivní nastavování VESA videorežimů
Na toto téma se v časopise určitě sejde více článků, ale já bych se chtěl zaměřit na dva aspekty, které programátoři v pascalu většinou příliš neřeší.
A to aktivní vyhledávání videomódů a nastavování obnovovací frekvence monitoru.
1)aktivní vyhledávání:
Videomódy VESA zavádí pomocí funkce INT 10/AX=4F02h
Tedy něco takového:
MOV AX,4f02h
MOV BX,videorezim
INT 10h
Jednoduché. Problém je, co zadat jako videorežim. Normy VESA v1.0 a v1.2 definovaly závazné kódy videorežimů. Např. 101h=640x480 8bit, 103h=800x600 8bit, 111h=640x480 16bit a tak dále. Tyto kódy naleznete všude možně po internetu, v tom problém není.
Jenže:
-Od verze VESA 2.0 už žádné nové módy závazně definovány nejsou a nebudou.
-Jednotliví výrobci ne vždy dodržují videomódy z VESA 1.0/1.2
-Tuplem nejisté je to s emulovanými prostředími jako DosBox nebo DOSemu
-Některé karty nemají módy 16bitové, ale 15bitové a některé karty místo 32bitových 24bitové. Tohle je potřeba ohlídat.
Proto je vhodné naprogramovat aktivní vyhledávání videomódů.
1) Napřed si pomocí VESA funkce 4F00h zjistíme ukazatel na seznam všech videorežimů karty
2) Pro každý kód z tabulky zavoláme funkci 4F01h (Vrať informaci o videomódu). Funkce vygeneruje tabulku s informacemi o dotazovaném videorežimu. V tabulce je (mimo jiné) uvedeno rozlišení a bitová hloubka.
3) Když údaje v tabulce vyhovují požadovanému videomódu, tak máme vyhráno a můžeme zavolat funkci 4F02h. Do registru BX dáme číslo onoho vyhovujícího módu.
Takto bude vypadat zdroják v Turbo pascalu v reálném módu:
Program VESAdemo;
Function NajdiVESArezim(xroz,yroz,bitu:word):word;
Function TestMode(videomod,xroz,yroz:word;bitu:byte):boolean;
var buffer:array[0..511] of byte;
vysledek:byte;
p:pointer;
begin
p:=@buffer;
asm
push es
push di
mov ax,4f01h
mov cx,videomod
mov es,word ptr p[2]
mov di,word ptr p[0]
int 10h
mov ax,es:[di]
test ax,8
jz @chyba
@graficky_rezim:
mov ax,es:[di+12h]
cmp ax,xroz
jnz @chyba
mov ax,es:[di+14h]
cmp ax,yroz
jnz @chyba
mov al,es:[di+19h]
cmp al,bitu
jnz @chyba
mov ax,1
jmp @konec
@chyba:
xor ax,ax
@konec:
pop di
pop es
mov vysledek,al
end;
Testmode:=vysledek<>0;
end;
var zakladni_info:array[0..511] of byte;
p:pointer;
tabulka_videomodu:^word;
begin
p:=@zakladni_info;
asm
push es
push di
mov ax,4f00h
mov es,word ptr p[2]
mov di,word ptr p[0]
int 10h
db 66hdb 66h
pop di
pop es
end;
while tabulka_videomodu^<>$0FFFF do
begin
if TestMode(tabulka_videomodu^,xroz,yroz,bitu) then
begin
NajdiVESArezim:=tabulka_videomodu^;
Exit;
end;
inc(tabulka_videomodu);
end;
NajdiVESArezim:=0;
end;
Procedure NastavRezim(rezim:word);assembler;
asm
mov ax,4f02h
mov bx,rezim
int 10h
end;
Procedure Zpatky;assembler;
asm
mov ax,3
int 10h
end;
var rezim:word;
sirka,vyska,hloubka:word;
begin
writeln('Zadej sirku:');readln(sirka);
writeln('Zadej vysku:');readln(vyska);
writeln('Zadej bitovou hloubku (pocet bitu na pixel):');readln(hloubka);
rezim:=NajdiVESArezim(sirka,vyska,hloubka);
if rezim=0 then writeln('Tvoje graficka karta tento rezim nepodporuje.') else
NastavRezim(rezim);
readln;
Zpatky;
writeln(rezim);
end.
Pro Freepascal je potřeba zdroják poněkud upravit. Vlastně přepsat.
Jde o to, že pokud nechceme použít protektové rozhraní rozhraní VESA (což nechceme, protože to by bylo ještě složitější), tak si musíme připravit buffer v konvenční paměti, protože buffer v paměti nad 1MB by VESA BIOS neviděl.
A taky musíme odbourat 16. bitové adresování v assembleru. Nejlépe tak, že žádný assembler nepoužijeme.
Program VESAdemo;
uses Go32,Dos;
var VesaBaseInfo:array[0..511] of byte;
VesaModeInfo:array[0..255] of byte;
PROCEDURE ReadVesaBaseInfos;
VAR LowMemPtr : LongInt;
Regs:Registers;
Begin
LowMemPtr:= Global_DOS_Alloc(512);
FillChar(VesaBaseInfo,SizeOf(VesaBaseInfo),0);
VesaBaseInfo[0]:= byte('V');
VesaBaseInfo[1]:= byte('B');
VesaBaseInfo[2]:= byte('E');
VesaBaseInfo[3]:= byte('2');
DOSMemPut(Word(LowMemPtr shr 16),0,VesaBaseInfo,512);
FillChar(Regs,SizeOf(Regs),0);
Regs.eax := $4F00;
Regs.es := Word(LowMemPtr shr 16);
Regs.edi := 0;
RealIntr($10,Regs);
DOSMemGet(Word(LowMemPtr shr 16),0,VesaBaseInfo,512);
Global_DOS_Free(Word(LowMemPtr));
End;
PROCEDURE ReadVesaModeInfos(Mode:Word);
VAR LowMemPtr : LongInt;
Regs:Registers;
Begin
LowMemPtr:= Global_DOS_Alloc(256);
FillChar(VesaModeInfo,SizeOf(VesaModeInfo),0);
FillChar(Regs,SizeOf(Regs),0);
Regs.es:= Word(LowMemPtr shr 16);
Regs.cx:= Mode;
Regs.ax:= $4F01;
RealIntr($10,Regs);
DOSMemGet(Word(LowMemPtr shr 16),0,VesaModeInfo,256);
Global_DOS_Free(Word(LowMemPtr));
End;
Function NajdiVESArezim(sirka,vyska,hloubka:word):longint;
var segm,ofss,i:word;
mode:array[0..255] of word;
sv,vv:word;
dd:longint;
begin
ReadVESABaseInfos;
Move(VesaBaseInfo[$0e],dd,4);
segm := Segment_To_Descriptor(dd shr 16);
ofss := dd and $FFFF;
seg_move(segm, ofss, get_ds, longint(@mode), SizeOf(mode));
for i:=0 to 255 do
if mode[i]=$FFFF then Exit(0) else
begin
ReadVESAmodeInfos(mode[i]);
move(VESAMODEINFO[$12],sv,2);
move(VESAMODEINFO[$14],vv,2);
if (sv=sirka)
and
(vv=vyska)
and
(VESAMODEINFO[$19]=hloubka)
then Exit(mode[i]);
end;
NajdiVESArezim:=0;
end;
Procedure NastavRezim(rezim:word);assembler;
asm
mov ax,4f02h
mov bx,rezim
int 10h
end;
Procedure Zpatky;assembler;
asm
mov ax,3; int 10h;
end;
var rezim:word;
sirka,vyska,hloubka:word;
begin
writeln('Zadej sirku:');readln(sirka);
writeln('Zadej vysku:');readln(vyska);
writeln('Zadej bitovou hloubku (pocet bitu na pixel):');readln(hloubka);
rezim:=NajdiVESArezim(sirka,vyska,hloubka);
if rezim=0 then writeln('Tvoje graficka karta tento rezim nepodporuje.') else
NastavRezim(rezim);
readln;
Zpatky;
writeln(rezim);
end.
2)nastavení obnovovací frekvence monitoru:
Od verze VESA 3.0 je definováno rozhraní pro nastavení obnovovací frekvence monitoru. Pochopitelně to má význam jenom na klasických CRT monitorech. Obraz se na nich vytváří následujícím způsobem:
Uvnitř monitoru je zařízení, kterému se říká elektronový emitor nebo také elektronové dělo. Ten vysílá proud elektronů na přesně stanovené místo na monitoru (na jeden pixel). Na místě dopadu se rozsvěcí fluorescenční vrstva (to je termín z monochromatických obrazovek, nevím, jak se tomu říká u barevných) a dělo zamíří na vedlejší pixel. Takto projíždí celou obrazovku odshora dolů vždy zleva do prava. Vpodstatě tedy takto.
repeat
For y:=o to MaxY do
For x:=0 to MaxX do
NechToChviliPusobit(cas);
until Vypnuti_pocitace;
Z uvedeneho vyplývá následující: paprsek vždy putuje jedním směrem.
Co se tedy stane, když paprsek dorazí na konec řádku? Emitor se na chvilinku vypne a zamíří na první pixel následující řádky.
Tento děj se nazývá horizontální návrat paprsku (horizontal refresh).
Pokud dorazí na poslední bod poslední řádky, vrací se na pozici [0,0] což se nazývá vertikální návrat paprsku (vertical refresh). Tento děj je pro programátora mnohem důležitější, než horizontální návrat, protože právě během této doby můžeme měnit obsah videopaměti bez rizika, že se na monitoru objeví tzv. blikání.
Pokud je ovšem celý tento děj příliš pomalý (opakuje se málokrát za vteřinu neboli obnovovací frekvence je příliš nízká), zírání do monitoru unavuje oči a práce u počítače je nepříjemná.
Dá se říct, že obnovovací frekvence 100Hz (obnova 100x ze vteřinu) je perfektní, 80Hz velmi dobré, 70Hz vyhovující, 60Hz nevyhovující a míň to už je na blázinec.
Klasická rozlišení VGA mívají frekvenci mezi 70 a 80Hz. Textové módy mají myslím 75Hz.
SVGA rozlišení ovšem standardně klesají někam k 60Hz a to už je blbé. Naštěstí rozhraní VESA VBE 3.0 umožňuje nastavit si frekvenci vlastní.
Následující zdroják je přeložitelný v TP i FP, ale v TP nemusí fungovat správně kvůli příliš malému rozsahu čísel Longint.
Taky nemusí fungovat pod běžícími windows, protože jejich ovladače mohou blokovat uživatelské nastavování obnovovací frekvence. Zkoušejte to tedy raději v čistém DOSu.
Program refresh;
uses Go32,Dos;
const
HNEG = 1 shl 2;
VNEG = 1 shl 3;
type CRTC_info=packed record
HorizontalTotal:word;
HorizontalSyncStart:word;
HorizontalSyncEnd:word;
VerticalTotal:word;
VerticalSyncStart:word;
VerticalSyncEnd:word;
Flags:byte;
PixelClock:longint;
RefreshRate:word;
reserved:array[0..39] of byte;
end;
dword = longint;
Procedure Vypocitej_crct_casovani(xres,yres,xadjust,yadjust:longint;var crtc:CRTC_info);
var
HTotal, VTotal:longint;
HDisp, VDisp:longint;
HSS, VSS:longint;
HSE, VSE:longint;
HSWidth, VSWidth:longint;
SS, SE:longint;
doublescan:boolean;
begin
doublescan:=false;
if (yres < 400) then
begin
doublescan := TRUE;
yres :=yres*2;
end;
HDisp := xres;
Htotal:=round(HDisp*1.27) and (not 7);
HSWidth := round((HTotal - HDisp) / 5) and (not 7);
HSS := HDisp + 16;
HSE := HSS + HSWidth;
VDisp := yres;
VTotal := round(VDisp * 1.07);
VSWidth := round(VTotal / 100) + 1;
VSS := VDisp + round((VTotal - VDisp) / 5) + 1;
VSE := VSS + VSWidth;
SS := HSS + xadjust;
SE := HSE + xadjust;
if (xadjust < 0) then
if SS < HDisp + 8 then
begin
SS := HDisp + 8;
SE := SS + HSWidth;
end else
else
if HTotal - 24 < SE then
begin
SE := HTotal - 24;
SS := SE - HSWidth;
end;
HSS := SS;
HSE := SE;
SS := VSS + yadjust;
SE := VSE + yadjust;
if (yadjust < 0) then
if SS < VDisp + 3 then
begin
SS := VDisp + 3;
SE := SS + VSWidth;
end else
else
if VTotal - 4 < SE then
begin
SE := VTotal - 4;
SS := SE - VSWidth;
end;
VSS := SS;
VSE := SE;
crtc.HorizontalTotal := HTotal;
crtc.HorizontalSyncStart := HSS;
crtc.HorizontalSyncEnd := HSE;
crtc.VerticalTotal := VTotal;
crtc.VerticalSyncStart := VSS;
crtc.VerticalSyncEnd := VSE;
crtc.Flags := HNEG or VNEG;
if doublescan then
crtc.flags:=crtc.flags or byte(doublescan);
end;
Function get_closest_pixel_clock(mode_no:word;vclk:longint):dword;
var r:registers;
begin
r.ax:=$4f0B;
r.bl:=0;
r.ecx:=vclk;
r.dx:=mode_no;
intr($10,r);
if r.ah<>0 then get_closest_pixel_clock:=0 else get_closest_pixel_clock:=r.ecx;
var l:longint;
begin
asm
mov ax,4f0bh
xor bl,bl
db 66h mov dx,mode_no
int 10h
cmp ah,0
jnz @preskoc
db 66h; xor cx,cx
@preskoc:
db 66h; mov l.word,cx
end;
get_closest_pixel_clock:=l;
end;
Function VESA_version_3_available:boolean;
begin
VESA_version_3_available:=true;
end;
Function Najdi_Videorezim(xr,yr,bpp:longint):word;
begin
Najdi_Videorezim:=$103+$4000;
end;
Procedure Nastav_grafiku(xr,yr,bpp,frek:longint);
var xadjust,yadjust:longint;
crtc:CRTC_info;
vclk,c,long:dword;
mode,w:word;
segm,ofsm:word;
f0:double;
regs:Registers;
begin
xadjust:=0;
yadjust:=0;
mode:=Najdi_Videorezim(xr,yr,bpp);
if VESA_version_3_available then
begin
Vypocitej_crct_casovani(xr,yr,xadjust,yadjust,crtc);
vclk := dword(crtc.HorizontalTotal * crtc.VerticalTotal * frek);
vclk := get_closest_pixel_clock(mode, vclk);
end else vclk:=0;
if (vclk <> 0) then
begin
f0 := vclk / (crtc.HorizontalTotal * crtc.VerticalTotal);
c:=round(f0+0.5);
crtc.PixelClock := vclk;
crtc.RefreshRate := frek * 100;
long:=Global_DOS_alloc(sizeOf(CRTC_info));
w:=Hi(long);
dosmemput(w,0,crtc, sizeof(CRTC_info));
Regs.eax:=$4F02;
Regs.di := 0;
Regs.es := w;
Regs.ebx:=mode or $0800;
RealIntr($10, Regs);
Global_DOS_free(Lo(long));
segm:=seg(crtc);
ofsm:=ofs(crtc);
asm
push es mov ax,4f02h
mov bx,segm
mov es,bx
mov di,ofsm
mov bx,mode
or mode,800h
int 10h
pop si; pop es
end;
end else
begin
asm
mov ax,4f02h
mov bx,mode
int 10h
end;
end;
end;
Procedure Test;
begin
OutPortb($3C8,0);
OutPortb($3C9,63);
OutPortb($3C9,63);
OutPortb($3C9,63);
Port[$3C8]:=0;
Port[$3C9]:=63;
Port[$3C9]:=63;
Port[$3C9]:=63;
readln;
end;
Procedure Zpatky;assembler;
asm
mov ax,3; int 10h;
end;
var sirka,vyska:longint;
begin
writeln('Napred zkusime nastavit 800x600 8bit na 55Hz');readln;
sirka:=800;
vyska:=600;
Nastav_grafiku(sirka,vyska,8,0);
Test;
Zpatky;
writeln('Ted 800x600 8bit na 100Hz. Je to lepsi?');readln;
Nastav_grafiku(sirka,vyska,8,100);
Test;
Zpatky;
end.
Datum: 8.8.2007 13:51
Od: Mircosoft
Titulek: Pozor!
Výstupní buffer funkce $4F01 (info o režimu) někdy přepíše výstupní buffer funkce $4F00 (info o vese). Jestli chceme obě tabulky používat současně, musíme tu první ručně zkopírovat někam do bezpečí.