VOGONS


First post, by UselessSoftware

User metadata
Rank Newbie
Rank
Newbie

I revived an old 80186 PC emu project of mine that's kind of been on hold for 7 years or so. It was written poorly, and I'm trying to rewrite or at least refactor most of it. I started a new VGA implementation from scratch, and I'm having some issues with VGA writes. A lot of things only seem to write properly to one of the four planes. It's very obvious in the menus and floor/ceilings in Wolf3D. Linear mode games are working perfectly.

The attachment fake86-rewrite-4.png is no longer available
The attachment fake86-rewrite-5.png is no longer available

Does anyone have any experience with this? I've peeked at 86box and QEMU code to see if I could figure out what I'm doing wrong, and either I'm missing something or the problem is not actually in the write code. I'll figure it out with more time, but frankly my brain is starting to melt and I thought I'd ask on here if anyone has any thoughts.

Here's my write function.

void vga_writememory(uint32_t addr, uint8_t value) {
uint8_t temp, plane;
if ((vga_misc & 0x02) == 0) return; //RAM writes are disabled
addr = (addr - vga_membase) & vga_memmask; //TODO: Is this right?

if (vga_gfxd[0x05] & 0x10) { //host odd/even mode
vga_RAM[addr & 1][addr >> 1] = value;
return;
}

switch (vga_wmode) {
case 0:
for (plane = 0; plane < 4; plane++) {
if (vga_enableplane & (1 << plane)) { //are we allowed to write to this plane?
if (vga_gfxd[0x01] & (1 << plane)) { //test enable set/reset bit for plane
temp = (vga_gfxd[0x00] & (1 << plane)) ? 0xFF : 0x00; //set/reset expansion as source data
} else { //host data as source
value = vga_dorotate(value);
temp = vga_dologic(value, vga_latch[plane]);
}
temp = (temp & vga_gfxd[0x08]) | (vga_latch[plane] & ~vga_gfxd[0x08]);
vga_RAM[plane][addr] = temp;
}
}
break;
case 1:
for (plane = 0; plane < 4; plane++) {
if (vga_enableplane & (1 << plane)) {
vga_RAM[plane][addr] = vga_latch[plane];
}
}
break;
case 2:
for (plane = 0; plane < 4; plane++) {
if (vga_enableplane & (1 << plane)) {
temp = (value & (1 << plane)) ? 0xFF : 0x00;
temp = (vga_dologic(temp, vga_latch[plane]) & vga_gfxd[0x08]) | (vga_latch[plane] & (~vga_gfxd[0x08])); //bit mask logic
vga_RAM[plane][addr] = temp;
}
}
break;
case 3:
for (plane = 0; plane < 4; plane++) {
if (vga_enableplane & (1 << plane)) {
uint8_t temp2;
temp2 = (vga_gfxd[0x00] & (1 << plane)) ? 0xFF : 0x00;
temp = temp2 & vga_dorotate(value);
temp = (temp2 & temp) | (vga_latch[plane] & (~vga_gfxd[0x08])); //bit mask logic
vga_RAM[plane][addr] = temp;
}
}
break;
}
}

Wolf3D only ever seems to switch between write modes 0 and 1.

And this is the rest of the VGA code, which is not at all complete, and needs support for more rendering modes, proper VGA font etc:

https://github.com/mikechambers84/fake86-expe … les/video/vga.c

Reply 1 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

Have a look at UniPCemu's VGA MMU VRAM mapping emulation:
https://bitbucket.org/superfury/unipcemu/src/ … e/vga/vga_mmu.c

In particular the VGAmemIO* functions. You'll want the decodeCPUAddress functions (the functions that they call) and VGA_ReadModeOperation/VGA_ReadWriteOperation that apply it to VGA memory.

Afaik UniPCemu is currently the only one that(including Tseng video card emulation) does it exactly as the documentation describes(in a correct way), without any weird specific handlers(like Dosbox and many other emulators do to apply weird effects and optimizations). UniPCemu is also the only one afaik that actually addresses the entire VRAM as doublewords as the documentation says, instead of addressing VRAM directly(which is incorrect) and in complicated ways.

