From aa3c978517e9076267cca8eb10701e876bdca64b Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 29 Jun 2015 15:42:43 -0700 Subject: [PATCH] More VGA tests --- tests/pc/vga/L30-1.ASM | 372 ++++++++++++++++++++++++++++++++++++ tests/pc/vga/L30-2.ASM | 419 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 791 insertions(+) create mode 100644 tests/pc/vga/L30-1.ASM create mode 100644 tests/pc/vga/L30-2.ASM diff --git a/tests/pc/vga/L30-1.ASM b/tests/pc/vga/L30-1.ASM new file mode 100644 index 000000000..de844a9e6 --- /dev/null +++ b/tests/pc/vga/L30-1.ASM @@ -0,0 +1,372 @@ +; Demonstrates the VGA/EGA split screen in action. +; +;********************************************************************* +IS_VGA equ 1 ;set to 0 to assemble for EGA +; +VGA_SEGMENT equ 0a000h +SCREEN_WIDTH equ 640 +SCREEN_HEIGHT equ 350 +CRTC_INDEX equ 3d4h ;CRT Controller Index register +OVERFLOW equ 7 ;index of Overflow reg in CRTC +MAXIMUM_SCAN_LINE equ 9 ;index of Maximum Scan Line register + ; in CRTC +START_ADDRESS_HIGH equ 0ch ;index of Start Address High register + ; in CRTC +START_ADDRESS_LOW equ 0dh ;index of Start Address Low register + ; in CRTC +LINE_COMPARE equ 18h ;index of Line Compare reg (bits 7-0 + ; of split screen start scan line) + ; in CRTC +INPUT_STATUS_0 equ 3dah ;Input Status 0 register +WORD_OUTS_OK equ 1 ;set to 0 to assemble for + ; computers that can't handle + ; word outs to indexed VGA registers +;********************************************************************* +; Macro to output a word value to a port. +; +OUT_WORD macro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +;********************************************************************* +MyStack segment para stack 'STACK' + db 512 dup (0) +MyStack ends +;********************************************************************* +Data segment +SplitScreenLine dw ? ;line the split screen currently + ; starts after +StartAddress dw ? ;display memory offset at which + ; scanning for video data starts +; Message displayed in split screen. +SplitScreenMsg db 'Split screen text row #' +DigitInsert dw ? + db '...$' +Data ends +;********************************************************************* +Code segment + assume cs:Code, ds:Data +;********************************************************************* +Start proc near + mov ax,Data + mov ds,ax +; +; Select mode 10h, 640x350 16-color graphics mode. +; + mov ax,0010h ;AH=0 is select mode function + ;AL=10h is mode to select, + ; 640x350 16-color graphics mode + int 10h +; +; Put text into display memory starting at offset 0, with each row +; labelled as to number. This is the part of memory that will be +; displayed in the split screen portion of the display. +; + mov cx,25 ;# of lines of text we'll draw into + ; the split screen part of memory +FillSplitScreenLoop: + mov ah,2 ;set cursor location function # + sub bh,bh ;set cursor in page 0 + mov dh,25 + sub dh,cl ;calculate row to draw in + sub dl,dl ;start in column 0 + int 10h ;set the cursor location + mov al,25 + sub al,cl ;calculate row to draw in again + sub ah,ah ;make the value a word for division + mov dh,10 + div dh ;split the row # into two digits + add ax,'00' ;convert the digits to ASCII + mov [DigitInsert],ax ;put the digits into the text + ; to be displayed + mov ah,9 + mov dx,offset SplitScreenMsg + int 21h ;print the text + loop FillSplitScreenLoop +; +; Fill display memory starting at 8000h with a diagonally striped +; pattern. +; + mov ax,VGA_SEGMENT + mov es,ax + mov di,8000h + mov dx,SCREEN_HEIGHT ;fill all lines + mov ax,8888h ;starting fill pattern + cld +RowLoop: + mov cx,SCREEN_WIDTH/8/2 ;fill 1 scan line a word at a time + rep stosw ;fill the scan line + ror ax,1 ;shift pattern word + dec dx + jnz RowLoop +; +; Set the start address to 8000h and display that part of memory. +; + mov [StartAddress],8000h + call SetStartAddress +; +; Slide the split screen half way up the screen and then back down +; a quarter of the screen. +; + mov [SplitScreenLine],SCREEN_HEIGHT-1 + ;set the initial line just off + ; the bottom of the screen + mov cx,SCREEN_HEIGHT/2 + call SplitScreenUp + mov cx,SCREEN_HEIGHT/4 + call SplitScreenDown +; +; Now move up another half a screen and then back down a quarter. +; + mov cx,SCREEN_HEIGHT/2 + call SplitScreenUp + mov cx,SCREEN_HEIGHT/4 + call SplitScreenDown +; +; Finally move up to the top of the screen. +; + mov cx,SCREEN_HEIGHT/2-2 + call SplitScreenUp +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Turn the split screen off. +; + mov [SplitScreenLine],0ffffh + call SetSplitScreenScanLine +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Display the memory at 0 (the same memory the split screen displays). +; + mov [StartAddress],0 + call SetStartAddress +; +; Flip between the split screen and the normal screen every 10th +; frame until a key is pressed. +; +FlipLoop: + xor [SplitScreenLine],0ffffh + call SetSplitScreenScanLine + mov cx,10 +CountVerticalSyncsLoop: + call WaitForVerticalSyncEnd + loop CountVerticalSyncsLoop + mov ah,0bh ;DOS character available status + int 21h + and al,al ;character available? + jz FlipLoop ;no, toggle split screen on/off status + mov ah,1 + int 21h ;clear the character +; +; Return to text mode and DOS. +; + mov ax,0003h ;AH=0 is select mode function + ;AL=3 is mode to select, text mode + int 10h ;return to text mode + mov ah,4ch + int 21h ;return to DOS +Start endp +;********************************************************************* +; Waits for the leading edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncStart proc near + mov dx,INPUT_STATUS_0 +WaitNotVerticalSync: + in al,dx + test al,08h + jnz WaitNotVerticalSync +WaitVerticalSync: + in al,dx + test al,08h + jz WaitVerticalSync + ret +WaitForVerticalSyncStart endp +;********************************************************************* +; Waits for the trailing edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncEnd proc near + mov dx,INPUT_STATUS_0 +WaitVerticalSync2: + in al,dx + test al,08h + jz WaitVerticalSync2 +WaitNotVerticalSync2: + in al,dx + test al,08h + jnz WaitNotVerticalSync2 + ret +WaitForVerticalSyncEnd endp +;********************************************************************* +; Sets the start address to the value specifed by StartAddress. +; Wait for the trailing edge of vertical sync before setting so that +; one half of the address isn't loaded before the start of the frame +; and the other half after, resulting in flicker as one frame is +; displayed with mismatched halves. The new start address won't be +; loaded until the start of the next frame; that is, one full frame +; will be displayed before the new start address takes effect. +; +; Input: none +; +; Output: none +; +; Registers altered: AX, DX +; +SetStartAddress proc near + call WaitForVerticalSyncEnd + mov dx,CRTC_INDEX + mov al,START_ADDRESS_HIGH + mov ah,byte ptr [StartAddress+1] + cli ;make sure both registers get set at once + OUT_WORD + mov al,START_ADDRESS_LOW + mov ah,byte ptr [StartAddress] + OUT_WORD + sti + ret +SetStartAddress endp +;********************************************************************* +; Sets the scan line the split screen starts after to the scan line +; specified by SplitScreenLine. +; +; Input: none +; +; Output: none +; +; All registers preserved +; +SetSplitScreenScanLine proc near + push ax + push cx + push dx +; +; Wait for the leading edge of the vertical sync pulse. This ensures +; that we don't get mismatched portions of the split screen setting +; while setting the two or three split screen registers (register 18h +; set but register 7 not yet set when a match occurs, for example), +; which could produce brief flickering. +; + call WaitForVerticalSyncStart +; +; Set the split screen scan line. +; + mov dx,CRTC_INDEX + mov ah,byte ptr [SplitScreenLine] + mov al,LINE_COMPARE + cli ;make sure all the registers get set at once + OUT_WORD ;set bits 7-0 of the split screen scan line + mov ah,byte ptr [SplitScreenLine+1] + and ah,1 + mov cl,4 + shl ah,cl ;move bit 8 of the split split screen scan + ; line into position for the Overflow reg + mov al,OVERFLOW +if IS_VGA +; +; The Split Screen, Overflow, and Line Compare registers all contain +; part of the split screen start scan line on the VGA. We'll take +; advantage of the readable registers of the VGA to leave other bits +; in the registers we access undisturbed. +; + out dx,al ;set CRTC Index reg to point to Overflow + inc dx ;point to CRTC Data reg + in al,dx ;get the current Overflow reg setting + and al,not 10h ;turn off split screen bit 8 + or al,ah ;insert the new split screen bit 8 + ; (works in any mode) + out dx,al ;set the new split screen bit 8 + dec dx ;point to CRTC Index reg + mov ah,byte ptr [SplitScreenLine+1] + and ah,2 + mov cl,3 + ror ah,cl ;move bit 9 of the split split screen scan + ; line into position for the Maximum Scan + ; Line register + mov al,MAXIMUM_SCAN_LINE + out dx,al ;set CRTC Index reg to point to Maximum + ; Scan Line + inc dx ;point to CRTC Data reg + in al,dx ;get the current Maximum Scan Line setting + and al,not 40h ;turn off split screen bit 9 + or al,ah ;insert the new split screen bit 9 + ; (works in any mode) + out dx,al ;set the new split screen bit 9 +else +; +; Only the Split Screen and Overflow registers contain part of the +; Split Screen start scan line and need to be set on the EGA. +; EGA registers are not readable, so we have to set the non-split +; screen bits of the Overflow register to a preset value, in this +; case the value for 350-scan-line modes. +; + or ah,0fh ;insert the new split screen bit 8 + ; (only works in 350-scan-line EGA modes) + OUT_WORD ;set the new split screen bit 8 +endif + sti + pop dx + pop cx + pop ax + ret +SetSplitScreenScanLine endp +;********************************************************************* +; Moves the split screen up the specified number of scan lines. +; +; Input: CX = # of scan lines to move the split screen up by +; +; Output: none +; +; Registers altered: CX +; +SplitScreenUp proc near +SplitScreenUpLoop: + dec [SplitScreenLine] + call SetSplitScreenScanLine + loop SplitScreenUpLoop + ret +SplitScreenUp endp +;********************************************************************* +; Moves the split screen down the specified number of scan lines. +; +; Input: CX = # of scan lines to move the split screen down by +; +; Output: none +; +; Registers altered: CX +; +SplitScreenDown proc near +SplitScreenDownLoop: + inc [SplitScreenLine] + call SetSplitScreenScanLine + loop SplitScreenDownLoop + ret +SplitScreenDown endp +;********************************************************************* +Code ends + end Start diff --git a/tests/pc/vga/L30-2.ASM b/tests/pc/vga/L30-2.ASM new file mode 100644 index 000000000..c29a26622 --- /dev/null +++ b/tests/pc/vga/L30-2.ASM @@ -0,0 +1,419 @@ +; Demonstrates the interaction of the split screen and +; horizontal pel panning. On a VGA, first pans right in the top +; half while the split screen jerks around, because split screen +; pel panning suppression is disabled, then enables split screen +; pel panning suppression and pans right in the top half while the +; split screen remains stable. On an EGA, the split screen jerks +; around in both cases, because the EGA doesn't support split +; screen pel panning suppression. +; +; The jerking in the split screen occurs because the split screen +; is being pel panned (panned by single pixels--intrabyte panning), +; but is not and cannot be byte panned (panned by single bytes-- +; "extrabyte" panning) because the start address of the split screen +; is forever fixed at 0. +;********************************************************************* +IS_VGA equ 1 ;set to 0 to assemble for EGA +; +VGA_SEGMENT equ 0a000h +LOGICAL_SCREEN_WIDTH equ 1024 ;# of pixels across virtual + ; screen that we'll pan across +SCREEN_HEIGHT equ 350 +SPLIT_SCREEN_START equ 200 ;start scan line for split screen +SPLIT_SCREEN_HEIGHT equ SCREEN_HEIGHT-SPLIT_SCREEN_START-1 +CRTC_INDEX equ 3d4h ;CRT Controller Index register +AC_INDEX equ 3c0h ;Attribute Controller Index reg +OVERFLOW equ 7 ;index of Overflow reg in CRTC +MAXIMUM_SCAN_LINE equ 9 ;index of Maximum Scan Line register + ; in CRTC +START_ADDRESS_HIGH equ 0ch ;index of Start Address High register + ; in CRTC +START_ADDRESS_LOW equ 0dh ;index of Start Address Low register + ; in CRTC +HOFFSET equ 13h ;index of Horizontal Offset register + ; in CRTC +LINE_COMPARE equ 18h ;index of Line Compare reg (bits 7-0 + ; of split screen start scan line) + ; in CRTC +AC_MODE_CONTROL equ 10h ;index of Mode Control reg in AC +PEL_PANNING equ 13h ;index of Pel Panning reg in AC +INPUT_STATUS_0 equ 3dah ;Input Status 0 register +WORD_OUTS_OK equ 1 ;set to 0 to assemble for + ; computers that can't handle + ; word outs to indexed VGA registers +;********************************************************************* +; Macro to output a word value to a port. +; +OUT_WORD macro +if WORD_OUTS_OK + out dx,ax +else + out dx,al + inc dx + xchg ah,al + out dx,al + dec dx + xchg ah,al +endif + endm +;********************************************************************* +MyStack segment para stack 'STACK' + db 512 dup (0) +MyStack ends +;********************************************************************* +Data segment +SplitScreenLine dw ? ;line the split screen currently + ; starts after +StartAddress dw ? ;display memory offset at which + ; scanning for video data starts +PelPan db ? ;current intrabyte horizontal pel + ; panning setting +Data ends +;********************************************************************* +Code segment + assume cs:Code, ds:Data +;********************************************************************* +Start proc near + mov ax,Data + mov ds,ax +; +; Select mode 10h, 640x350 16-color graphics mode. +; + mov ax,0010h ;AH=0 is select mode function + ;AL=10h is mode to select, + ; 640x350 16-color graphics mode + int 10h +; +; Set the Offset register to make the offset from the start of one +; scan line to the start of the next the desired number of pixels. +; This gives us a virtual screen wider than the actual screen to +; pan across. +; Note that the Offset register is programmed with the logical +; screen width in words, not bytes, hence the final division by 2. +; + mov dx,CRTC_INDEX + mov ax,(LOGICAL_SCREEN_WIDTH/8/2 shl 8) or HOFFSET + OUT_WORD +; +; Set the start address to display the memory just past the split +; screen memory. +; + mov [StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) + call SetStartAddress +; +; Set the split screen start scan line. +; + mov [SplitScreenLine],SPLIT_SCREEN_START + call SetSplitScreenScanLine +; +; Fill the split screen portion of display memory (starting at +; offset 0) with a choppy diagonal pattern sloping left. +; + mov ax,VGA_SEGMENT + mov es,ax + sub di,di + mov dx,SPLIT_SCREEN_HEIGHT + ;fill all lines in the split screen + mov ax,0FF0h ;starting fill pattern + cld +RowLoop: + mov cx,LOGICAL_SCREEN_WIDTH/8/4 + ;fill 1 scan line +ColumnLoop: + stosw ;draw part of a diagonal line + mov word ptr es:[di],0 ;make vertical blank spaces so + ; panning effects can be seen easily + inc di + inc di + loop ColumnLoop + rol ax,1 ;shift pattern word + dec dx + jnz RowLoop +; +; Fill the portion of display memory that will be displayed in the +; normal screen (the non-split screen part of the display) with a +; choppy diagonal pattern sloping right. +; + mov di,SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) + mov dx,SCREEN_HEIGHT ;fill all lines + mov ax,0c510h ;starting fill pattern + cld +RowLoop2: + mov cx,LOGICAL_SCREEN_WIDTH/8/4 + ;fill 1 scan line +ColumnLoop2: + stosw ;draw part of a diagonal line + mov word ptr es:[di],0 ;make vertical blank spaces so + ; panning effects can be seen easily + inc di + inc di + loop ColumnLoop2 + ror ax,1 ;shift pattern word + dec dx + jnz RowLoop2 +; +; Pel pan the non-split screen portion of the display; because +; split screen pel panning suppression is not turned on, the split +; screen jerks back and forth as the pel panning setting cycles. +; + mov cx,200 ;pan 200 pixels to the left + call PanRight +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Return to the original screen location, with pel panning turned off. +; + mov [StartAddress],SPLIT_SCREEN_HEIGHT*(LOGICAL_SCREEN_WIDTH/8) + call SetStartAddress + mov [PelPan],0 + call SetPelPan +; +; Turn on split screen pel panning suppression, so the split screen +; won't be affected by pel panning. Not done on EGA because both +; readable registers and the split screen pel panning suppression bit +; aren't supported by EGAs. +; +if IS_VGA + mov dx,INPUT_STATUS_0 + in al,dx ;reset the AC Index/Data toggle to + ; Index state + mov al,20h+AC_MODE_CONTROL + ;bit 5 set to 1 to keep video on + mov dx,AC_INDEX ;point to AC Index/Data register + out dx,al + inc dx ;point to AC Data reg (for reads only) + in al,dx ;get the current AC Mode Control reg + or al,20h ;enable split screen pel panning + ; suppression + dec dx ;point to AC Index/Data reg (Data for + ; writes only) + out dx,al ;write the new AC Mode Control setting + ; with split screen pel panning + ; suppression turned on +endif +; +; Pel pan the non-split screen portion of the display; because +; split screen pel panning suppression is turned on, the split +; screen will not move as the pel panning setting cycles. +; + mov cx,200 ;pan 200 pixels to the left + call PanRight +; +; Wait for a key press (don't echo character). +; + mov ah,8 ;DOS console input without echo function + int 21h +; +; Return to text mode and DOS. +; + mov ax,0003h ;AH=0 is select mode function + ;AL=3 is mode to select, text mode + int 10h ;return to text mode + mov ah,4ch + int 21h ;return to DOS +Start endp +;********************************************************************* +; Waits for the leading edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncStart proc near + mov dx,INPUT_STATUS_0 +WaitNotVerticalSync: + in al,dx + test al,08h + jnz WaitNotVerticalSync +WaitVerticalSync: + in al,dx + test al,08h + jz WaitVerticalSync + ret +WaitForVerticalSyncStart endp +;********************************************************************* +; Waits for the trailing edge of the vertical sync pulse. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +WaitForVerticalSyncEnd proc near + mov dx,INPUT_STATUS_0 +WaitVerticalSync2: + in al,dx + test al,08h + jz WaitVerticalSync2 +WaitNotVerticalSync2: + in al,dx + test al,08h + jnz WaitNotVerticalSync2 + ret +WaitForVerticalSyncEnd endp +;********************************************************************* +; Sets the start address to the value specifed by StartAddress. +; Wait for the trailing edge of vertical sync before setting so that +; one half of the address isn't loaded before the start of the frame +; and the other half after, resulting in flicker as one frame is +; displayed with mismatched halves. The new start address won't be +; loaded until the start of the next frame; that is, one full frame +; will be displayed before the new start address takes effect. +; +; Input: none +; +; Output: none +; +; Registers altered: AX, DX +; +SetStartAddress proc near + call WaitForVerticalSyncEnd + mov dx,CRTC_INDEX + mov al,START_ADDRESS_HIGH + mov ah,byte ptr [StartAddress+1] + cli ;make sure both registers get set at once + OUT_WORD + mov al,START_ADDRESS_LOW + mov ah,byte ptr [StartAddress] + OUT_WORD + sti + ret +SetStartAddress endp +;********************************************************************* +; Sets the horizontal pel panning setting to the value specified +; by PelPan. Waits until the start of vertical sync to do so, so +; the new pel pan setting can be loaded during non-display time +; and can be ready by the start of the next frame. +; +; Input: none +; +; Output: none +; +; Registers altered: AL, DX +; +SetPelPan proc near + call WaitForVerticalSyncStart ;also resets the AC + ; Index/Data toggle + ; to Index state + mov dx,AC_INDEX + mov al,PEL_PANNING+20h + ;bit 5 set to 1 to keep video on + out dx,al ;point the AC Index to Pel Pan reg + mov al,[PelPan] + out dx,al ;load the new Pel Pan setting + ret +SetPelPan endp +;********************************************************************* +; Sets the scan line the split screen starts after to the scan line +; specified by SplitScreenLine. +; +; Input: none +; +; Output: none +; +; All registers preserved +; +SetSplitScreenScanLine proc near + push ax + push cx + push dx +; +; Wait for the leading edge of the vertical sync pulse. This ensures +; that we don't get mismatched portions of the split screen setting +; while setting the two or three split screen registers (register 18h +; set but register 7 not yet set when a match occurs, for example), +; which could produce brief flickering. +; + call WaitForVerticalSyncStart +; +; Set the split screen scan line. +; + mov dx,CRTC_INDEX + mov ah,byte ptr [SplitScreenLine] + mov al,LINE_COMPARE + cli ;make sure all the registers get set at once + OUT_WORD ;set bits 7-0 of the split screen scan line + mov ah,byte ptr [SplitScreenLine+1] + and ah,1 + mov cl,4 + shl ah,cl ;move bit 8 of the split split screen scan + ; line into position for the Overflow reg + mov al,OVERFLOW +if IS_VGA +; +; The Split Screen, Overflow, and Line Compare registers all contain +; part of the split screen start scan line on the VGA. We'll take +; advantage of the readable registers of the VGA to leave other bits +; in the registers we access undisturbed. +; + out dx,al ;set CRTC Index reg to point to Overflow + inc dx ;point to CRTC Data reg + in al,dx ;get the current Overflow reg setting + and al,not 10h ;turn off split screen bit 8 + or al,ah ;insert the new split screen bit 8 + ; (works in any mode) + out dx,al ;set the new split screen bit 8 + dec dx ;point to CRTC Index reg + mov ah,byte ptr [SplitScreenLine+1] + and ah,2 + mov cl,3 + ror ah,cl ;move bit 9 of the split split screen scan + ; line into position for the Maximum Scan + ; Line register + mov al,MAXIMUM_SCAN_LINE + out dx,al ;set CRTC Index reg to point to Maximum + ; Scan Line + inc dx ;point to CRTC Data reg + in al,dx ;get the current Maximum Scan Line setting + and al,not 40h ;turn off split screen bit 9 + or al,ah ;insert the new split screen bit 9 + ; (works in any mode) + out dx,al ;set the new split screen bit 9 +else +; +; Only the Split Screen and Overflow registers contain part of the +; Split Screen start scan line and need to be set on the EGA. +; EGA registers are not readable, so we have to set the non-split +; screen bits of the Overflow register to a preset value, in this +; case the value for 350-scan-line modes. +; + or ah,0fh ;insert the new split screen bit 8 + ; (only works in 350-scan-line EGA modes) + OUT_WORD ;set the new split screen bit 8 +endif + sti + pop dx + pop cx + pop ax + ret +SetSplitScreenScanLine endp +;********************************************************************* +; Pan horizontally to the right the number of pixels specified by CX. +; +; Input: CX = # of pixels by which to pan horizontally +; +; Output: none +; +; Registers altered: AX, CX, DX +; +PanRight proc near +PanLoop: + inc [PelPan] + and [PelPan],07h + jnz DoSetStartAddress + inc [StartAddress] +DoSetStartAddress: + call SetStartAddress + call SetPelPan + loop PanLoop + ret +PanRight endp +;********************************************************************* +Code ends + end Start