18.8 High-Resolution Graphics

Now for the fun stuff! High-resolution graphics is where protected mode really shows off its full capabilities. Most of what we'll do in this section is nearly impossible in real mode due to 64k segment limitations. We're going to make a very short program which will load a 640x480 graphics file from disk and display it on the screen.

18.8.1 Graphics Files

We won't go into all the details of the various graphics file formats here, but let's briefly list the formats supported by PModeLib and its two graphics file libraries:

All of these functions assume an internal pixel format of uncompressed 32-bit RGBA (the loaded A channel is 0 for formats that don't support it). This may or may not be the same format as the video mode selected, so it may be necessary to write conversion functions to convert between the pixel format used by these functions and the pixel format of the display.

18.8.2 Video Graphics

In real mode, we used BIOS Interrupt 10h to set graphics modes and segment B800h or A000h to address the graphics memory. In protected mode, we'll use PModeLib functions to both set the graphics mode and copy image data to the screen. In fact, there isn't a way to directly access the graphics memory, so it's necessary to do double-buffering (although it's possible to double-buffer just regions of the screen rather than the entire display area).

There's another issue with the graphics drivers we use: they require that the keyboard be remapped to a different IRQ and I/O port than the normal keyboard interface (which is at IRQ 1, I/O port 60h). The InitGraphics() function returns the remapped values in the variables whose addresses are passed to it. This will be clearer when we look at the code.

Under Windows 2000/XP, we need to load a special graphics driver called EX291 before running any program that uses PModeLib graphics. Just enter EX291 at the command prompt before running the program to load the driver. Currently, PModeLib graphics require Windows 2000 or Windows XP, they cannot work on Windows 98 or Windows ME.

18.8.3 Displaying an Image on the Screen

Okay, here's the complete code to a simple program that loads a 640x480 image named image.jpg and displays it on the 640x480 display. Since this program uses PModeLib graphics, we'll need to load the EX291 driver before running it on Windows 2000/XP.

This program combines all of the concepts earlier in this chapter, except we're using a specialized image loading function instead of general file I/O to load the image.

Example 18-6. Displaying an Image

%include "lib291.inc"

GLOBAL _main

imagesize equ 640*480*4          ; 640x480 image, 32 bits per pixel

SECTION .bss    ; Uninitialized data

imageoff        resd 1  ; Offset of the image data

doneflag        resb 1  ; =1 when we're ready to exit (set by KeyboardHandler)

kbINT   resb 1  ; keyboard interrupt number (standard = 9)
kbIRQ   resb 1  ; keyboard IRQ (standard = 1)
kbPort  resw 1  ; keyboard port (standard = 60h)

SECTION .data   ; Initialized data

imagefn db "image.jpg",0         ; image file to read (notice 0-terminated)

SECTION .text

KeyboardHandler
        ; Indicate that we're finished on any keypress
        ; If we wanted to check the key, we'd need to use [kbPort], not 60h.
        mov     byte [doneflag], 1

        ; Acknowledge interrupt to PIC.
        ; As the IRQ might be >=8 (a high IRQ), we may need to
        ;  out A0h, 20h, in addition to the normal out 20h, 20h.
        mov     al, 20h
        cmp     byte [kbIRQ], 8
        jb      .lowirq
        out     0A0h, al
.lowirq:
        out     20h, al

        xor     eax, eax         ; Don't chain to old handler
        ret
KeyboardHandler_end

_main
        push    esi              ; Save registers

        call    _LibInit         ; You could use invoke here, too
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .initerror

        ; Allocate memory for image
        invoke  _AllocMem, dword imagesize
        cmp     eax, -1          ; Check for error (-1 return value)
        je      near .error
        mov     [imageoff], eax  ; Save offset

        ; Load image
        invoke  _LoadJPG, dword imagefn, dword [imageoff], dword 0, dword 0
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .error

        ; Initialize graphics (and find remapped keyboard info)
        invoke  _InitGraphics, dword kbINT, dword kbIRQ, dword kbPort
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .error

        ; Lock up memory the handler will access
        invoke  _LockArea, ds, dword doneflag, dword 1
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .exitgraphics

        invoke  _LockArea, ds, dword kbIRQ, dword 1
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .exitgraphics

        ; Lock the interrupt handler itself.
        invoke  _LockArea, cs, dword KeyboardHandler, \
                           dword KeyboardHandler_end-KeyboardHandler
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .exitgraphics

        ; Install the keyboard handler
        movzx   eax, byte [kbINT]
        invoke  _Install_Int, dword eax, dword KeyboardHandler
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .exitgraphics

        ; Find 640x480x32 graphics mode, allowing driver-emulated modes
        invoke  _FindGraphicsMode, word 640, word 480, word 32, dword 1
        cmp     ax, -1           ; Did we find a mode?  If not, exit.
        je      near .uninstallkb

        ; Go into graphics mode (finally :)
        invoke  _SetGraphicsMode, ax
        test    eax, eax         ; Check for error (nonzero return value)
        jnz     near .uninstallkb

        ; Copy the image to the screen
        invoke  _CopyToScreen, dword [imageoff], dword 640*4, \
                               dword 0, dword 0, dword 640, dword 480, dword 0, dword 0

        ; Wait for a keypress
.loop:
        cmp     byte [doneflag], 0
        jz      .loop

        ; Get out of graphics mode
        invoke  _UnsetGraphicsMode

.uninstallkb:
        ; Uninstall the keyboard handler
        movzx   eax, byte [kbINT]
        invoke  _Remove_Int, dword eax

.exitgraphics:
        ; Shut down graphics driver
        invoke  _ExitGraphics

.error:
        call    _LibExit
.initerror:
        pop     esi              ; Restore registers
        ret                      ; Return to DOS