It just occurred to me: my CGA-on-VGA applies the start address by simply multiplying the CGA start address by 2(SHL 1) to obtain the VGA Start Address. This might be the cause? Only multiply by 2 when in word mode(not mode 6h)? If it's monochrome(byte) mode, the start address is to be used directly: the VRAM is linear to the CPU(Both the CPU memory and rendering memory(plane 0 to be exact) are mapped 1:1 in this mode, thus no need to multiply by 2 in that case. Else row 100+ would end up rendering line 200+ in increments of 2 lines for each scanline)
It just occurred to me: my CGA-on-VGA applies the start address by simply multiplying the CGA start address by 2(SHL 1) to obtain the VGA Start Address. This might be the cause? Only multiply by 2 when in word mode(not mode 6h)? If it's monochrome(byte) mode, the start address is to be used directly: the VRAM is linear to the CPU(Both the CPU memory and rendering memory(plane 0 to be exact) are mapped 1:1 in this mode, thus no need to multiply by 2 in that case. Else row 100+ would end up rendering line 200+ in increments of 2 lines for each scanline)
The fact that the top half of the picture is correct makes this seem unlikely to me - if you were shifting it wrong the picture would be compressed or stretched. The CGA's start address is always measured in 2-byte words, but the VGA's start address is (IIUC) always measured in bytes.
I've just verified that by trying to convert it into byte quantities in VGA's byte mode and word quantities in VGA's word mode. It immediately messes up some parts of the demo. And the half black screens stay(it only makes it worse).
Maybe the VGA's CRTC AW bit is the cause? This shifts bit 13/15 into bit 1 of the adresses?
1OPTINLINE word patch_map1314(VGA_Type *VGA, word addresscounter) //Patch full VRAM address! 2{ //Check this! 3 word memoryaddress = addresscounter; //New row scan to use! 4 SEQ_DATA *Sequencer; 5 Sequencer = (SEQ_DATA *)VGA->Sequencer; //The sequencer! 6 7 word rowscancounter = Sequencer->rowscancounter; //The row scan counter we use! 8 9 register word bit; //Load row scan counter! 10 if (!VGA->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13) //a13=Bit 0 of the row scan counter! 11 { 12 //Row scan counter bit 1 is placed on the memory bus bit 14 during active display time. 13 //Bit 1, placed on memory address bit 14 has the effect of quartering the memory. 14 bit = rowscancounter; //Current row scan counter! 15 bit &= 1; //Bit0 only! 16 bit <<= 13; //Shift to our position (bit 13)! 17 memoryaddress &= 0xDFFF; //Clear bit13! 18 memoryaddress |= bit; //Set bit13 if needed! 19 } 20 21 if (!VGA->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP14) //a14<=Bit 1 of the row scan counter! 22 { 23 bit = rowscancounter; //Current row scan counter! 24 bit &= 2; //Bit1 only! 25 bit <<= 13; //Shift to our position (bit 14)! 26 memoryaddress &= 0xBFFF; //Clear bit14; 27 memoryaddress |= bit; //Set bit14 if needed! 28 } 29 30 return memoryaddress; //Give the linear address! 31} 32 33OPTINLINE word addresswrap(VGA_Type *VGA, word memoryaddress) //Wraps memory arround 64k! 34{ 35 register word address2; //Load the initial value for calculating! 36 register word result; 37 register byte temp; 38 if (VGA->precalcs.BWDModeShift == 1) //Word mode? 39 { 40 result = memoryaddress; //Default: don't change! 41 address2 = memoryaddress; //Load the address for calculating! 42 temp = 0xD; //Load default location (13) 43 temp |= (VGA->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW << 1); //MA15 instead of MA13 when set! 44 address2 >>= temp; //Apply MA15/MA13 to bit 0! 45 address2 &= 1; //Only load bit 0! 46 result &= 0xFFFE; //Clear bit 0! 47 result |= address2; //Add bit MA15/MA13 at bit 0! 48 return result; //Give the result! 49 } 50 return memoryaddress; //Original address! 51}
Also one little thing: do MAP13, MAP14 and/or Address Wrap affect the memory addressed by the CPU itself on a VGA? To imitate the effect the CGA and MDA have with their repeating 16K and 4K VRAM window?
Edit: Tried running the CGA Compatiblity Tester again:
The part that scrolls vertically does scroll vertically, but gives a bit odd output:
The first window is correct.
The second window is displaced about 60% to the right.
The third window is displaced about 30% to the left.
The fourth window is displaced about 30% to the right.
This repeats infinitely in that order.
The part that scrolls horizontally seems to shift left pretty correctly (besides tearing up borders and crosses) and moving up and down 1 pixel every screen scrolled it seems.
Btw it's CGA monitor tests give correct output now! 😁 Except of course the calibration screen only showing 50%.
Edit: I've moved the memory wrapping of CGA/MDA to the VGA_MMU module. It will now wrap around 0x4000 on CGA and 0x1000 on MDA(simply by anding the address of the CPU, after substracting B8000, with 0x3FFF(CGA) or 0xFFF(MDA)).
Also one little thing: do MAP13, MAP14 and/or Address Wrap affect the memory addressed by the CPU itself on a VGA? To imitate the effect the CGA and MDA have with their repeating 16K and 4K VRAM window?
I think they just affect the CRTC/RAM relationship - IIUC the CPU/RAM relationship on VGA is always linear (times the 4 bitplanes).
superfury wrote:
Btw it's CGA monitor tests give correct output now! 😁 Except of course the calibration screen only showing 50%.
So it sounds like everything is working correctly when the start address is 0 - it's just when the CGA start address is >=0x1000 (i.e. VGA start address is >=0x2000, i.e. the second 8kB region) that you're getting this weird blanking.
I've edited the way the CGA/MDA handle the MAP13 and Address Word values. They now look at the CGA Interlace register and CGA Mode Control register to determine the 'correct' values:
1void updateCGAmapping() 2{ 3 switch (getActiveVGA()->registers->CGARegisters[8]&3) //What mode? 4 { 5 case 0: 6 case 2: //Straight? 7 if (getActiveVGA()->registers->Compatibility_CGAModeControl&2) //Graphics mode? 8 { 9 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 0; //Graphics enables CGA graphics MAP13, else text! 10 } 11 else //Text mode? 12 { 13 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 1; //Graphics enables CGA graphics MAP13, else text! 14 } 15 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 1; //CGA mapping is done by the renderer mapping CGA! 16 break; 17 case 1: //Interleaved? 18 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 0; //Graphics enables CGA graphics MAP13, else text! 19 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 0; //CGA mapping is done by the renderer mapping CGA! 20 break; 21 case 3: //Interleaved video? 22 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 0; //Graphics enables CGA graphics MAP13, else text! 23 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 1; //CGA mapping is done by the renderer mapping CGA! 24 break; 25 } 26}
This function is called when either the CGA Mode Control register updates the mode or when the Interlace register is updated.
Now most of the demo shows up without many errors (the faces still being horizontally displaced, but they're vertically whole again. Also the flower girl has flashes in the middle of the screen (I think the image is reduced to about 20 pixels high in the center of the screen about one frame per second, or something close to that. It looks like turning the monitor on again once a second)).
So the only problems currently left is the flower girl 'blinking', the Kefrens Bars blanking completely and the final heads going wrong horizontally(over the edge of the active display? Wrapping?).
Anyone knows what the cause might be? In the meantime I'll make a little video of the current rendering again:)
Now most of the demo shows up without many errors (the faces still being horizontally displaced, but they're vertically whole again. Also the flower girl has flashes in the middle of the screen (I think the image is reduced to about 20 pixels high in the center of the screen about one frame per second, or something close to that. It looks like turning the monitor on again once a second)).
Sounds like the CPU is getting out of sync with the raster beam, which suggests a problem with the status register (but we already went over that). Maybe if you can make a dump of 40ms or so worth of CGA register writes and status register reads during the effect I can tell what's going on from that.
superfury wrote:
the Kefrens Bars blanking completely
This effect is never going to display correctly without emulating the CPU instruction timings correctly. The vertical overscan/retrace showing up during what should be active video time is a typical symptom of this.
superfury wrote:
and the final heads going wrong horizontally(over the edge of the active display? Wrapping?).
This is a standard 80-column text mode horizontally. The one thing that's slightly unusual is that it doesn't use an "int 10" to go into this mode - it just programs all the CGA registers directly. So if there's some VGA register (9/8 Dot Mode?) that you're accidentally depending on getting set correctly and your "int 10" handler normally does this, that could account for what you're seeing.
The sync part is already obvious by now, else the effect wouldn't be happening. The emulator doesn't do anything but listen to the CGA Mode Control/pallette registers/CRTC register changing. Those should be fully translated to VGA-compatible (in the case of the normal registers). With of course the CGA CRTC values (display, blanking, retrace and total) being used and calculated instead of the normal VGA registers. All other CRTC registers are simply translated to VGA values (when needed) and stored in their VGA equivalents for the renderer to see.
The CGA mode control updates the palettes when needed(just as the palette register does when written to) and updates some CRT timing in the VGA to be compatible to the mode selected. The CGA CRT register translations take care of the rest of the timing etc. required.
1//Foreground colors: Red green yellow(not set), Magenta cyan white(set), Black red cyan white on a color monitor(RGB)! 2byte CGA_lowcolors[3][4] = {{0,0x2,0x4,0x6},{0,0x3,0x5,0x7},{0,0x3,0x4,0x7}}; 3extern byte CGA_RGB; //Are we a RGB monitor(1) or Composite monitor(0)? 4 5void setCGAMDAColors(byte isGraphics, byte GraphicsMode) 6{ 7 byte i,color; 8 if ((!isGraphics) && GraphicsMode) //MDA enabled? 9 { 10 //Apply the MDA palette registers! 11 for (i=0;i<0x10;i++) //Process all colours! 12 { 13 color = 0; //Default to black! 14 if (i&0x8) //Bright(8-F)? 15 { 16 color |= 2; //Set bright attribute! 17 } 18 if (i&0x7) //On (1-7 and 9-15)? 19 { 20 color |= 1; //Set on attribute! 21 } 22 switch (color) //What color to map? 23 { 24 default: 25 case 0: //Black=Black! 26 case 3: //Bright foreground=Bright foreground! 27 break; 28 case 1: //Normal on? 29 color = 2; //Bright! 30 break; 31 case 2: //Bright background! 32 color = 1; //Lighter! 33 break; 34 } 35 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.PALETTEREGISTERS[i].DATA = color; //Make us equal! 36 } 37 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.OVERSCANCOLORREGISTER = 0; //This forces black overscan! We don't have overscan! 38 } 39 //Apply the new CGA palette register? 40 else if (isGraphics) //Graphics mode? 41 { 42 if (!GraphicsMode) //High resolution graphics mode(640 pixels)? 43 { 44 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.OVERSCANCOLORREGISTER = 0; //Black overscan! 45 } 46 47 for (i=0;i<0x10;i++) //Process all colours! 48 { 49 color = i; //Default to the normal color! 50 if (GraphicsMode) //Color mode? 320x200! 51 { 52 if (!i) //Background color? 53 { 54 color = (getActiveVGA()->registers->Compatibility_CGAPaletteRegister&0xF); //Use the specified background color! 55 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.OVERSCANCOLORREGISTER = color; //It also applies to the overscan! 56 } 57 else //Three foreground colors? 58 { 59 if (getActiveVGA()->registers->Compatibility_CGAModeControl&0x4) //Disabling the color burst bit applies 3rd palette on RGB monitor? 60 {
…Show last 271 lines
61 color = CGA_lowcolors[2][i&3]; //Use the RGB-specific 3rd palette! 62 } 63 else //Normal palettes? 64 { 65 color = CGA_lowcolors[(getActiveVGA()->registers->Compatibility_CGAPaletteRegister&0x20)?1:0][i&3]; //Don't use the RGB palette, use the normal palettes! 66 } 67 if (getActiveVGA()->registers->Compatibility_CGAPaletteRegister&0x10) //Display in high intensity? 68 { 69 color |= 0x8; //Display in high intensity! 70 } 71 } 72 } 73 else //B/W mode? 74 { 75 if (i) //We're on? 76 { 77 color = (getActiveVGA()->registers->Compatibility_CGAPaletteRegister&0xF); //Use the specified ON color! 78 } 79 } 80 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.PALETTEREGISTERS[i].DATA = color; //Make us the specified value! 81 } 82 } 83 else //Text mode? 84 { 85 for (i=0;i<0x10;i++) //Process all colours! 86 { 87 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.PALETTEREGISTERS[i].DATA = i; //Make us equal! 88 } 89 /*if (getActiveVGA()->registers->Compatibility_CGAModeControl&0x10) //High resolution graphics mode(640 pixels)? 90 { 91 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.OVERSCANCOLORREGISTER = 0; //This forces black overscan! 92 } 93 else //Use overscan! 94 {*/ 95 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.OVERSCANCOLORREGISTER = (getActiveVGA()->registers->Compatibility_CGAPaletteRegister&0xF); 96 //} 97 } 98 99 VGA_calcprecalcs(getActiveVGA(),WHEREUPDATED_ALL_SECTION|WHEREUPDATED_ATTRIBUTECONTROLLER); //We have been updated(whole attribute controller mode)! 100 RENDER_updateCGAColors(); //Update the NTSC color translation if required! 101} 102 103//Compatibility handling on both writes and reads to compatibility registers! 104void applyCGAMDAPaletteRegisters() 105{ 106 if (getActiveVGA()->registers->specialMDAflags&1) //MDA mode? 107 { 108 setCGAMDAColors(0,1); //MDA text mode! 109 } 110 else if (getActiveVGA()->registers->Compatibility_CGAModeControl&0x2) //Graphics mode? 111 { 112 if (getActiveVGA()->registers->Compatibility_CGAModeControl&0x10) //2 colour graphics? 113 { 114 setCGAMDAColors(1,0); //Set up basic 2-color graphics! 115 } 116 else //4 colour? 117 { 118 setCGAMDAColors(1,1); //Set up basic 4-color graphics! 119 } 120 } 121 else //Text mode? 122 { 123 setCGAMDAColors(0,0); //CGA Text mode! 124 } 125} 126 127void applyCGAPaletteRegister() //Update the CGA colors! 128{ 129 applyCGAMDAPaletteRegisters(); //Apply the palette registers! 130} 131 132void updateCGAmapping() 133{ 134 switch (getActiveVGA()->registers->CGARegisters[8]&3) //What mode? 135 { 136 case 0: 137 case 2: //Straight? 138 if (getActiveVGA()->registers->Compatibility_CGAModeControl&2) //Graphics mode? 139 { 140 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 0; //Graphics enables CGA graphics MAP13, else text! 141 } 142 else //Text mode? 143 { 144 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 1; //Graphics enables CGA graphics MAP13, else text! 145 } 146 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 1; //CGA mapping is done by the renderer mapping CGA! 147 break; 148 case 1: //Interleaved? 149 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 0; //Graphics enables CGA graphics MAP13, else text! 150 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 0; //CGA mapping is done by the renderer mapping CGA! 151 break; 152 case 3: //Interleaved video? 153 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP13 = 0; //Graphics enables CGA graphics MAP13, else text! 154 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 1; //CGA mapping is done by the renderer mapping CGA! 155 break; 156 } 157} 158 159//useGraphics: 0 for text mode, 1 for graphics mode! GraphicsMode: 0=B/W graphics or CGA text mode, 1=4 color graphics or MDA text mode 160void setCGAMDAMode(byte useGraphics, byte GraphicsMode, byte blink) //Rendering mode set! 161{ 162 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.ShiftRegisterInterleaveMode = ((useGraphics && GraphicsMode)?1:0); 163 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.MISCGRAPHICSREGISTER.AlphaNumericModeDisable = useGraphics; 164 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.COLORPLANEENABLEREGISTER.DATA = (useGraphics&&!GraphicsMode)?0x1:0xF; //CGA: enable all color planes! 165 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.AttributeControllerGraphicsEnable = useGraphics; //Text mode! 166 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.MonochromeEmulation = ((!useGraphics) && GraphicsMode); //MDA attributes! 167 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.BlinkEnable = ((!useGraphics) && blink)?1:0; //Use blink when not using graphics and blink is enabled! 168 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.UNDERLINELOCATIONREGISTER.UnderlineLocation = ((!useGraphics) && GraphicsMode)?0xC:0x1F; //Monochrome emulation applies MDA-compatible underline, simple detection by character height! 169 updateCGAmapping(); //Update the rendering mapping by the CGA! 170} 171 172void applyCGAMemoryMap(byte useGraphics, byte GraphicsMode) //Apply the current CGA memory map! 173{ 174 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.MISCGRAPHICSREGISTER.MemoryMapSelect = ((!useGraphics) && GraphicsMode)?2:3; //Use map B000(MDA) or B800(CGA), depending on the adapter used! 175 if (useGraphics && (!GraphicsMode)) //Special case? CGA monochrome mode? 176 { 177 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.OddEvenMode = 0; //Don't force odd/even mode! 178 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.READMAPSELECTREGISTER.ReadMapSelect = 0; //Only read map #0! 179 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.COLORDONTCAREREGISTER.ColorCare = 0; //Care about this only! 180 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.MISCGRAPHICSREGISTER.EnableOddEvenMode = 0; //Disable chaining! 181 getActiveVGA()->registers->SequencerRegisters.REGISTERS.SEQUENCERMEMORYMODEREGISTER.OEDisabled = 1; //Disable odd/even mode! 182 getActiveVGA()->registers->SequencerRegisters.REGISTERS.MAPMASKREGISTER.MemoryPlaneWriteEnable = 1; //Write to plane 0 only, since we're emulating CGA! 183 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.UseByteMode = 1; //CGA byte mode! 184 } 185 else //Revert to normal memory mode! 186 { 187 if (getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.UseByteMode) //We were using data that now becomes junk data? 188 { 189 uint_32 x; 190 for (x=1;x<0x10000;) //Clear all odd data we've used in monochrome mode! 191 { 192 writeVRAMplane(getActiveVGA(),0,x,0,0); //Clear the byte of data at byte offsets only! 193 ++x; //Next byte is skipped(even plane)! 194 ++x; //We skip 2 bytes for the next offset to use! 195 } 196 } 197 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.OddEvenMode = 1; //Force odd/even mode! 198 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.READMAPSELECTREGISTER.ReadMapSelect = 0; //Only read map #0! 199 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.COLORDONTCAREREGISTER.ColorCare |= 0xF; //Care about this only! 200 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.MISCGRAPHICSREGISTER.EnableOddEvenMode = 1; //Enable chaining! 201 getActiveVGA()->registers->SequencerRegisters.REGISTERS.SEQUENCERMEMORYMODEREGISTER.OEDisabled = 0; //Disable odd/even mode! 202 getActiveVGA()->registers->SequencerRegisters.REGISTERS.MAPMASKREGISTER.MemoryPlaneWriteEnable = 3; //Write to planes 0/1 only, since we're emulating CGA! 203 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.UseByteMode = 0; //CGA word mode! 204 } 205 VGA_calcprecalcs(getActiveVGA(),WHEREUPDATED_CGACRTCONTROLLER_HORIZONTAL|0x1); //The horizontal size might have been updated! 206} 207 208void applyCGAModeControl() 209{ 210 //Apply the new CGA mode control register? 211 if (getActiveVGA()->registers->Compatibility_CGAModeControl&8) //Video enabled on the CGA? 212 { 213 if (getActiveVGA()->registers->Compatibility_MDAModeControl&8) //MDA also enabled? 214 { 215 getActiveVGA()->registers->Compatibility_MDAModeControl &= ~8; //Disable the MDA! 216 } 217 } 218 if (getActiveVGA()->registers->Compatibility_CGAModeControl&0x2) //Graphics mode? 219 { 220 if (getActiveVGA()->registers->Compatibility_CGAModeControl&0x10) //2 colour? 221 { 222 setCGAMDAMode(1,0,(getActiveVGA()->registers->Compatibility_CGAModeControl&0x20)); //Set up basic 2-color graphics! 223 applyCGAMemoryMap(1,0); 224 } 225 else //4 colour? 226 { 227 setCGAMDAMode(1,1,(getActiveVGA()->registers->Compatibility_CGAModeControl&0x20)); //Set up basic 4-color graphics! 228 applyCGAMemoryMap(1,1); 229 } 230 } 231 else //Text mode? 232 { 233 setCGAMDAMode(0,0,(getActiveVGA()->registers->Compatibility_CGAModeControl&0x20)); //Text mode! 234 applyCGAMemoryMap(0,0); 235 } 236 getActiveVGA()->registers->SequencerRegisters.REGISTERS.CLOCKINGMODEREGISTER.ScreenDisable = (getActiveVGA()->registers->Compatibility_CGAModeControl&8)?0:1; //Disable the screen when requested! Interpret the RAM as zeroes(black) when disabled! 237 applyCGAMDAPaletteRegisters(); //Apply the palette registers according to our settings! 238 VGA_calcprecalcs(getActiveVGA(),WHEREUPDATED_ALL); //We have been updated! 239} 240 241void applyMDAModeControl() 242{ 243 //Apply the new MDA mode control register? 244 if (getActiveVGA()->registers->Compatibility_MDAModeControl&8) //Video enabled on the MDA? 245 { 246 if (getActiveVGA()->registers->Compatibility_CGAModeControl&8) //CGA also enabled? 247 { 248 getActiveVGA()->registers->Compatibility_CGAModeControl &= ~8; //Disable the CGA! 249 } 250 setCGAMDAMode(0,1,(getActiveVGA()->registers->Compatibility_MDAModeControl&0x20)); //Text mode! 251 applyCGAMemoryMap(0,1); 252 } 253 applyCGAMDAPaletteRegisters(); //Apply the palette registers according to our settings! 254 getActiveVGA()->registers->SequencerRegisters.REGISTERS.CLOCKINGMODEREGISTER.ScreenDisable = (getActiveVGA()->registers->Compatibility_MDAModeControl&8)?0:1; //Disable the screen when requested! Interpret the RAM as zeroes(black) when disabled! 255 VGA_calcprecalcs(getActiveVGA(),WHEREUPDATED_ALL); //We have been updated! 256} 257 258//Support for the I/O updating! 259void updateCGAMDAflags() 260{ 261 if ((getActiveVGA()->registers->specialCGAflags!=getActiveVGA()->precalcs.LastCGAFlags) || (getActiveVGA()->registers->specialMDAflags!=getActiveVGA()->precalcs.LastMDAFlags)) //CGA/MDA flags updated? 262 { 263 getActiveVGA()->precalcs.LastCGAFlags = getActiveVGA()->registers->specialCGAflags; //Update the last value used! 264 getActiveVGA()->precalcs.LastMDAFlags = getActiveVGA()->registers->specialMDAflags; //Update the last value used! 265 VGA_calcprecalcs(getActiveVGA(),WHEREUPDATED_ALL_SECTION|WHEREUPDATED_CRTCONTROLLER); //We have been updated! Update the whole section, as we don't know anything about the exact registers affected by the special action! 266 } 267} 268 269void applyCGAMDAMode() //Apply VGA to CGA/MDA Mode conversion(setup defaults for all registers not used by the CGA/MDA emulation)! 270{ 271 //Now, setup defaults for the CGA/MDA to be used(normal data)! Clear any unused registers! 272 //Graphics Controller: Fully set! 273 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.SETRESETREGISTER.SetReset = 0; //Disable! 274 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.ENABLESETRESETREGISTER.EnableSetReset = 0; //No set/reset used! 275 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.DATAROTATEREGISTER.RotateCount = 0; //No special operation! 276 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.DATAROTATEREGISTER.LogicalOperation = 0; //No special operation! 277 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.COLORCOMPAREREGISTER.ColorCompare = 0; //Disable! 278 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.READMAPSELECTREGISTER.ReadMapSelect = 0; //Disable/Plane 0! 279 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.WriteMode = 0; //Disable! 280 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.ReadMode = 0; //Disable! 281 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.OddEvenMode = 1; //Enable! 282 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.GRAPHICSMODEREGISTER.Color256ShiftMode = 0; //Disable! 283 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.MISCGRAPHICSREGISTER.EnableOddEvenMode = 1; //Enable! 284 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.COLORDONTCAREREGISTER.ColorCare |= 0xF; //Care about this only! 285 getActiveVGA()->registers->GraphicsRegisters.REGISTERS.BITMASKREGISTER = 0xFF; //Use all bits supplied by the CPU! 286 //Sequencer: Fully set! 287 getActiveVGA()->registers->SequencerRegisters.REGISTERS.RESETREGISTER.AR = 1; //Start the sequencer! 288 getActiveVGA()->registers->SequencerRegisters.REGISTERS.RESETREGISTER.SR = 1; //Start the sequencer! 289 getActiveVGA()->registers->SequencerRegisters.REGISTERS.CLOCKINGMODEREGISTER.S4 = 0; //CGA display! 290 getActiveVGA()->registers->SequencerRegisters.REGISTERS.CLOCKINGMODEREGISTER.DCR = 0; //CGA display! Single pixels only! 291 getActiveVGA()->registers->SequencerRegisters.REGISTERS.CLOCKINGMODEREGISTER.SLR = 0; //CGA display! Single load rate! 292 getActiveVGA()->registers->SequencerRegisters.REGISTERS.CLOCKINGMODEREGISTER.DotMode8 = 1; //CGA display! 8 dots/character! 293 getActiveVGA()->registers->SequencerRegisters.REGISTERS.MAPMASKREGISTER.MemoryPlaneWriteEnable = 3; //Write to planes 0/1 only, since we're emulating CGA! 294 getActiveVGA()->registers->SequencerRegisters.REGISTERS.SEQUENCERMEMORYMODEREGISTER.ExtendedMemory = 0; //Write to planes 0/1 only, since we're emulating CGA! We're wrapping around 16KB, so only 64K applied(16K with double 4 bytes per index=64K memory). 295 getActiveVGA()->registers->SequencerRegisters.REGISTERS.SEQUENCERMEMORYMODEREGISTER.OEDisabled = 0; //Write to planes 0/1 only, since we're emulating CGA! 296 getActiveVGA()->registers->SequencerRegisters.REGISTERS.SEQUENCERMEMORYMODEREGISTER.Chain4Enable = 0; //Write to planes 0/1 only, since we're emulating CGA! 297 //CRT Controller: Fully set! 298 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.UNDERLINELOCATIONREGISTER.DW = 0; //CGA normal mode! 299 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.UNDERLINELOCATIONREGISTER.DIV4 = 0; //CGA normal mode! 300 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.LINECOMPAREREGISTER = 0xFF; //Maximum line compare: no split screen! 301 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.OVERFLOWREGISTER.LineCompare8 = 1; //Maximum line compare: no split screen! 302 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.MAXIMUMSCANLINEREGISTER.LineCompare9 = 1; //Maximum line compare: no split screen! 303 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CURSORENDREGISTER.CursorSkew = 0; //Don't use any cursor skewing: we're direct line numbers for the scanline within the character! 304 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.VERTICALRETRACEENDREGISTER.VerticalInterrupt_Disabled = 1; //Disable the VRetrace interrupts! 305 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.SLDIV = 0; //CGA no scanline division! 306 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.DIV2 = 0; //CGA normal mode? 307 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.SE = 1; //CGA enable CRT rendering HSYNC/VSYNC! 308 //Memory mapping special: always map like a CGA! 309 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.UseByteMode = 0; //CGA word mode! 310 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.AW = 0; //CGA mapping is done by the renderer mapping CGA! 311 getActiveVGA()->registers->CRTControllerRegisters.REGISTERS.CRTCMODECONTROLREGISTER.MAP14 = 1; //CGA mapping! 312 //Attribute Controller: Fully set! 313 VGA_3C0_PAL = 1; //Enable the palette! 314 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.LineGraphicsEnable = 1; //CGA line graphics! 315 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.PixelPanningMode = 0; //CGA pixel panning mode! 316 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.ColorEnable8Bit = 0; //Not using 8-bit colors! 317 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.ATTRIBUTEMODECONTROLREGISTER.PaletteBits54Select = 0; //Not using the high Palette bits! 318 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.COLORPLANEENABLEREGISTER.DATA |= 0xF; //CGA: enable all color planes! 319 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.HORIZONTALPIXELPANNINGREGISTER.PixelShiftCount = 0; //Don't shift! 320 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.COLORSELECTREGISTER.ColorSelect54 = 0; //Don't use! 321 getActiveVGA()->registers->AttributeControllerRegisters.REGISTERS.COLORSELECTREGISTER.ColorSelect76 = 0; //Don't use! 322 //External registers: Fully set! 323 getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER.IO_AS = (getActiveVGA()->registers->specialCGAflags&1)?1:0; //CGA/MDA address! 324 getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER.RAM_Enable = 1; //CGA! 325 getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER.OE_HighPage = 0; //CGA! 326 getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER.HSyncP = 0; //CGA has positive polarity! 327 getActiveVGA()->registers->ExternalRegisters.MISCOUTPUTREGISTER.VSyncP = 0; //CGA has positive polarity! 328 getActiveVGA()->registers->ExternalRegisters.FEATURECONTROLREGISTER.FC0 = 0; //CGA! 329 getActiveVGA()->registers->ExternalRegisters.FEATURECONTROLREGISTER.FC1 = 1; //CGA! 330 VGA_calcprecalcs(getActiveVGA(),WHEREUPDATED_ALL); //We have been updated(full VGA)! 331}
applyCGAMDAMode is called when the VGA is switched from VGA to CGA mode (VGA with CGA or VGA with MDA modes). It's called when starting the emulator when Pure CGA or Pure MDA mode is selected. applyMDAModeControl, applyCGAModeControl and applyCGAPaletteRegister are called when those respective registers are written to (by the CPU). Writing to CGA CRTC register 8h(Interlace Register) also calls updateCGAmapping to register the changes in CGA interlace modes.
Although the audio is a bit displaced because the emulator has to be starting recording itself and the recording software (Ezvid) doesn't record audio, only video. Also unfortunately Windows 10 refuses to record the application itself using Win+G (Gaming toolbar) on it.
Getting there! The palette change on the intro screen (as it rolls down over the comparison text) is supposed to be synchronized with the rolldown (so the text is in palette 7 and the graphical title screen is in palette 15). The fact that it isn't means that your PIT implementation is not synchronized with your CGA implementation. That could also account for the flickering on the 1K colour screens. There are supposed to be exactly 19912 (=76*262) PIT cycles per CGA frame (i.e. a PIT cycle is 12 hdots).
You've also got some lines on the 256 colour plasma. These suggest that you might not have the correct CGA font ROM. If that's not the problem, can I see a still image of the RGBI output for this effect?
For the 100-line (faces) image, I wonder if the start address is taking effect at the wrong time. These registers are only supposed to have an effect at the start of the CRTC frame. It seems like you also have an issue with the vsync position, as the image is supposed to be vertically centered. This effect switches between 1 scanline per row (for the 100 scanlines or so of the actual image) to 2 scanlines per row (for the vertical overscan and sync part of the screen).
Well the PIT, CGA and 8086/88 all share the same 14MHz clock. The CGA takes it as pixel clock, the CPU divides by 3 and the PIT divides by 12.
Of course every CPU instruction takes 9 cycles on a 80(1)88 and 8 cycles on a 80(1)86 atm. So PIT ticks might not execute right away when the CPU executes too slow(multiple IRQ0 triggers at once will be missed, only triggering one IRQ0). That will currently cause a little IRQ0 drift/miss with PIT not exactly 8/9 cycle multiples.
The start address is currently reloaded on vertical total(at horizontal(scanline) pixel #0). It will reset the scanline of the buffer, rendering the first pixel of the new(2-line if requested) frame. It also triggers the vertical sync reset signal, which currently has no effect anymore, since it's unneeded(old seperate hsync/vsync processing to reset the hsync/vsync timing, which is currently set to the current pixel instead of using independant timing).
Do I need to load the start register at the start of the frame instead(at Sequencer pixel 0,0)?
Also, here is the RGBI output of the 256 color plasma (16/256 color part of the demo):
It's rendered 1:1 inside the window using the emulator's RGBI conversion (just a simple lookup table of 15 RGBI values according to a wiki(wikipedia I think)).
https://en.wikipedia.org/wiki/Color_Graphics_Adapter
It's the one listed as the "Full CGA 16-color palette". Although the color #6's green signal can be switched to full (0xAA) for a dark yellow, or half (0x55) for the normal brown color in my emulator. It's currently set to brown for compatibility though.
The fonts used for CGA and MDA are the Dosbox fonts(Taken from Dosbox-X I think, though I don't know what commit anymore(it was a long time ago I implemented this). And the ROMs read within 8088 MPH are the fonts stored in the Turbo XT BIOS v2.5, don't know if that one's accurate.
Although full PIT synchronization might be impossible currently, since the CPU emulation can only execute the PIT IRQ0 after it's processed that corresponding instruction(which adds cycles to tick) and ticking the PIT&IRQ0 based on that number added. At the same time the PIT is ticked, the VGA/EGA/CGA/MDA also measure timing and process that time.
Since a CPU tick takes 8 cycles, so 4 PIT ticks when it's at it's highest frequency(1.19MHz clock set with a reload counter set to 1), the CPU ticks 8 cycles of time(at ~4.77MHz), the PIT ticks 1 or 2 times(at ~1.19MHz) and the CGA ticks ~23 pixel timings(at ~14MHz). This happens every instruction, thus CPU/CGA timing might be drifting due to cycles taking too much time to process the faster events from the PIT(currently running twice as fast as the CPU(can expire up to 2 times in one CPU instruction, with the CPU instruction cycles being set)). The CPU will only execute the PIT0 after the cycles triggering that IRQ (and all hardware updating status up to the current time) finish and the next instruction is timestamped for execution (the emulator waits for the next instruction time to arrive, after which it checks for interrupts(actually the start of the cycle), updates input status(physical keyboard/mouse/PSP keys)) and then executes the next instruction, at which point the whole thing starts again.
The emulator will of course start with zero time(nothing has been done yet after all). Then the following steps will loop:
1. Interrupts are checked. This includes IRQ0 triggering.
1. The instruction is executed and timing is calculated and added(Default setting is 8/9 cycles) to current time for real time synchronization.
2. Hardware times (PIT, Video adapter(VGA/CGA/MDA) and other hardwares) are updated using the time spend on the instruction.
3. Too slow core loops (they're combined if possible) are aborted(preventing an unresponsive emulator with too high cycle settings).
4. When the main loop ends, physical keyboard&mouse status is updated. Then the emulator waits until the current CPU time is reached in realtime (thus synchronizing CPU time with realtime).
5. Finally, if changed, the display resolution (when using the normal Forced mode combined with Fullscreen stretching, which generates a 1:1 window of the output by the Video adapter) of the SDL display window is updated.
6. SDL is polled for any input (Mouse/Keyboard/Window status/PSP keys/Joystick input(Joystick #0 only)) and the OSK and emulated keyboard/mouse status is updated as needed(though actual processing of input/mouse movement is in the main CPU loop itself, at step 2).
So CGA might always run faster than the CPU, as long as the CPU takes 8 or 9 cycles each instruction.
Last edited by superfury on 2016-04-13, 06:06. Edited 1 time in total.
Also, here is the RGBI output of the 256 color plasma (16/256 color part of the demo):
Snap 2016-04-12 at 23.02.24.png
That looks like it's using 8 scanlines per character row. The effect programs the CRTC for 6 scanlines (r9=5). That could account for the lines.
If your PIT clock is correct then the next most likely problem affecting the palette change in the intro is loading a new PIT count. If you write to port 0x43 to set the mode before the count, then the new count takes effect immediately, but if you don't set the mode (and just write to port 0x40, in this case) the new count should take effect once the current count reaches 0.
Another possible problem is your implementation of the HLT instruction. Once the CPU is halted, the next interrupt should take effect a constant number of cycles after it occurs (no jitter).
The reloading of PIT counters is done according to the osdev wiki article.
The CGA Maximum scanline programmed is copird directly into the VGA Maximum scanline register. The renderer character height should the value in the VGA Maximum Scanline Register + 1. In this case 5 is programmed, copied and increased to become 6 scanlines/character in the renderer, if that's indeed what's programmed in the CGA CRTC register #9.
The HLT instruction takes it's defined time(4 cycles I believe?). The delay between interrupt checks(and hardware updates etc.) is 1 CPU cycle during HLT state.
I've also tried moving the Start Address reloading to when the frame ends (the moment the new frame starts). This doesn't seem to have any effect on the demo.
Just verified the value in the CGA&VGA Maximum Scanline registers. Both are set to 5, so 6 scanlines per character should be used during that part. Vertical active display is 264 scanlines high for some reason though (which uses 8 scanlines/character)?
Edit: It looks like it wasn't updating the CRTC timings when the character height was updated. This is now fixed. So far it at least fixes the height of the SALC part (the 3d text tube part at the start of the demo).
The height of the characters in the 16/256 color image is now only a fraction of the old height according to the RGBI monitor.
The faces at the end now flash left and right (moving half a face left and right very fast).
The command prompt still displays at the same row as the final line of 8088MPH. It's disappearing behind the content of 8088MPH(including anything typed, until you reach the next line, which shifts display up one row, using any command prompt method (typing too much or pressing enter)).
Edit: Moving the start address reloading back to the end of vertical total(start of new display) seems to fix the problems introduced with the heads moving left&right. It also fixes the problem introduced in the detection screen with the change in start address logic.
I also notice that the + sign in the middle of the table comparing the C64 and IBM PC is slightly displaced? Although the font won't have any effect on the textmode font (which is probably different), as the font used at that point is in the Turbo XT BIOS itself (it's graphics mode with characters read from the Turbo XT BIOS ROM or in low RAM(0-640K))?
Well, now at least the horizontal timing of the final image showing the faces is correct (thanks to the character height fix):
(Alhough it still isn't centered vertically)
Also, the rotozoomer (Intel logo) part looks more grainy now (less colours displayed).
Btw the CGA Compatibility Tester's "Row reprogramming" option now gives the correct images 😀
The Interlaced mode test gives errors (showing incomplete text on the screen).
Also the informational texts reporting stuff give a little window with, for example:
1xt mode. 2Press a key to enable in 3terlacing; when done vie 4wing, press any key to e 5xit.
When the Interlaced mode is turned on, it only shows "Intelaced mode on, Press key", with the bottom of the 4th line of the baseline of the "y" being the last scanline seen on the screen.
Most of the demo seems to work now. Only four problems left:
1. The 1K images are flashing (seemingly reduced to small, vertically centered beams of about 16 pixels during at least some of those periods).
2. The Kefrens Bars are flashing completely black (probably a timing problem, which would need the CPU to become 100% accurate to get working properly(which is of course the next step after all other parts of 8088 MPH work)).
3. The MS-DOS prompt at the end is positioned correctly (directly after 8088 MPH), but the screen isn't shifted upwards for the prompt to show? This causes it to 'hide' behind the final line of the credits somehow? Although it's a text line and such a thing SHOULD be impossible(text and older text mixing together)?
4. The faces won't be centered vertically for some reason? They're displayed fine, but the timing for retracing might be off by a quarter screen somehow?
Reenigne? Scali? Do you know anything about these (current) problems?
Reenigne? Scali? Do you know anything about these (current) problems?
Yes, as previously mentioned:
superfury wrote:
1. The 1K images are flashing (seemingly reduced to small, vertically centered beams of about 16 pixels during at least some of those periods).
4. The faces won't be centered vertically for some reason? They're displayed fine, but the timing for retracing might be off by a quarter screen somehow?
If you can make a dump of 40ms or so worth of CGA register writes and status register reads during the effect I can tell what's going on from that.
superfury wrote:
3. The MS-DOS prompt at the end is positioned correctly (directly after 8088 MPH), but the screen isn't shifted upwards for the prompt to show? This causes it to 'hide' behind the final line of the credits somehow? Although it's a text line and such a thing SHOULD be impossible(text and older text mixing together)?
That's most likely a DOS and/or BIOS problem. Try it with the 1501512 BIOS and PC DOS 3.1. If it's correct there, change one of those back to isolate it. The effect uses int 0x10 with ah=2, bh=0, dh=24, dl=0 to move the BIOS's idea of the cursor position to the last line of the screen before the effect starts, and at the end of the effect the start address should be 0.
Trying to run the 1501512 BIOS (first revision of the IBM XT BIOS) makes the emulator end up in a HLT instruction somehow? Maybe something is wrong/inaccurate enough in my emulator making it detect wrong/invalid hardware?
Trying to run the 1501512 BIOS (first revision of the IBM XT BIOS) makes the emulator end up in a HLT instruction somehow? Maybe something is wrong/inaccurate enough in my emulator making it detect wrong/invalid hardware?
Btw I do remember valuely when trying a IBM PC XT (third revision if I remember correctl) BIOS a few weeks ago, which was endlessly looping when testing the PIT counters (I believe it set up PIT counter 2 and waited it to become 0x40 somehow after some ANDing of the values read from PIT0(port 0x40) or PIT1(port 0x41)).
I'll switch back to the Turbo XT BIOS for now and start dumping those 40ms logs (are you sure you need 40ms and not 40us? 40ms=40/1000th second), so that amounts to around 2.4 frames being logged?
superfury wrote:The debugger (which is now adjusted to log hlt/halt state as well) logs: […] Show full quote
The debugger (which is now adjusted to log hlt/halt state as well) logs:
10:00:09:29.5.0253: F000:E12E (74424242)<HLT>[/quote] 2 3That means you're not implementing the DMA controller properly. 4 5[quote="superfury"]I'll switch back to the Turbo XT BIOS for now and start dumping those 40ms logs (are you sure you need 40ms and not 40us? 40ms=40/1000th second), so that amounts to around 2.4 frames being logged?[/quote] 6 7Yes. If I can see all the CGA register accesses for a couple of frames worth of time I think I'll be able to figure out what's going on.