BIOS-Level Programmierung - Grafik-Modi |
Geschrieben von: StarShaper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Donnerstag, den 01. Juni 2006 um 10:30 Uhr | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Seite 4 von 5
Grafik-ModusNeben den verschiedenen Text-Modi existieren auch einige Grafik-Modi (Graphics Mode). Mithilfe der INT 10h Funktion 0Ch lassen sich Pixel und Linien sehr einfach auf den Bildschirm zeichnen. Bevor Sie jedoch Pixel auf den Bildschirm zeichnen können, müssen Sie den Video Adapter in einen standardisierten Grafik-Modus bringen. Einen Überblick über die vorhandenen Video-Modi finden Sie in der Tabelle 4. Der Modus lässt sich genau wie im Abschnitt "Text-Modus" erklärt über die Funktion INT 10h 00h setzen.
Die Funktion 0Ch schreibt nicht direkt in den Video RAM, so dass es beim Zeichnen vieler Pixel zu geringfügigen Verzögerungen kommen kann. Die Verzögerung hängt proportional mit der Anzahl der Zeichenaufrufe für einen Pixel zusammen. Neben der Möglichkeit Pixel zu zeichnen ist es auch möglich ein Pixel vom Bildschirm zu lesen. Der prinzipielle Code für das Schreiben und Lesen eines Pixels kann folgendermaßen aussehen. ;-------------------------------------------------- ; INT 10h Funktion 0Ch ; Schreibt einen Pixel auf den Bildschirm. ;-------------------------------------------------- mov ah, 0Ch mov al, pixelValue ; Der Pixelwert mov bh, videoPage ; Die Bildschirmseite mov cx, xcoord ; X-Koordinate mov dx, ycoord ; Y-Koordinate int 10h ;-------------------------------------------------- ; INT 10h Funktion 0Dh ; Liest einen Pixel vom Bildschirm. Die Funktion ; gibt das Pixel im AL-Register zurück. ;-------------------------------------------------- mov ah, 0Dh mov bh, 0 ; Die Bildschirmseite mov cx, xcoord mov dx, ycoord int 10h Bevor wir uns einer deutlich schnelleren Variante Pixel auf den Bildschirm zu zeichnen zuwenden, werden wir das bisher errungene Wissen dazu benutzen ein einfaches Schachbrett auf den Bildschirm zu zeichnen. Das folgende Programm zeichnet zunächst das einfache Gitternetz und füllt danach die weißen Felder auf. Dies dient ledeglich Demonstrationszwecken und ist für das Endresultat nur bedingt erforderlich. TITLE Chess field (chessfield.asm) COMMENT ! Dieses Programm schaltet in den Grafik-Modus 6Ah (800 x 600) und zeichnet zunächst ein Gitternetz aus Schachfeldern. Anschließend füllt das Programm abwechselnd die entsprechenden Felder weiß auf, so dass am Ende ein Schachbrett auf dem Bildschirm zu sehen ist. Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 ;------------------------------------------------------ ; Video-Modi Konstanten ;------------------------------------------------------ Mode_06 = 6 ; 640x200, 2 colors Mode_0D = 0Dh ; 320 x 200, 16 colors Mode_0E = 0Eh ; 640 x 200, 16 colors Mode_0F = 0Fh ; 640 x 350, 2 colors Mode_10 = 10h ; 640 x 350, 16 colors Mode_11 = 11h ; 640 x 480, 2 colors Mode_12 = 12h ; 640 x 480, 16 colors Mode_13 = 13h ; 320 x 200, 256 colors Mode_6A = 6Ah ; 800 x 600, 16 colors white = 1111b .data saveMode BYTE ? ; Speichert den Video-Modus fRows WORD 20 ; Speichert die Reihe der horizontalen Linie fCols WORD 120 ; Speichert die Spalte der vertikalen Linie tmp WORD 0 ; Temporäre Variable innerCounter Word 0 ; Innerer Schleifenzähler outerCounter WORD 1 ; Äußerer Schleifenzähler fields BYTE 0 ; Anzahl der Felder pro Spalte fieldsCols WORD 0 ; Anzahl der Spalten .code main PROC mov ax, @data mov ds, ax mov ah, 0Fh ; Empfange den aktuell eingestellten Video-Modus int 10h mov saveMode, al ; Speichere diesen Video-Modus mov ah, 0 ; Setze den Video-Modus mov al, Mode_6A ; Nimm den Grafik-Modus mit 800 x 600 und 16 Farben int 10h Rows: ; Zeichne die horizontalen Gitterlinien mov cx, 120 ; X-Koordinate der Starlinie mov dx, fRows ; Y-Koordinate der Starlinie mov ax, 560 ; Länge der Linie mov bl, white ; Farbe der Linie call DrawHorizLine ; Zeichne die horizontale Linie add fRows, 70 ; Nächste Linie cmp fRows, 580 ; Sind wir fertig? jbe Rows ; Falls fRows <= 580 springe zurück Cols: mov cx, fCols ; Zeichne die vertikalen Gitterlinien mov dx, 20 mov ax, 560 mov bl, white call DrawVerticalLine ; Zeichne die vertikale Linie add fCols, 70 cmp fCols, 680 jbe Cols ; Falls fCols <= 680 springe zurück mov tmp, 20 ; Setze die temporäre Variable auf x = 20 add fieldsCols, 120 ; Setze die Variable auf y = 120 ; Oberer linker Eckpunkt P(120|20) L1: ; Fülle nun die weißen Felder aus indem jeweils ; 70 horizontale Linien gezeichnet werden mov cx, fieldsCols mov dx, tmp mov ax, 70 mov bl, white call DrawHorizLine inc innerCounter ; Inkrementiere den inneren Schleifenzähler add tmp, 1 cmp innerCounter, 70 jb L1 add fields, 1 ; Inkrementiere die Anzahl der weißen Felder in der Spalte cmp fields, 4 ; Haben wir 4 Felder gezeichnet? jae NextCol mov innerCounter, 0 ; Nein, inkrementiere die innere Schleife add tmp, 70 ; Addiere 70 dazu und überspringe damit das schwarze Feld jmp L1 NextCol: cmp outerCounter, 8 ; Haben wir die achte Spalte erreicht? jae Finished ; Falls outerCounter >= 8 springe inc outerCounter mov fields, 0 mov innerCounter, 0 add fieldsCols, 70 mov dx, 0 mov ax, outerCounter mov cx, 2 ; Teile durch 2. Dies überprüft ob die Spalte gerade ; oder ungerade ist. div cx ; 16-Bit unsigned Division cmp dx, 0 ; Checke ob der Rest (Remainder) in DX gleich 0 ; (Modulo Division) ist je EVENx ; Springe an EVENx, falls gleich jmp ODDx ; Andernfalls an ODDx EVENx: mov tmp, 90 jmp L1 ODDx: mov tmp, 20 jmp L1 Finished: mov ah, 10h ; Warte auf einen Tastendruck int 16h mov ah, 0 ; Stelle den anfangs gespeicherten Video-Modus wieder her mov al, saveMode int 10h mov ax, 4C00h ; Beende das Programm samt Exit Code int 21h ; Gebe die Kontrolle wieder an DOS zurück main endp ;------------------------------------------------------ ; DrawHorizLine ; Zeichnet eine horizontale Linie beginnend an der ; Position X,Y mit einem übergebenen Längenwert und Farbe. ; Empfängt: CX = X-Koordinate, DX = Y-Koordinate, ; AX = Länge, und BL = Farbe ; Rückgabewert: - ;------------------------------------------------------ DrawHorizLine PROC .data currX WORD ? .code pusha mov currX, cx ; Speichert die X-Koordinate mov cx, ax ; Schleifenzähler DHL1: push cx ; Speichert den Schleifenzähler mov al, bl ; Farbe mov ah, 0Ch ; Zeichne Pixel mov bh, 0 ; Video Page mov cx, currX ; Empfabge die X-Koordinate int 10h inc currX ; Gehe 1 Pixel nach rechts pop cx ; Stelle den Schleifenzähler wieder her Loop DHL1 popa ret DrawHorizLine ENDP ;------------------------------------------------------ ; DrawVerticalLine ; Zeichnet eine vertikale Linie beginnend an der ; Position X,Y mit einem übergebenen Längenwert und Farbe. ; Empfängt: CX = X-Koordinate, DX = Y-Koordinate, ; AX = Länge, und BL = Farbe ; Rückgabewert: - ;------------------------------------------------------ DrawVerticalLine PROC .data currY WORD ? .code pusha mov currY, dx ; Speichert die Y-Koordinate mov currX, cx mov cx, ax ; Schleifenzähler DVL1: push cx ; Speichert den Schleifenzähler mov al, bl ; Farbe mov ah, 0Ch ; Zeichne Pixel mov bh, 0 mov cx, currX ; Setze die X-Koordinate mov dx, currY ; Setze die Y-Koordinate int 10h ; Zeichne Pixel inc currY ; Gehe 1 Pixel nach unten pop cx ; Stelle den Schleifenzähler wieder her Loop DVL1 popa ret DrawVerticalLine ENDP END main Vergessen Sie nicht das Programm unter Windows im Vollbildmodus auszuführen! Memory-Mapped I/ODas Zeichnen mittels Interrupt 10h ist bei vielen Pixeln oft eine träge Angelegenheit. Der Grund liegt darin das sehr oft sehr viele Codezeilen ausgeführt werden müssen. Dies verlangsamt das Programm ganz erheblich. Eine wesentlich effizientere und oft verwendete Variante ist eine Technik namens memory-mapped graphics, die alle Daten direkt im VRAM ablegt bevor sie auf dem Bildschirm gezeichnet werden. Der Video-Modus 13h ist der bevorzugte Modus sobald man direkt mit dem Speicher arbeitet. Der Modus besitzt eine Auflösung von 320 x 200 und kann 256 Farben darstellen. Die Bildschirm-Pixel werden als zweidimensionale Arrays von Bytes dargestellt, indem jeder Pixel ein eigenes Byte belegt. Das Array beginnt mit dem Pixel in der oberen linken Ecke des Bildschirms und erstreckt sich bis zum Pixel der oberen rechten Ecke. Im Video-Modus 13h sind das exakt 320 Bytes. Das nächste Byte bildet das Pixel in der zweiten Zeile ab und so weiter. Ein Byte kann einschließlich der 0 insgesamt 256 verschiedene Ganzzahlenwerte darstellen. Damit lassen sich die 256 verschiedene Farben repräsentieren. Alle Pixel und Farbwerte können zum Video Adapter transferiert werden indem die OUT Instruktion verwendet wird. Dazu wird eine 16-Bit Port Adresse an DX zugewiesen und der Wert der an den Port gesendet wird, wird in AL abgelegt. Die Video Farbpalette befindet sich an der Port Adresse 3C8h. Die Farben werden mithilfe einer RGB Farbpalette dargestellt. Es können insgesamt 262144 verschiedene Farben ausgegeben werden. Zeitgleich können natürlich nur 256 Farben angezeigt werden, jedoch kann die Ausgabe zur Laufzeit geändert werden. Im nächsten Programm werden wir sehen wie sich Animationen programmieren lassen. Das Programm FireScreen bildet ein flackerndes Feuer auf dem Bildschirm ab. Falls Sie Probleme haben den folgenden, etwas komplexeren Quellcode nachzuvollziehen, können Sie sich parallel dazu die anderen Beispiele zum Memory-Mapping im Anhang dieses Tutorials ansehen. Diese sind teilweise einfacher strukturiert. TITLE Fire Screen (firescreen.asm) COMMENT ! Dieses Programm nutzt memory-mapped graphics um mithilfe einer RGB Farbpalette ein Feuereffekt auf dem Bildschirm darzustellen. Last update: 25/05/06 ! .model small, stdcall .stack 200h .386 .data saveMode BYTE ? ; Gespeicherter Video Modus urlmessage BYTE "http://www.codeplanet.eu/$" ; Wird am Ende angezeigt xVal = 80 ; Bildschirmweite in Pixeln yVal = 112 ; Bildschirmhöhe in Pixeln + 2 seed WORD ? ; Zufallszahlen ; RGB Farbpalette von 0-63 (maximum), die Palette repräsentiert das Feuer palette BYTE 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 8 BYTE 0, 0, 9, 0, 0, 10, 2, 0, 10, 4, 0, 9, 6, 0, 9, 8, 0, 8 BYTE 10, 0, 7, 12, 0, 7, 14, 0, 6, 16, 0, 5, 18, 0, 5, 20, 0, 4 BYTE 22, 0, 4, 24, 0, 3, 26, 0, 2, 28, 0, 2, 30, 0, 1, 32, 0, 0 BYTE 32, 0, 0, 33, 0, 0, 34, 0, 0, 35, 0, 0, 36, 0, 0, 36, 0, 0 BYTE 37, 0, 0, 38, 0, 0, 39, 0, 0, 40, 0, 0, 40, 0, 0, 41, 0, 0 BYTE 42, 0, 0, 43, 0, 0, 44, 0, 0, 45, 0, 0, 46, 1, 0, 47, 1, 0 BYTE 48, 2, 0, 49, 2, 0, 50, 3, 0, 51, 3, 0, 52, 4, 0, 53, 4, 0 BYTE 54, 5, 0, 55, 5, 0, 56, 6, 0, 57, 6, 0, 58, 7, 0, 59, 7, 0 BYTE 60, 8, 0, 61, 8, 0, 63, 9, 0, 63, 9, 0, 63, 10, 0, 63, 10, 0 BYTE 63, 11, 0, 63, 11, 0, 63, 12, 0, 63, 12, 0, 63, 13, 0, 63, 13, 0 BYTE 63, 14, 0, 63, 14, 0, 63, 15, 0, 63, 15, 0, 63, 16, 0, 63, 16, 0 BYTE 63, 17, 0, 63, 17, 0, 63, 18, 0, 63, 18, 0, 63, 19, 0, 63, 19, 0 BYTE 63, 20, 0, 63, 20, 0, 63, 21, 0, 63, 21, 0, 63, 22, 0, 63, 22, 0 BYTE 63, 23, 0, 63, 24, 0, 63, 24, 0, 63, 25, 0, 63, 25, 0, 63, 26, 0 BYTE 63, 26, 0, 63, 27, 0, 63, 27, 0, 63, 28, 0, 63, 28, 0, 63, 29, 0 BYTE 63, 29, 0, 63, 30, 0, 63, 30, 0, 63, 31, 0, 63, 31, 0, 63, 32, 0 BYTE 63, 32, 0, 63, 33, 0, 63, 33, 0, 63, 34, 0, 63, 34, 0, 63, 35, 0 BYTE 63, 35, 0, 63, 36, 0, 63, 36, 0, 63, 37, 0, 63, 38, 0, 63, 38, 0 BYTE 63, 39, 0, 63, 39, 0, 63, 40, 0, 63, 40, 0, 63, 41, 0, 63, 41, 0 BYTE 63, 42, 0, 63, 42, 0, 63, 43, 0, 63, 43, 0, 63, 44, 0, 63, 44, 0 BYTE 63, 45, 0, 63, 45, 0, 63, 46, 0, 63, 46, 0, 63, 47, 0, 63, 47, 0 BYTE 63, 48, 0, 63, 48, 0, 63, 49, 0, 63, 49, 0, 63, 50, 0, 63, 50, 0 BYTE 63, 51, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0, 63, 52, 0 BYTE 63, 53, 0, 63, 53, 0, 63, 53, 0, 63, 53, 0, 63, 54, 0, 63, 54, 0 BYTE 63, 54, 0, 63, 54, 0, 63, 54, 0, 63, 55, 0, 63, 55, 0, 63, 55, 0 BYTE 63, 55, 0, 63, 56, 0, 63, 56, 0, 63, 56, 0, 63, 56, 0, 63, 57, 0 BYTE 63, 57, 0, 63, 57, 0, 63, 57, 0, 63, 57, 0, 63, 58, 0, 63, 58, 0 BYTE 63, 58, 0, 63, 58, 0, 63, 59, 0, 63, 59, 0, 63, 59, 0, 63, 59, 0 BYTE 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 60, 0, 63, 61, 0 BYTE 63, 61, 0, 63, 61, 0, 63, 61, 0, 63, 62, 0, 63, 62, 0, 63, 62, 0 BYTE 63, 62, 0, 63, 63, 0, 63, 63, 1, 63, 63, 2, 63, 63, 3, 63, 63, 4 BYTE 63, 63, 5, 63, 63, 6, 63, 63, 7, 63, 63, 8, 63, 63, 9, 63, 63, 10 BYTE 63, 63, 10, 63, 63, 11, 63, 63, 12, 63, 63, 13, 63, 63, 14, 63, 63, 15 BYTE 63, 63, 16, 63, 63, 17, 63, 63, 18, 63, 63, 19, 63, 63, 20, 63, 63, 21 BYTE 63, 63, 21, 63, 63, 22, 63, 63, 23, 63, 63, 24, 63, 63, 25, 63, 63, 26 BYTE 63, 63, 27, 63, 63, 28, 63, 63, 29, 63, 63, 30, 63, 63, 31, 63, 63, 31 BYTE 63, 63, 32, 63, 63, 33, 63, 63, 34, 63, 63, 35, 63, 63, 36, 63, 63, 37 BYTE 63, 63, 38, 63, 63, 39, 63, 63, 40, 63, 63, 41, 63, 63, 42, 63, 63, 42 BYTE 63, 63, 43, 63, 63, 44, 63, 63, 45, 63, 63, 46, 63, 63, 47, 63, 63, 48 BYTE 63, 63, 49, 63, 63, 50, 63, 63, 51, 63, 63, 52, 63, 63, 52, 63, 63, 53 BYTE 63, 63, 54, 63, 63, 55, 63, 63, 56, 63, 63, 57, 63, 63, 58, 63, 63, 59 BYTE 63, 63, 60, 63, 63, 61, 63, 63, 62, 63, 63, 63 screen BYTE xVal * yVal dup (?) ; Virt. Bildschirm (nach der palette!) .code main PROC mov ax, @data mov ds, ax call SetUpScreen mov seed, 7895h mov si, OFFSET screen mov cx, LENGTHOF screen xor ax, ax rep stosb ; Leere den Bildschirm. ML1: ; Das nächste Bit gibt entweder 0 oder 255 entlang der mov si, OFFSET screen ; untersten Reihe des virtuellen Bildschrims aus. add si, LENGTHOF screen sub si, xVal ; SI=OFFSET(screen)+LENGTHOF(screen)-xVal. Letzte Reihe mov cx, xVal ; Wiederhole die komplette letzte Reihe xor dx, dx L1: call Randomize ; Erzeuge Zufallswerte mov ds:[si], dl inc si dec cx jnz L1 mov cx, LENGTHOF screen ; Mildere die Werte im virtuellen Array ab, sub cx, xVal ; so dass ein sanfter Feuereffekt entsteht mov si, OFFSET screen add si, xVal L3: xor ax, ax mov al, ds:[si] add al, ds:[si+1] adc ah, 0 add al, ds:[si-1] adc ah, 0 add al, ds:[si+xVal] adc ah, 0 shr ax, 2 jz ZERO ; Am Ende? dec ax ZERO: mov ds:[si-xVal], al ; AL=((pos)+(pos+1)+(pos-1)+(pos+80))/4 - 1 inc si dec cx jnz L3 mov dx, 3DAh ; VGA-Ausgabe L4: in al, dx and al, 8h jnz L4 L5: in al, dx and al, 8h jz L5 mov cx, LENGTHOF screen shr cx, 1 mov si, OFFSET screen xor di, di rep movsw mov ah, 11h ; Überprüfe den Tastaturpuffer int 16h ; Wurde eine Taste gedrückt (ZF)? jz ML1 ; Nein, mache weiter call RestoreVideoMode ; Warte auf Tastendruck, stelle VM wieder her mov ah, 9 ; Ausgabe eines $-terminierter Strings mov dx, OFFSET urlmessage ; DS:DX = Segment/Offset des Strings int 21h mov ah, 10h ; Warte auf Tastendruck int 16h mov ax, 4C00h ; Beende das Programm samt Exit Code int 21h ; Gebe die Kontrolle wieder an DOS zurück main ENDP ;---------------------------------------------------------- ; Randomize ; Diese Prozedur erzeugt Zufallswerte für den bewegenden ; Feuereffekt. ;---------------------------------------------------------- Randomize PROC NEAR mov ax, seed ; Initialisiere mov dx, 8405h mul dx ; DX:AX = AX * DX inc ax mov seed, ax ret Randomize ENDP ;---------------------------------------------------------- ; SetUpScreen ; Diese Prozedur bereitet den Bildschrim vor. Mithilfe der Prozedur ; SetVideoMode wird der Video-Modus umgeschaltet. Die Werte ; an Port 3C8h gesendet, zeigen welche Video Palette geändert wird. ;---------------------------------------------------------- SetUpScreen PROC NEAR call SetVideoMode ; Schalte in den Mode 13h, 320 x 200, 256 Farben cli ; Leere die Interrupt Flags (Clear Interrupt Flag) cld mov dx, 3C4h mov ax, 604h out dx, ax mov ax, 0F02h out dx, ax xor ax, ax mov cx, 32767 rep stosw ; Leere den Bildschirm mov dx, 3D4h mov ax, 14h ; Deaktiviere DWORD-Modus out dx, ax mov ax, 0E317h ; Aktiviere Byte-Modus out dx, ax out dx, ax mov ax, 00409h ; Zellenhöhe out dx, ax mov si, OFFSET palette mov dx, 3C8h ; Video Paletten Port mov al, 0 ; Setze den Palettenindex out dx, al ; Beginne bei der Farbe null inc dx mov cx, 768 PLOOP: outsb ; Stringausgabe seitens ES:(E)DI auf Port zeigend dec cx jnz PLOOP ret SetUpScreen ENDP ;---------------------------------------------------------- ; SetVideoMode ; Diese Prozedur speichert den aktuellen Video-Modus, schaltet ; in einen neuen Modus und zeigt ES auf das Video Segment. ;---------------------------------------------------------- SetVideoMode PROC mov ah, 0Fh ; Empfange den aktuellen current Video-Modus int 10h mov saveMode, al ; Speichere den Modus mov ah, 0 ; Setze den Video-Modus mov al, 13h ; auf Modus 13h int 10h push 0A000h ; Video Segment Adresse pop es ; ES = A000h (Video Segment). ret SetVideoMode ENDP ;---------------------------------------------------------- ; RestoreVideoMode ; Diese Prozedur wartet auf die Betätigung einer Taste und ; stellt den Video Modus wieder her. ;---------------------------------------------------------- RestoreVideoMode PROC mov ah, 10h ; Warte auf Tastendruck int 16h mov ah, 0 ; Resete den Video-Modus mov al, saveMode ; auf den gespeicherten Video-Modus. int 10h ret RestoreVideoMode ENDP END main |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Zuletzt aktualisiert am Samstag, den 22. Mai 2010 um 17:08 Uhr |
AUSWAHLMENÜ | ||||||||
|