SuperVGA-Grafikmodi über die VESA-Schnittstelle benützen

Einleitung und Motivation

QuickBASIC als auch GWBASIC.EXE besitzen für Grafik den SCREEN-Befehl, um den Grafik-Videomodus setzen zu können. Dabei geben Sie eine Nummer an, um die gewünschte Auflösung wählen zu können. Leider :-( ist die Liste der verfügbaren Grafikmodi sehr beschränkt. Somit bietet Ihnen GWBASIC.EXE lediglich 640×350 Bildpunkte bei 16 aus 64 Farben als beste Auflösung an, was dem technischen Stand von 1985 entspricht, als IBM den Enhanced Graphics Adapter (EGA) mit 256 KB Videospeicher vorstellte. Und QuickBASIC beschränkt sich von Haus aus ebenfalls auf Standard-VGA, so dass 320×200 Bildpunkte bei 256 Farben sowie 640×480 Pixel bei 16 Farben aus jeweils 262'144 Farben das Höchste der Gefüge darstellen.

Mittlerweile besitzen aber die meisten PC für Windows 95/98/NT Grafikkarten, welche viel bessere Auflösungen und Farbtiefen erlauben. In diesem Artikel soll gezeigt werden, wie Sie dieses Potential aus QuickBASIC heraus nutzen können.

Die Wurzel des Übels

Microsoft BASIC leidet wie die meisten alten Programme an einem fehlenden Treiberkonzept, weil so etwas wie eine SCRMODES.INI-Datei mit Einträgen wie

1: XRes=320 YRes=200 ColorDpt=2 Pages=1 Drv=CGA.DRV
2: XRes=640 YRes=200 ColorDpt=1 Pages=1 Drv=CGA.DRV
3: XRes=720 YRes=348 ColorDpt=1 Pages=2 Drv=HERCULES.DRV
4: XRes=640 YRes=400 ColorDpt=4 Pages=1 Drv=OLIVETTI.DRV
7: XRes=320 YRes=200 ColorDpt=4 Pages=8 Drv=EGA.DRV
8: XRes=640 YRes=200 ColorDpt=4 Pages=4 Drv=EGA.DRV
9: XRes=640 YRes=350 ColorDpt=4 Pages=2 Drv=EGA.DRV
10: XRes=640 YRes=350 ColorDpt=2 Pages=2 Drv=EGA.DRV
11: XRes=640 YRes=480 ColorDpt=4 Pages=1 Drv=VGA.DRV
12: XRes=640 YRes=480 ColorDpt=4 Pages=1 Drv=VGA.DRV
13: XRes=320 YRes=200 ColorDpt=8 Pages=1 Drv=VGA.DRV

komplett fehlt. Stattdessen sind die Grafikroutinen fest im Maschinencode von GWBASIC.EXE bzw. QBASIC.EXE bzw. BRUN45.EXE eincodiert, auch als hard coded bezeichnet.

Kurzer Auszug aus der Entstehungsgeschichte vom Standard-DOS/Windows-PC

Um die Bedeutung der VESA-Schnittstelle besser zu verstehen, ist es von Vorteil, einen kurzen Blick in die Entstehungsgeschichte vom IBM-PC zu werfen.

Im Jahre 1981 stellte IBM seine erste PC-Generation mit Intel x86-Prozessor vor. Diese ersten Geräte waren mit einem Monochrome Display Adapter (MDA) als Grafikkarte ausgestattet. Etwas später folgte dann der IBM-XT, bei welchem die Kunden bei der Grafikkarte zwischen Hercules Graphics Card (HGC => SCREEN 3, 720×348 Pixel schwarzweiss) und Color Graphics Adapter (CGA => SCREEN 1 mit 320×200/4 Farben und SCREEN 2 mit 640×200/2 Farben) auswählen konnten. Zugleich hat IBM Konkurrenz von Compaq und Amstrad bekommen, so dass die bekannten PC-Clones erstmals aufgetaucht waren. Von IBM folgte 1985 der IBM AT mit Intel 80286-Prozessor und Enhanced Graphics Adapter (EGA => SCREEN 7 mit 320×200/16 Farben, SCREEN 8 mit 640×200/16 Farben, SCREEN 9 mit 640×350/16 Farben und SCREEN 10 mit 640×350/4 Monochrom-Farben). 1987 führte IBM mit der PS/2-Rechnerserie den Video Graphics Array (VGA => SCREEN 11 mit 640×480/2 Farben, SCREEN 12 mit 640×480/16 Farben und SCREEN 13 mit 320×200/256 Farben) der Öffentlichkeit vor.

Bis Ende der 80er-Jahre hatte IBM immer den Ton angegeben, was die Einführung von neuen technischen Standards in der PC-Welt betrifft, die Konkurrenz hat also immer nur voll kompatible Hardware nachgebaut. Danach hatte jedoch IBM diese führende Position bei der Schaffung solcher Industrie-Standards verloren, denn der erste 80386er-PC wurde von der Konkurrenz gebaut. Gleichzeitig überboten sich die Hersteller von Grafikkarten laufend, in dem jeder Karten mit noch besseren Grafikauflösungen entwickelte. Leider geschah dies total unkoordiniert, so dass ein riesiges Chaos an »Standards« im PC-Markt herrschte. Diese unbefriedigende Situation machte es den Software-Entwicklern natürlich unmöglich, aus jeder Grafikkarte die volle Leistung auszuschöpfen. Da all diese SuperVGA-Karten immerhin voll kompatibel zu Standard-VGA von IBM sind, beschränkten sich die meisten Software-Hersteller auf die maximale Unterstützung von Standard-VGA, darunter auch QuickBASIC.

Die grosse Rettung aus diesem Durcheinander erfolgte durch einen Zusammenschluss der Grafikkarten-Hersteller zur Video Electronics Standards Association (VESA), welche eine einheitliche Software-Schnittstelle zur Ansteuerung der Grafikkarten in den SuperVGA-Auflösungen herausgab. Daraufhin implementierte beinahe jeder Grafikkarten-Hersteller diese Spezifikationen im VGA-BIOS als VESA BIOS Extension (VBE). Für die älteren Grafikkarten waren dann immerhin entsprechende Treiber in Form von Terminate and Stay Resident (TSR)-Programmen erhältlich, welche in die AUTOEXEC.BAT eingetragen werden konnten. So konnte man beispielsweise bei einer Spea/V7 Mercury Lite durch eine Zeile

LH C:\SPEA\GDC\V7LITVBE.EXE

die VESA-Kompatibilität nachrüsten.

Ist Ihre Grafikkarte VESA-kompatibel?

Mit MSD.EXE (Microsoft Diagnostics) können Sie dies wunderbar überprüfen. MSD.EXE liegt jedem MS-DOS 6.2x sowie jedem Windows 3.1x bei, bei Windows 95 und 98 finden Sie dieses Werkzeug auf der Installations-CD unter \TOOLS\OLDMSDOS. Beispiel einer Grafikkarte, welche VESA unterstützt:

 ____________________                         ____________________
      Computer...    _-------------- Video ---------------+s...   _ A: B: C:
 ____________________|     Video Adapter Type: VGA        |________ E: F: G:
 ____________________|           Manufacturer: ATI        |_______
       Memory...     |                  Model:            |...    _ 1
 ____________________|           Display Type: VGA Color  |________
 ____________________|             Video Mode: 3          |_______
       Video...      |      Number of Columns: 80         |...    _ 2
 ____________________|         Number of Rows: 25         |________
 ____________________|     Video BIOS Version:            |_______
      Network...     |        Video BIOS Date: 98/05/25   |..     _ 3.10
 ____________________| VESA Support Installed: Yes        |________ Enhanced
 ____________________|           VESA Version: 2.00       |_______
     OS Version...   |          VESA OEM Name: ATI MACH64 |s...   _
 ____________________|      Secondary Adapter: None       |________
 ____________________+------------------------------------|_______
       Mouse...      |                 OK   _             |ms...  _
 ____________________|               ________             |________
 ____________________+------------------------------------+_______
   Other Adapters... _ Game Adapter             Device Drivers... _
 _____________________                        _____________________

Finds files, prints reports, exits.

Und hier ein Beispiel einer nicht VESA-fähigen Grafikkarte:

 ____________________                         ____________________
      Computer...    _ Olivetti/Olivetti         Disk Drives...   _ A: C: D:
 _---------------------------------- Video --------------------------------+
 |     Video Adapter Type: VGA                                             |
 |           Manufacturer: Olivetti                                        |
 |                  Model:                                                 |
 |           Display Type: VGA Color                                       |
 |             Video Mode: 3                                               |
 |      Number of Columns: 80                                              |
 |         Number of Rows: 25                                              |
 |     Video BIOS Version: VETTI 1990 IBM VGA Compatible EVC rev. 1.09.U01u|
 |        Video BIOS Date:                                                 |
 | VESA Support Installed: No                                              |
 |      Secondary Adapter: None                                            |
 +-------------------------------------------------------------------------|
 |                                     OK   _                              |
 |                                   ________                              |
 +-------------------------------------------------------------------------+
 ____________________                         ____________________
   Other Adapters... _                          Device Drivers... _
 _____________________                        _____________________

Press ALT for menu, or press highlighted letter, or F3 to quit MSD.

Wie mache ich meine Grafikkarte VESA-kompatibel?

Zuerst sollten Sie sich auf den mitgelieferten Treiberdisketten bzw. Treiber-CD auf die Suche nach einem solchen TSR-Programm wie V7LITVBE.EXE vom zuvor gezeigten Beispiel machen. Werden Sie nicht fündig, so schauen Sie unter technischer Unterstützung im Treiber-Downloadbereich des Grafikkarten-Herstellers (z.B. ATI, Matrox, Diamond Multimedia) nach einer entsprechenden Datei und installieren diese entsprechend den Herstelleranweisungen. Vereinzelt können Sie auch mit einem Flash-BIOS-Update Ihre Grafikkarte VESA-tauglich aktualisieren, so dass Sie auch danach im Download-Bereich suchen sollten.

Falls nichts weiterhilft, so bietet SciTech Software Inc. das Produkt SciTech Display Doctor an, welches auch bei ziemlich exotischen Grafik-Chipsätzen eine VESA-Unterstützung anbietet. Unter anderem konnte ich damit auf meinem alten 80386/25 MHz-PC mit Chips and Technologies Inc. 82c452-Onbard-VGA-Controller immerhin noch 640×480/256 Farben herausholen.

Programmierung der VESA-Schnittstelle

Welche VESA-Version wird hier verwendet?

Die aktuellen Spezifikationen liegen mittlerweile in der Version 3.0 (Stand Mai 2000) vor. Gegenüber der Version 1.2 betrifft dies einzig und alleine die Protected Mode-Schnittstelle beim 80386 im 32-Bit-Modus, wo Sie den Videospeicher linear adressieren können. Auch Version 2.0 unterscheidet sich nur in der Einführung dieses linearen Adressierungsmodells gegenüber dem Vorgänger 1.2. Da wir in QuickBASIC ausschliesslich im Real Mode der CPU arbeiten, verwende ich im Beispielprogramm die 1.2er-Schnittstelle, was auch zugleich die minimale Anforderung für Ihre Grafikkarte darstellt. Bei Bedarf sollten Sie also Ihren VBE-Treiber aktualisieren, wofür Sie wie oben beschrieben auf die Heimseite des Grafikkarten-Herstellers gehen sollten.

Bezug der Spezifikationen

Die aktuellen VBE-Spezifikation in der Version 3.0 finden Sie im FTP-Bereich der VESA-Heimseite als vbe3.pdf, welche Sie mit dem Adobe Acrobat Reader lesen können. Falls Sie sich für ältere Versionen interessieren, finden Sie diese noch auf zahlreichen Web-Seiten und Download-Archiven, wovon ich Ihnen hier einige aufzähle:

Aus meiner Sicht sind die älteren Versionen in der Regel wesentlich übersichtlicher. In den folgenden Ausführungen beziehe ich mich daher immer auf Version 1.2. Um möglichst viel zu profitieren, sollten Sie jedoch auch einen Blick in die übrigen Versionen werfen, insbesondere sollten Sie die Veränderungen zwischen den einzelnen Versionen genauer unter die Lupe nehmen, denn Sie lernen dabei, worauf Sie achten müssen, damit Ihre QuickBASIC-Anwendung mit SuperVGA auch in Zukunft voll kompatibel bleibt.

Grundlagen und Philosophie der VESA-Schnittstelle

VESA stellt in diesem Sinne keine Grafikbibliothek dar, fertige Befehle wie LINE und CIRCLE suchen Sie daher vergeblich. VESA stellt Ihnen eigentlich nur die Funktionalitäten auf der niedrigsten Stufe zur Verfügung. Dies sind:

Vielleicht kennen Sie noch den Commodore 64, welcher keine Grafikbefehle in seinem BASIC hatte, so dass Sie direkt mit POKE die Pixel in den Videospeicher schreiben mussten. Vielleicht ist Ihnen dieses Programmfragment dabei noch in guter Erinnerung:

1000 REM PIXEL SETZEN (COMMODORE 64 HIRES-MODUS)
1010 ADR=8192+8*INT(X/8)+320*INT(Y/8)+(Y AND 7)
1020 POKE ADR, PEEK(ADR)OR 2^(7-(X AND 7))
1030 RETURN

Die Grafikprogrammierung mit VESA funktioniert im Grunde genommen sehr ähnlich. Im Gegensatz zum Commodore 64, wo jedes Gerät genau gleich ausgestattet ist, existieren in der PC-Welt sehr vielfältige und unterschiedliche Grafikkarten-Fabrikate. Gäbe es keine VESA-Schnittstelle, so dass wir heute immer noch dieses wilde Chaos bezüglich Standards hätten, so müssten Sie den obigen Algorithmus auf jedem PC individuell anpassen. Ein wesentliches Grundmerkmal von VESA war auch damals die Anforderung, dass die Grafikkarten-Hersteller ihre bereits bestehenden Produkte nachrüsten konnten, mit Hilfe der oben beschriebenen TSR-Programmen. Bei VESA wird dieses Ziel dadurch erreicht, dass Sie einen ähnlichen, jedoch viel universeller geschriebenen POKE-Algorithmus wie oben dargestellt verwenden, welcher vollständig ausparametrisiert ist, d.h. Sie ersetzen sämtliche Zahlenkonstanten durch Variablen. Die VESA-Schnittstelle liefert Ihnen dann eine Beschreibung des Videomodus in Form der ModeInfoBlock-Struktur, aus welcher Sie die konkreten Werte dieser Variablen auslesen können.

Zugriff auf die VESA-Schnittstelle

Der Zugriff auf die VESA-Schnittstelle erfolgt über DOS-Interrupts, und zwar ist dies INT 10h mit Funktionsnummer AH=4Fh und AL=Unterfunktion im Detail:

' Allgemeiner Funktionsaufruf
' $INCLUDE: 'qb.bi'
DIM dosIntEin AS RegTypeX, dosIntAus AS RegTypeX
dosIntEin.ax = &H4Fnn
CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
IF dosIntAus.ax <> &H4F THEN
  PRINT "Fehler beim Aufrufen"
END IF

Das Demonstrationsprogramm VESADEMO.BAS

Für das Demonstrationsprogramm benötigen Sie wegen den CALL INTERRUPT-Aufrufen die Compilerversion von QuickBASIC! Andernfalls müssen Sie diese Aufrufe durch eine Maschinensprache-Routine wie unter DOS-Interrupts beschrieben ersetzen.

Das Demonstrationsprogramm können Sie hier herunterladen, anschliessend sollten Sie Windows komplett beenden, also Start -> Beenden..., Im MS-DOS-Modus neustarten oder beim Einschalten des PC F8 drücken, da Windows (in meinem Fall mit einer ATI xpert@WORK) keine solchen Videomoduswechsel beim MS-DOS-Eingabeaufforderungsfenster mag.

Das Programm liefert Ihnen zu Beginn wie MSD.EXE generelle Informationen zu Ihrer Grafikkarte sowie die Liste aller verfügbaren Videomodi, anschliessend wird Ihnen jeder Videomodus demonstriert, in dem der Bildschirm mit Farbquadraten gefüllt wird. Auf meiner ATI xpert@WORK mit 8 MB Videospeicher kann ich beispielsweise als beste Auflösung 1280×1024 mit 16,7 Millionen Farben (24 Bit TrueColor) dadurch nutzen, bei entsprechendem Monitor wären sogar 1600×1200 Bildpunkte möglich! :-)

