Skip to content

Instantly share code, notes, and snippets.

@Enichan
Last active December 25, 2022 22:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Enichan/8cefb15f1c325630420c313914706bf6 to your computer and use it in GitHub Desktop.
Save Enichan/8cefb15f1c325630420c313914706bf6 to your computer and use it in GitHub Desktop.
160x120 256 color VGA display mode in QBasic 1.1
DEFINT A-Z
' this program sets 160x120 256 color VGA mode in QB1.1
' downsides: qbasic drawing commands absolutely don't work correctly
' trick qbasic into thinking we're in VGA mode
SCREEN 13
' set EGA mode 0Dh through int 10h in assembly
' this is equivalent to SCREEN 7 and using SCREEN 7 will work, but
' sets flags inside QB that throw errors when using VGA commands
DIM asm AS STRING
asm = asm + CHR$(&H50) ' push ax
asm = asm + CHR$(&HB4) + CHR$(&H00) ' mov ah,0
asm = asm + CHR$(&HB0) + CHR$(&H0D) ' mov al,0Dh
asm = asm + CHR$(&HCD) + CHR$(&H10) ' int 10h
asm = asm + CHR$(&H58) ' pop ax
asm = asm + CHR$(&HCB) ' retf
' call assembly
DEF SEG = VARSEG(asm)
CALL ABSOLUTE(SADD(asm))
' do a whole bunch of VGA port stuff that I stole from allegro's source code
' https://github.com/msikma/allegro-4.2.2-xc/blob/ecd87af7f43f9e08f49acc78f4901c39221dbab5/src/misc/vga.c#L192
' access CRT controller, 3D4h sets register to access (index), 3D5 reads/writes bytes (data)
' http://www.osdever.net/FreeVGA/vga/crtcreg.htm
' http://www.osdever.net/FreeVGA/vga/portidx.htm
' unset protect bit to allow writing registers 0-7
OUT &H3D4, &H11
OUT &H3D5, INP(&H3D5) AND &H7F
' add 1 to horizontal retrace start (add 8 horizontal pixels? 328?)
OUT &H3D4, &H04
OUT &H3D5, INP(&H3D5) + 1
' set protect bit to disallow writing registers 0-7
OUT &H3D4, &H11
OUT &H3D5, INP(&H3D5) OR &H80
' read/write to miscellaneous output register (write 3C2h, read 3CCh)
' set bit 6 (hsyncp) to 0 and bit 7 (vsyncp) to 1
'
' vsyncp:
' Determines the polarity of the vertical sync pulse and can be used (with HSP) to control the vertical size of the display by utilizing the autosynchronization feature of VGA displays. = 0 selects a positive vertical retrace sync pulse.
' hsyncp:
' Determines the polarity of the horizontal sync pulse. = 0 selects a positive horizontal retrace sync pulse.
'
' so this sets a negative vertical retrace sync pulse and a positive horizontal retrace sync pulse
' whatever the hell that means
OUT &H3C2, (INP(&H3CC) AND (NOT &HC0)) OR &H80
' unset protect bit to allow writing registers 0-7
OUT &H3D4, &H11
OUT &H3D5, INP(&H3D5) AND &H7F
' set low 8 bits of vertical total field to 11
' or 00001011b
OUT &H3D4, &H06
OUT &H3D5, &H0B
' set overflow register:
' vrs9 (bit 7): 0
' vde9 (bit 6): 0
' vt9 (bit 5): 1
' lc8 (bit 4): 1
' svb8 (bit 3): 1
' vrs8 (bit 2): 1
' vde8 (bit 1): 1
' vt8 (bit 0): 0
'
' so this sets bits 8 and 9 of vertical total to 10b
' so vertical total is 1000001011b or 523
OUT &H3D4, &H07
OUT &H3D5, &H3E
' set vertical retrace start to 11101010b
' combined with overflow register this becomes 0111101010b or 490
OUT &H3D4, &H10
OUT &H3D5, &HEA
' set vertical retrace end register to 10001100b
' bits 0-3 set the vertical retrace end value and bit 7 reprotects registers 0-7
' vertical retrace end value is 00001100b or 12:
' This field determines the end of the vertical retrace pulse, and thus its length. This field contains the lower four bits of the vertical scanline counter at the beginning of the scanline immediately after the last scanline where the vertical retrace signal is asserted.
OUT &H3D4, &H11
OUT &H3D5, &H8C
' set vertical display end register to 11011111b
' combined with overflow register this becomes 0111011111b or 479
OUT &H3D4, &H12
OUT &H3D5, &HDF
' set start vertical blanking register to 11100111b
' combined with overflow register this becomes 111100111b
' final value depends on maximum scan line register 9h because bit 5 sets bit 9 for this value
OUT &H3D4, &H15
OUT &H3D5, &HE7
' set end vertical blanking register to 4
OUT &H3D4, &H16
OUT &H3D5, &H04
' set protect bit to disallow writing registers 0-7
' shouldn't this be redundant since we set the protect bit earlier when writing 8Ch?
OUT &H3D4, &H11
OUT &H3D5, INP(&H3D5) OR &H80
' set end horizontal retrace register to 01?00000
' some kind of screen centering shenanigans?
OUT &H3CE, &H05
OUT &H3CF, (INP(&H3CF) AND &H60) OR &H40
' read input status register. but why? is this some flushing mechanism?
_ = INP(&H3DA)
' set attribute address register to PAS and attribute index 16 (10h)
' this seems to set the attribute controller to normal operation (as opposed to loading color values)
OUT &H3C0, &H30
' set bit 6 (8BIT) on attribute mode control register:
' When this bit is set to 1, the video data is sampled so that eight bits are available to select a color in the 256-color mode (0x13). This bit is set to 0 in all other modes.
OUT &H3C0, INP(&H3C1) OR &H40
' above code seems to switch the graphics controller to 8-bit palette mode?
' i think this resets the internal 16 color palette? why tho
FOR c = 0 TO 15
OUT &H3C0, c
OUT &H3C0, c
NEXT c
' set the attribute controller to normal operation (as opposed to loading color values)
' why though? is this redundant?
OUT &H3C0, &H20
' set DAC write address to 0 (i think this is where the internal palette is transferred to)
' see palette register documentation:
' These 6-bit registers allow a dynamic mapping between the text attribute or graphic color input value and the display color on the CRT screen. When set to 1, this bit selects the appropriate color. The Internal Palette registers should be modified only during the vertical retrace interval to avoid problems with the displayed image. These internal palette values are sent off-chip to the video DAC, where they serve as addresses into the DAC registers.
OUT &H3C8, &H00
' sequencer registers, 3C4h address register, 3C5h data register
' this unsets bit 3 of the sequencer mode register and then sets bit 3
' which is very weird. wouldn't just the OR suffice?
'
' anyway bit 3 is the chain 4 bit:
' This bit controls the map selected during system read operations. When set to 0, this bit enables system addresses to sequentially access data within a bit map by using the Map Mask register. When set to 1, this bit causes the two low-order bits to select the map accessed as shown below.
' Address Bits
' A0 A1 Map Selected
' 0 0 0
' 0 1 1
' 1 0 2
' 1 1 3
OUT &H3C4, &H04
OUT &H3C5, (INP(&H3C5) AND &HF7) OR 8
' set bit 6 (double-word addressing) of the underline location register register:
' When this bit is set to 1, memory addresses are doubleword addresses. See the description of the word/byte mode bit (bit 6) in the CRT Mode Control Register
OUT &H3D4, &H14
OUT &H3D5, (INP(&H3D5) AND (NOT &H40)) OR 64
' set bit 6 (word/byte) of the crtc mode control register:
' When this bit is set to 0, the word mode is selected. The word mode shifts the memory-address counter bits to the left by one bit; the most-significant bit of the counter appears on the least-significant bit of the memory address outputs. The doubleword bit in the Underline Location register (0x14) also controls the addressing. When the doubleword bit is 0, the word/byte bit selects the mode. When the doubleword bit is set to 1, the addressing is shifted by two bits. When set to 1, bit 6 selects the byte address mode.
OUT &H3D4, &H017
OUT &H3D5, (INP(&H3D5) AND (NOT &H40)) OR 64
' this sets the maximum scan line register to 0??0011b where ? keeps the original bit values
' bit 5 sets bit 9 of the start vertical blanking value
' bit 6 sets bit 9 of the line compare field value
' bits 0-4 set the maximum scan line value:
' In graphics modes, a non-zero value in this field will cause each scan line to be repeated by the value of this field + 1.
' so each scanline is repeated 4 times (presumably for 120 visible lines, since i read that 320x240 mode-x repeats twice)
OUT &H3D4, &H09
OUT &H3D5, (INP(&H3D5) AND &H60) OR 3
' end of VGA controller shenanigans
' if we hadn't tricked qbasic earlier this wouldn't work
PALETTE 1, &H20103F
' draw some stuff
CLS
n = 0
c = 0
DO
DEF SEG = &HA000
FOR i = 0 to 255
POKE i, i
NEXT i
FOR i = 256 to 19199
POKE i, c
NEXT i
POKE 19199, 3
n = n + 1
IF n >= 16 THEN
n = 0
c = c + 1
ENDIF
any$ = INKEY$
LOOP UNTIL any$ <> ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment