nes-emu
Version:
A NES emulator
148 lines (138 loc) • 4.65 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _registers = require("./registers");
var _PPUMemoryMap = _interopRequireDefault(require("./PPUMemoryMap"));
var _renderers = _interopRequireDefault(require("./renderers"));
var _tables = require("./renderers/tables");
var _constants = _interopRequireDefault(require("../constants"));
var _helpers = require("../helpers");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const FULL_ALPHA = 0xff000000;
/** The Picture Processing Unit. It generates a video signal of 256x240 pixels. */
class PPU {
constructor() {
_helpers.WithContext.apply(this);
this.frame = 0;
this.scanline = 0;
this.cycle = 0;
this.memory = new _PPUMemoryMap.default();
this.oamRam = null;
this.registers = null;
this.loopy = null;
this.nameTable = new _tables.NameTable();
this.attributeTable = new _tables.AttributeTable();
this.patternTable = new _tables.PatternTable();
this.framePalette = new _tables.FramePalette();
this.oam = new _tables.OAM();
this.frameBuffer = new Uint32Array(_constants.default.TOTAL_PIXELS);
this.paletteIndexes = new Uint8Array(_constants.default.TOTAL_PIXELS);
}
/** When a context is loaded. */
onLoad(context) {
this.memory.loadContext(context);
this.oamRam = new Uint8Array(_constants.default.PPU_OAM_SIZE);
this.registers = new _registers.PPURegisterSegment(context);
this.loopy = new _registers.LoopyRegister();
this.nameTable.loadContext(context);
this.attributeTable.loadContext(context);
this.patternTable.loadContext(context);
this.framePalette.loadContext(context);
this.oam.loadContext(context);
this._reset();
}
/**
* Executes a number of `cycles`.
* It calls `onFrame` when it generates a new frame.
* It calls `onIntr` on interrupts;
*/
step(cycles, onFrame, onIntr) {
for (let i = 0; i < cycles; i++) {
// <optimization>
if (this.cycle > 1 && this.cycle < 256) {
i += this._skip(256, cycles, i);
continue;
} else if (this.cycle > 260 && this.cycle < 304) {
i += this._skip(304, cycles, i);
continue;
} else if (this.cycle > 304 && this.cycle < 340) {
i += this._skip(340, cycles, i);
continue;
}
// </optimization>
const scanlineType = getScanlineType(this.scanline);
const interrupt = _renderers.default[scanlineType](this.context);
if (interrupt) onIntr(interrupt);
this._incrementCounters(onFrame);
}
}
/** Draws a pixel (ABGR) in (`x`, `y`) using BGR `color`. */
plot(x, y, color) {
this.frameBuffer[y * _constants.default.SCREEN_WIDTH + x] = FULL_ALPHA | this.registers.ppuMask.transform(color);
}
/** Returns the palette index of pixel (`x`, `y`). Used for sprite drawing. */
paletteIndexOf(x, y) {
return this.paletteIndexes[y * _constants.default.SCREEN_WIDTH + x];
}
/** Returns a snapshot of the current state. */
getSaveState() {
return {
frame: this.frame,
scanline: this.scanline,
cycle: this.cycle,
memory: this.memory.getSaveState(),
oamRam: Array.from(this.oamRam),
loopy: this.loopy.getSaveState()
};
}
/** Restores state from a snapshot. */
setSaveState(saveState) {
this.frame = saveState.frame;
this.scanline = saveState.scanline;
this.cycle = saveState.cycle;
this.memory.setSaveState(saveState.memory);
this.oamRam = new Uint8Array(saveState.oamRam);
this.loopy.setSaveState(saveState.loopy);
}
_skip(destinationCycle, cycles, i) {
const skippedCycles = Math.min(destinationCycle - this.cycle, cycles - i);
this.cycle += skippedCycles;
return skippedCycles - 1;
}
_incrementCounters(onFrame) {
this.cycle++;
if (this.cycle > _constants.default.PPU_LAST_CYCLE) {
this.cycle = 0;
this.scanline++;
if (this.scanline > _constants.default.PPU_LAST_SCANLINE) {
this.scanline = -1;
this.frame++;
onFrame(this.frameBuffer);
}
}
}
_reset() {
this.frame = 0;
this.scanline = -1;
this.cycle = 0;
for (let i = 0; i < this.frameBuffer.length - 1; i++) {
this.frameBuffer[i] = 0;
this.paletteIndexes[i] = 0;
}
}
}
/** Returns the type of `scanLine`. */
exports.default = PPU;
function getScanlineType(scanLine) {
if (scanLine === -1) {
return "PRELINE";
} else if (scanLine < 240) {
return "VISIBLE";
} else if (scanLine === 241) {
return "VBLANK_START";
} else {
return "IDLE";
}
}