Although some little precalculations are used(based on what Dosbox does, Used by UniPCemu as well(which is pretty much the only precalculated thing in there)), which can be found here, in the initialization/allocation function: https://bitbucket.org/superfury/unipcemu/src/ … dware/vga/vga.c

One nice little thing is that said code describes how the EGA/VGA decodes and applies the memory address in a simple way, instead of the many handlers I keep seeing in Dosbox's code(which has/had one for every single weird memory mapping case(overcomplicating things), instead of simple, accurate handlers(based on the documentation)).
That's one of the little things I'm glad UniPCemu got working properly and as documented in a readable way. 😁

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 2 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

After some more tinkering(byte panning, preset row scan, character height(for preset row scan check) and start address in vertical retrace start and horizontal panning, pixel panning mode, top window start after vertical total(when the first new active display cycle begins), most of the graphical glitches seem to be gone.

There still seems to be some weird periodic glitch happening always, though?

I've made a little video that shows what's happening:
https://www.dropbox.com/s/wz8cp6i89fjy7bu/Uni … kRabbit.7z?dl=0

Anyone knows something about this glitch? Dosbox doesn't seem to exhibit said glitch(0.74 seems to run correctly without issues, even on extremly low cycle counts(below 500 kIPS), it'll just hang when getting extremely low(below 100 cycles or so))?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 9, by UselessSoftware

User metadata
Rank Newbie
Rank
Newbie

I think you meant to put the second post in a different thread, but UniPCemu looks nice. I've always been meaning to add 386+ support to mine. I don't know if I have the patience, but I'd love to see Windows 2000 or Linux run.

As far as VGA, at least my logic appears to match yours at a glance but I'll need to dig deeper tonight when I have more time.

As far as Jazz Jackrabbit, is it hanging on some mis-implemented status bit somewhere? Just a generic thought off the top of my head. It's a similar problem to something I've run into before with other games.

Reply 4 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, the Input Status registers are the only real status bits in a VGA, aren't they? Their horizontal and/or vertical retrace running bit(bit 0 of the Input Status 1 register set) and vertical retrace running(bit 3 set) are implemented in the renderer(the renderer updates said bits itself).

There's some other bits in there:

	case 0x3DA: //Input Status #1 Register (color)	DATA
if (GETBITS(getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER,0,1)) //Block: we're a mono mode addressing as color!
{
readInputStatus1:
SETBITS(getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.ATTRIBUTECONTROLLERTOGGLEREGISTER,7,1,0); //Reset flipflop for 3C0!

*result = getActiveVGA()->registers->ExternalRegisters.INPUTSTATUS1REGISTER; //Give!
const static byte bittablelow[2][4] = {{0,4,1,6},{0,4,1,8}}; //Bit 6 is undefined on EGA!
const static byte bittablehigh[2][4] = {{2,5,3,7},{2,5,3,8}}; //Bit 7 is undefined on EGA!
byte DACOutput = getActiveVGA()->CRTC.DACOutput; //Current DAC output to give!
SETBITS(*result,4,1,GETBITS(DACOutput,bittablelow[(getActiveVGA()->enable_SVGA==3)?1:0][GETBITS(getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.COLORPLANEENABLEREGISTER,4,3)],1));
SETBITS(*result,5,1,GETBITS(DACOutput,bittablehigh[(getActiveVGA()->enable_SVGA==3)?1:0][GETBITS(getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.COLORPLANEENABLEREGISTER,4,3)],1));
if (getActiveVGA()->enable_SVGA==3) //EGA has lightpen support here?
{
SETBITS(*result,1,1,GETBITS(getActiveVGA()->registers->EGA_lightpenstrobeswitch,1,1)); //Light pen has been triggered and stopped pending? Set light pen trigger!
SETBITS(*result,2,1,GETBITS(~getActiveVGA()->registers->EGA_lightpenstrobeswitch,2,1)); //Light pen switch is open(not pressed)?
}
ok = 1;
}
break;

