UNPKG

nes-emu

Version:

A NES emulator

148 lines (138 loc) 4.65 kB
"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"; } }