Merge branch 'next-release'

This commit is contained in:
Jeff Parsons 2016-04-01 15:46:22 -07:00
commit d0480ecb90
297 changed files with 9645 additions and 2541 deletions

File diff suppressed because it is too large Load diff

View file

@ -184,6 +184,14 @@ function Computer(parmsComputer, parmsMachine, fSuspended) {
}
this.dbg = /** @type {Debugger} */ (Component.getComponentByType("Debugger", this.id));
/*
* Enumerate all Video components for future updateVideo() calls.
*/
this.aVideo = [];
for (var video = null; (video = this.getMachineComponent("Video", video));) {
this.aVideo.push(video);
}
/*
* Initialize the Bus component
*/
@ -1437,6 +1445,65 @@ Computer.prototype.getMachineComponent = function(sType, componentPrev)
return null;
};
/**
* updateFocus(fScroll)
*
* NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
* the scroll feature has annoying effect on iOS, so we no longer do it by default (fScroll must be true).
*
* @this {Computer}
* @param {boolean} [fScroll]
*/
Computer.prototype.updateFocus = function(fScroll)
{
if (this.aVideo.length) {
/*
* This seems to be recommended work-around to prevent the browser from scrolling the focused element
* into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent
* is to ensure that keyboard input is fielded properly.
*/
var x = 0, y = 0;
if (fScroll && window) {
x = window.scrollX;
y = window.scrollY;
}
/*
* TODO: We need a mechanism to determine the "active" display, instead of hard-coding this to aVideo[0].
*/
this.aVideo[0].setFocus();
if (fScroll && window) {
window.scrollTo(x, y);
}
}
};
/**
* updateStatus()
*
* @this {Computer}
*/
Computer.prototype.updateStatus = function()
{
if (this.panel) this.panel.updateStatus();
};
/**
* updateVideo(fForce)
*
* Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to
* 60 times per second (see VIDEO_UPDATES_PER_SECOND).
*
* @this {Computer}
* @param {boolean} [fForce] (true to force a video update)
*/
Computer.prototype.updateVideo = function(fForce)
{
for (var i = 0; i < this.aVideo.length; i++) {
this.aVideo[i].updateScreen(fForce);
}
if (this.panel) this.panel.updateAnimation();
};
/**
* Computer.init()
*

View file

@ -122,11 +122,6 @@ function CPU(parmsCPU, nCyclesDefault)
this.aCounts.nCyclesChecksumInterval = parmsCPU["csInterval"];
this.aCounts.nCyclesChecksumStop = parmsCPU["csStop"];
/*
* Initially, no video devices are attached that require CPU-driven updates. initBus() will update this.
*/
this.aVideo = [];
this.onRunTimeout = this.runCPU.bind(this); // function onRunTimeout() { cpu.runCPU(); };
this.setReady();
@ -180,14 +175,6 @@ CPU.prototype.initBus = function(cmp, bus, cpu, dbg)
this.fpu = cmp.getMachineComponent("FPU");
/*
* Attach the Video component to the CPU, so that the CPU can periodically update
* the video display via updateVideo(), as cycles permit.
*/
for (var video = null; (video = cmp.getMachineComponent("Video", video));) {
this.aVideo.push(video);
}
/*
* Attach the ChipSet component to the CPU so that it can obtain the IDT vector number
* of pending hardware interrupts in response to the ChipSet's updateINTR() notifications.
@ -323,7 +310,7 @@ CPU.prototype.autoStart = function()
*/
if (this.flags.fAutoStart || (!DEBUGGER || !this.dbg) && this.bindings["run"] === undefined) {
/*
* Now we ALSO set fSetFocus when calling runCPU(), on the assumption that in the "auto-starting" context,
* Now we ALSO set fUpdateFocus when calling runCPU(), on the assumption that in the "auto-starting" context,
* a machine without focus is like a day without sunshine.
*/
this.runCPU(true);
@ -483,66 +470,6 @@ CPU.prototype.displayValue = function(sLabel, nValue, cch)
}
};
/**
* updateStatus(fForce)
*
* This provides periodic Control Panel updates (eg, a few times per second; see STATUS_UPDATES_PER_SECOND).
* The X86CPU subclasses updateStatus() to take care of any DOM updates (eg, register values) while the CPU is running.
*
* @this {CPU}
* @param {boolean} [fForce]
*/
CPU.prototype.updateStatus = function(fForce)
{
if (this.cmp && this.cmp.panel) this.cmp.panel.updateStatus();
};
/**
* updateVideo(fForce)
*
* Any high-frequency updates should be performed here. Avoid DOM updates, since updateVideo() can be called up to
* 60 times per second (see VIDEO_UPDATES_PER_SECOND).
*
* @this {CPU}
* @param {boolean} [fForce] (true to force a video update)
*/
CPU.prototype.updateVideo = function(fForce)
{
for (var i = 0; i < this.aVideo.length; i++) {
this.aVideo[i].updateScreen(fForce);
}
if (this.cmp && this.cmp.panel) this.cmp.panel.updateAnimation();
};
/**
* setFocus(fScroll)
*
* NOTE: When soft keyboard buttons call us to return focus to the machine (and away from the button),
* the scroll feature has annoying effect on iOS, so we no longer do it by default (fScroll must be true).
*
* @this {CPU}
* @param {boolean} [fScroll]
*/
CPU.prototype.setFocus = function(fScroll)
{
if (this.aVideo.length) {
/*
* This seems to be recommended work-around to prevent the browser from scrolling the focused element
* into view. The CPU is not a visual component, so when the CPU wants to set focus, the primary intent
* is to ensure that keyboard input is fielded properly.
*/
var x = 0, y = 0;
if (fScroll && window) {
x = window.scrollX;
y = window.scrollY;
}
this.aVideo[0].setFocus();
if (fScroll && window) {
window.scrollTo(x, y);
}
}
};
/**
* setBinding(sHTMLType, sBinding, control, sValue)
*
@ -823,20 +750,20 @@ CPU.prototype.getSpeedTarget = function()
};
/**
* setSpeed(nMultiplier, fSetFocus)
* setSpeed(nMultiplier, fUpdateFocus)
*
* NOTE: This used to return the target speed, in mhz, but no callers appear to care at this point.
*
* @this {CPU}
* @param {number} [nMultiplier] is the new proposed multiplier (reverts to 1 if the target was too high)
* @param {boolean} [fSetFocus] is true to give the CPU focus
* @param {boolean} [fUpdateFocus] is true to update Computer focus
* @return {boolean} true if successful, false if not
*
* @desc Whenever the speed is changed, the running cycle count and corresponding start time must be reset,
* so that the next effective speed calculation obtains sensible results. In fact, when runCPU() initially calls
* setSpeed() with no parameters, that's all this function does (it doesn't change the current speed setting).
*/
CPU.prototype.setSpeed = function(nMultiplier, fSetFocus)
CPU.prototype.setSpeed = function(nMultiplier, fUpdateFocus)
{
var fSuccess = false;
if (nMultiplier !== undefined) {
@ -857,7 +784,7 @@ CPU.prototype.setSpeed = function(nMultiplier, fSetFocus)
if (controlSpeed) controlSpeed.textContent = sSpeed;
this.println("target speed: " + sSpeed);
}
if (fSetFocus) this.setFocus();
if (fUpdateFocus && this.cmp) this.cmp.updateFocus();
}
this.addCycles(this.nRunCycles);
this.nRunCycles = 0;
@ -1014,12 +941,12 @@ CPU.prototype.calcRemainingTime = function()
};
/**
* runCPU(fSetFocus)
* runCPU(fUpdateFocus)
*
* @this {CPU}
* @param {boolean} [fSetFocus] is true to give the CPU focus
* @param {boolean} [fUpdateFocus] is true to update Computer focus
*/
CPU.prototype.runCPU = function(fSetFocus)
CPU.prototype.runCPU = function(fUpdateFocus)
{
if (!this.setBusy(true)) {
this.updateCPU();
@ -1027,7 +954,7 @@ CPU.prototype.runCPU = function(fSetFocus)
return;
}
this.startCPU(fSetFocus);
this.startCPU(fUpdateFocus);
/*
* calcStartTime() initializes the cycle counter and timestamp for this runCPU() invocation, and optionally
@ -1078,13 +1005,13 @@ CPU.prototype.runCPU = function(fSetFocus)
this.aCounts.nCyclesNextVideoUpdate -= nCycles;
if (this.aCounts.nCyclesNextVideoUpdate <= 0) {
this.aCounts.nCyclesNextVideoUpdate += this.aCounts.nCyclesPerVideoUpdate;
this.updateVideo();
if (this.cmp) this.cmp.updateVideo();
}
this.aCounts.nCyclesNextStatusUpdate -= nCycles;
if (this.aCounts.nCyclesNextStatusUpdate <= 0) {
this.aCounts.nCyclesNextStatusUpdate += this.aCounts.nCyclesPerStatusUpdate;
this.updateStatus();
if (this.cmp) this.cmp.updateStatus();
}
this.aCounts.nCyclesNextYield -= nCycles;
@ -1106,13 +1033,13 @@ CPU.prototype.runCPU = function(fSetFocus)
};
/**
* startCPU(fSetFocus)
* startCPU(fUpdateFocus)
*
* WARNING: Other components must use runCPU() to get the CPU running; this is a runCPU() helper function only.
*
* @param {boolean} [fSetFocus]
* @param {boolean} [fUpdateFocus]
*/
CPU.prototype.startCPU = function(fSetFocus)
CPU.prototype.startCPU = function(fUpdateFocus)
{
if (!this.flags.fRunning) {
/*
@ -1128,8 +1055,10 @@ CPU.prototype.startCPU = function(fSetFocus)
if (this.chipset) this.chipset.setSpeaker();
var controlRun = this.bindings["run"];
if (controlRun) controlRun.textContent = "Halt";
this.updateStatus(true);
if (fSetFocus) this.setFocus(true);
if (this.cmp) {
this.cmp.updateStatus();
if (fUpdateFocus) this.cmp.updateFocus(true);
}
}
};
@ -1187,8 +1116,10 @@ CPU.prototype.stopCPU = function(fComplete)
*/
CPU.prototype.updateCPU = function(fForce)
{
this.updateVideo(fForce);
this.updateStatus();
if (this.cmp) {
this.cmp.updateVideo(fForce);
this.cmp.updateStatus();
}
};
/**

View file

@ -2182,11 +2182,11 @@ if (DEBUGGER) {
};
/**
* setFocus()
* updateFocus()
*
* @this {Debugger}
*/
Debugger.prototype.setFocus = function()
Debugger.prototype.updateFocus = function()
{
if (this.controlDebug) this.controlDebug.focus();
};
@ -3945,16 +3945,16 @@ if (DEBUGGER) {
};
/**
* runCPU(fSetFocus)
* runCPU(fUpdateFocus)
*
* @this {Debugger}
* @param {boolean} [fSetFocus] is true to give the CPU focus
* @param {boolean} [fUpdateFocus] is true to update focus
* @return {boolean} true if run request successful, false if not
*/
Debugger.prototype.runCPU = function(fSetFocus)
Debugger.prototype.runCPU = function(fUpdateFocus)
{
if (!this.isCPUAvail()) return false;
this.cpu.runCPU(fSetFocus);
this.cpu.runCPU(fUpdateFocus);
return true;
};
@ -4258,7 +4258,7 @@ if (DEBUGGER) {
this.println(sStopped);
}
this.updateStatus(true);
this.setFocus();
this.updateFocus();
this.clearTempBreakpoint(this.cpu.regLIP);
}
};
@ -7618,7 +7618,7 @@ if (DEBUGGER) {
if (this.nStep) {
this.setTempBreakpoint(dbgAddr);
if (!this.runCPU()) {
this.cpu.setFocus();
if (this.cmp) this.cmp.updateFocus();
this.nStep = 0;
}
/*

View file

@ -771,7 +771,7 @@ FDC.prototype.initController = function(data)
if (this.aDrives === undefined) {
this.nDrives = 4; // default to the maximum number of drives
if (this.chipset) this.nDrives = this.chipset.getSWFloppyDrives();
if (this.chipset) this.nDrives = this.chipset.getDIPFloppyDrives();
/*
* I would prefer to allocate only nDrives, but as discussed in the handling of the FDC.REG_DATA.CMD.SENSE_INT
* command, we're faced with situations where the controller must respond to any drive in the range 0-3, regardless
@ -789,7 +789,7 @@ FDC.prototype.initController = function(data)
* the drive's physical limits accordingly (ie, max tracks, max heads, and max sectors/track).
*/
drive = this.aDrives[iDrive] = {};
var nKb = (this.chipset? this.chipset.getSWFloppyDriveSize(iDrive) : 0);
var nKb = (this.chipset? this.chipset.getDIPFloppyDriveSize(iDrive) : 0);
switch(nKb) {
case 160:
case 180:
@ -1279,7 +1279,7 @@ FDC.prototype.loadSelectedDrive = function(sDisketteName, sDiskettePath, file)
if (DEBUG) this.println("loading disk " + sDiskettePath + "...");
while (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, false, file)) {
while (this.loadDiskette(iDrive, sDisketteName, sDiskettePath, false, file) < 0) {
if (!window.confirm("Click OK to reload the original disk.\n(WARNING: All disk changes will be discarded)")) {
return;
}
@ -1327,7 +1327,7 @@ FDC.prototype.mountDiskette = function(iDrive, sDisketteName, sDiskettePath)
* @param {string} sDiskettePath
* @param {boolean} [fAutoMount]
* @param {File} [file] is set if there's an associated File object
* @return {boolean} true if diskette (already) loaded, false if queued up (or busy)
* @return {number} 1 if diskette loaded, 0 if queued up (or busy), -1 if already loaded
*/
FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAutoMount, file)
{
@ -1336,7 +1336,7 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
this.unloadDrive(iDrive, fAutoMount, true);
if (drive.fBusy) {
this.notice("Drive " + iDrive + " busy");
return true;
return 0;
}
drive.fBusy = true;
if (fAutoMount) {
@ -1347,10 +1347,11 @@ FDC.prototype.loadDiskette = function(iDrive, sDisketteName, sDiskettePath, fAut
drive.fLocal = !!file;
var disk = new Disk(this, drive, DiskAPI.MODE.PRELOAD);
if (!disk.load(sDisketteName, sDiskettePath, file, this.doneLoadDiskette)) {
return false;
return 0;
}
return 1;
}
return true;
return -1;
};
/**
@ -1436,6 +1437,12 @@ FDC.prototype.doneLoadDiskette = function onFDCLoadNotify(drive, disk, sDiskette
drive.nDiskCylinders = aDiskInfo[0];
drive.nDiskHeads = aDiskInfo[1];
drive.nDiskSectors = aDiskInfo[2];
/*
* Since you usually want the Computer to have focus again after loading a new diskette, let's try automatically
* updating the focus after a successful load.
*/
if (this.cmp) this.cmp.updateFocus();
}
else {
drive.fLocal = false;
@ -1733,6 +1740,85 @@ FDC.prototype.outFDCOutput = function(port, bOut, addrFrom)
this.regOutput = bOut;
};
/**
* inFDCDiagnostic(port, addrFrom)
*
* It turns out that any 5170 configuration without an HDC component that attempts to use either the REV2 or REV3
* PC AT ROM BIOS will fail with error "601-Diskette Error", unless we also provide this "D/S/P DIAGNOSTIC REGISTER".
* The original 5170 REV1 BIOS didn't have this requirement.
*
* I'm unable to find any documentation on this so-called "D/S/P DIAGNOSTIC REGISTER" (port 0x3F1) or the "D/S/P CARD"
* to which the ROM BIOS refers. But it seems clear that if we don't provide the expected response from the DIAGNOSTIC
* REGISTER, and there's no HDC to respond to the MULTIPLE DATA RATE CAPABLE test that follows, then an error is inevitable.
* Clearly, there is a very intimate relationship between the FDC and HDC portions of this card.
*
* Here's the relevant code from the REV3 PC AT ROM BIOS (TEST2.ASM):
*
* ;----- CHECK FOR MULTIPLE DATA RATE CAPABILITY
*
* J_OK:
* MOV DX,03F1H ; D/S/P DIAGNOSTIC REGISTER
* IN AL,DX ; READ D/S/P TYPE CODE
* AND AL,11111000B ; KEEP ONLY UNIQUE CODE FOR D/S/P
* CMP AL,01010000B ; D/S/P CARD - MULTIPLE DATA RATE?
* JZ J_OK3 ; IF SO JUMP
*
* MOV DX,05F7H ; FIXED DISK DIAGNOSTIC REGISTER
* IN AL,DX ; READ FIXED DISK TYPE CODE
* AND AL,11110000B ; KEEP ONLY UNIQUE CODE FOR F/D
* CMP AL,10100000B ; FIXED DISK ADAPTER ?
* JZ J_FAIL ; MUST BE COMBO ELSE ERROR
*
* MOV BL,0FH ; OUTER LOOP COUNT WAIT FOR BUSY OFF
* SUB CX,CX
* MOV DX,01F7H ; HARD FILE STATUS PORT
* J_OK1:
* IN AL,DX ; GET THE STATUS
* TEST AL,080H ; IS THE CONTROLLER BUSY?
* JZ J_OK2 ; CONTINUE IF NOT
* LOOP J_OK1 ; TRY AGAIN
* DEC BL ; DECREMENT OUTER LOOP
* JNZ J_OK1 ; TRY AGAIN IF NOT ZERO
* AND AL,0CH ; BITS 2 & 3 = 0 IF MULTI DATA CAPABLE
* JZ J_OK3 ; GO IF YES
* JMP SHORT J_FAIL ; NO MULTIPLE DATA RATE CAPABILITY
* J_OK2:
* MOV DX,1F4H ; VERIFY MULTIPLE DATA RATE CAPABLE
* MOV AL,055H ; WRITE TO THE CYLINDER BYTE
* OUT DX,AL
* JMP $+2 ; I/O DELAY
* IN AL,DX ; CHECK DATA WRITTEN = DATA READ
* CMP AL,055H
* JNZ J_FAIL ; GO IF NOT
* MOV AL,0AAH ; WRITE ANOTHER PATTERN
* OUT DX,AL
* JMP $+2 ; I/O DELAY
* IN AL,DX
* CMP AL,0AAH ; IS DATA PATTERN THE SAME?
* JZ J_OK3 ; GO IF SO
*
* J_FAIL:
* OR @MFG_ERR_FLAG+1,DSK_FAIL; <><><><><><><><><><><><><>
* ; <><> DISKETTE FAILED <><>
* MOV SI,OFFSET E601 ; GET ADDRESS OF MESSAGE
* CALL E_MSG ; GO PRINT ERROR MESSAGE
* JMP SHORT F15C ; SKIP SETUP IF ERROR
*
* J_OK3:
* OR @LASTRATE,DUAL ; TURN ON DSP/COMBO FLAG
*
* @this {FDC}
* @param {number} port (0x3F1, input only)
* @param {number} [addrFrom] (not defined whenever the Debugger tries to read the specified port)
* @return {number} simulated port value
*/
FDC.prototype.inFDCDiagnostic = function(port, addrFrom)
{
var b = 0x50; // we simply return the expected pattern (01010000B); see code excerpt above
this.printMessageIO(port, null, addrFrom, "DIAG", b);
return b;
};
/**
* inFDCStatus(port, addrFrom)
*
@ -2539,6 +2625,7 @@ FDC.prototype.writeFormat = function(drive, b)
* way out and always emulating it. So, consider an FDC parameter to disable that feature for stricter compatibility.
*/
FDC.aPortInput = {
0x3F1: FDC.prototype.inFDCDiagnostic,
0x3F4: FDC.prototype.inFDCStatus,
0x3F5: FDC.prototype.inFDCData,
0x3F7: FDC.prototype.inFDCInput

View file

@ -2958,6 +2958,11 @@ HDC.aXTCPortInput = {
0x322: HDC.prototype.inXTCConfig
};
/*
* For future reference, the REV2 and REV3 PC AT ROM BIOS also refer to a "FIXED DISK DIAGNOSTIC REGISTER" at
* port 0x5F7, but I have no documentation on it, and failure to respond is non-fatal. See the discussion of the
* FDC diagnostic register in inFDCDiagnostic() for more details.
*/
HDC.aATCPortInput = {
0x1F0: HDC.prototype.inATCData,
0x1F1: HDC.prototype.inATCError,

View file

@ -1187,7 +1187,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
case "caps-lock":
this.bindings[id] = control;
control.onclick = function onClickCapsLock(event) {
if (kbd.cpu) kbd.cpu.setFocus();
if (kbd.cmp) kbd.cmp.updateFocus();
return kbd.toggleCapsLock();
};
return true;
@ -1195,7 +1195,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
case "num-lock":
this.bindings[id] = control;
control.onclick = function onClickNumLock(event) {
if (kbd.cpu) kbd.cpu.setFocus();
if (kbd.cmp) kbd.cmp.updateFocus();
return kbd.toggleNumLock();
};
return true;
@ -1203,7 +1203,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
case "scroll-lock":
this.bindings[id] = control;
control.onclick = function onClickScrollLock(event) {
if (kbd.cpu) kbd.cpu.setFocus();
if (kbd.cmp) kbd.cmp.updateFocus();
return kbd.toggleScrollLock();
};
return true;
@ -1218,7 +1218,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
control.onclick = function(kbd, sKey, simCode) {
return function onClickKeyboard(event) {
if (!COMPILED && kbd.messageEnabled()) kbd.printMessage(sKey + " clicked", Messages.KEYS);
if (kbd.cpu) kbd.cpu.setFocus();
if (kbd.cmp) kbd.cmp.updateFocus();
kbd.updateShiftState(simCode, true); // future-proofing if/when any LOCK keys are added to CLICKCODES
kbd.addActiveKey(simCode, true);
};
@ -1255,7 +1255,7 @@ Keyboard.prototype.setBinding = function(sHTMLType, sBinding, control, sValue)
*/
this.bindings[id] = control;
control.onclick = function onClickTest(event) {
if (kbd.cpu) kbd.cpu.setFocus();
if (kbd.cmp) kbd.cmp.updateFocus();
return kbd.injectKeys(sValue);
};
return true;
@ -1323,6 +1323,7 @@ Keyboard.prototype.findBinding = function(simCode, sType, fDown)
*/
Keyboard.prototype.initBus = function(cmp, bus, cpu, dbg)
{
this.cmp = cmp;
this.bus = bus;
this.cpu = cpu;
this.dbg = dbg;

View file

@ -158,7 +158,7 @@ RAM.prototype.powerDown = function(fSave, fShutdown)
RAM.prototype.reset = function()
{
if (!this.addrRAM && !this.fInstalled && this.chipset) {
var baseRAM = this.chipset.getSWMemorySize() * 1024;
var baseRAM = this.chipset.getDIPMemorySize() * 1024;
if (this.sizeRAM && baseRAM != this.sizeRAM) {
this.bus.removeMemory(this.addrRAM, this.sizeRAM);
this.fAllocated = false;

View file

@ -58,10 +58,10 @@ if (NODE) {
* scale: true for font scaling, false (default) to center the display on the screen
* charCols: number of character columns
* charRows: number of character rows
* fontROM: path to .rom file (or a JSON representation) that defines the character set
* fontROM: path to .rom file (or a JSON representation) containing the character set
* screenColor: background color of the screen canvas (default is black)
* touchScreen: string specifying desired touch-screen support (default is ''); see initBus()
* autoLock: true to (attempt to) automatically lock the mouse to the canvas (default is false)
* touchScreen: string specifying desired touch-screen support (default is none)
* autoLock: true to (attempt to) auto-lock the mouse to the canvas (default is false)
*
* An EGA may specify the following additional properties:
*
@ -73,22 +73,23 @@ if (NODE) {
* the port level, and whenever reset() is called. setMode() also invokes updateScreen(true),
* which forces reallocation of our internal buffer (aCellCache) that mirrors the video buffer.
*
* The CPU periodically calls updateScreen(), at an assumed rate of 60 times/second,
* to update any blinking elements (the cursor and any characters with the blink attribute),
* to compare/update the contents of our internal buffer with the video buffer, and to render
* any differences between the two buffers into the associated screen canvas, via either
* updateChar() or setPixel().
* The CPU periodically calls updateVideo(), which in turn calls updateScreen() for each Video
* instance. These updates should occur at a rate of 60 times/second, to update any blinking
* elements (the cursor and any cells with the blink attribute), to compare/update the contents
* of our internal buffer with the video buffer, and to render any differences between the two
* buffers into the associated screen canvas, via either updateChar() or setPixel().
*
* Thanks to the CPU's new block-based memory manager that allows us to sparse-allocate memory
* Thanks to the Bus' new block-based memory manager that allows us to sparse-allocate memory
* (in 4Kb increments on 20-bit buses, 16Kb increments on 24-bit buses), updateScreen()
* can also ask the CPU for the "dirty" state of all the blocks underlying the video buffer,
* bypassing the update completely if the buffer is still clean.
*
* Unfortunately, that optimization is defeated if our count of active blink elements is non-zero,
* Sadly, that optimization is defeated if the count of active blink elements is non-zero,
* because we must rescan the entire buffer to locate and redraw them all; I'm assuming for now
* that, more often than not, blink attributes will not be present, and therefore they're not worth
* a separate caching mechanism. If the only blinking element is the cursor, that's no problem,
* as we redraw only the one cell containing the cursor (assuming the buffer is otherwise clean).
* that, more often than not, very few (if any) blink attributes will be present, and therefore
* they're not worth a separate caching mechanism. If the only blinking element is the cursor,
* that's no problem, as we redraw only the one cell containing the cursor (assuming the buffer
* is otherwise clean).
*
* @constructor
* @extends Component
@ -1000,7 +1001,7 @@ Video.aEGADWToByte[0x80808000|0] = 0xe;
Video.aEGADWToByte[0x80808080|0] = 0xf;
/**
* Card(video, iCard, data, cbMemory)
* Card(video, nCard, data, cbMemory)
*
* Creates an object representing an initial video card state;
* can also restore a video card from state data created by saveCard().
@ -1011,25 +1012,25 @@ Video.aEGADWToByte[0x80808080|0] = 0xf;
*
* @constructor
* @param {Video} [video]
* @param {number} [iCard] (see Video.CARD.*)
* @param {number} [nCard] (see Video.CARD.*)
* @param {Array|null} [data]
* @param {number} [cbMemory] is specified if the card must allocate its own memory buffer
*/
function Card(video, iCard, data, cbMemory)
function Card(video, nCard, data, cbMemory)
{
/*
* If a card was originally not present (eg, EGA), then the state will be empty,
* so we need to detect that case and continue indicating that the card is not present.
*/
if (iCard !== undefined && (!data || data.length)) {
if (nCard !== undefined && (!data || data.length)) {
this.video = video;
var specs = Video.cardSpecs[iCard];
var specs = Video.cardSpecs[nCard];
var nMonitorType = video.nMonitorType || specs[5];
if (!data || data.length < 6) {
data = [false, 0, null, null, 0, new Array(iCard < Video.CARD.EGA? Card.CRTC.TOTAL_REGS : Card.CRTC.EGA.TOTAL_REGS)];
data = [false, 0, null, null, 0, new Array(nCard < Video.CARD.EGA? Card.CRTC.TOTAL_REGS : Card.CRTC.EGA.TOTAL_REGS)];
}
/*
@ -1041,7 +1042,7 @@ function Card(video, iCard, data, cbMemory)
this.port = specs[1];
}
this.nCard = iCard;
this.nCard = nCard;
this.addrBuffer = specs[2]; // default (physical) video buffer address
this.sizeBuffer = specs[3]; // default video buffer length (this is the total size, not the current visible size; this.cbScreen is calculated on the fly to reflect the latter)
@ -1073,7 +1074,7 @@ function Card(video, iCard, data, cbMemory)
this.nCRTCRegs = Card.CRTC.TOTAL_REGS;
this.asCRTCRegs = DEBUGGER? Card.CRTC.REGS : [];
if (iCard >= Video.CARD.EGA) {
if (nCard >= Video.CARD.EGA) {
this.nCRTCRegs = Card.CRTC.EGA.TOTAL_REGS;
this.asCRTCRegs = DEBUGGER? Card.CRTC.EGA_REGS : [];
this.initEGA(data[6], nMonitorType);
@ -2500,20 +2501,15 @@ Card.prototype.dumpRegs = function(sName, iReg, aRegs, asRegs)
{
if (DEBUGGER) {
if (!aRegs) {
this.dbg.println(sName + ": " + str.toHexByte(iReg));
this.dbg.println(sName + ": " + str.toHex(iReg, 2));
return;
}
var i, cchMax = 19, s = "";
/*
var s = "", i, cchMax = 0;
for (i = 0; i < asRegs.length; i++) {
if (cchMax < asRegs[i].length) cchMax = asRegs[i].length;
}
cchMax++;
*/
var i, cchMax = 18, s = "";
for (i = 0; i < asRegs.length; i++) {
var reg = (aRegs === this.regCRTData)? this.getCRTCReg(i) : aRegs[i];
if (s) s += '\n';
s += sName + "[" + str.toHexByte(i) + "]: " + str.pad(asRegs[i], cchMax) + str.toHexByte(aRegs[i]) + (i === iReg? "*" : "");
s += sName + "[" + str.toHex(i, 2) + "]: " + str.pad(asRegs[i], cchMax) + (i === iReg? '*' : ' ') + str.toHex(reg, reg > 0xff? 4 : 2);
if (reg != null) s += " (" + reg + ".)"
}
this.dbg.println(s);
}
@ -2536,11 +2532,11 @@ Card.prototype.dumpVideoCard = function()
this.dumpRegs(" GRC", this.regGRCIndx, this.regGRCData, this.asGRCRegs);
this.dumpRegs(" SEQ", this.regSEQIndx, this.regSEQData, this.asSEQRegs);
this.dumpRegs(" ATC", this.regATCIndx, this.regATCData, this.asATCRegs);
this.dumpRegs(" ATCINDX", this.regATCIndx);
this.dbg.println(" ATCDATA: " + this.fATCData);
this.dumpRegs(" FEAT", this.regFeat);
this.dumpRegs(" MISC", this.regMisc);
this.dumpRegs(" STATUS0", this.regStatus0);
this.dumpRegs(" ATCINDX", this.regATCIndx);
this.dbg.println(" ATCDATA: " + this.fATCData);
this.dumpRegs(" FEAT", this.regFeat);
this.dumpRegs(" MISC", this.regMisc);
this.dumpRegs(" STATUS0", this.regStatus0);
/*
* There are few more EGA regs we could dump, like GRCPos1, GRCPos2, but does anyone care?
*/
@ -2550,19 +2546,19 @@ Card.prototype.dumpVideoCard = function()
* TODO: This simply dumps the last value read from the STATUS1 register, not necessarily
* its current state; consider dumping getRetraceBits() instead of (or in addition to) this.
*/
this.dumpRegs(" STATUS1", this.regStatus);
this.dumpRegs(" STATUS1", this.regStatus);
if (this.nCard == Video.CARD.MDA || this.nCard == Video.CARD.CGA) {
this.dumpRegs(" MODEREG", this.regMode);
this.dumpRegs(" MODEREG", this.regMode);
}
if (this.nCard == Video.CARD.CGA) {
this.dumpRegs(" COLOR", this.regColor);
this.dumpRegs(" COLOR", this.regColor);
}
if (this.nCard >= Video.CARD.EGA) {
this.dbg.println(" LATCHES: 0x" + str.toHex(this.latches));
this.dbg.println(" ACCESS: " + str.toHexWord(this.nAccess));
this.dbg.println(" LATCHES: " + str.toHex(this.latches));
this.dbg.println(" ACCESS: " + str.toHex(this.nAccess, 4));
this.dbg.println("Use 'dump video [addr]' to dump video memory");
/*
* There are few more EGA regs we could dump, like GRCPos1, GRCPos2, but does anyone care?
@ -2782,6 +2778,52 @@ Card.prototype.setMemoryAccess = function(nAccess)
}
};
/**
* getCRTCReg()
*
* @this {Card}
* @param {number} iReg
* @return {number}
*/
Card.prototype.getCRTCReg = function(iReg)
{
var reg = this.regCRTData[iReg];
if (reg != null && this.nCard >= Video.CARD.EGA) {
var bOvrflowBit8 = 0, bOvrflowBit9 = 0, bMaxScanBit9 = 0;
switch(iReg) {
case Card.CRTC.EGA.VTOTAL: // 0x06
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT8; // 0x01
if (this.nCard == Video.CARD.VGA) bOvrflowBit9 = Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT9;
break;
case Card.CRTC.EGA.CURSOR_START.INDX: // 0x0A
if (this.nCard == Video.CARD.EGA) bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.CURSOR_START_BIT8;
break;
case Card.CRTC.EGA.VRETRACE_START: // 0x10
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VRETRACE_START_BIT8; // 0x04
if (this.nCard == Video.CARD.VGA) bOvrflowBit9 = Card.CRTC.EGA.OVERFLOW.VRETRACE_START_BIT9;
break;
case Card.CRTC.EGA.VDISP_END: // 0x12
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VDISP_END_BIT8; // 0x02
if (this.nCard == Video.CARD.VGA) bOvrflowBit9 = Card.CRTC.EGA.OVERFLOW.VDISP_END_BIT9;
break;
case Card.CRTC.EGA.VBLANK_START: // 0x15
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.VBLANK_START_BIT8; // 0x08
if (this.nCard == Video.CARD.VGA) bMaxScanBit9 = Card.CRTC.EGA.MAX_SCAN.VBLANK_START_BIT9;
break;
case Card.CRTC.EGA.LINE_COMPARE: // 0x18
bOvrflowBit8 = Card.CRTC.EGA.OVERFLOW.LINE_COMPARE_BIT8; // 0x10
if (this.nCard == Video.CARD.VGA) bMaxScanBit9 = Card.CRTC.EGA.MAX_SCAN.LINE_COMPARE_BIT9;
break;
}
if (bOvrflowBit8) {
reg |= ((this.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & bOvrflowBit8)? 0x100 : 0);
reg |= ((this.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & bOvrflowBit9)? 0x200 : 0);
reg |= ((this.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX] & bMaxScanBit9)? 0x200 : 0);
}
}
return reg;
};
/*
* Card Specifications
*
@ -2904,7 +2946,9 @@ Video.prototype.initBus = function(cmp, bus, cpu, dbg)
this.bEGASwitches = 0x09; // our default "switches" setting (see aEGAMonitorSwitches)
this.chipset = cmp.getMachineComponent("ChipSet");
if (this.chipset && this.sSwitches) {
if (this.nCard == Video.CARD.EGA) this.bEGASwitches = this.chipset.parseSwitches(this.sSwitches, this.bEGASwitches);
if (this.nCard == Video.CARD.EGA) {
this.bEGASwitches = this.chipset.parseDIPSwitches(this.sSwitches, this.bEGASwitches);
}
}
/*
@ -3535,7 +3579,7 @@ Video.prototype.reset = function()
* on the EGA's own switch settings instead.
*/
if (this.chipset) {
nMonitorType = this.chipset.getSWVideoMonitor();
nMonitorType = this.chipset.getDIPVideoMonitor();
}
/*
@ -4693,14 +4737,14 @@ Video.prototype.setDimensions = function()
* to use the 9x14 "EGA" color font instead.
*
* TODO: Can an EGA with a monochrome monitor be programmed for 43-line mode as well? If so,
* then we'll need to load another MDA font variation, because we only load an 9x14 font for MDA.
* then we'll need to load another MDA font variation, because we only load the 9x14 font for MDA.
*/
if (this.cardActive === this.cardEGA && this.nFont == Video.FONT.CGA) {
if (this.cardEGA.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX] == 7) {
if ((this.cardEGA.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX] & Card.CRTC.EGA.MAX_SCAN.SCAN_LINE) == 7) {
/*
* Vertical resolution of 350 divided by 8 (ie, scan lines 0-7) yields 43 whole rows.
*/
this.nRows = 43;
this.nRows = this.cardEGA.getCRTCReg(Card.CRTC.EGA.VDISP_END) < 350? 43 : 50;
}
/*
* Since we can also be called before any hardware registers have been initialized,
@ -4950,17 +4994,12 @@ Video.prototype.checkMode = function(fForce)
}
}
var fSEQDotClock = (card.regSEQData[Card.SEQ.CLOCKING.INDX] & Card.SEQ.CLOCKING.DOTCLOCK);
var nCRTCVertTotal = card.regCRTData[Card.CRTC.EGA.VTOTAL];
nCRTCVertTotal |= ((card.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT8)? 0x100 : 0);
if (card.nCard == Video.CARD.VGA) {
nCRTCVertTotal |= ((card.regCRTData[Card.CRTC.EGA.OVERFLOW.INDX] & Card.CRTC.EGA.OVERFLOW.VTOTAL_BIT9)? 0x200 : 0);
}
var nCRTCVertTotal = card.getCRTCReg(Card.CRTC.EGA.VTOTAL);
var nCRTCMaxScan = card.regCRTData[Card.CRTC.EGA.MAX_SCAN.INDX];
var nCRTCModeCtrl = card.regCRTData[Card.CRTC.EGA.MODE_CTRL.INDX];
var fSEQDotClock = (card.regSEQData[Card.SEQ.CLOCKING.INDX] & Card.SEQ.CLOCKING.DOTCLOCK);
if (nMode != Video.MODE.UNKNOWN) {
if (!(regGRCMisc & Card.GRC.MISC.GRAPHICS)) {
/*
@ -4991,6 +5030,11 @@ Video.prototype.checkMode = function(fForce)
*/
if (card.regGRCData[Card.GRC.MODE.INDX] & Card.GRC.MODE.COLOR256) {
if (nCRTCMaxScan & Card.CRTC.EGA.MAX_SCAN.SCAN_LINE) {
/*
* NOTE: Technically, VDISP_END is one of those CRTC registers that should be read using
* card.getCRTCReg(), because there are overflow bits (8 and 9). However, all known modes
* always SET bit 8 and CLEAR bit 9, so examining only bits 0-7 is sorta OK.
*/
if (card.regCRTData[Card.CRTC.EGA.VDISP_END] <= 0x8F) {
nMode = Video.MODE.VGA_320X200;
}
@ -5052,7 +5096,7 @@ Video.prototype.checkMode = function(fForce)
* setMode(nMode, fForce)
*
* Set fForce to true to update the mode regardless of previous mode, or false to perform
* a normal update that bypasses updateScreen() but still calls initCellCache().
* a normal update that bypasses updateScreen() but still calls initCache().
*
* @this {Video}
* @param {number|null} nMode
@ -5123,7 +5167,7 @@ Video.prototype.setMode = function(nMode, fForce)
}
}
this.setDimensions();
this.invalidateScreen(true);
this.invalidateCache(true);
this.updateScreen();
}
return true;
@ -5150,17 +5194,17 @@ Video.prototype.setPixel = function(imageData, x, y, rgb)
};
/**
* initCellCache()
* initCache()
*
* Initializes the contents of our internal cell cache.
*
* TODO: Consider changing this to a cache of RGB values, so that when the buffer is merely being color-cycled,
* we don't have to update the entire screen. This will also allow invalidateScreen() to honor the fModified flag,
* bypassing initCellCache() when it is false.
* we don't have to update the entire screen. This will also allow invalidateCache() to honor the fModified flag,
* bypassing initCache() when it is false.
*
* @this {Video}
*/
Video.prototype.initCellCache = function()
Video.prototype.initCache = function()
{
this.cBlinkVisible = -1; // invalidate the visible blinking character count, to force updateScreen() to recount
this.fCellCacheValid = false;
@ -5171,7 +5215,7 @@ Video.prototype.initCellCache = function()
};
/**
* invalidateScreen(fModified)
* invalidateCache(fModified)
*
* Ensure that the next updateScreen() will update every cell; intended for situations where the entire screen needs
* to be redrawn, even though the underlying data in the video buffer has not changed (and therefore cleanMemory() will
@ -5182,10 +5226,10 @@ Video.prototype.initCellCache = function()
* @this {Video}
* @param {boolean} [fModified] (true if the buffer may have been modified, false if only color(s) may have changed)
*/
Video.prototype.invalidateScreen = function(fModified)
Video.prototype.invalidateCache = function(fModified)
{
if (!fModified) this.fRGBValid = false;
this.initCellCache();
this.initCache();
};
/**
@ -5338,7 +5382,7 @@ Video.prototype.updateChar = function(col, row, data, context)
* are the periodic updates coming from the CPU.
*
* For every cell in the video buffer, compare it to the cell stored in the cell cache, render if it differs,
* and then update the cell cache to match. Since initCellCache() sets every cell in the cell cache to an
* and then update the cell cache to match. Since initCache() sets every cell in the cell cache to an
* invalid value, we're assured that the next call to updateScreen() will redraw the entire (visible) video buffer.
*
* @this {Video}
@ -5370,7 +5414,7 @@ Video.prototype.updateScreen = function(fForce)
if (!fEnabled && !fForce) return;
if (fForce) {
this.initCellCache();
this.initCache();
}
else {
/*
@ -5433,7 +5477,14 @@ Video.prototype.updateScreen = function(fForce)
* multi-display configuration.
*/
if ((this.getRetraceBits(card) & Card.CGA.STATUS.VRETRACE) || card.nVertPeriodsStartAddr && card.nVertPeriodsStartAddr < card.nVertPeriods) {
card.offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
/*
* PARANOIA: Don't call invalidateCache() unless the address we're about to "latch" actually changed.
*/
var offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
if (card.offStartAddr !== offStartAddr) {
card.offStartAddr = offStartAddr;
this.invalidateCache();
}
card.nVertPeriodsStartAddr = 0;
}
@ -5495,8 +5546,18 @@ Video.prototype.updateScreen = function(fForce)
if (!fForce && this.fCellCacheValid && this.bus.cleanMemory(addrScreen, cbScreen)) {
if (!fBlinkUpdate) return;
if (!this.cBlinkVisible) {
if (this.iCellCursor < 0) return;
iCell = this.iCellCursor;
/*
* Note that since iCellCursor is a cell-based (not byte-based) index, we must subtract
* offStartAddr, which is also cell-based; subtracting offScreen would not be appropriate,
* as it has already been converted to a byte-based offset (remember that in text modes,
* cell are words, not bytes).
*/
iCell = this.iCellCursor - card.offStartAddr;
/*
* Note that iCellCursor may have already been negative (-1 hides the cursor), and
* since offStartAddr should never be negative, we only need one iCell underflow check.
*/
if (iCell < 0) return;
nCells = iCell + 1;
}
// else if (this.cBlinks & 0x1) return;
@ -5564,6 +5625,12 @@ Video.prototype.updateScreenText = function(addrScreen, addrScreenLimit, iCell,
fBlinkEnable = (this.cardActive.regATCData[Card.ATC.MODE.INDX] & Card.ATC.MODE.BLINK_ENABLE);
}
/*
* Since iCell is always relative to addrScreen, we must make iCellCursor similarly relative,
* otherwise the cursor test below fails when the active video page is something other than page 0.
*/
var iCellCursor = this.iCellCursor - this.cardActive.offStartAddr;
if (fBlinkEnable) {
dataBlink = (Video.ATTRS.BGND_BLINK << 8);
dataMask &= ~dataBlink;
@ -5578,7 +5645,7 @@ Video.prototype.updateScreenText = function(addrScreen, addrScreenLimit, iCell,
this.cBlinkVisible++;
data &= dataMask;
}
if (iCell == this.iCellCursor) {
if (iCell == iCellCursor) {
data |= ((this.cBlinks & 0x1)? (Video.ATTRS.DRAW_CURSOR << 8) : 0);
}
this.assert(iCell < this.aCellCache.length);
@ -5756,28 +5823,20 @@ Video.prototype.updateScreenGraphicsEGA = function(addrBuffer, addrScreen, addrS
if (x < xDirty) xDirty = x;
for (iPixel = 0; iPixel < nPixels; iPixel++) {
/*
* We must follow the golden JavaScript rule of appending "|0" to all hex constants with bit 31 set.
* The innocuous use of the bit-wise OR operator has the side-effect of producing a negative value,
* matching how entries in Video.aEGADWToByte are initialized (eg, "Video.aEGADWToByte[0x80000000|0]").
*/
var dwPixel = data & (0x80808080|0);
/*
* This was the old approach to dealing with negative hex values, by converting them to positive
* values that didn't alter the low 32 bits. But it's not ideal, because it requires using values here
* and in the array that are outside the signed 32-bit range, potentially triggering floating-point.
*
* if (dwPixel < 0) dwPixel += 0x100000000;
*
* An even simpler solution would be to use the unsigned right-shift operator:
*
* dwPixel >>> 0
*
* but again, all that does is produce a value outside the signed 32-bit range, which is sub-optimal.
* 0x80808080 may LOOK like a 32-bit value, but it is not, because JavaScript treats it as a POSITIVE
* number, and therefore outside the normal 32-bit integer range; however, the AND operator guarantees
* that the result will be a 32-bit value, so it doesn't matter.
*/
var dwPixel = data & 0x80808080;
this.assert(Video.aEGADWToByte[dwPixel] !== undefined);
/*
* Since assertions don't fix problems (only catch them, and only in DEBUG builds), I'm also ensuring
* that bPixel will always default to 0 if an undefined value ever slips through again.
* that bPixel will default to 0 if an undefined value ever slips through again.
*
* How did an undefined value slip through? We had (incorrectly) initialized entries in aEGADWToByte;
* for example, we used to set aEGADWToByte[0x80808080] instead of aEGADWToByte[0x80808080|0]. The
* former is a POSITIVE index that is outside the 32-bit integer range, whereas the latter is a NEGATIVE
* index, which is what this code requires.
*/
var bPixel = Video.aEGADWToByte[dwPixel] || 0;
this.setPixel(this.imageScreenBuffer, x++, y, aPixelColors[bPixel]);
@ -5921,8 +5980,8 @@ Video.prototype.getRetraceBits = function(card)
/*
* NOTE: The CGA bits CGA.STATUS.RETRACE (0x01) and CGA.STATUS.VRETRACE (0x08) match the EGA definitions,
* and they also correspond to the MDA bits MDA.STATUS.HDRIVE (0x01) and MDA.STATUS.BWVIDEO (0x08); I'm not sure why
* the MDA uses different designations, but the bits appear to serve the same purpose.
* and they also correspond to the MDA bits MDA.STATUS.HDRIVE (0x01) and MDA.STATUS.BWVIDEO (0x08); I'm not sure
* why the MDA uses different designations, but the bits appear to serve the same purpose.
*
* TODO: Decide whether this more faithful emulation of the retrace bits should be extended to the MDA/CGA, too;
* doing so might slow down the BIOS scroll code a bit, though.
@ -6168,8 +6227,14 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
/*
* HACK: offStartAddr is supposed to be "latched" ONLY at the start of every VRETRACE interval, but
* other "triggers" are helpful; see updateScreen() for details.
*
* PARANOIA: Don't call invalidateCache() unless the start address we just "latched" actually changed.
*/
card.offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
var offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
if (card.offStartAddr != offStartAddr) {
card.offStartAddr = offStartAddr;
this.invalidateCache();
}
card.nVertPeriodsStartAddr = 0;
} else {
card.fATCData = false;
@ -6180,7 +6245,7 @@ Video.prototype.outATC = function(port, bOut, addrFrom)
this.printMessageIO(port, bOut, addrFrom, "ATC." + card.asATCRegs[iReg]);
}
card.regATCData[iReg] = bOut;
this.invalidateScreen(false);
this.invalidateCache(false);
}
}
}
@ -6516,7 +6581,7 @@ Video.prototype.outDACData = function(port, bOut, addrFrom)
var dwNew = (dw & ~(0x3f << this.cardEGA.regDACShift)) | ((bOut & 0x3f) << this.cardEGA.regDACShift);
if (dw !== dwNew) {
this.cardEGA.regDACData[this.cardEGA.regDACAddr] = dwNew;
this.invalidateScreen(false);
this.invalidateCache(false);
}
this.cardEGA.regDACShift += 6;
if (this.cardEGA.regDACShift > 12) {
@ -6801,7 +6866,7 @@ Video.prototype.outCGAColor = function(port, bOut, addrFrom)
}
if (this.cardColor.regColor !== bOut) {
this.cardColor.regColor = bOut;
this.invalidateScreen(false);
this.invalidateCache(false);
}
};
@ -6915,7 +6980,14 @@ Video.prototype.outCRTCData = function(card, port, bOut, addrFrom)
* the vertical period count and latch it later, in updateScreen(), once the count has advanced.
*/
if (this.getRetraceBits(card) & Card.CGA.STATUS.RETRACE) {
card.offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
/*
* PARANOIA: Don't call invalidateCache() unless the address we're about to "latch" actually changed.
*/
var offStartAddr = ((card.regCRTData[Card.CRTC.START_ADDR_HI] << 8) + card.regCRTData[Card.CRTC.START_ADDR_LO])|0;
if (card.offStartAddr !== offStartAddr) {
card.offStartAddr = offStartAddr;
this.invalidateCache();
}
} else if (!card.nVertPeriodsStartAddr) {
card.nVertPeriodsStartAddr = card.nVertPeriods;
}

View file

@ -1116,4 +1116,8 @@
</xsl:call-template>
</xsl:template>
<xsl:template match="comment">
<xsl:comment><xsl:apply-templates/></xsl:comment>
</xsl:template>
</xsl:stylesheet>