Kommentierung des Programms VESADEMO.BAS

Im folgenden wird nur auf die wichtigen Stellen eingegangen, daher finden Sie auch nur einen Teil der Zeilen zitiert.

' VESA-Demonstration: Demonstriert die Verwendung der SuperVGA-Grafikmodi
' aus QuickBASIC heraus unter Zuhilfenahme der VESA-Schnittstelle
' (c) 2000 by Andreas Meile, CH-8242 Hofen SH
' e-Mail: info@dreael.ch  WWW: http://www.hofen.ch/~andreas/

' $INCLUDE: 'qb.bi'

Beachten Sie lediglich die Verwendung von /l qb beim Laden des Compilers QB.EXE, da viele CALL INTERRUPT-Aufrufe zum Einsatz kommen.

TYPE VgaInfoBlock
  VESASignatur AS STRING * 4
  VESAVersionMinor AS STRING * 1
  VESAVersionMajor AS STRING * 1
  OEMStringOffs AS INTEGER
  OEMStringSeg AS INTEGER
  Faehigkeiten AS LONG
  VideoModeListeOffs AS INTEGER
  VideoModeListeSeg AS INTEGER
  TotalSpeicher AS INTEGER
  Reserviert AS STRING * 236
END TYPE

TYPE ModeInfoBlock
  ModusAttribute AS INTEGER
  FenstAAttr AS STRING * 1
  FenstBAttr AS STRING * 1
  FenstGranularitaet AS INTEGER
  FenstGroesse AS INTEGER
  FenstASeg AS INTEGER
  FenstBSeg AS INTEGER
  FenstFktPtrOffs AS INTEGER
  FenstFktPtrSeg AS INTEGER
  ByteProPixelZeile AS INTEGER
  ' ab hier die früher einmal noch freiwilligen Felder
  xAufl AS INTEGER
  YAufl AS INTEGER
  XZeiGroe AS STRING * 1
  YZeiGroe AS STRING * 1
  AnzBitPlanes AS STRING * 1
  AnzBitProPixel AS STRING * 1
  AnzSpeiBaenke AS STRING * 1
  SpeiModell AS STRING * 1
  BankGroesse AS STRING * 1
  AnzBildSeiten AS STRING * 1
  Reserviert1 AS STRING * 1
  ' ab hier die seit VESA 1.2 eingeführten Farbfelder
  RotMaskenGroesse AS STRING * 1
  RotFeldPos AS STRING * 1
  GruenMaskenGroesse AS STRING * 1
  GruenFeldPos AS STRING * 1
  BlauMaskenGroesse AS STRING * 1
  BlauFeldPos AS STRING * 1
  ReservMaskenGroesse AS STRING * 1
  ReservFeldPos AS STRING * 1
  DirektFarbInfo AS STRING * 1
  Reserviert AS STRING * 216
