Finised sprite rendering
This commit is contained in:
parent
c1bd4c863f
commit
352c1588ae
@ -26,7 +26,9 @@ add_executable(nes
|
|||||||
src/Controller.cpp
|
src/Controller.cpp
|
||||||
src/Controller.h
|
src/Controller.h
|
||||||
src/SdlKeyboardController.cpp
|
src/SdlKeyboardController.cpp
|
||||||
src/SdlKeyboardController.h src/Dma.cpp src/Dma.h)
|
src/SdlKeyboardController.h src/Dma.cpp src/Dma.h
|
||||||
|
src/Oam.cpp
|
||||||
|
src/Oam.h)
|
||||||
|
|
||||||
find_package(SDL2 CONFIG REQUIRED)
|
find_package(SDL2 CONFIG REQUIRED)
|
||||||
find_package(fmt REQUIRED)
|
find_package(fmt REQUIRED)
|
||||||
|
|||||||
26
main.cpp
26
main.cpp
@ -3,7 +3,6 @@
|
|||||||
#include "src/SdlKeyboardController.h"
|
#include "src/SdlKeyboardController.h"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@ -15,22 +14,31 @@ int main() {
|
|||||||
window.setSize(nes::Ppu::SCREEN_WIDTH * 4, nes::Ppu::SCREEN_HEIGHT * 4);
|
window.setSize(nes::Ppu::SCREEN_WIDTH * 4, nes::Ppu::SCREEN_HEIGHT * 4);
|
||||||
|
|
||||||
SDL_Event e;
|
SDL_Event e;
|
||||||
device.setNewFrameCallback([&window, &e](auto buffer){
|
bool frameRendered = false;
|
||||||
|
device.setNewFrameCallback([&window, &e, &frameRendered](auto buffer){
|
||||||
window.drawFrame(buffer);
|
window.drawFrame(buffer);
|
||||||
while(SDL_PollEvent(&e));
|
while(SDL_PollEvent(&e));
|
||||||
|
frameRendered = true;
|
||||||
});
|
});
|
||||||
device.connect(std::make_shared<SdlKeyboardController>());
|
device.connect(std::make_shared<SdlKeyboardController>());
|
||||||
device.insertCartridge("/home/selim/Downloads/dk.nes");
|
//device.insertCartridge("/home/selim/Downloads/dk.nes");
|
||||||
//device.insertCartridge("/Users/selim/Documents/nestest.nes");
|
device.insertCartridge("/Users/selim/Documents/smb.nes");
|
||||||
//device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes");
|
//device.insertCartridge("C:\\Users\\selim\\Documents\\nestest.nes");
|
||||||
|
|
||||||
uint64_t cycles = 0;
|
auto frameStart = std::chrono::steady_clock::now();
|
||||||
while (cycles < 1000000000) {
|
while (true) {
|
||||||
device.tick();
|
device.tick();
|
||||||
cycles++;
|
|
||||||
|
|
||||||
//int64_t us = static_cast<int64_t>(1000000000.0/(60*nes::Ppu::SCREEN_WIDTH*nes::Ppu::SCREEN_HEIGHT));
|
if(frameRendered) {
|
||||||
//std::this_thread::sleep_for(std::chrono::nanoseconds(1));
|
auto renderingTime = std::chrono::steady_clock::now();
|
||||||
|
auto elapsedTime = std::chrono::duration_cast<std::chrono::nanoseconds>(renderingTime - frameStart).count();
|
||||||
|
int64_t waitTime = 1000000000/60 - elapsedTime;
|
||||||
|
if(waitTime > 0) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::nanoseconds(waitTime));
|
||||||
|
}
|
||||||
|
frameStart = std::chrono::steady_clock::now();
|
||||||
|
frameRendered = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -40,4 +40,8 @@ namespace nes {
|
|||||||
return _chrRom[mappedAddress];
|
return _chrRom[mappedAddress];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cartridge::Mirroring Cartridge::mirroring() const {
|
||||||
|
return _header->flags.mirroring == 0 ? Mirroring::Horizontal : Mirroring::Vertical;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,20 +19,33 @@ namespace nes {
|
|||||||
constexpr size_t PRG_CHUNK_SIZE = 16*1024;
|
constexpr size_t PRG_CHUNK_SIZE = 16*1024;
|
||||||
constexpr size_t CHR_CHUNK_SIZE = 8*1024;
|
constexpr size_t CHR_CHUNK_SIZE = 8*1024;
|
||||||
|
|
||||||
struct RomHeader {
|
|
||||||
uint32_t magic;
|
|
||||||
uint8_t prgChunks;
|
|
||||||
uint8_t chrChunks;
|
|
||||||
uint8_t reserved[10];
|
|
||||||
};
|
|
||||||
|
|
||||||
class Cartridge {
|
class Cartridge {
|
||||||
|
public:
|
||||||
|
enum Mirroring {
|
||||||
|
Horizontal,
|
||||||
|
Vertical
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Flags {
|
||||||
|
uint8_t mirroring: 1;
|
||||||
|
uint8_t reserved: 7;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RomHeader {
|
||||||
|
uint32_t magic;
|
||||||
|
uint8_t prgChunks;
|
||||||
|
uint8_t chrChunks;
|
||||||
|
Flags flags;
|
||||||
|
uint8_t reserved[9];
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Cartridge(const fs::path& path);
|
explicit Cartridge(const fs::path& path);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint8_t readPrg(uint16_t address);
|
uint8_t readPrg(uint16_t address);
|
||||||
uint8_t readChr(uint16_t address);
|
uint8_t readChr(uint16_t address);
|
||||||
|
Mirroring mirroring() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<uint8_t[]> _romData;
|
std::unique_ptr<uint8_t[]> _romData;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include "Cpu.h"
|
#include "Cpu.h"
|
||||||
#include "Nes.h"
|
#include "Nes.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <format>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
namespace nes {
|
namespace nes {
|
||||||
|
|
||||||
@ -224,7 +224,7 @@ namespace nes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string Cpu::state() const {
|
std::string Cpu::state() const {
|
||||||
return std::format("{} ({:02X}), PC: {:X}, SP: {:X}, A: {:02X}, X: {:02X}, Y: {:02X}, [N:{}, V:{}, B{}, D{}, I{}, Z:{}, C:{}], H: {:08X}",
|
return fmt::format("{} ({:02X}), PC: {:X}, SP: {:X}, A: {:02X}, X: {:02X}, Y: {:02X}, [N:{}, V:{}, B{}, D{}, I{}, Z:{}, C:{}], H: {:08X}",
|
||||||
_instructions[_currentOpcode].name,
|
_instructions[_currentOpcode].name,
|
||||||
_currentOpcode,
|
_currentOpcode,
|
||||||
PC, SP, A, X, Y,
|
PC, SP, A, X, Y,
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <format>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
namespace nes {
|
namespace nes {
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ namespace nes {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string cycleString = std::format("[{:06d}] ", cycle);
|
std::string cycleString = fmt::format("[{:06d}] ", cycle);
|
||||||
std::memcpy(_data.get() + _offset, cycleString.data(), cycleString.size());
|
std::memcpy(_data.get() + _offset, cycleString.data(), cycleString.size());
|
||||||
_offset += cycleString.size();
|
_offset += cycleString.size();
|
||||||
|
|
||||||
|
|||||||
144
src/Oam.cpp
Normal file
144
src/Oam.cpp
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 24.09.2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Oam.h"
|
||||||
|
#include "Ppu.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace nes {
|
||||||
|
|
||||||
|
Oam::Oam(Ppu *ppu): _ppu{ppu} {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oam::setAddress(uint8_t address) {
|
||||||
|
_address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Oam::read() const {
|
||||||
|
return reinterpret_cast<const uint8_t*>(_data)[_address];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oam::write(uint8_t value) {
|
||||||
|
reinterpret_cast<uint8_t*>(_data)[_address] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oam::write(uint8_t address, uint8_t value) {
|
||||||
|
reinterpret_cast<uint8_t*>(_data)[address] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Oam::detectVisibleSprites(int16_t scanline, bool bigSprite) {
|
||||||
|
memset(_visibleSprites, 0xFF, MAX_SPRITES_PER_SCANLINE*sizeof(SpriteInfo));
|
||||||
|
_visibleSpriteCount = 0;
|
||||||
|
|
||||||
|
for (auto& shifter: _spriteShifters) {
|
||||||
|
shifter.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t oamIndex = 0;
|
||||||
|
uint8_t spriteHeight = bigSprite ? 16 : 8;
|
||||||
|
bool spriteOverflow = false;
|
||||||
|
_spriteZeroVisible = false;
|
||||||
|
|
||||||
|
while (oamIndex < OAM_SIZE && _visibleSpriteCount < MAX_SPRITES_PER_SCANLINE + 1) {
|
||||||
|
uint8_t yStart = _data[oamIndex].y;
|
||||||
|
uint8_t yEnd = yStart + spriteHeight;
|
||||||
|
|
||||||
|
if(scanline >= yStart && scanline < yEnd) {
|
||||||
|
if(_visibleSpriteCount < MAX_SPRITES_PER_SCANLINE) {
|
||||||
|
_visibleSprites[_visibleSpriteCount] = _data[oamIndex];
|
||||||
|
_visibleSpriteCount++;
|
||||||
|
if(oamIndex == 0) {
|
||||||
|
_spriteZeroVisible = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spriteOverflow = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oamIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return spriteOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oam::loadShifters(int16_t scanline, bool bigSprite, bool patternSprite) {
|
||||||
|
for(uint8_t i = 0; i < _visibleSpriteCount; ++i) {
|
||||||
|
|
||||||
|
auto sprite = _visibleSprites[i];
|
||||||
|
bool flippedVertically = sprite.attr & 0x80;
|
||||||
|
bool flippedHorizontally = sprite.attr & 0x40;
|
||||||
|
bool isTopHalf = (scanline - sprite.y) < 8;
|
||||||
|
|
||||||
|
uint8_t tileOffset = isTopHalf ? 0 : 1;
|
||||||
|
uint8_t rowOffset = flippedVertically ? (7 - scanline + sprite.y) : (scanline - sprite.y);
|
||||||
|
uint16_t patternAddress = 0;
|
||||||
|
|
||||||
|
if(bigSprite) {
|
||||||
|
patternAddress = (sprite.id & 0x01) << 12
|
||||||
|
| ((sprite.id & 0xFE) + tileOffset) << 4
|
||||||
|
| (rowOffset & 0x07);
|
||||||
|
} else {
|
||||||
|
patternAddress = patternSprite << 12
|
||||||
|
| sprite.id << 4
|
||||||
|
| rowOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t patternBitsLo = _ppu->internalRead(patternAddress);
|
||||||
|
uint8_t patternBitsHi = _ppu->internalRead(patternAddress + 8);
|
||||||
|
|
||||||
|
if(flippedHorizontally) {
|
||||||
|
patternBitsLo = flipByte(patternBitsLo);
|
||||||
|
patternBitsHi = flipByte(patternBitsHi);
|
||||||
|
}
|
||||||
|
|
||||||
|
_spriteShifters[i].loadHighByte(patternBitsLo, patternBitsHi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Oam::flipByte(uint8_t byte) const {
|
||||||
|
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||||
|
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||||
|
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oam::updateShifters() {
|
||||||
|
for (size_t i = 0; i < _visibleSpriteCount; ++i) {
|
||||||
|
if(_visibleSprites[i].x > 0) {
|
||||||
|
_visibleSprites[i].x--;
|
||||||
|
} else {
|
||||||
|
_spriteShifters[i].shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Oam::PixelInfo Oam::getPixel() {
|
||||||
|
|
||||||
|
uint8_t pattern, palette, priority;
|
||||||
|
_firstVisibleSpriteBeingRendered = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _visibleSpriteCount; ++i) {
|
||||||
|
|
||||||
|
if(_visibleSprites[i].x != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = _spriteShifters[i].getValue(0);
|
||||||
|
palette = (_visibleSprites[i].attr & 0x03) + SPRITE_PALETTE_OFFSET;
|
||||||
|
priority = (_visibleSprites[i].attr & 0x20) == 0;
|
||||||
|
|
||||||
|
if(pattern > 0) {
|
||||||
|
_firstVisibleSpriteBeingRendered = (i == 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_tuple(pattern, palette, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Oam::spriteZeroBeingRendered() const {
|
||||||
|
return _spriteZeroVisible && _firstVisibleSpriteBeingRendered;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/Oam.h
Normal file
61
src/Oam.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 24.09.2023.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef NES_OAM_H
|
||||||
|
#define NES_OAM_H
|
||||||
|
|
||||||
|
#include "Shifter.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace nes {
|
||||||
|
|
||||||
|
class Ppu;
|
||||||
|
|
||||||
|
struct SpriteInfo {
|
||||||
|
uint8_t y = 0xFF;
|
||||||
|
uint8_t id = 0x00;
|
||||||
|
uint8_t attr = 0x00;
|
||||||
|
uint8_t x = 0xFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Oam {
|
||||||
|
public:
|
||||||
|
static constexpr size_t OAM_SIZE = 64;
|
||||||
|
static constexpr size_t MAX_SPRITES_PER_SCANLINE = 8;
|
||||||
|
static constexpr uint8_t SPRITE_PALETTE_OFFSET = 4;
|
||||||
|
using PixelInfo = std::tuple<uint8_t, uint8_t, uint8_t>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Oam(Ppu* ppu);
|
||||||
|
void setAddress(uint8_t address);
|
||||||
|
[[nodiscard]] uint8_t read() const;
|
||||||
|
void write(uint8_t value);
|
||||||
|
void write(uint8_t address, uint8_t value);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool detectVisibleSprites(int16_t scanline, bool bigSprite);
|
||||||
|
void loadShifters(int16_t scanline, bool bigSprite, bool patternSprite);
|
||||||
|
void updateShifters();
|
||||||
|
[[nodiscard]] PixelInfo getPixel();
|
||||||
|
[[nodiscard]] bool spriteZeroBeingRendered() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] uint8_t flipByte(uint8_t byte) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ppu* _ppu;
|
||||||
|
uint8_t _address = 0x00;
|
||||||
|
SpriteInfo _data[OAM_SIZE];
|
||||||
|
SpriteInfo _visibleSprites[MAX_SPRITES_PER_SCANLINE];
|
||||||
|
Shifter _spriteShifters[MAX_SPRITES_PER_SCANLINE];
|
||||||
|
uint8_t _visibleSpriteCount = 0;
|
||||||
|
bool _spriteZeroVisible = false;
|
||||||
|
bool _firstVisibleSpriteBeingRendered = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //NES_OAM_H
|
||||||
118
src/Ppu.cpp
118
src/Ppu.cpp
@ -3,16 +3,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "Ppu.h"
|
#include "Ppu.h"
|
||||||
#include <format>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
namespace nes {
|
namespace nes {
|
||||||
|
|
||||||
Ppu::Ppu(): _column{}, _scanline{}, _status{}, _control{}, _mask{} {
|
Ppu::Ppu(): _column{}, _scanline{}, _status{}, _control{}, _mask{} {
|
||||||
_frameBuffer = std::make_unique<Pixel[]>(SCREEN_WIDTH*SCREEN_HEIGHT);
|
_frameBuffer = std::make_unique<Pixel[]>(SCREEN_WIDTH*SCREEN_HEIGHT);
|
||||||
_nameTable = std::make_unique<uint8_t[]>(1024);
|
_nameTable = std::make_unique<uint8_t[]>(1024);
|
||||||
|
_nameTable2 = std::make_unique<uint8_t[]>(1024);
|
||||||
_paletteTable = std::make_unique<uint8_t[]>(32);
|
_paletteTable = std::make_unique<uint8_t[]>(32);
|
||||||
_palette = std::make_unique<Pixel[]>(64);
|
_palette = std::make_unique<Pixel[]>(64);
|
||||||
_oamData = std::make_unique<SpriteInfo[]>(64);
|
_oam = std::make_unique<Oam>(this);
|
||||||
|
|
||||||
_palette[0x00] = Pixel(84, 84, 84);
|
_palette[0x00] = Pixel(84, 84, 84);
|
||||||
_palette[0x01] = Pixel(0, 30, 116);
|
_palette[0x01] = Pixel(0, 30, 116);
|
||||||
@ -100,6 +101,8 @@ namespace nes {
|
|||||||
|
|
||||||
if (_scanline == -1 && _column == 1) {
|
if (_scanline == -1 && _column == 1) {
|
||||||
_status.verticalBlank = 0;
|
_status.verticalBlank = 0;
|
||||||
|
_status.spriteOverflow = 0;
|
||||||
|
_status.spriteZeroHit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preloading some data ahead of time
|
// Preloading some data ahead of time
|
||||||
@ -108,6 +111,9 @@ namespace nes {
|
|||||||
_bgPatternShifter.shift();
|
_bgPatternShifter.shift();
|
||||||
_bgAttribShifter.shift();
|
_bgAttribShifter.shift();
|
||||||
}
|
}
|
||||||
|
if(_mask.renderSprites && _column >= 2 && _column < 258) {
|
||||||
|
_oam->updateShifters();
|
||||||
|
}
|
||||||
prepareNextBgTile(_column);
|
prepareNextBgTile(_column);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,22 +137,59 @@ namespace nes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t colorIndex = 0;
|
if(_column == 257 && _scanline >= 0) {
|
||||||
uint8_t palette = 0;
|
_status.spriteOverflow = _oam->detectVisibleSprites(_scanline, _control.spriteSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_column == 340) {
|
||||||
|
_oam->loadShifters(_scanline, _control.spriteSize, _control.patternSprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t bgPattern = 0;
|
||||||
|
uint8_t bgPalette = 0;
|
||||||
|
|
||||||
if(_mask.renderBackground) {
|
if(_mask.renderBackground) {
|
||||||
colorIndex = _bgPatternShifter.getValue(_fineX);
|
bgPattern = _bgPatternShifter.getValue(_fineX);
|
||||||
palette = _bgAttribShifter.getValue(_fineX);
|
bgPalette = _bgAttribShifter.getValue(_fineX);
|
||||||
Pixel pixel = getColor(palette, colorIndex);
|
}
|
||||||
|
|
||||||
_curPalette = palette;
|
uint8_t fgPattern = 0;
|
||||||
_curColorIndex = colorIndex;
|
uint8_t fgPalette = 0;
|
||||||
|
uint8_t priority = 0;
|
||||||
|
|
||||||
if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) {
|
if(_mask.renderSprites) {
|
||||||
setPixel(_scanline, _column, pixel);
|
std::tie(fgPattern, fgPalette, priority) = _oam->getPixel();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t pattern = 0;
|
||||||
|
uint8_t palette = 0;
|
||||||
|
|
||||||
|
if(bgPattern == 0 && fgPattern > 0) {
|
||||||
|
pattern = fgPattern;
|
||||||
|
palette = fgPalette;
|
||||||
|
}
|
||||||
|
else if(bgPattern > 0 && fgPattern == 0) {
|
||||||
|
pattern = bgPattern;
|
||||||
|
palette = bgPalette;
|
||||||
|
}
|
||||||
|
else if(bgPattern > 0 && fgPattern > 0) {
|
||||||
|
pattern = priority ? fgPattern : bgPattern;
|
||||||
|
palette = priority ? fgPalette : bgPalette;
|
||||||
|
|
||||||
|
if(_mask.renderBackground && _mask.renderSprites && _oam->spriteZeroBeingRendered()) {
|
||||||
|
bool renderLeft = _mask.renderBackgroundLeft && _mask.renderSpritesLeft;
|
||||||
|
int16_t firstRow = renderLeft ? 0 : 8;
|
||||||
|
if(_column > firstRow && _column < 258) {
|
||||||
|
_status.spriteZeroHit = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(_column < SCREEN_WIDTH && _scanline < SCREEN_HEIGHT && _scanline >= 0) {
|
||||||
|
Pixel pixel = getColor(palette, pattern);
|
||||||
|
setPixel(_scanline, _column, pixel);
|
||||||
|
}
|
||||||
|
|
||||||
_column++;
|
_column++;
|
||||||
if(_column >= 341) {
|
if(_column >= 341) {
|
||||||
_column = 0;
|
_column = 0;
|
||||||
@ -178,7 +221,7 @@ namespace nes {
|
|||||||
_vRamAddress.value += _control.incrementMode ? 32 : 1;
|
_vRamAddress.value += _control.incrementMode ? 32 : 1;
|
||||||
break;
|
break;
|
||||||
case OamData:
|
case OamData:
|
||||||
value = reinterpret_cast<uint8_t*>(_oamData.get())[_oamAddress];
|
value = _oam->read();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@ -219,10 +262,10 @@ namespace nes {
|
|||||||
_vRamAddress.value += _control.incrementMode ? 32 : 1;
|
_vRamAddress.value += _control.incrementMode ? 32 : 1;
|
||||||
break;
|
break;
|
||||||
case OamAddress:
|
case OamAddress:
|
||||||
_oamAddress = value;
|
_oam->setAddress(value);
|
||||||
break;
|
break;
|
||||||
case OamData:
|
case OamData:
|
||||||
reinterpret_cast<uint8_t*>(_oamData.get())[_oamAddress] = value;
|
_oam->write(value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -262,7 +305,25 @@ namespace nes {
|
|||||||
}
|
}
|
||||||
else if(address >= 0x2000 && address < 0x3F00) {
|
else if(address >= 0x2000 && address < 0x3F00) {
|
||||||
address &= 0x0FFF;
|
address &= 0x0FFF;
|
||||||
return _nameTable[address & 0x03FF];
|
if(_cartridge->mirroring() == Cartridge::Mirroring::Vertical) {
|
||||||
|
if (address >= 0x0000 && address <= 0x03FF)
|
||||||
|
return _nameTable[address & 0x03FF];
|
||||||
|
if (address >= 0x0400 && address <= 0x07FF)
|
||||||
|
return _nameTable2[address & 0x03FF];
|
||||||
|
if (address >= 0x0800 && address <= 0x0BFF)
|
||||||
|
return _nameTable[address & 0x03FF];
|
||||||
|
if (address >= 0x0C00 && address <= 0x0FFF)
|
||||||
|
return _nameTable2[address & 0x03FF];
|
||||||
|
} else {
|
||||||
|
if (address >= 0x0000 && address <= 0x03FF)
|
||||||
|
return _nameTable[address & 0x03FF];
|
||||||
|
if (address >= 0x0400 && address <= 0x07FF)
|
||||||
|
return _nameTable[address & 0x03FF];
|
||||||
|
if (address >= 0x0800 && address <= 0x0BFF)
|
||||||
|
return _nameTable2[address & 0x03FF];
|
||||||
|
if (address >= 0x0C00 && address <= 0x0FFF)
|
||||||
|
return _nameTable2[address & 0x03FF];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(address >= 0x3F00 && address < 0x4000) {
|
else if(address >= 0x3F00 && address < 0x4000) {
|
||||||
address &= 0x1F;
|
address &= 0x1F;
|
||||||
@ -284,7 +345,25 @@ namespace nes {
|
|||||||
}
|
}
|
||||||
else if(address >= 0x2000 && address < 0x3F00) {
|
else if(address >= 0x2000 && address < 0x3F00) {
|
||||||
address &= 0x0FFF;
|
address &= 0x0FFF;
|
||||||
_nameTable[address & 0x03FF] = value;
|
if(_cartridge->mirroring() == Cartridge::Mirroring::Vertical) {
|
||||||
|
if (address >= 0x0000 && address <= 0x03FF)
|
||||||
|
_nameTable[address & 0x03FF] = value;
|
||||||
|
if (address >= 0x0400 && address <= 0x07FF)
|
||||||
|
_nameTable2[address & 0x03FF] = value;
|
||||||
|
if (address >= 0x0800 && address <= 0x0BFF)
|
||||||
|
_nameTable[address & 0x03FF] = value;
|
||||||
|
if (address >= 0x0C00 && address <= 0x0FFF)
|
||||||
|
_nameTable2[address & 0x03FF] = value;
|
||||||
|
} else {
|
||||||
|
if (address >= 0x0000 && address <= 0x03FF)
|
||||||
|
_nameTable[address & 0x03FF] = value;
|
||||||
|
if (address >= 0x0400 && address <= 0x07FF)
|
||||||
|
_nameTable[address & 0x03FF] = value;
|
||||||
|
if (address >= 0x0800 && address <= 0x0BFF)
|
||||||
|
_nameTable2[address & 0x03FF] = value;
|
||||||
|
if (address >= 0x0C00 && address <= 0x0FFF)
|
||||||
|
_nameTable2[address & 0x03FF] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(address >= 0x3F00 && address < 0x4000) {
|
else if(address >= 0x3F00 && address < 0x4000) {
|
||||||
address &= 0x1F;
|
address &= 0x1F;
|
||||||
@ -394,7 +473,7 @@ namespace nes {
|
|||||||
|
|
||||||
auto palettes = (uint32_t*)_paletteTable.get();
|
auto palettes = (uint32_t*)_paletteTable.get();
|
||||||
|
|
||||||
return std::format("X:{:03d}, Y:{:03d}, V:{:d}, vR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], tR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], FX:{:d}, AL:{:d}, PI:{:02X}, CI:{:02X}, ASL:{:04X}, ASH:{:04X}",
|
return fmt::format("X:{:03d}, Y:{:03d}, V:{:d}, vR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], tR: [CX:{:02X}, CY:{:02X}, NX:{:d}, NY:{:d}, FY:{:d}], FX:{:d}, AL:{:d}, ASL:{:04X}, ASH:{:04X}",
|
||||||
_column,
|
_column,
|
||||||
_scanline,
|
_scanline,
|
||||||
_status.verticalBlank,
|
_status.verticalBlank,
|
||||||
@ -410,14 +489,11 @@ namespace nes {
|
|||||||
_tRamAddress.fineY,
|
_tRamAddress.fineY,
|
||||||
_fineX,
|
_fineX,
|
||||||
_addressWriteInProgress,
|
_addressWriteInProgress,
|
||||||
_curPalette,
|
|
||||||
_curColorIndex,
|
|
||||||
_bgAttribShifter._lo,
|
_bgAttribShifter._lo,
|
||||||
_bgAttribShifter._hi);
|
_bgAttribShifter._hi);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ppu::writeOam(uint8_t address, uint8_t data) {
|
void Ppu::writeOam(uint8_t address, uint8_t data) {
|
||||||
auto oamPtr = reinterpret_cast<uint8_t*>(_oamData.get());
|
_oam->write(address, data);
|
||||||
oamPtr[address] = data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
21
src/Ppu.h
21
src/Ppu.h
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "Cartridge.h"
|
#include "Cartridge.h"
|
||||||
#include "Shifter.h"
|
#include "Shifter.h"
|
||||||
|
#include "Oam.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -97,13 +98,6 @@ namespace nes {
|
|||||||
uint8_t msb;
|
uint8_t msb;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SpriteInfo {
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t id;
|
|
||||||
uint8_t attr;
|
|
||||||
uint8_t x;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Ppu();
|
Ppu();
|
||||||
bool tick();
|
bool tick();
|
||||||
@ -114,13 +108,13 @@ namespace nes {
|
|||||||
void reset();
|
void reset();
|
||||||
[[nodiscard]] std::string state() const;
|
[[nodiscard]] std::string state() const;
|
||||||
void writeOam(uint8_t address, uint8_t data);
|
void writeOam(uint8_t address, uint8_t data);
|
||||||
|
uint8_t internalRead(uint16_t address);
|
||||||
|
void internalWrite(uint16_t address, uint8_t value);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::function<void(const Pixel*)> onNewFrame;
|
std::function<void(const Pixel*)> onNewFrame;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t internalRead(uint16_t address);
|
|
||||||
void internalWrite(uint16_t address, uint8_t value);
|
|
||||||
Pixel getColor(uint8_t palette, uint8_t pixel);
|
Pixel getColor(uint8_t palette, uint8_t pixel);
|
||||||
void prepareNextBgTile(uint16_t column);
|
void prepareNextBgTile(uint16_t column);
|
||||||
void incrementScrollX();
|
void incrementScrollX();
|
||||||
@ -147,19 +141,14 @@ namespace nes {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<uint8_t[]> _nameTable;
|
std::unique_ptr<uint8_t[]> _nameTable;
|
||||||
|
std::unique_ptr<uint8_t[]> _nameTable2;
|
||||||
std::unique_ptr<Pixel[]> _palette;
|
std::unique_ptr<Pixel[]> _palette;
|
||||||
std::unique_ptr<uint8_t[]> _paletteTable;
|
std::unique_ptr<uint8_t[]> _paletteTable;
|
||||||
|
std::unique_ptr<Oam> _oam;
|
||||||
std::unique_ptr<SpriteInfo[]> _oamData;
|
|
||||||
uint8_t _oamAddress;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<Pixel[]> _frameBuffer;
|
std::unique_ptr<Pixel[]> _frameBuffer;
|
||||||
Cartridge* _cartridge;
|
Cartridge* _cartridge;
|
||||||
|
|
||||||
private: // For debug
|
|
||||||
uint8_t _curPalette;
|
|
||||||
uint8_t _curColorIndex;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,15 +14,25 @@ namespace nes {
|
|||||||
_hi = (_hi & 0xFF00) | hi;
|
_hi = (_hi & 0xFF00) | hi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shifter::loadHighByte(uint8_t lo, uint8_t hi) {
|
||||||
|
_lo = lo << 8;
|
||||||
|
_hi = hi << 8;
|
||||||
|
}
|
||||||
|
|
||||||
void Shifter::shift() {
|
void Shifter::shift() {
|
||||||
_lo <<= 1;
|
_lo <<= 1;
|
||||||
_hi <<= 1;
|
_hi <<= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t Shifter::getValue(uint8_t offset) {
|
uint8_t Shifter::getValue(uint8_t offset) const {
|
||||||
uint16_t mask = 0x8000 >> offset;
|
uint16_t mask = 0x8000 >> offset;
|
||||||
bool loBit = _lo & mask;
|
bool loBit = _lo & mask;
|
||||||
bool hiBit = _hi & mask;
|
bool hiBit = _hi & mask;
|
||||||
return (hiBit << 1) | loBit;
|
return (hiBit << 1) | loBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Shifter::reset() {
|
||||||
|
_lo = 0;
|
||||||
|
_hi = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -13,8 +13,10 @@ namespace nes {
|
|||||||
public:
|
public:
|
||||||
Shifter();
|
Shifter();
|
||||||
void load(uint8_t lo, uint8_t hi);
|
void load(uint8_t lo, uint8_t hi);
|
||||||
|
void loadHighByte(uint8_t lo, uint8_t hi);
|
||||||
void shift();
|
void shift();
|
||||||
uint16_t getValue(uint8_t offset);
|
uint8_t getValue(uint8_t offset) const;
|
||||||
|
void reset();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint16_t _lo;
|
uint16_t _lo;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user