UNPKG

broken-neees

Version:

A really broken NEEES emulator that introduces glitches and random bugs on purpose!

110 lines (109 loc) 4.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _Sprite = _interopRequireDefault(require("../lib/ppu/Sprite")); var _Tile = _interopRequireDefault(require("./Tile")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const TILE_SIZE_PIXELS = 8; const SPRITE_SIZE_BYTES = 4; const SPRITE_BYTE_Y = 0; const SPRITE_BYTE_TILE_ID = 1; const SPRITE_BYTE_ATTRIBUTES = 2; const SPRITE_BYTE_X = 3; const SPRITE_8x16_PATTERN_TABLE_MASK = 0b1; const SPRITE_8x16_TILE_ID_MASK = 0xfe; const MAX_SPRITES = 64; const MAX_SPRITES_PER_SCANLINE = 8; class SpriteRenderer { constructor(ppu) { this.ppu = ppu; } renderScanline() { if (!this.ppu.registers.ppuMask.showSprites) return; const sprites = this._evaluate(); const buffer = this._render(sprites); this._draw(buffer); } _evaluate() { const sprites = []; for (let spriteId = 0; spriteId < MAX_SPRITES; spriteId++) { const sprite = this._createSprite(spriteId); if (sprite.shouldRenderInScanline(this.ppu.scanline) && sprites.length < MAX_SPRITES_PER_SCANLINE + 1) { if (sprites.length < MAX_SPRITES_PER_SCANLINE) { sprites.push(sprite); } else { this.ppu.registers.ppuStatus.spriteOverflow = 1; break; } } } if (this.ppu.cpu.unbroken) { return sprites.reverse(); } else { // [!!!] return sprites; } } _render(sprites) { const y = this.ppu.scanline; const buffer = []; for (let sprite of sprites) { const insideY = sprite.diffY(y); const tileInsideY = insideY % TILE_SIZE_PIXELS; const tile = new _Tile.default(this.ppu, sprite.patternTableId, sprite.tileIdFor(insideY), sprite.flipY ? TILE_SIZE_PIXELS - 1 - tileInsideY : tileInsideY); const paletteColors = this.ppu.getPaletteColors(sprite.paletteId); for (let insideX = 0; insideX < TILE_SIZE_PIXELS; insideX++) { const x = sprite.x + insideX; if (!this.ppu.registers.ppuMask.showSpritesInFirst8Pixels && x < 8) continue; const colorIndex = tile.getColorIndex(sprite.flipX ? TILE_SIZE_PIXELS - 1 - insideX : insideX); if (colorIndex > 0) { const color = paletteColors[colorIndex]; buffer[x] = { x, color, isInFrontOfBackground: sprite.isInFrontOfBackground }; if (sprite.id === 0 && this.ppu.isBackgroundPixelOpaque(x, y) && this.ppu.registers.ppuMask.showBackground && this.ppu.registers.ppuMask.showSprites) this.ppu.registers.ppuStatus.sprite0Hit = 1; } } } return buffer; } _draw(buffer) { const y = this.ppu.scanline; for (let object of buffer) { if (!object) continue; const { x, color, isInFrontOfBackground } = object; const isBackgroundPixelOpaque = this.ppu.isBackgroundPixelOpaque(x, y); const shouldDraw = isInFrontOfBackground || !isBackgroundPixelOpaque; if (shouldDraw) this.ppu.plot(x, y, color); } } _createSprite(id) { const oamRam = this.ppu.memory.oamRam; const ppuCtrl = this.ppu.registers.ppuCtrl; const is8x16 = ppuCtrl.spriteSize === 1; const address = id * SPRITE_SIZE_BYTES; let yByte; if (this.ppu.cpu.unbroken) { yByte = oamRam[address + SPRITE_BYTE_Y]; } else { // [!!!] yByte = oamRam[address + SPRITE_BYTE_Y] + (id > 0 ? 10 : 0); } const tileIdByte = oamRam[address + SPRITE_BYTE_TILE_ID]; const attributes = oamRam[address + SPRITE_BYTE_ATTRIBUTES]; const x = oamRam[address + SPRITE_BYTE_X]; const y = yByte + 1; const patternTableId = is8x16 ? tileIdByte & SPRITE_8x16_PATTERN_TABLE_MASK : ppuCtrl.sprite8x8PatternTableId; const tileId = is8x16 ? tileIdByte & SPRITE_8x16_TILE_ID_MASK : tileIdByte; return new _Sprite.default(this.ppu, id, x, y, is8x16, patternTableId, tileId, attributes); } } exports.default = SpriteRenderer;