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.pdfbyte 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 clearedpart <<= 2; //High nibble=4, Low nibble=0activex >>= 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 pixelsshift <<= 1; //Every rotate contains 2 bitsbitshift = 6; //Shift for pixel 0!bitshift -= shift; //Get the shift with the actual shift!//Get the pixelplanelow >>= bitshift; //Shift plane low correct.planelow &= 3; //We're 2 bits onlyplanehigh >>= bitshift; //Shift plane high correctplanehigh &= 3; //We're 2 bits onlyplanehigh <<= 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 VRAMbyte 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