VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm currently testing my VGA emulation routines using different graphics modes. Atm modes 0xF gives no output and mode 4, 5 and 6 give a quarter of the horizontal scanlines with (as far as I can see in the tiny window which contains the pixels) 8 or 9 pixels (depending on the 8/9 pixel character width) bars on every scanline. The vertical refresh seems to be working fine (I get the central horizontal-plotted line in the center of the screen). The horizontal refresh gives even/odd character clocks with black bars every odd character clock instead of foureground color. All other modes work perfectly. Could this have anything to do with the MAP13/14 bits in the CRTC mode control register (address 13&14 replaced with bit 0/1 of the scanline counter, which equals the vertical refresh in this case afaik (1 screen scanline per buffer scanline)).

My rendering routine:

//This should be OK, according to: http://www.nondot.org/sabre/Mirrored/GraphicsProgrammingBlackBook/gpbb31.pdf
byte get256colorshiftmode(VGA_Type *VGA, SEQ_DATA *Sequencer, word x) //256-color shift mode!
{
register word activex; //X!
register byte part, plane, result;

//First: calculate the nibble to shift into our result!
activex = x; //Load x!
activex >>= VGA->precalcs.characterclockshift; //Apply pixel DIVIDE when needed!


part = x; //Load x into part&activeX for processing!
part &= 1; //Take the lowest bit only for the part!
part ^= 1; //Reverse: High nibble=bit 0 set, Low nibble=bit 0 cleared
part <<= 2; //High nibble=4, Low nibble=0

activex >>= 1; //Ignore the part number to get our nibble: Every part is a nibble, so increase every 2 pixels!

//Now we're just a simple index to maintain and find the correct byte!

//Determine plane and offset within the plane!
plane = activex; //Load plane!
plane &= 3; //We walk through the planes!
activex >>= 2; //Apply the pixel (every 4 increment)!

//Apply startmap!
activex += Sequencer->charystart; //Apply the line and start map to retrieve!

result = readVRAMplane(VGA, plane, activex, 1); //The full offset of the plane all stuff is already done, so 0 at the end!
result >>= part; //Shift to the required part (low/high nibble)!
result &= 0xF; //Only the low resulting nibble is used!
return result; //Give the result!
}

/*

SHIFT REGISTER INTERLEAVE MODE

*/

byte getpackedshiftmode(VGA_Type *VGA, SEQ_DATA *Sequencer, word x) //Packed shift mode!
{
//Calculate the plane index!
register word shift,bitshift,tempx,planebase,planeindex;
register byte planelow, planehigh;
tempx = x; //Init tempx!
tempx >>= VGA->precalcs.characterclockshift; //Apply pixel DIVIDE when needed!

planebase = shift = tempx; //Start with the x value and load it for usage in base and shift!

planebase >>= 2; //The base changes state every 4 pixels (1 byte processed)

planeindex = planebase; //Take the same rate as the base for the index, but ...
planeindex >>= 1; //The index goes at half the rate of the plane: every 2 planes processed, one index is taken!

planebase &= 1; //Base plane (0/1)! OK!

planeindex += Sequencer->charystart; //Add the start address and start map!

//Read the low&high planes!
Show last 84 lines
	planelow = readVRAMplane(VGA,planebase,planeindex,0); //Read low plane!
planebase |= 2; //Take the high plane now!
planehigh = readVRAMplane(VGA,planebase,planeindex,1); //Read high plane!

//Determine the shift for our pixels!
shift &= 3; //The shift rotates every 4 pixels
shift <<= 1; //Every rotate contains 2 bits
bitshift = 6; //Shift for pixel 0!
bitshift -= shift; //Get the shift with the actual shift!

//Get the pixel
planelow >>= bitshift; //Shift plane low correct.
planelow &= 3; //We're 2 bits only

planehigh >>= bitshift; //Shift plane high correct
planehigh &= 3; //We're 2 bits only

planehigh <<= 2; //Prepare high plane for combination!

//Build the result!
planelow |= planehigh; //Add high plane to the result!

//Source plane bits for all possibilities!
return planelow; //Give the result!
}

