VOGONS


First post, by katastic

User metadata
Rank Newbie
Rank
Newbie

I want to do some research into how "scenes" are drawn in various oldschool games.

The simplest example: A game with no double buffering or page flipping draws to the screen (hopefully!) after every vsync. That means it's simply modifying VRAM and I could hook into each time that happens.

So if I were to force a draw-to-the-OS every time the CGA/EGA/VGA/whatever memory was written to, I could--in essence--build a video with each "frame" being the individual draw calls as they appear.

So, does anyone have any tips for exploring the code base (or existing debug solutions?). Where to touch, what APIs/code files. Any information on how you would do that, what function you would hijack, etc.

I would imagine that there's a "VGA" source file with all the registers and memory accesses go through a memory-access function.

Also, any ideas for how to hijack double buffering or page flipping. I would imagine double buffering would be difficult, but page flipping is (as far as I can remember about 90's coding) simply a indexing register where you change where the "start of" page (the screen) begins in VRAM, and it's still drawing to VRAM.

Another "problem" to solve is, how would I get this data to the screen (or image dump)? For example, "Mode X" is a VGA paged memory mode where red, green, blue are all separate pages. e.g. All reds pixels in a row for the screen. Then all green. Then all blue. (Whatever the actual pixel order is). But if I'm dumping that, I'd have to recombine it somehow.

Thanks.

Reply 1 of 14, by ripa

User metadata
Rank Oldbie
Rank
Oldbie

I did something like that when I was reverse engineering Test Drive III maps.

First of all, get this ready-made tool called Bitmap Memory Debugger:
http://www.theycamefromhollywood.com/bmd/

You will be able to examine Dosbox's memory visually.

Dosbox global variable MemBase holds the pointer to the emulated RAM. Modify Dosbox memory.cpp to print the address of MemBase after allocating it. Then you can look at the RAM contents with BMD, and you'll probably be able to spot the rendering framebuffer of the game. If you then run the game in Dosbox debugger, break into the debugger while the game is rendering, and then let Dosbox execute a few thousand cycles at a time and watch how the rendering buffer changes.

If you want to examine the video memory, you will need to look elsewhere. I think you can find the pixel data at global variable vga.mem.linear_orgptr or vga.fastmem_orgptr. You can print the address of those and look into them with BMD.

VGA memory starts at virtual address A000:0000. If the emulated program accesses VGA memory, I think it goes through Dosbox's page handler. There's a function called MEM_SetPageHandler. There's a different page handler per video hardware. See VGA_SetupHandlers in vga_memory.cpp.

Reply 2 of 14, by reenigne

User metadata
Rank Oldbie
Rank
Oldbie

I'm not sure where you got the idea that red, green and blue are on separate pages in Mode X - I've never heard of a video mode where memory is organised like that! Most VGA games use 256 colours with one byte per pixel and a palette. The separate pages in Mode X correspond to the low two bits of the X coordinate, though many VGA games (including all those that support MCGA) use mode 0x13 which has a linear memory organisation.

To tell when the game is starting a new frame you could wait for it to poll the status register - I think pretty much every game that synchronise to the raster beam will use this technique to do so. Of course, not all games are synchronised to the raster beam so you'll have to find another way to define "frame" for those that aren't.

I will be interested to see the results of your research!

Reply 3 of 14, by katastic

User metadata
Rank Newbie
Rank
Newbie

ripa: GREAT HELP. I'm looking through source code and making progerss!

reenigne: You're right. I was thinking of EGA or something. Mode X is "planer" but it's pixels per bank--not colors.

That's a good point for games that ARE vsync'd. For direct drawing, I can see it by scanning/dumping VRAM during periods of retrace. For double buffered, that won't work. For page flipping, that might still work but I'd have to combine "every other frame" back together.

Ideally, I'd really like to (if simple enough) track the exact periods (with respect to retrace) of drawing, and then dump them as "frames" any time there is a write to that memory. Which again, should work for page flipping and direct drawing, but not double buffer. I could easily divide "sections" or "intervals" between each retrace as a complete frame and then log the individual writes as a list.

HMMMM. Since the double buffer (but not triple buffering?) usually has a fixed position in RAM, I might be able to detect a memcpy from RAM to VRAM (starting with the VRAM write), and reverse engineer/lookup the source RAM position, and then watch that position in RAM for writes.

Reply 4 of 14, by rasz_pl

User metadata
Rank l33t
Rank
l33t

speaking of tracing draw calls, stumbled upon this gem yesterday "Profiling Of 3 Games Running On The S3 ViRGE Chip" http://www-graphics.stanford.edu/~bjohanso/in … irge-study.html

Open Source AT&T Globalyst/NCR/FIC 486-GAC-2 proprietary Cache Module reproduction

Reply 5 of 14, by katastic

User metadata
Rank Newbie
Rank
Newbie

Anyone know which variable holds the VGA palette?

You would think it's:

vga.dac.rgb[index].red/green/blue

But, there's also

vga.dac.vga.dac.xlat16

which is in a packed 16-bit format of 6 bits, 6 bits, 6 bits. Because vga.dac.rgb appears to have the default VGA palette (though it's possible I'm not poking it at the right time).

Oh, come to think of it, it's possible my RGB's are out-of-order in [xlat16] and I just assumed I had the correct order. Because xlat16 has palette entries but they're all green and yellow. (Assuming again, I didn't screw up the bitmasks and they're bleeding into each other)

It's been forever since I did bitmasking so splitting up a 16-bit variable into three segments of 6-bits... wait.... 16? 6...12...18. How could this thing hold palette entries if it's only 16-bits?

I noticed it here:

// vga_dac.cpp
static void VGA_DAC_SendColor( Bitu index, Bitu src ) {
const Bit8u red = vga.dac.rgb[src].red;
const Bit8u green = vga.dac.rgb[src].green;
const Bit8u blue = vga.dac.rgb[src].blue;
//Set entry in 16bit output lookup table
vga.dac.xlat16[index] = ((blue>>1)&0x1f) | (((green)&0x3f)<<5) | (((red>>1)&0x1f) << 11);

RENDER_SetPal( index, (red << 2) | ( red >> 4 ), (green << 2) | ( green >> 4 ), (blue << 2) | ( blue >> 4 ) );
}

So xlat16 is some kind of lookup table but... that still doesn't make sense if we're not keeping all 6 bits of color information.

Reply 8 of 14, by Scali

User metadata
Rank l33t
Rank
l33t
reenigne wrote:

I'm not sure where you got the idea that red, green and blue are on separate pages in Mode X - I've never heard of a video mode where memory is organised like that!

The only thing that comes close is the 16-colour planar modes for EGA and hires VGA, where you have R, G, B and I bitplanes.
Or well, technically you don't necessarily have those, because the 4 bits are still used as a palette index, so rewriting the palette will break the 'mapping' of the bitplanes to the bits. Only the default palette works like that.

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

Reply 10 of 14, by katastic

User metadata
Rank Newbie
Rank
Newbie

Thanks for the tips! I'm making progress.

I've got:

static void VGA_VertInterrupt(Bitu /*val*/) {
if ((!vga.draw.vret_triggered) && ((vga.crtc.vertical_retrace_end&0x30)==0x10)) {
vga.draw.vret_triggered=true;
if (GCC_UNLIKELY(machine==MCH_EGA)) PIC_ActivateIRQ(9);
}
}

which seems to be called regularly. But, the inner section never seems to fire? I don't know what that section is doing. You would think it's setting some state that sets in motion vertical retraces but... it never fires as far as I can tell.

-------------------------------------------------------------------------------

Another question: Any clues on how to hook into accesses for video ram? Is VGA memory "memory mapped I/O"? That is, you write to a specific RAM address and it "magically" gets noticed and put into VRAM? (If so, what kind of words should I search for? "Handler"?) There's also BIOS calls through 0x0C, AFAICT. I imagine there's a 0x0C function somewhere.

As much as I'd prefer a singular solution, I imagine I'll have to hook each applicable video mode's drawing methods (including maybe VESA for SVGA, CGA, and EGA).

Reply 11 of 14, by Scali

User metadata
Rank l33t
Rank
l33t
katastic wrote:

which seems to be called regularly. But, the inner section never seems to fire? I don't know what that section is doing. You would think it's setting some state that sets in motion vertical retraces but... it never fires as far as I can tell.

EGA and VGA have the option to generate an IRQ2/9 at every vertical retrace. However, this is only supported for the EGA machine setting in DOSBox, and in practice, most real VGA cards do not have the IRQ2/9 pin connected anyway, so even though the chip can generate the IRQ, the CPU will never receive it. On cards that do have it connected, the IRQ tends to be buggy.
As a result, it was basically never used in practice.
For more info, you can check this topic: The myth of the vertical retrace interrupt on EGA/VGA

But you might be able to use this code in DOSBox to check whenever the display emulation layer starts a new frame.

katastic wrote:

Another question: Any clues on how to hook into accesses for video ram? Is VGA memory "memory mapped I/O"? That is, you write to a specific RAM address and it "magically" gets noticed and put into VRAM?

Pretty much yes. VRAM is just mapped into the address space of the machine directly, and can be addressed by the CPU as if it were regular memory.
DOSBox uses a temporary buffer for this, and converts the contents of this buffer to the host display once per frame.

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

Reply 13 of 14, by hail-to-the-ryzen

User metadata
Rank Member
Rank
Member

The 3dfx Voodoo emulation patch has two modes, writing through the dosbox video renderer and another on an opengl window. This is in voodoo_interface.cpp:

static void Voodoo_VerticalTimer(Bitu /*val*/) {
vdraw.frame_start = PIC_FullIndex();
PIC_AddEvent( Voodoo_VerticalTimer, vdraw.vfreq );
...
if (!v->ogl) {
if (!RENDER_StartUpdate()) return; // frameskip

rectangle r;
r.min_x = r.min_y = 0;
r.max_x = (int)v->fbi.width;
r.max_y = (int)v->fbi.height;

// draw all lines at once
Bit16u *viewbuf = (Bit16u *)(v->fbi.ram + v->fbi.rgboffs[v->fbi.frontbuf]);
for(Bitu i = 0; i < v->fbi.height; i++) {
RENDER_DrawLine((Bit8u*) viewbuf);
viewbuf += v->fbi.rowpixels;
}
RENDER_EndUpdate(false);
} else {
voodoo_set_window();
}
}

Reply 14 of 14, by ripa

User metadata
Rank Oldbie
Rank
Oldbie
katastic wrote:

So would I just find and hook into a general CPU memory write function and go "if(inside VRAM range)run_hook()"?

You don't need to check "if inside VRAM range" - Dosbox does it for you. Look at vga_memory.cpp VGA_SetupHandlers function. There are already several page handlers (set up using MEM_SetPageHandler) which are called by Dosbox core upon access to VRAM range.

Also, forget about VGA_VertInterrupt. Look at VGA_VerticalTimer instead. It's a function called by Dosbox core for every emulated video frame.

Dosbox has a system called "PIC" which you can use for scheduling function calls. You can easily use it to dump the VRAM contents e.g., 10 times per video frame if you want to see the intermediate contents of the buffer. You can see that VGA_VerticalTimer schedules itself to be called using PIC_AddEvent( VGA_VerticalTimer, (float)vga.draw.delay.vtotal ); --> you can insert code to schedule your own function to be called several times per video frame. Something like this:

const int callsPerFrame = 10;
for (int i = 0; i < callsPerFrame; ++i)
{
const auto interval = (float)vga.draw.delay.vtotal / callsPerFrame;
PIC_AddEvent( MyVramDumpFunction, i*interval );
}