END TYPE

Hier finden Sie in QuickBASIC eine direkte Abbildung der in der Spezifikation aufgeführten Strukturen. Beachten Sie insbesondere die Umsetzung der einzelnen Datentypen aus den dortigen Assembler-Datentypen; aus db (einzelnes Byte!) wird beispielsweise STRING * 1, also Zeichenkette mit fester Länge von 1, dw wird zu Integer% und dd zu Longint&. FAR-Adresszeiger sind als Kombination von Seg% und Offs% dargestellt. Auf die STRING * 1-Werte wird sinnvollerweise immer über ASC()/CHR$() zugegriffen, weil es ja in Wirklichkeit numerische Werte sind.

w% = 128
FOR i% = 0 TO 7
  XPot%(i%) = w%
  w% = w% \ 2
NEXT i%

Die Variable XPot%() wird aus Geschwindigkeitsgründen wegen den bei QuickBASIC fehlenden SHL/SHR-Operatoren erzeugt.

dosIntEin.ax = &H4F00   ' Funktion 4fh (VESA-BIOS), Unterfunktion 00h
                        ' (SuperVGA-Informationen zurückliefern)
dosIntEin.es = VARSEG(infBlk)
dosIntEin.di = VARPTR(infBlk)
CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)

IF dosIntAus.ax <> &H4F THEN  ' AL=4Fh und AH=00h?
  PRINT "Fehler: Keine VESA-fähige Grafikkarte vorhanden!"
  ..
  END
