You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm trying to activate the 3Dfx Voodoo1 window (i.e., the 3dfx output surface) in DOSBox-X, using pure assembly (FASM). The code is a .COM file that enters protected mode and writes directly to the card’s MMIO registers (e.g. initEnable, FBI_INITx, etc.).
The problem: The window only appears if I run test00.exe (a benchmark tool from a 3dfx SDK) first. After that, even a single write like FBI_INIT0 |= 1 makes the window open. But without running test00.exe first, nothing happens - not even with full init.
This suggests that test00.exe enables some global or persistent state in DOSBox-X that stays active after exit. I’d like to know exactly what that is, so I can reproduce it fully within my own program. I want to avoid Glide and any external tools entirely.
I even did an MMIO dump before and after running test00.exe, and tried to reproduce the differences without luck.
My code sets up PMODE, enables I/O (IOPL=3), configures the PCI command register, reads BAR0 (assumed at 0xD0000000), and writes all the known MMIO registers - including FBI_INIT0–5, MISCINIT0–1, REG_CLOCK_ENABLE, VIDEODIMS, HSYNC, VSYNC, and so on - followed by FIFO commands like SWAPBUFFERCMD. Still no window.
I’ve read the SST-1 Rev 1.61 documentation, used the DOSBox-X debugger, experimented with glide2x.ovl, and even tried to reverse test00.exe and parts of Glide. I've also looked into voodoo.cpp and pci_bus.cpp from dosbox-pure, but I still don't see what triggers the actual window display in DOSBox-X.
...but that depends on glide2x.ovl. I'm trying to emulate all of this manually in ASM.
Ultimately, I’d like to shrink this to a 256b or 1k intro. But before optimizing anything, I need to understand how the Voodoo1 window is actually activated in DOSBox-X. Which MMIO register, PCI flag, or internal state triggers it?
If anyone with technical insight into DOSBox-X or 3Dfx internals has ideas, especially about what triggers the graphics window for voodoo1. I’d really appreciate your help.
Full source is attached in ZIP file to this post with necessary .INC files. VOOSRC.ZIP. I also provided the main assembly code below.
🙏 Thanks in advance!
include "pci.inc"
include "print.inc"
include "util.inc"
include "VOODEFS.INC"
org 0x100
use16
start:
Clearscreen
; ---- save real-mode CS ----
mov ax, cs
mov [REAL_SEGMENT], ax
mov [LINEAR_SEG_BASE], cs
shl dword [LINEAR_SEG_BASE], 4 ; CS*16
; Initialize GDT
mov eax, [LINEAR_SEG_BASE] ; Set GDTR base
add ax, gdt
mov dword [gdt_info + 2], eax
;-----------------------------------------------------
; Enter PMODE / “unreal mode” (flat 4 GB data segment)
;-----------------------------------------------------
use16
cli
lgdt [gdt_info] ; point GDTR at our GDT
; IOPL = 3 / CPL = 0 (I/O privileges)
mov eax, cr0
or eax, 1 ; set PE -> enter protected mode
mov cr0, eax
PATCH_GDT_BASE_MEM 0x08, LINEAR_SEG_BASE ; code @ selector 0x08
PATCH_GDT_BASE_ZERO 0x10 ; data @ selector 0x10 at base = 0
jmp far 8:pm_start ; CS = 0x08
;-----------------------------------------------------
; 3-entry GDT (exactly 24 bytes total)
; Entry 0 = null
; Entry 1 = code, base=0, limit=0xFFFFF, 32-bit
; Entry 2 = data, base=0, limit=0xFFFFF, 32-bit
;-----------------------------------------------------
use16
gdt:
dq 0 ; 8 bytes, descriptor #0
dw 0FFFFh ; limit low
dw 00000h ; base low
db 00000000b ; base mid
db 10011010b ; access = 0x9A (P=1, DPL=0, S=1, Ex/R)
db 11001111b ; flags = 0xCF (G=1, D/B=1, L=0, AVL=0, limit hi=0xF)
db 00000000b ; base high
dw 0FFFFh
dw 00000h
db 00000000b
db 10010010b ; access = 0x92 (P=1, DPL=0, S=1, RW)
db 11001111b
db 00000000b
gdt_end:
gdt_info:
dw (3*8)-1 ;gdt_end - gdt - 1
dd 0 ;gdt
;-----------------------------------------------------------------
; Protected-mode “middle” to load flat DS/ES then pop back to real
;-----------------------------------------------------------------
use32
pm_start:
; — enable I/O privileges (IOPL=3) —
pushfd
or dword [esp], 0x3000 ; sett bit 12+13 i EFLAGS
popfd
mov ax, 0x10 ; sel=0x10 → our flat data descriptor
mov ds, ax
; mov ax, ds
; PRINT_HEXBYTE_AT32 ah, 0,0 ; shows high byte of DS (should be 0x10)
; PRINT_HEXBYTE_AT32 al, 2,0 ; low byte = 0x10
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax ; alternative, but clean
; mov esp, 0x90000
; disable paging (bit 31 of CR0) for full 4 GB window
mov eax, cr0
btr eax, 31 ; clear PG
mov cr0, eax
;jmp tst
; — Read VendorID/DeviceID @ cfg-offset 0x00 —
READ32_CF8 0
; EAX = [DeviceID(31–16) | VendorID(15–0)]
mov cx, ax ; CX = VendorID (low 16 bits)
shr eax, 16
mov dx, ax ; DX = DeviceID (now in low 16 bits of AX)
; print VendorID at (col=0,row=0)
PRINT_HEXBYTE_AT32 ch, 0,0
PRINT_HEXBYTE_AT32 cl, 2,0
PRINT_CHAR_AT32 ' ', 4,0
; print DeviceID at (col=5,row=0)
PRINT_HEXBYTE_AT32 dh, 5,0
PRINT_HEXBYTE_AT32 dl, 7,0
;==================================================================
; Enable Memory-Space + Bus-Master on function 0 (offset 4)
;==================================================================
; build CONFIG_ADDRESS for bus=0,dev=0,func=0,offset=4 —
mov ebx, 0x80000104 ; target function 1 (not 0)
mov edx, 0xCF8 ; read current DWORD
mov eax, ebx
out dx, eax
mov edx, 0xCFC
in eax, dx
mov esi, eax ; ESI = [Status<<16 | Command]
or esi, 3 ; set MEM_EN (bit1) and BM_EN (bit2)
mov edx, 0xCF8 ; write back
mov eax, ebx
out dx, eax
mov edx, 0xCFC
mov eax, 3 ;esi
out dx, eax
;— verify and display Command/Status at row 1 —
mov edx, 0xCF8
mov eax, ebx
out dx, eax
mov edx, 0xCFC
in eax, dx
mov dx, ax ; DX = Command
shr eax, 16
mov cx, ax ; CX = Status
PRINT_HEXBYTE_AT32 dh, 0,1
PRINT_HEXBYTE_AT32 dl, 2,1
PRINT_CHAR_AT32 ' ',4,1
PRINT_HEXBYTE_AT32 ch, 5,1
PRINT_HEXBYTE_AT32 cl, 7,1
; Turn on INIT_ENABLE (PCI config 0x40) in pmode:
; — build CONFIG_ADDRESS for bus=0,dev=0,func=0,offset=0x40 —
mov ebx, 0x80000140 ; target function 1
;— les original verdi —
mov edx, 0xCF8
mov eax, ebx
out dx, eax
mov edx, 0xCFC
in eax, dx ; EAX ← INIT_ENABLE dword
;— sett bit0+1 —
or eax, 3
mov esi, eax ; bevar nytt INIT_ENABLE
;— skriv tilbake —
mov edx, 0xCF8
mov eax, ebx
out dx, eax
mov edx, 0xCFC
mov eax, esi ; bruk OR-resultatet nå!
out dx, eax
;— re-les for verify —
mov edx, 0xCF8
mov eax, ebx
out dx, eax
mov edx, 0xCFC
in eax, dx ; EAX ← ny verdi
;— print på ei sikker plass —
PRINT_HEXBYTE_AT32 al, 12, 1
; -----------------------------------------------
; in PMODE, read BAR0 from PCI config via CF8/CFC
; -----------------------------------------------
READ32_CF8 0x10 ; EAX = raw BAR0
and eax, 0FFFFFFF0h ; mask bottom bits
add eax, 0C0h ; add your voodoo window offset
; now EAX holds the 32-bit MMIO address, let’s split and display it
mov ecx, eax
shr ecx, 16 ; DH:DL = high word of (BAR0+C0)
mov bh, byte ch ; high byte of high word
mov bl, byte cl ; low byte of high word
; print BH,BL at e.g. (col=20,row=10)
PRINT_HEXBYTE_AT32 bh, 0, 2
PRINT_HEXBYTE_AT32 bl, 2, 2
mov ecx, eax ; now low word
and ecx, 0FFFFh
PRINT_HEXBYTE_AT32 ch, 4, 2
PRINT_HEXBYTE_AT32 cl, 6, 2
;tst:
; — grGlideInit() emulation —
PCI_INIT_ENABLE 07Fh ; Reset + enable init-writes + FIFO
;MMIO writes
V1_POKE32 FBI_INIT0,000001FBh
V1_POKE32 FBI_INIT1,00400140h
V1_POKE32 FBI_INIT2,186000E0h
V1_POKE32 FBI_INIT3,00000040h
V1_POKE32 FBI_INIT4,00000002h
V1_POKE32 FBI_INIT5,00000002h
WAIT_IDLE_SAFE
PCI_INIT_ENABLE 067h ; Release reset (keep mem/video/FIFO enabled)
WAIT_IDLE_SAFE
PCI_INIT_ENABLE 0E7h ; Finalize: turn on host-FIFO
WAIT_IDLE_SAFE
V1_POKE32 MISCINIT0, 0x00C00000
V1_POKE32 MISCINIT1, 0x01A00010
WAIT_IDLE_SAFE
V1_POKE32 REG_CLOCK_ENABLE, 0x1F
WAIT_IDLE_SAFE
; — grSstSelect(0) —
; is only used if it supporst more than one voodoo card.
; V1_POKE32 0xC0,00000000h
; WAIT_IDLE_SAFE
; V1_POKE32 MISCINIT0, 0x00C00000
; V1_POKE32 MISCINIT1, 0x01A00010
; WAIT_IDLE_SAFE
; — grSstWinOpen(0,640x480,…) —
V1_POKE32 VIDEODIMS, (480 shl 16) or (640-1)
V1_POKE32 HSYNC, (31 shl 16) or (96-1)
V1_POKE32 VSYNC, (520 shl 16) or 490
V1_POKE32 BACKPORCH, (10 shl 16) or (40-2)
V1_POKE32 FBI_COLBUFBASE,0
V1_POKE32 FBI_COLBUFSTRIDE, 1280
V1_POKE32 FBI_COLBUFORG,0
V1_POKE32 FBI_COLBUFRES,(480 shl 16) or 640
WAIT_IDLE_SAFE
V1_POKE32 REG_DAC_EN,0FFh
V1_POKE32 REG_DAC_MODE,02h
V1_POKE32 FBI_INIT_ENABLE, 0x67
V1_POKE32 FBI_INIT_ENABLE, 0xE7
WAIT_IDLE_SAFE
V1_POKE32 FBI_INIT1, 0x0041E140
; V1_POKE32 FBI_INIT1,00400040h ; video reset off.
WAIT_IDLE_SAFE
; V1_POKEFIFO SWAPBUFFERCMD,1
WAIT_IDLE_SAFE
jmp over2
; ==== now we can draw ====
; clear to dark blue:
V1_POKE32 FBZMODE, 04000001h ; flat RGB, no Z
WAIT_IDLE_SAFE
V1_POKE32 COLBUFF_ENABLE, 1 ; color buffer #1
WAIT_IDLE_SAFE
V1_POKE32 FASTFILL_COLOR, 00000080h ; low‐byte=blue
WAIT_IDLE_SAFE
V1_POKE32 FASTFILL_AREA, (480 shl 16) or 640
WAIT_IDLE_SAFE
V1_POKE32 FASTFILL_TRIGGER, 1
WAIT_IDLE_SAFE
over2:
; clear the depth‐buffer
V1_POKE32 FBZMODE, 08000001h ; depth‐only clear
WAIT_IDLE_SAFE
V1_POKE32 FASTFILL_COLOR, 0FFFFFFFh ; Farthest
WAIT_IDLE_SAFE
V1_POKE32 FASTFILL_TRIGGER, 1
WAIT_IDLE_SAFE
V1_POKEFIFO FBI_RGBMODE, 0x03
V1_POKEFIFO FBZMODE, 0x04000001
V1_POKEFIFO FBI_DRAWMODE, 0x00000100
V1_POKEFIFO CLIP_LEFTRIGHT, 0 or (639 shl 16)
V1_POKEFIFO CLIP_LOWYHIGHY, 0 or (479 shl 16)
jmp again3
V1_POKE32 FBI_INIT1, 0x00400040 ; aktivér RAMDAC
WAIT_IDLE_SAFE
V1_POKE32 LFBMODE, 0x001 ; 16-bit RGB565 direct writes, pipeline bypassed
WAIT_IDLE_SAFE
; V1_POKE32 FBZMODE, 0x04000001 ; Flat-shaded RGB, pipeline activated for color.
V1_POKE32 FBZMODE, 0x00060001 ; Flat-shaded RGB, pipeline activated for color.
WAIT_IDLE_SAFE
again3:
; draw one yellow triangle:
; kick the FIFO & set up verts
V1_POKEFIFO TRI_CMD, 1 ; begin triangle
V1_POKEFIFO TRI_SETUP_0, (10 shl 16) or 10
V1_POKEFIFO TRI_SETUP_1, (50 shl 16) or 200
V1_POKEFIFO TRI_SETUP_2, (150 shl 16) or 100
V1_POKEFIFO TRI_COLOR, 0xFF00FFFF
; V1_POKEFIFO SWAPBUFFERCMD,1
; WAIT_IDLE_SAFE
; mov eax, 0x3fff3fff
; mov ecx, 640*480
; mov edi, LFB_BASE
; .fill:
; mov [edi], eax
; add edi, 4
; loop .fill
V1_POKEFIFO SWAPBUFFERCMD, 1 ; Trigger refresh
WAIT_IDLE_SAFE
jmp again3
; now clear PE -> return to real mode
mov eax, cr0
and eax, 0xfffffffe ; clear PE → go back to real mode
mov cr0, eax
; build the far‐pointer in JMP_TARGET…
mov ax, real_resume
mov [JMP_TARGET], ax ; low word = IP
mov ax, [REAL_SEGMENT]
mov [JMP_TARGET+2], ax ; high word = CS
; indirect far jump via the 32-bit pointer
jmp dword [JMP_TARGET]
ret
use16
real_resume:
cli
mov ax, 0x10
mov ss, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov esp, 0x7C00
mov ah, 4Ch
int 21h
; === COM base segment pointer ===
LINEAR_SEG_BASE: dd 0 ; compute and store LINEAR_SEG_BASE at runtime
REAL_SEGMENT dw 0 ; filled with “mov [REAL_SEGMENT], cs” at startup
JMP_TARGET dw 0,0 ; offset (2 bytes), segment (2 bytes)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I'm trying to activate the 3Dfx Voodoo1 window (i.e., the 3dfx output surface) in DOSBox-X, using pure assembly (FASM). The code is a .COM file that enters protected mode and writes directly to the card’s MMIO registers (e.g. initEnable, FBI_INITx, etc.).
The problem: The window only appears if I run test00.exe (a benchmark tool from a 3dfx SDK) first. After that, even a single write like FBI_INIT0 |= 1 makes the window open. But without running test00.exe first, nothing happens - not even with full init.
This suggests that test00.exe enables some global or persistent state in DOSBox-X that stays active after exit. I’d like to know exactly what that is, so I can reproduce it fully within my own program. I want to avoid Glide and any external tools entirely.
I even did an MMIO dump before and after running test00.exe, and tried to reproduce the differences without luck.
My code sets up PMODE, enables I/O (IOPL=3), configures the PCI command register, reads BAR0 (assumed at 0xD0000000), and writes all the known MMIO registers - including FBI_INIT0–5, MISCINIT0–1, REG_CLOCK_ENABLE, VIDEODIMS, HSYNC, VSYNC, and so on - followed by FIFO commands like SWAPBUFFERCMD. Still no window.
I’ve read the SST-1 Rev 1.61 documentation, used the DOSBox-X debugger, experimented with glide2x.ovl, and even tried to reverse test00.exe and parts of Glide. I've also looked into voodoo.cpp and pci_bus.cpp from dosbox-pure, but I still don't see what triggers the actual window display in DOSBox-X.
Note: the C code that does work is just this:
...but that depends on glide2x.ovl. I'm trying to emulate all of this manually in ASM.
Ultimately, I’d like to shrink this to a 256b or 1k intro. But before optimizing anything, I need to understand how the Voodoo1 window is actually activated in DOSBox-X. Which MMIO register, PCI flag, or internal state triggers it?
If anyone with technical insight into DOSBox-X or 3Dfx internals has ideas, especially about what triggers the graphics window for voodoo1. I’d really appreciate your help.
Full source is attached in ZIP file to this post with necessary .INC files. VOOSRC.ZIP. I also provided the main assembly code below.
🙏 Thanks in advance!
Beta Was this translation helpful? Give feedback.
All reactions