First post, by superfury
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!
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!
}
//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