END IF

Und hier erfolgt der allererste Zugriff auf die VESA-Schnittstelle: Wir holen generelle Informationen zur eingebauten Grafikkarte ein. Als Puffer wird direkt eine Strukturvariable vom Typ ModeInfoBlock übergeben, sodass die Felder beim Aufruf an der korrekten Position abgefüllt werden. Beachten Sie dabei noch die Auswertung des Rückgabewertes in AX, ob der Aufruf erfolgreich war oder nicht.

Im Anschluss wird die Struktur ausgewertet, wobei bei den Adresszeigern mit DEF SEG und PEEK() gearbeitet wird. PeekWord%() stellt dabei eine selber definierte Funktion dar, um einen 16-Bit-Wert auslesen zu können.

nModes% = 0
WHILE PeekWord%(i%) <> -1
  vModes%(nModes%) = PeekWord%(i%)
  PRINT " " + FormatHex$(vModes%(nModes%), 4) + "h";
  i% = i% + 2
  nModes% = nModes% + 1
WEND

Einen wichtigen Schritt stellt dabei noch das Auslesen der vorhandenen Videomodi dar. Dazu gibt die VESA-Spezifikation folgende Programmierregel:

Gehen Sie niemals von einer festen Zuordnung der Videomodus-Nummern zu bestimmten Grafikauflösungen und Farbtiefen aus! Fragen Sie stattdessen immer die Liste der verfügbaren Videomodi ab und wählen Sie den für Ihre Anwendung möglichst gut passenden Videomodus aus.

Ebenfalls sehr wichtig:

' Version prüfen
ma% = ASC(infBlk.VESAVersionMajor)
mi% = ASC(infBlk.VESAVersionMinor)
IF ma% < 1 OR ma% = 1 AND mi% < 2 THEN
  PRINT "Es wird im Minimum VESA Version 1.2 benötigt!"
  PRINT "Bitte im WWW nach einem BIOS-Update für die Grafikkarte oder"
  PRINT "neueren VESA-TSR-Programmversion nachschauen"
  END
END IF

Entscheiden Sie sich stets anhand Ihren Anforderungen auf eine bestimmte VESA-Version und fragen Sie diese ab, ob die Mindestanforderung erfüllt ist. Dabei sollten Sie höhere Versionen immer zulassen, da das VESA-Konsortium Abwärtskompatibilität bei neuen Versionen garantiert!