/*

SINGLE SHIFT MODE

*/

byte getplanarshiftmode(VGA_Type *VGA, SEQ_DATA *Sequencer, word x) //Planar shift mode!
{
//16-color mode!
register byte result; //Init result!
register word offset, bit;

offset = x; //Load x!
offset >>= VGA->precalcs.characterclockshift; //Apply pixel DIVIDE when needed!

bit = offset;
bit &= 7; //The bit in the byte (from the start of VRAM byte)! 8 bits = 8 pixels!

offset >>= 3; //Shift to the byte: every 8 pixels we go up 1 byte!
offset += Sequencer->charystart; //VRAM start of row and start map!

//Standard VGA processing!
result = getBitPlaneBit(VGA,3,offset,bit,1); //Add plane to the result!
result <<= 1; //Shift to next plane!
result |= getBitPlaneBit(VGA,2,offset,bit,1); //Add plane to the result!
result <<= 1; //Shift to next plane!
result |= getBitPlaneBit(VGA,1,offset,bit,1); //Add plane to the result!
result <<= 1; //Shift to next plane!
result |= getBitPlaneBit(VGA,0,offset,bit,1); //Add plane to the result!
return result; //Give the result!
}

//Shiftregister: 2=ShiftRegisterInterleave, 1=Color256ShiftMode. Priority list: 1, 2, 0; So 1&3=256colorshiftmode, 2=ShiftRegisterInterleave, 0=SingleShift.
//When index0(VGA->registers->GraphicsRegisters.REGISTERS.MISCGRAPHICSREGISTER.AlphaNumericModeDisable)=1, getColorPlanesAlphaNumeric
//When index1(IGNOREATTRPLANES)=1, getColorPlanesIgnoreAttrPlanes

//http://www.openwatcom.org/index.php/VGA_Fundamentals:
//Packed Pixel: Color 256 Shift Mode.
//Parallel Planes: Else case!
//Interleaved: Shift Register Interleave!

/*

Core functions!

*/

void VGA_Sequencer_GraphicsMode(VGA_Type *VGA, SEQ_DATA *Sequencer, VGA_AttributeInfo *attributeinfo)
{
static agetpixel getpixel_jmptbl[4] = {
getplanarshiftmode,
getpackedshiftmode,
get256colorshiftmode,
get256colorshiftmode
}; //All the getpixel functionality!
attributeinfo->fontpixel = 1; //Graphics attribute is always font enabled!
attributeinfo->attribute = getpixel_jmptbl[VGA->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.ShiftRegister](VGA,Sequencer,Sequencer->activex);
}

VRAM Routines:

//Retrieval function for single bits instead of full bytes!
byte getBitPlaneBit(VGA_Type *VGA, byte plane, word offset, byte bit, byte is_renderer)
{
byte bits;
bits = readVRAMplane(VGA,plane,offset,is_renderer); //Get original bits!
return GETBIT(bits,7-bit); //Give the bit!
}

//Below patches input addresses for rendering only.
OPTINLINE uint_32 patch_map1314(VGA_Type *VGA, uint_32 rowscanaddress) //Patch full VRAM address!
{ //Check this!
word newrowscan = rowscanaddress; //New row scan to use!
SEQ_DATA *Sequencer;
Sequencer = (SEQ_DATA *)VGA->Sequencer; //The sequencer!

register uint_32 bit; //Load row scan counter!
if (!VGA->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13) //a13=Bit 0 of the row scan counter!
{
//Row scan counter bit 1 is placed on the memory bus bit 14 during active display time.
//Bit 1, placed on memory address bit 14 has the effect of quartering the memory.
newrowscan &= 0xDFFF; //Clear bit13!
bit = Sequencer->Scanline; //Load the row scan counter!
bit &= 1; //Bit0 only!
bit <<= 13; //Shift to our position!
newrowscan |= bit;
}

if (!VGA->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP14) //a14<=Bit 1 of the row scan counter!
{
newrowscan &= 0xBFFF; //Clear bit14;
bit = Sequencer->Scanline; //Load the row scan counter!
bit &= 2; //Bit1 only!
bit <<= 13; //Shift to our position!
newrowscan |= bit;
}

return newrowscan; //Give the linear address!
}