case 0x3C2: //Read: Input Status #0 Register DATA
//Switch sense: 0=Switch closed(value of the switch being 1)
switchval = ((getActiveVGA()->registers->switches)>>GETBITS(getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER,2,3)); //Switch value to set!
switchval = ~switchval; //Reverse the switch for EGA+!
SETBITS(getActiveVGA()->registers->ExternalRegisters.INPUTSTATUS0REGISTER,4,1,(switchval&1)); //Depends on the switches. This is the reverse of the actual switches used! Originally stuck to 1s, but reported as 0110!
*result = getActiveVGA()->registers->ExternalRegisters.INPUTSTATUS0REGISTER; //Give the register!
SETBITS(*result, 5, 3, (getActiveVGA()->registers->ExternalRegisters.FEATURECONTROLREGISTER & 3)); //Feature bits 0&1!
SETBITS(*result, 7, 1, getActiveVGA()->registers->verticalinterruptflipflop); //Vertical retrace interrupt pending?
*result &= VGA_RegisterWriteMasks_InputStatus0[(getActiveVGA()->enable_SVGA==3)?1:0]; //Apply the write mask to the data written to the register!
ok = 1;
break;

The pixel shift count, pixel panning mode and top window start(split screen) are latched the first active display clock after vertical total is reached.
The start map, byte panning, preset row scan and character height(used for zeroing the preset row scan if it's more than the character height) are latched until the next vertical retrace once vertical retrace starts(status 1 register bit 3 becomes 1).
Would that be correct behaviour? Those latches are constantly (except split screen top window) added to the current scanline counter(after dividing by 2 for required cases, like double scanning) to provide the scanline number to fetch from VRAM(calculated as row times offset register times 2 as per the documentation).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 5 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

Also, concerning your problem, have you verified the behaviour of your byte/word/doubleword mode(CRTC registers) and the 32-bit latches and shifting out of their bits in the sequencer/renderer?

https://bitbucket.org/superfury/unipcemu/src/ … /vga_renderer.c
https://bitbucket.org/superfury/unipcemu/src/ … _graphicsmode.c

Edit: Also, VGA VRAM isn't 4 planes of 64k 8-bit values. They're actually 64k 32-bit values, with each 8-bit byte of the 32-bit value being a plane. That's why it can load and latch(both in rendering and MMU) so easily, with the renderer just shifting out bits in parallel(from all 4 bytes at once) in varying chunks(1-bit, 2-bit or 4-bit chunks). UniPCemu does this as well(see the graphics decoder functions).

Your issue was in UniPCemu originally too, until I fixed it somewhere down the road.
I believe Wolfenstein 3D used plain mode 13h, so that's doubleword mode in the CRTC mode control/underline location register.

I could make a VGA dump if you want(including UniPCemu's timings used)? Or you could make such a dump using UniPCemu yourself and look at the registers and timings in the log?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 6 of 9, by UselessSoftware

User metadata
Rank Newbie
Rank
Newbie
superfury wrote on 2020-06-17, 20:13:
Also, concerning your problem, have you verified the behaviour of your byte/word/doubleword mode(CRTC registers) and the 32-bit […]
Show full quote

Also, concerning your problem, have you verified the behaviour of your byte/word/doubleword mode(CRTC registers) and the 32-bit latches and shifting out of their bits in the sequencer/renderer?

https://bitbucket.org/superfury/unipcemu/src/ … /vga_renderer.c
https://bitbucket.org/superfury/unipcemu/src/ … _graphicsmode.c

Edit: Also, VGA VRAM isn't 4 planes of 64k 8-bit values. They're actually 64k 32-bit values, with each 8-bit byte of the 32-bit value being a plane. That's why it can load and latch(both in rendering and MMU) so easily, with the renderer just shifting out bits in parallel(from all 4 bytes at once) in varying chunks(1-bit, 2-bit or 4-bit chunks). UniPCemu does this as well(see the graphics decoder functions).

Your issue was in UniPCemu originally too, until I fixed it somewhere down the road.
I believe Wolfenstein 3D used plain mode 13h, so that's doubleword mode in the CRTC mode control/underline location register.

I could make a VGA dump if you want(including UniPCemu's timings used)? Or you could make such a dump using UniPCemu yourself and look at the registers and timings in the log?

I was debating whether or not to implement VRAM as 32-bit data, but in the end decided to just go with 4 arrays of 8-bit 64 KB. I just felt like it would be easier to read later, and one of my goals is making this easy to read for someone trying to learn how a PC emulator works. I hope I'm succeeding, but I'm probably not! 😀

I recall having this problem like 8 or 9 years ago when I wrote the original version of this emulator but I can't remember what caused it now.

I am going to play around with UniPCemu tonight, I haven't seen it before and it looks good based on the wiki. I'll also check your VGA code out and try to find what I'm missing. I need to read up on the details of the doubleword mode and get that implemented, along with a number of other features. Lots of VGA stuff isn't used very often, but I'm going for more accuracy this time around. Also you can ignore a lot of the code in that vga.c file of mine, lots of it is left over from copy and pasting the CGA code and I just haven't changed it yet. Especially in the render function. I was going to totally refactor that for VGA.

This is still very early on, and there's a lot of stuff through the whole project that I'm still going to change and improve.

Reply 7 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

Just improved some little aspects of the vertical timing as well: the vertical counter now properly behaves like one (as documented) and instead of using the precalculated table values and shifts on the scanline number, it now properly uses a divider on the horizontal total clock and applies it's timing based on that(adding the offset register precalc to each new scanline). That also improves rendeirng if the software decides to change the offset register in the middle of the screen, causing different increments to happen from that point on at the specified address.

So you can so:
First row: Row width 30h
Row width 30h (change it to 20h during this row)
Row width 20h at the location the previous row left off.
Row width 20h
...
Vertical retrace

Ther's one weird thing I notice now when testing, though: the display rate seems halved somehow for pretty much all video modes? So mode 12h is at 13 FPS at 45%, so at 100% it should be at 28.8%, while it should be at about 50 FPS or bigger?
Perhaps a clock being divided by 2 when it shouldn't?
Edit: Yup. The ET4000 is somehow setting Sequencer register 7 bit 6(and bit 1 cleared), causing the Master Clock to be divided by 2 for all rendering purposes. So it's using a 12.5MHz clock instead of the proper 25MHz one? UniPCemu has the ET4000 first 2 clocks connected to the normal VGA crystals after all(of 25 and 28MHz). Perhaps it shouldn't?
Edit: Apparently, the ET4000 at least uses a 50MHz and 66MHz clock instead for the VGA clocks(instead of 25MHz and 28MHz)?

Edit: After fixing said clock crystal, it should now use a better clocking.
Now I notice that for some reason, it's limiting the VGA speed when it shouldn't?
Having fixed that, the reported framerate goes up a lot(27FPS at 37% CPU speed at 1 MIPS IPS clocking mode, so that's a more correct 72 FPS in total running Jazz Jackrabbit(when it's in realtime at 100% emulation speed)).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 8 of 9, by UselessSoftware

User metadata
Rank Newbie
Rank
Newbie

I figured it out. I forgot my 16-bit CPU port I/O functions were still placeholders! Stupid stuff. 😀

It fixed other issues like the cursor in text mode not changing position from 0,0 and probably other things I haven't noticed yet. I also added a few other things to the code, including supporting the VGA programmable character RAM and various little VGA and other bugfixes/additions.

Next up is supporting different text modes and video modes actually based on the values of the registers. Then a proper PC speaker implementation and completing my 8237 controller. Having DMA working properly will hopefully allow the real IBM XT BIOS to boot.

The attachment fake86-rewrite-6.png is no longer available
The attachment fake86-rewrite-7.png is no longer available

I also played briefly with your emulator, though I haven't had a lot of time to test much yet. It seems like a good potential competitor to 86Box/PCem.

Reply 9 of 9, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, unfortunately UniPCemu still has one big con to those other emulators: emulation speed. Mainly due to the many accurate parts taking lots of time to calculate constantly(mostly CPU's MMIO(both memory and I/O) 30%, video card rendering emulation 15% and screen refreshes(resizing ~1% and other GUI text surfaces updates(4%))).

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io