Durch Einhaltung dieser Programmdesign-Regel ersparen Sie zum einen Ihren Endanwender unklare Abstürze infolge von Zugriffen auf bei älteren VESA-Versionen noch nicht vorhandene Funktionen, und zum anderen vermeiden Sie damit die Notwendigkeit solcher Flickschustereien wie SETVER.EXE von MS-DOS oder neuerdings APCOMPAT.EXE bei Windows 2000, denn aus meiner Sicht zeugt solche Software überhaupt nicht von Fachkompetenz! :-(

FOR i% = 1 TO nModes%
  PRINT "Modus"; i%; "von"; nModes%; ": "; FormatHex$(vModes%(i% - 1), 4); "h"
  PRINT "===================================="
  dosIntEin.ax = &H4F01  ' Funktion SuperVGA-Modus zurückliefern
  dosIntEin.cx = vModes%(i% - 1)
  dosIntEin.es = VARSEG(vModBesch)
  dosIntEin.di = VARPTR(vModBesch)
  CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
  IF dosIntAus.ax <> &H4F THEN
    COLOR 30
    PRINT "Fehler: Nicht unterstützt"

In diesem Demonstrationsprogramm gehen wir jeden einzelnen Modus einmal durch, dabei werden zuerst allgemeine Informationen zum Videomodus eingeholt. Im nachfolgenden PRINT-Zeilen-Programmteil sehen Sie selber, welche Informationen es dazu gibt und wie man sie extrahiert.

    dosIntEin.ax = &H4F03   ' SuperVGA-Modus zurückliefern
    CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
    IF dosIntAus.ax <> &H4F THEN
      PRINT "Fehler bei Videomodus lesen"
      END
    END IF
    AlterModus% = dosIntAus.bx

Denken Sie daran: Sauberes Programmdesign bedeutet, dass wir jeweils am Schluss der Arbeit wieder alles so hinterlassen, wie es am Anfang war, unter anderem auch der Videomodus (meistens 80×25-Textmodus).

    dosIntEin.ax = &H4F02   ' SuperVGA-Modus setzen
    dosIntEin.bx = vModes%(i% - 1)
    CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
    IF dosIntAus.ax <> &H4F THEN
      PRINT "Fehler bei Videomodus setzen: ax="; FormatHex$(dosIntAus.ax, 4); "h"

Hier kommt der SCREEN-Ersatz zum Zug: Setzen des Videomodus.

Bevor Sie darauf los zeichnen können, müssen noch die Variablen dieses universell gestalteten PSET-Algorithmus (Unterprogramme VesaPset und VesaPsetPlanar) einmalig vorbereitet werden:

      IF (ASC(vModBesch.FenstAAttr) AND 5) = 5 THEN
        SchrFenst% = 0
        DEF SEG = vModBesch.FenstASeg
      ELSEIF (ASC(vModBesch.FenstBAttr) AND 5) = 5 THEN
        SchrFenst% = 1
        DEF SEG = vModBesch.FenstBSeg
      ELSE
        SchrFenst% = -1  ' Kein Fenster
      END IF
      IF (ASC(vModBesch.FenstAAttr) AND 3) = 3 THEN
        LeseFenst% = 0
      ELSEIF (ASC(vModBesch.FenstBAttr) AND 3) = 3 THEN
        LeseFenst% = 1
      ELSE
        LeseFenst% = -1  ' Kein Fenster
      END IF
      ' In der Demo wird jedoch kein separates Schreib-/Lese-
      ' Speicherfenster benötigt
      dosIntEin.ax = &H4F05
      dosIntEin.bx = SchrFenst% OR 256
      CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
      AktFenst% = dosIntAus.dx
      IF LeseFenst% <> SchrFenst% THEN
        ' separates Lese- und Schreibfenster: Sind beide identisch?
        dosIntEin.ax = &H4F05
        dosIntEin.bx = LeseFenst% OR 256
        CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
        IF dosIntAus.dx <> AktFenst% THEN
          ' Nein => auf gleichen Wert setzen
          dosIntEin.ax = &H4F05
          dosIntEin.bx = LeseFenst%
          dosIntEin.dx = AktFenst%
          CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)
        END IF
      END IF

Beachten Sie hierbei die unterschiedlichen Architekturmerkmale einzelner Grafikkarten-Fabrikate. Einige Hersteller verwenden für das Lesen (PEEK()!) und Schreiben (POKE!) separate Fenster für den Zugriff auf das RAM der Grafikkarte, andere Grafikkarten-Produkte bieten nur ein gemeinsames Speicherfenster an. Einzelne Fabrikate bieten sogar zwei Speicherfenster an, in welchen sowohl gelesen als auch geschrieben werden kann.

Diesem Umstand müssen wir volle Rechnung tragen. Bei dieser Demo wird allgemein nur ein Schreibfenster benötigt. Die einzige Ausnahme stellt der aus 4 Bitebenen zusammengesetzte planare 16-Farben-Modus dar, wo es eine Leseoperation braucht. Die Abfrage sowie das bedingte Setzen des Lesefensters soll sicherstellen, dass beide Fenster auf denselben Speicherbereich zeigen.

      xSpr% = (ASC(vModBesch.AnzBitProPixel) + 7) \ 8
      ySpr& = CLNG(vModBesch.ByteProPixelZeile)
      yJmp& = CLNG(ASC(vModBesch.BankGroesse)) * 1024&
      yInterleav% = ASC(vModBesch.AnzSpeiBaenke)
      FenstGroe& = CLNG(vModBesch.FenstGroesse) * 1024&
      GranSpr% = vModBesch.FenstGroesse \ vModBesch.FenstGranularitaet
      xAufl% = vModBesch.xAufl
      YAufl% = vModBesch.YAufl
      rPos& = Pot2&(ASC(vModBesch.RotFeldPos))
      rDiv% = CINT(Pot2&(8 - ASC(vModBesch.RotMaskenGroesse)))
      gPos& = Pot2&(ASC(vModBesch.GruenFeldPos))
      gDiv% = CINT(Pot2&(8 - ASC(vModBesch.GruenMaskenGroesse)))
      bPos& = Pot2&(ASC(vModBesch.BlauFeldPos))
      bDiv% = CINT(Pot2&(8 - ASC(vModBesch.BlauMaskenGroesse)))