OPTINLINE uint_32 addresswrap(VGA_Type *VGA, uint_32 memoryaddress) //Wraps memory arround 64k!
{
register uint_32 address2; //Load the initial value for calculating!
register uint_32 result;
result = memoryaddress; //Default: don't change!
if (VGA->precalcs.VRAMmemaddrsize==2) //Word mode?
{
address2 = memoryaddress; //Load the address for calculating!
if (VGA->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW) //MA15 has to be on MA0
{
address2 >>= 15;
}
else //MA13 has to be on MA0?
{
address2 >>= 13;
}
address2 &= 1; //Only load 1 bit!
result &= ~1; //Clear bit 0!
result |= address2; //Add bit MA15 at position 0!
}
return result; //Adjusted address!
Show last 52 lines
}

//Planar access to VRAM
byte readVRAMplane(VGA_Type *VGA, byte plane, word offset, byte is_renderer) //Read from a VRAM plane!
{
if (!VGA) return 0; //Invalid VGA!
if (!VGA->VRAM_size) return 0; //No size!
word patchedoffset = offset; //Default offset to use!

if (is_renderer) //First address wrap, next map13&14!
{
//First, apply addressing mode!
patchedoffset = addresswrap(VGA,patchedoffset); //Wrap first!
patchedoffset = patch_map1314(VGA,patchedoffset); //Patch MAP13&14!
}

plane &= 3; //Only 4 planes are available!

register uint_32 fulloffset2;
fulloffset2 = plane; //Load full plane!
fulloffset2 <<= 16; //Move to the start of the plane!
fulloffset2 |= patchedoffset; //Generate full offset!

if (fulloffset2<VGA->VRAM_size) //VRAM valid, simple check?
{
return VGA->VRAM[fulloffset2]; //Read the data from VRAM!
}
return 0; //Nothing there: invalid VRAM!
}

void writeVRAMplane(VGA_Type *VGA, byte plane, word offset, byte value) //Write to a VRAM plane!
{
if (!VGA) return; //Invalid VGA!
if (!VGA->VRAM_size) return; //No size!

plane &= 3; //Only 4 planes are available!

register uint_32 fulloffset2;
fulloffset2 = plane; //Load full plane!
fulloffset2 <<= 16; //Move to the start of the plane!
fulloffset2 |= offset; //Generate full offset!

if (fulloffset2<VGA->VRAM_size) //VRAM valid, simple check?
{
VGA->VRAM[fulloffset2] = value; //Set the data in VRAM!
if (plane==2) //Character RAM updated?
{
VGA_plane2updated(VGA,offset); //Plane 2 has been updated!
}
}
}

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

Reply 1 of 2, by Scali

User metadata
Rank l33t
Rank
l33t

I'm not sure if it's deliberate... but modes 4, 5 and 6 are CGA modes, and F is an EGA mode.
If you set DOSBox to CGA, it responds very differently to modes 4,5,6, and emulates most CRTC stuff correctly (although it is broken for composite though).
In VGA, it seems to ignore most CRTC stuff when using any of the CGA modes.
I haven't tested EGA, but I wouldn't be surprised if it's the same.

I'm not sure if this is by design (because VGA cards aren't 100% CGA-compatible anyway, so it might reflect what real VGA cards do), or if it's just because there's a lot of duplicate code and/or code in wrong places.
For example, one CRTC-bug I fixed with composite is because it treated composite as a Tandy-mode, which just used some default values rather than taking the values from the CRTC registers. The same might be happening in a VGA configuration when using CGA modes.

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

Reply 2 of 2, by superfury

User metadata
Rank l33t++
Rank
l33t++

So I need to adjust the int10_modes VGA settings of the source code to make it work on a real IBM VGA (which I'm emulating as far as I can find the documentation on it)? Or are the int10_modes settings correct and I need to fix my VGA script (which seems to have bugs)?

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