VOGONS


CGA on VGA emulation in x86EMU?

Topic actions

First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm trying to get the CGA emulation submode (simple responses to CGA registers while emulating the VGA to get 8088 MPH working) working without destroying the VGA emulation itself.

Currently writes to the CGA mode control register with bits 6&7 cleared enable the CGA emulation. When one/both of those bits are set, VGA is emulated normally. Enabled CGA emulation(on top of the VGA emulation) forces the renderer to render like it's using double scanning. This fixes some bugs in 8088 MPH(like the screen with 3D objects(pyramid, cube and donut) only using half the screen(bottom half junk copies of display), becoming full screen).

Writes/reads to any MDA/EGA/VGA register or address disable CGA emulation.

While CGA emulation is enabled:
- Writing to CRTC index D3 and up disables CGA emulation.
- Writing to CRTC index 8=2 when protected will set a flag(cleared with other values), so does index 4=1. When both flags are set, the offset register is forced to zero to supply one-line frames(to support 8088 MPH's Kefrens Bars effect).
- Disabling the lock on any CRTC registers (VGA Vertical Retrace End Register's Protect bit) also disables CGA emulation.

This works reasonably with 8088 MPH, but for some reason messes up VGA emulation (when initializing from the BIOS and including any OS loading). Anyone knows how to fix this(e.g. make it compatible with both CGA(partly) accesses(when programmed like a CGA) and normal VGA operation when programmed like a VGA)?

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

Reply 1 of 187, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie

I'm not convinced that what you're trying to do is reasonable. No matter what CGA/VGA register accesses you try to use to switch the mode of your emulator, there may be some piece of software (if not now then in the future) which uses the same register accesses with the expectation that they will not cause the hardware to be switched out from under them. All the bits of all the IO ports in the range 0x3d0-0x3df have a defined effect on CGA (most are aliases for other ports or ignored), and all the bits of all the IO ports in the range 0x3b0-0x3df have a defined effect on VGA. I have an effect (which didn't make it into 8088MPH but may become part of another demo) which uses (deliberately, and for good reason) some of the unused bits of some of the CGA registers.

In general it isn't possible for an emulator to try to detect what hardware it should emulate by what the software does - the set of emulated hardware you have has to be configured by the user somehow or other.

That doesn't mean that all is hopeless, though. You could make a special-purpose piece of (emulated) hardware (perhaps on an otherwise unused IO port, perhaps using an unused CPU instruction like DOSBox does) by which guest software can communicate with (and configure) the emulator. Then you could make small .com files (say, cga.com and vga.com) which send the right commands to this "emulator host" device to set the video card. Then you can have completely separate pieces of code to emulate CGA and VGA, and have each work the way that the equivalent real video card does. Then when you want to run 8088MPH, just run cga.com first, and when you want to run some piece of VGA software, run vga.com first.

Reply 2 of 187, by Joey_sw

User metadata
Rank Oldbie
Rank
Oldbie
reenigne wrote:

Then you could make small .com files (say, cga.com and vga.com) which send the right commands to this "emulator host" device to set the video card. Then you can have completely separate pieces of code to emulate CGA and VGA, and have each work the way that the equivalent real video card does. Then when you want to run 8088MPH, just run cga.com first, and when you want to run some piece of VGA software, run vga.com first.

Similar approach also used by some early RealTek VGAs,
it was supplied with dos program/executable (i believe it was VMode) that somehow work similar to MS-DOS MODE.
that program can change graphic card operational mode into CGA/MDA+Hercules/EGA/VGA but unfortunately PCJr or Tandy compatible mode weren't available ,
its even has reboot-after-keypress confirmation feature for more compatibility with floppy-disk booters games/program.

-fffuuu

Reply 3 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, the type of 'VGA' that's emulated is now determined by the emulator's BIOS menu(The configuration menu of the emulator). It was used as a third option in the VGA NMI option (Which could be set to Enabled, Disabled and Compatibility mode, where Compatibility mode(with disabled NMI of course) will force some register data values into the VGA, making it render(invisible from software in the emulator) differently as described in the first post).

I would have to make a copy of my int10_modes.cpp adjustment from Dosbox-X(adjusted to fit my emulator) which directly accesses the VGA registers(instead of using the normal I/O, which is disabled when the CGA emulation is enabled) and use that adjustment to switch video modes when the CGA Mode Control register and CGA Color Palette register is written to. Also I would have to patch the updates of the CGA CRTC registers to write to a seperate CGA CRT registers(as they have a different format) and change VGA(which is still rendered normally) to use converted values of those(although most CRTC settings should be about the same with the CGA CRTC registers, except CGA-specific things like the CRTC index 8, which need to be patched to VGA-compatible values, or simply alter normal VGA rendering in those specific cases(should be easy enough to implement in the current CRTC emulation)).

Also I wonder one thing: The code on the kefrens bars seems to set the VGA Vertical Total register to 1(Thus only one scan line is rendered), but the retrace etc. is past that number. According to FreeVGA(should be applyable to CGA CRTC vertical total/retrace/blank somewhat too?), this will cause the CGA(&VGA) to never go past scanline 1(essentially only scanlines 0 and 1 are processed). Thus the vertical retrace is never triggered(as it lies way behind the scanlines it counts)? It will only be retraced when the scanline counter(0/1) reached the specified line(350 or somewhere close)? Thus it will never retrace?

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

Reply 4 of 187, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

The code on the kefrens bars seems to set the VGA Vertical Total register to 1(Thus only one scan line is rendered), but the retrace etc. is past that number. According to FreeVGA(should be applyable to CGA CRTC vertical total/retrace/blank somewhat too?), this will cause the CGA(&VGA) to never go past scanline 1(essentially only scanlines 0 and 1 are processed). Thus the vertical retrace is never triggered(as it lies way behind the scanlines it counts)? It will only be retraced when the scanline counter(0/1) reached the specified line(350 or somewhere close)? Thus it will never retrace?

And yet it does retrace! The image is quite stable on my monitors. So you're making a false assumption. Specifically, you're assuming that the values in the CRTC registers don't change over the course of the effect (or even over the course of a single frame). The source is here if you want to see how it's done.

Reply 6 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well, with my old hack(currenly disabled) enabled, I do notice that the screen displays the kefrens correctly. It just moves from the bottom to the top of the screen very fast instead of staying at the bottom like it's supposed to do.

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

Reply 7 of 187, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

the screen displays the kefrens correctly.

superfury wrote:

It just moves from the bottom to the top of the screen very fast instead of staying at the bottom like it's supposed to do.

These two statements seem to contradict each other.

I'd be very surprised if you can get the Kefrens bars working correctly in an emulator without cycle-exact emulation of an 8088 and proper emulation of the CGA wait states (both things that no emulator yet does).

Reply 8 of 187, by Scali

User metadata
Rank l33t
Rank
l33t
reenigne wrote:

I'd be very surprised if you can get the Kefrens bars working correctly in an emulator without cycle-exact emulation of an 8088 and proper emulation of the CGA wait states (both things that no emulator yet does).

Well, I guess there is a way, if you bake specific 8088 MPH-support into your emulator 😀
Detect whether or not the Kefrens-part of 8088 MPH is running (eg by calculating a hash for the the code segment), and if so, hard-sync your display emulation to the CRTC writes 😀
nVidia, AMD and Intel do that sort of thing all the time in their display drivers 😀

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 9 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

@reenigne: What I meant with those two statements is that the kefrens effect itself is running like it should(the images displayed itself are correct). So if you freeze output at any moment, successive scanlines will have the current kefrens screen.

The only thing going wrong is that the screen with the kefrens effect (say e.g. take a screenshot of the screen at any time) is moving from the bottom of the screen to the top, scrolling off the screen, then reappearing at the bottom and repeating. This happens a few times a second. Think a bit it's losing vertical sync only (horizontal timing is correct).

So the first frame rendered will have the correct data:
0,0 to 320,200 contains the frame.

The second frame will have shifted up:
0,0 to 320,200 contains bottom 190 lines followed by top 10 lines.

The third frame will have shifted further:
0,0 to 320,200 contains bottom 180 lines followed by top 20 lines.

Etc. until it's back at the first frame (assuming the shift each frame is 10 lines, this could be different due to timing errors).

It could be because the start address register is modified? It's about the same effect having a normal static 320x200 screen with 320x200 display area and, wrapping arround 320x200 pixels of RAM, increasing the start address register 10 lines each interval until the end of VRAM is reached(at which point it resets to zero).

I remember the CGA compatibility tester on this forum doing the same in one of the 320x200x4 graphics modes using the start address register(with the big X on the screen through all the content), by increasing it one line at a time for the vertical scrolling effect and one pixel for horizontal scrolling effect(with overflow appearing at the opposite direction).

So:

First frame and onwards(text line each frame) with horizontal scrolling:

[             <active display>                        ]
[ <active display> ]
[<active display ]
[ve display acti]
[play active ]

But in this case it's graphics moving instead of text, and it's scrolling vertically, not horizontally. Also only the active display is scrolling(the start of the frame moves from the top, then starts at the bottom and moving up until again at the top, then at the bottom again and moving up etc.)

Though, when I look at only one of the about 3/4 frames flashing by at different heights (about 1/4 screen apart) I do see the normal animation like in the videos of 8088 MPH, but in monochrome b/w.

So the kefrens bar effect itself is working correctly in the active display area(if you would put the active display area on 0,0 instead of it moving it would be correct output, but in monochrome, since the multiple colors won't work on the VGA). The active display area itself is scrolling off the top of the screen and wrapping around, reappearing at the bottom of the screen at about 4-6 times each second.

So it's essentially the same effect as in this video:
https://www.youtube.com/watch?v=t45AAApAnEw

Only it moves in reverse(down to up instead of up to down), with the image being scrolled being the correct image as far as I can see.

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

Reply 10 of 187, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie
superfury wrote:

the kefrens effect itself is running like it should(the images displayed itself are correct). So if you freeze output at any moment, successive scanlines will have the current kefrens screen.

Can I see a screenshot? Assuming you're correct, how did you get the horizontal timing correct? Did you just tune your average CPU speed until each the palette changes lined up?

superfury wrote:

The only thing going wrong is that the screen with the kefrens effect (say e.g. take a screenshot of the screen at any time) is moving from the bottom of the screen to the top, scrolling off the screen, then reappearing at the bottom and repeating.

Sounds like your emulator is either not generating the vsync pulses at the correct times, or not paying attention to them. Another thing: the PIT clock needs to tick a rate of once per exactly 12 hi-res CGA pixels for this effect to work correctly.

superfury wrote:

It could be because the start address register is modified?

This effect does not modify the start address register.

Reply 12 of 187, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie

Hmm... without the raster bars, it's hard to tell if the horizontal timing is correct or not!

Reply 13 of 187, by Scali

User metadata
Rank l33t
Rank
l33t
reenigne wrote:

This effect does not modify the start address register.

No, but to clarify, it retriggers the start address every 2 scanlines because the CRTC is set up for frames of 2 scanlines with no vblank.
So from the emulator point-of-view the start address will get updated mid-screen.

http://scalibq.wordpress.com/just-keeping-it- … ro-programming/

Reply 14 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

Well it seems to process that start process correctly:
vga_precalcs.c:

		if (CRTUpdated || (whereupdated==(WHEREUPDATED_CRTCONTROLLER|0xC))
|| (whereupdated==(WHEREUPDATED_CRTCONTROLLER|0xD))) //Updated?
{
word startaddress;
startaddress = VGA->registers->CRTControllerRegisters.REGISTERS.STARTADDRESSHIGHREGISTER;
startaddress <<= 8;
startaddress |= VGA->registers->CRTControllerRegisters.REGISTERS.STARTADDRESSLOWREGISTER;
//lockVGA(); //We don't want to corrupt the renderer's data!
VGA->precalcs.startaddress[0] = startaddress; //Updated start address!
//unlockVGA(); //We're finished with the VGA!
recalcScanline = 1; //Recalc scanline data!
//dolog("VGA","VTotal after startaddress: %i",VGA->precalcs.verticaltotal); //Log it!
}

vga_sequencer.c:

OPTINLINE void VGA_Sequencer_calcScanlineData(VGA_Type *VGA) //Recalcs all scanline data for the sequencer!
{
//First, all our variables!
uint_32 bytepanning;
byte allow_pixelshiftcount;

SEQ_DATA *Sequencer;
Sequencer = GETSEQUENCER(VGA); //Our sequencer!

//Determine panning
bytepanning = VGA->precalcs.PresetRowScanRegister_BytePanning; //Byte panning for Start Address Register for characters or 0,0 pixel!

//Determine shifts and reset the start map if needed!
byte reset_startmap;
reset_startmap = 0; //Default: don't reset!

allow_pixelshiftcount = 1; //Allow by default!
if (Sequencer->Scanline>VGA->precalcs.topwindowstart) //Top window reached?
{
reset_startmap = 1; //Enforce start of map to 0 for the top window!
if (VGA->precalcs.AttributeModeControlRegister_PixelPanningMode)
{
bytepanning = 0; //Act like no byte panning is enabled!
allow_pixelshiftcount = 0; //Don't allow it anymore!
}
}

Sequencer->startmap = VGA->precalcs.startaddress[reset_startmap]; //What start address to use?

//Determine byte panning and pixel shift count!
Sequencer->bytepanning = bytepanning; //Pass!

if (allow_pixelshiftcount) //Allow pixel shift count to be applied?
{
Sequencer->pixelshiftcount = VGA->precalcs.pixelshiftcount; //Allowable pixel shift count!
Sequencer->presetrowscan = VGA->precalcs.presetrowscan; //Preset row scan!
}
else
{
Sequencer->pixelshiftcount = Sequencer->presetrowscan = 0; //Nothing to shift!
}
}

Applying the start address of the current line:

OPTINLINE void VGA_Sequencer_updateRow(VGA_Type *VGA, SEQ_DATA *Sequencer)
{
register word row;
register uint_32 charystart;
row = Sequencer->Scanline; //Default: our normal scanline!
if (row>VGA->precalcs.topwindowstart) //Splitscreen operations?
{
row -= VGA->precalcs.topwindowstart; //This starts after the row specified, at row #0!
--row; //We start at row #0, not row #1(1 after topwindowstart).
}
row >>= VGA->precalcs.scandoubling; //Apply Scan Doubling on the row scan counter: we take effect on content (double scanning)!
Sequencer->rowscancounter = row; //Set the current row scan counter!

row >>= VGA->precalcs.CRTCModeControlRegister_SLDIV; //Apply scanline division to the current row timing!
row <<= 1; //We're always a multiple of 2 by index into charrowstatus!

//Row now is an index into charrowstatus
word *currowstatus = &VGA->CRTC.charrowstatus[row]; //Current row status!
Sequencer->chary = row = *currowstatus++; //First is chary (effective character/graphics row)!
Sequencer->charinner_y = *currowstatus; //Second is charinner_y!

charystart = getVRAMScanlineStart(VGA, row); //Calculate row start!
charystart += Sequencer->startmap; //Calculate the start of the map while we're at it: it's faster this way!
charystart += Sequencer->bytepanning; //Apply byte panning!
Sequencer->charystart = charystart; //What row to start with our pixels!

//Some attribute controller special 8-bit mode support!
Sequencer->active_nibblerate = 0; //Reset pixel load rate status & nibble load rate status for odd sized screens.
Sequencer->extrastatus = &VGA->CRTC.extrahorizontalstatus[0]; //Start our extra status at the beginning of the row!

VGA_loadcharacterplanes(VGA, Sequencer, 0); //Initialise the character planes for usage!
}

Edit: It was only calling the scanline data calculations during horizontal timing (htotal function), but not in the vertical total (vtotal) function. This is now fixed.

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

Reply 15 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've changed the BIOS NMI options to specify Pure VGA, CGA and MDA modes, as well as the VGA-CGA and VGA-MDA hybrid modes used to test 8088 MPH(the VGA-CGA hybrid mode). Both VGA and CGA mode control/palette registers currently do nothing until implemented.

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

Reply 16 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

I changed the CGA on VGA emulation to store it's CRT data in seperate CGA registers. Then any change of those registers should apply CGA timing instead of VGA timing accordingly.
Address wrapping and MAP13/14 are applies according to what's said in the previous post.
Writing to the CGA Mode Control register will now apply some changes to the VGA registers (essentially anything not having anything to do with the CGA timing, which is seperated from normal VGA emulation(timing generation lookup table only)).
Writing to the CGA Palette register will now update the Attribute Controller Palette and Overscan color according to the Palette register(Assuming RGB monitor for now instead of Composite monitor), using data of the CGA palette register and CGA Mode Control register(to enable the third palette),

https://bitbucket.org/superfury/x86emu/src/d9 … /vga/?at=master

Look at the attribute controller, VGA_IO, Precalcs, VGA_CGA and VGA_Sequencer for the changes applied to convert to CGA output(the parts applying VGA->registers->specialCGAflags&0x(8)1, which apply the CGA emulation(and of course complete VGA_CGA.c itself)).

Anyone knows if this is correct? I'm not getting any output(essentially 0FPS) or sound from the Turbo XT BIOS v2.5 with CGA only mode enabled(CGA flags are set to 0x81 in that case, to imply Pure CGA mode).

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

Reply 17 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

It seems it's not rendering for some reason(probably Sequencer AR and SR bits being set for some reason, blocking sequencer rendering? In the meanwhile the BIOS is waiting for horizontal/vertical retrace(It not accessing VRAM, seeing Input Status Register bit 1 is reset(0). Thus it waits forever to be able to access the CGA initialization, which will never happen, as the CRT(Sequencer) is halted by those two bits?

Edit: Looking at the CGA mode control register it priperly clears those bits. Could it be that the VGA sets them by default(Usual VGA default), but the CGA expects it to be running, thus waiting forever for retrace that never comes(Since the CGA mode control is never written up to that point?)?

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

Reply 18 of 187, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've improved CGA's CRT timing in x86EMU:
https://bitbucket.org/superfury/x86emu/src/ef … cga.c?at=master

Anyone can see if there's something wrong here? What's the effect of the Horizontal Sync and Vertical Sync registers? Trying to run the Turbo XT BIOS makes it hang during initialisation (all CGA registers are still cleared at any point in time, so they're never set) of the CGA, as far as I can see: It's waiting for the Display Disabled bit with the CGA uninitialized(CRT registers, CGA Mode Control register and CGA Palette registers are all still zeroed out). Is there an initial mode expected to be set on power up(power applied to the CGA card)?

I already fixed the CGA to apply timings immediately(Instead of the VGA waiting at least one character clock/line until starting H/V Blanking or Retracing), like the VGA does.

Anyone knows the correct CGA timing to be applied or how to fix this? Scali? Reenigne?
Btw loading the CGA mode control or palette registers doesn't load values into the CRT registers, do they? I haven't implemented those.

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

Reply 19 of 187, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie

The mode control and palette registers don't affect the CRTC registers - they're completely separate. I don't know what the initial register values are supposed to be - I don't think they're defined anywhere. I don't have an easy way to check - I'd need to get code onto the machine before anything else has a chance to initialize the CGA.

I'm not as familiar with the Turbo XT BIOS as I am with the IBM one, but waiting for the display disabled bit without setting any CGA registers seems wrong. One possibility springs to mind - as your emulation has both CGA and VGA, do you by any chance have the VGA BIOS mapped into the address space? If you do, I'd expect that the system BIOS would detect and initialize it, after which the interrupt 0x10 vector will point to the VGA versions of the routines. If you've got the emulator set to CGA only but the VGA BIOS is active, that might account for what you're seeing.

Otherwise I guess that on real hardware the default CRTC parameters just happen to generate a signal on the status register that doesn't cause the Turbo XT BIOS to crash.