Hier erfolgt die eigentliche Parameterinitialisierung. Beachten Sie hierbei, dass die Parameterstruktur zugunsten einer möglichst hohen Ausführgeschwindigkeit der Punktsetz-Routinen optimiert ist und daher entsprechend durch Umrechnungen aufbereitet werden muss. Weiter ist noch bei Auflösungen wie 15 bpp zu bemerken, dass der X-Bytesprung (xSpr%) immer auf ganze Bytes aufzurunden ist.

      keinDemo% = 0
      SELECT CASE ASC(vModBesch.SpeiModell)
      CASE &H0

Die Auswahl der Demo erfolgt anhand des Speichermodells, also der Datenstrukturierung im RAM der Grafikkarte.

        FOR y% = 1 TO YAufl%
          PrintText y%, 1, 1, 2, FuehrNull$(y%, 3)
        NEXT y%
        FOR x% = 4 TO xAufl%
          h% = x%
          FOR y% = 3 TO 1 STEP -1
            PrintText y%, x%, 13, 0, CHR$(48 + h% MOD 10)
            h% = h% \ 10
          NEXT y%
        NEXT x%
        PrintText 5, 5, 12, 0, "Zeichenvorrat:"
        PrintText 5, 22, 3, 0, "Farben:"
        FOR y% = 0 TO 15
          FOR x% = 0 TO 15
            PrintText 6 + y%, 5 + x%, 7, 0, CHR$(16 * y% + x%)
            PrintText 6 + y%, 22 + x%, x%, y%, "A"
          NEXT x%
        NEXT y%

Im Textmodus sollen die Dimensionen des Bildschirms sowie der Zeichensatz gezeigt werden. Da viele moderne Grafikkarten mittlerweile keine BIOS-Unterstützung anbieten, kommt hier ein eigener PRINT-Ersatz zur Anwendung, bei welchem Sie die Cursorposition zusammen mit den Farben und dem Text angeben können. Die entsprechende Routine verwendet dann direkt POKE auf den Videospeicher.

      CASE &H3
        ' Sequenz aus initPlanar(), um den sog. Write Mode auf 2 zu setzen
        OUT &H3C4, &H2
        OUT &H3C5, &HF
        OUT &H3CE, &H3
        OUT &H3CF, &H0
        OUT &H3CE, &H5
        OUT &H3CF, &H2

Und hier erfolgt im Falle eines 16-Farben-Videomodus die Demo. Beachten Sie hierbei die nötigen Vorbereitungen bei den VGA-Registern, um Farbpixel setzen zu können. Diese OUT-Sequenz habe ich direkt aus dem C-Programmbeispiel aus Anhang 2 der VESA-Spezifikationen Version 3 übernommen.

        FOR y% = 0 TO YAufl% - 1
          FOR x% = 0 TO xAufl% - 1
            VesaPsetPlanar x%, y%, (x% + y%) \ 20 AND 15
          NEXT x%
        NEXT y%

Als Grafikdemo wird ein ganz einfaches Diagonalstreifenmuster mit allen 16 Farben und 20 Pixel breiten Streifen erzeugt.

        ' Wieder auf BIOS-Vorgabewerte setzen gemäss setWriteMode0()
        OUT &H3CE, &H8
        OUT &H3CF, &HFF
        OUT &H3CE, &H5
        OUT &H3CF, &H0

Sauberkeit beim Beenden ist alles :-), daher werden hier die VGA-Register wie im C-Beispiel der VESA-Spezifikationen wieder zurückgesetzt.

      CASE &H6
        FOR y% = 0 TO YAufl% - 1
          FOR x% = 0 TO xAufl% - 1
            VesaPset x%, y%, Farbe&(y% AND 255, x% AND 255, 133)
          NEXT x%
        NEXT y%

Freunde von rasterfreier Echtfarbgrafik aufgepasst: Hier erfolgt die Demonstration des Echtfarbmodus (HiColor und TrueColor), wo Sie jedem Bildpunkt individuelle Rot-Grün-Blau-Werte verpassen können. Als Beispiel werden zweidimensionale Farbkeile in Form von Quadraten dargestellt. Mit einem guten Monitor können Sie dabei die Unterschiede zwischen HiColor und TrueColor sehr gut sehen! Bei mir ist sogar der Unterschied zwischen 32K- (15 bpp) und 64K-HiColor (16 bpp) noch gut zu erkennen. Falls Sie die Grafikauflösung ablesen möchten: Ein Quadrat entspricht immer 256 Pixel.

      CASE &H4
        FOR y% = 0 TO YAufl% - 1
          FOR x% = 0 TO xAufl% - 1
            VesaPset x%, y%, CLNG((x% \ 5 AND 15) + 16 * (y% \ 5 AND 15))
          NEXT x%
        NEXT y%

Und hier erfolgt die 256-Palettengrafik-Demo: Alle 256 Farben je als Farbquadrat von 16×16 Felder, jedes Feld mit 5 Pixel Seitenlänge => Seitenlänge eines ganzen »Ornaments« 80 Pixel, so dass Sie durch Abzählen auch wieder die Grafikauflösung ablesen können.

      dosIntEin.ax = &H4F02   ' SuperVGA-Modus setzen
      dosIntEin.bx = AlterModus%
      CALL INTERRUPTX(&H10, dosIntEin, dosIntAus)

Und hier verlassen wir wieder den SuperVGA-Videomodus und gehen zum ursprünglichen Videomodus zurück.

Nachfolgend noch alle wichtigen Unterprogramme:

FUNCTION Farbe& (r%, g%, b%)
  Farbe& = rPos& * CLNG(r% \ rDiv%) OR gPos& * CLNG(g% \ gDiv%) OR bPos& * CLNG(b% \ bDiv%)
END FUNCTION

Diese Umwandlungsroutine wird nur bei den Echtfarb-Grafikmodi benötigt, um den Speicherinhalt-Farbwert zu berechnen. Beachten Sie dabei die Verwendung der Parametervariablen, insbesondere das offene und neutrale Programmdesign, so dass diese Routine mit jeder nur erdenklichen, auch exotischen Farbwert-Bit-Unterteilung funktioniert. Aus diesem Grund wurden bei VESA 1.2 diese Felder hinzugefügt.

FUNCTION PeekWord% (Adr%)
  PeekWord% = CVI(CHR$(PEEK(Adr%)) + CHR$(PEEK(Adr% + 1)))
END FUNCTION

Die zu Beginn erwähnte 16-Bit-PEEK()-Funktion.

FUNCTION Pot2& (e%)
  h& = 1&
  FOR i% = 1 TO e%
    h& = h& * 2
  NEXT i%
  Pot2& = h&
END FUNCTION

Auch diese Funktion dient nur als Ersatz für die bei QuickBASIC fehlenden Bit-Verschiebeoperatoren.

SUB PrintText (y%, x%, VFar%, HFar%, t$)
  ' Diese Routine läuft auch ohne BIOS-Unterstützung
  Adr% = (y% - 1) * CINT(ySpr&) + 2 * (x% - 1)
  fa% = 16 * HFar% OR VFar%
  FOR i% = 1 TO LEN(t$)
    POKE Adr%, ASC(MID$(t$, i%, 1))
    POKE Adr% + 1, fa%
    Adr% = Adr% + 2
  NEXT i%
END SUB

Hier der BIOS-unterstützungsunabhängige PRINT-Ersatz für eine Textausgabe in Text-Bildschirmmodi.

SUB VesaPset (x%, y%, Farb&)
  DIM dosIntEin1 AS RegType, dosIntAus1 AS RegType
  IF x% >= 0 AND y% >= 0 AND x% < xAufl% AND y% < YAufl% THEN
    AbsAdr& = CLNG(x% * xSpr%) + CLNG(y% \ yInterleav%) * ySpr& + yJmp& * (y% MOD yInterleav%)
    f1& = Farb&
    FOR i% = 1 TO xSpr%
      NeuFenst% = CINT(AbsAdr& \ FenstGroe&) * GranSpr%
      IF AktFenst% <> NeuFenst% THEN
        dosIntEin1.ax = &H4F05
        dosIntEin1.bx = SchrFenst%
        dosIntEin1.dx = NeuFenst%
        CALL INTERRUPT(&H10, dosIntEin1, dosIntAus1)
        AktFenst% = NeuFenst%
      END IF
      POKE AbsAdr& MOD FenstGroe&, CINT(f1& AND 255&)
      AbsAdr& = AbsAdr& + 1
      f1& = f1& \ 256&
    NEXT i%
  END IF
END SUB

Die wichtigste Kernfunktionalität in der VESA-Grafikprogrammierung: Die universell gestaltete POKE-Routine für das Setzen eines Bildpunktes. Beachten Sie hierbei das Setzen des Schreibfensters, welches aus Geschwindigkeitsgründen nur bei Bedarf erfolgt. Dabei ist noch zu bemerken, dass mit der an und für sich langsameren INT-Methode der Aufruf erfolgt anstelle eines FAR-Sprungs. QuickBASIC bietet zwar CALL ABSOLUTE, jedoch können Sie dort keine Parameter in den Registern der CPU hinterlegen.

SUB VesaPsetPlanar (x%, y%, Farb%)
  DIM dosIntEin1 AS RegType, dosIntAus1 AS RegType
  IF x% >= 0 AND y% >= 0 AND x% < xAufl% AND y% < YAufl% THEN
    AbsAdr& = CLNG(x% \ 8) + CLNG(y% \ yInterleav%) * ySpr& + yJmp& * (y% MOD yInterleav%)
    NeuFenst% = CINT(AbsAdr& \ FenstGroe&) * GranSpr%
    IF AktFenst% <> NeuFenst% THEN
      dosIntEin1.ax = &H4F05
      dosIntEin1.bx = SchrFenst%
      dosIntEin1.dx = NeuFenst%
      CALL INTERRUPT(&H10, dosIntEin1, dosIntAus1)
      IF SchrFenst% <> LeseFenst% THEN
        ' Separates Schreib- und Lesefenster => Lesefenster auch setzen
        dosIntEin1.ax = &H4F05
        dosIntEin1.bx = LeseFenst%
        dosIntEin1.dx = NeuFenst%
        CALL INTERRUPT(&H10, dosIntEin1, dosIntAus1)
      END IF
      AktFenst% = NeuFenst%
    END IF
    OUT &H3CE, 8
    OUT &H3CF, XPot%(x% AND 7)
    dummy% = PEEK(AbsAdr& MOD FenstGroe&)
    POKE AbsAdr& MOD FenstGroe&, Farb%
  END IF
END SUB

Spezialfall planarer 16-Farben-Videomodus mit 4 bpp: Die Programmierung entspricht weitgehend der Standard-VGA-Programmierung, ausser dass die Grössen (Auflösung!) als neutrale Parameter erscheinen und ausserdem das Lese- und Schreibfenster bei Bedarf gesetzt werden.

Verwendung in eigenen Programmen

Dieses Beispielprogramm ist eigentlich nicht als Bibliothek gedacht, sondern es soll Ihnen mehr die Grundprinzipien der SuperVGA-Programmierung aufzeigen. Dies haben Sie sicherlich auch bei der Grafikaufbaugeschwindigkeit sehr gut bemerkt. Und dies ist auch der Grund, weshalb fast alle diese im Internet auffindbaren SuperVGA-Grafikbibliotheken auf schnellen Assemblerroutinen basieren.

Aber mit etwas Flair für Software-Design können Sie dieses Beispielprogramm ohne weiteres in eine Bibliothek umwandeln, welche Sie dann nach Belieben in eigenen Projekten verwenden können.

Tips beim Testen von Anwendungen

Gerade bei der Programmierung mit VESA kann es Ihnen recht schnell passieren, dass Sie Ihr Programm ganz unbewusst spezifisch auf die Grafikkarte Ihres PC anpassen und somit Fälle übersehen, mit welchen Sie gemäss Spezifikationen auch immer voll damit rechnen müssen. Typisches Beispiel wäre das Speicherfenster, wenn Sie beispielsweise davon ausgehen, dass dieses für Lesen und Schreiben identisch ist. Daher einige Tips:

Auch ich verwendete zum Test ausser der ATI xpert@WORK auch einmal mein alter 386er mit dem SciTech Display Doctor. Ebenso probierte ich das Demoprogramm noch mit einer Trident 9440 (PCI, 1 MB) sowie mit einer alten Diamond SpeedStar 24 aus. Bei der SpeedStar 24 stiess ich sogar auf einem Fehler in der VBE-Implementation: Bei den Textmodi werden die Pixel statt Textzeilen und -spalten in der ModeInfoBlock-Struktur geliefert, was klar die VESA-Spezifikationen missachtet. Auch Compaq passierte bei der Implementation von CPQVESA.EXE für einen DeskPro XE450 einen Lapsus: Im 800×600/16 Farben-Videomodus werden 800 statt 100 Bytes pro Zeile gemeldet, was entsprechende Darstellungsfehler (immer 7 Pixelzeilen leer gelassen) bewirkt. Ein anderer Bug bewirkt dort ausserdem, dass vom Bild nur Streifen erscheinen. Wenn man die Demonstrationsroutinen in VBETEST.EXE von SciTech vorher einmal laufen gelassen hat, dann verschwindet dieses Problem seltsamerweise, als würde VBETEST.EXE eine »heilende« Wirkung auf die VBE-Routinen im RAM ausüben... Sie sehen: Auch bei den Grafikkarten-Herstellern arbeiten nur Menschen wie Sie und ich... :-)

Für GW-BASIC-Programmierer: Nutzung von VGA

In diesem kleinen Beitrag zeige ich Ihnen noch einen Trick, mit welchem Sie auch in GWBASIC.EXE die Auflösungen SCREEN 11, SCREEN 12 und SCREEN 13 hinzaubern können, und zwar ohne ein einziges Byte Assembler/Maschinensprache. Der Trick beruht auf der Verwendung des con-Gerätes von MS-DOS im Zusammenhang mit ANSI.SYS. Dazu ergänzen Sie zuerst die Datei CONFIG.SYS um eine Zeile

DEVICEHIGH=C:\DOS\ANSI.SYS

bei MS-DOS Version 5.x und 6.x bzw.

DEVICEHIGH=C:\WINDOWS\COMMAND\ANSI.SYS

bei Windows 95 und 98, und starten anschliessend Ihren Rechner neu. Laden Sie dann das Beispielprogramm VGADEMO.BAS. Kurz daraus die wichtigsten Stellen:

150 KEY OFF:SCREEN 1:SCREEN 0

In Zeile 150 wird ein von der Anzahl Zeichen kompatibler Textmodus gesetzt. Dies erlaubt es dann mit PRINT und COLOR arbeiten zu können.

160 OPEN "con" FOR OUTPUT AS 1
170 PRINT#1,CHR$(27);"[19h";
180 CLOSE 1

Hier erfolgt die maschinensprachefreie Videomodusaktivierung durch Ausgabe der passenden Steuersequenz an den ANSI.SYS-Treiber. Unter MS-DOS 6.2x können Sie mit help ansi.sys genauere Details zu diesen Steuersequenzen nachschlagen. In diesem Fall wird der 320×200/256 Farben-Videomodus verwendet.

190 FOR I%=0 TO 240 STEP 16
200 DEF SEG=&HA000+I%*15
210 FOR J%=0 TO 15:C%=I%+J%:B%=20*J%
220 FOR K%=B% TO B%+3520 STEP 320:FOR L%=K% TO K%+19:POKE L%,C%:NEXT:NEXT:NEXT:NEXT

Die Grafikpixel müssen ganz analog dem VESA-Beispielprogramm direkt mit POKE gesetzt werden. Da GWBASIC.EXE nichts von unserem Videomoduswechsel weiss, steht auch dementsprechend PSET nicht zur Verfügung!

230 LOCATE 25,1
240 PRINT"VGA-Demo in GW-BASIC * 1991 by A. Meile";

Weil PRINT im Hintergrund über das BIOS geht, und das BIOS im Gegensatz zu GWBASIC.EXE selber vom Videomodus-Wechsel »Bescheid« weiss, kommt die Textausgabe entsprechend richtig.

270 OPEN "con" FOR OUTPUT AS 1
280 PRINT#1,CHR$(27);"[2J";
290 CLOSE 1

Auch die CLS-Anweisung erfolgt über eine ANSI.SYS-Steuersequenz. Zeilen 300-370 stellen lediglich die zweite Demonstration dar.

380 OPEN "con" FOR OUTPUT AS 1
390 PRINT#1,CHR$(27);"[?1h";
400 CLOSE 1

Sauberes Aufräumen ist oberes Gebot! Daher wird der ursprünglich, »offiziell« gesetzte 40×25-Textmodus mittels Steuerzeichenfolge zurückgesetzt.

410 SCREEN 2:KEY ON:SCREEN 0

Und hier wird GWBASIC.EXE-seitig wieder in den 80×25-Zeichenmodus zurückgekehrt.


Wieder zurück zur Übersicht


© 2000 by Andreas Meile