Update to v087r19 release.

byuu says:

Changelog:
- added FIFO buffer emulation (with DMA and all that jazz) [Cydrak]
- fixed timers and vcounter assign [Cydrak]
- emulated EEPROM (you have to change size manually for 14-bit mode, we
  need a database badly now) [SMA runs now]
- removed OAM array, now decoding directly to struct Object {} [128] and
  ObjectParam {} [32] (faster this way)
- check forceblank (still doesn't remove all garble between transitions,
  though??)
- lots of other stuff

Delete your settings.cfg, or manually change frequencyGBA to 32768, or
bad things will happen (this may change back to 256KHz-4MHz later.)

15 of 16 games are fully playable now, and look and sound great.
The major missing detail right now is PPU blending support, and we
really need to optimize the hell out of the code.
This commit is contained in:
Tim Allen 2012-04-09 16:19:32 +10:00
parent 6189c93f3d
commit 17b5bae86a
34 changed files with 474 additions and 192 deletions

View File

@ -1,7 +1,7 @@
#ifndef BASE_HPP
#define BASE_HPP
static const char Version[] = "087.18";
static const char Version[] = "087.19";
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>

View File

@ -96,6 +96,7 @@ void APU::Square1::write(unsigned r, uint8 data) {
if(initialize) {
enable = dac_enable();
period = 4 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
frequency_shadow = frequency;
@ -106,8 +107,6 @@ void APU::Square1::write(unsigned r, uint8 data) {
//if(length == 0) length = 64;
}
}
period = 4 * (2048 - frequency);
}
void APU::Square1::power() {

View File

@ -65,13 +65,12 @@ void APU::Square2::write(unsigned r, uint8 data) {
if(initialize) {
enable = dac_enable();
period = 4 * (2048 - frequency);
envelope_period = envelope_frequency;
volume = envelope_volume;
//if(length == 0) length = 64;
}
}
period = 4 * (2048 - frequency);
}
void APU::Square2::power() {

View File

@ -52,12 +52,11 @@ void APU::Wave::write(unsigned r, uint8 data) {
if(initialize) {
enable = dac_enable;
period = 2 * (2048 - frequency);
pattern_offset = 0;
//if(length == 0) length = 256;
}
}
period = 2 * (2048 - frequency);
}
void APU::Wave::write_pattern(unsigned p, uint8 data) {

View File

@ -10,15 +10,59 @@ namespace GBA {
#include "wave.cpp"
#include "noise.cpp"
#include "sequencer.cpp"
#include "fifo.cpp"
APU apu;
void APU::Enter() { apu.enter(); }
void APU::Enter() { apu.main(); }
void APU::enter() {
void APU::main() {
while(true) {
runsequencer();
interface->audioSample(sequencer.lsample, sequencer.rsample);
step(4);
for(unsigned n = 0; n < 128; n++) {
runsequencer();
}
signed lsample = regs.bias.level - 0x0200;
signed rsample = regs.bias.level - 0x0200;
//(4-bit x 4 -> 6-bit) + 3-bit volume = 9-bit output
if(sequencer.masterenable) {
signed lsequence = 0;
if(sequencer.lenable[0]) lsequence += square1.output;
if(sequencer.lenable[1]) lsequence += square2.output;
if(sequencer.lenable[2]) lsequence += wave.output;
if(sequencer.lenable[3]) lsequence += noise.output;
signed rsequence = 0;
if(sequencer.renable[0]) rsequence += square1.output;
if(sequencer.renable[1]) rsequence += square2.output;
if(sequencer.renable[2]) rsequence += wave.output;
if(sequencer.renable[3]) rsequence += noise.output;
if(sequencer.volume < 3) {
lsample += lsequence * (sequencer.lvolume + 1) >> (2 - sequencer.volume);
rsample += rsequence * (sequencer.rvolume + 1) >> (2 - sequencer.volume);
}
}
//(8-bit x 2 -> 7-bit) + 1-bit volume = 10-bit output
signed fifo0 = fifo[0].output + (1 << fifo[0].volume);
signed fifo1 = fifo[1].output + (1 << fifo[1].volume);
if(fifo[0].lenable) lsample += fifo0;
if(fifo[1].lenable) lsample += fifo1;
if(fifo[0].renable) rsample += fifo0;
if(fifo[1].renable) rsample += fifo1;
lsample = sclamp<10>(lsample);
rsample = sclamp<10>(rsample);
if(regs.bias.amplitude == 1) lsample &= ~3, rsample &= ~3;
if(regs.bias.amplitude == 2) lsample &= ~7, rsample &= ~7;
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
interface->audioSample(lsample << 5, rsample << 5);
step(512);
}
}
@ -35,6 +79,8 @@ void APU::power() {
wave.power();
noise.power();
sequencer.power();
fifo[0].power();
fifo[1].power();
regs.bias = 0x0200;

View File

@ -2,7 +2,7 @@ struct APU : Thread, MMIO {
#include "registers.hpp"
static void Enter();
void enter();
void main();
void step(unsigned clocks);
uint8 read(uint32 addr);

28
bsnes/gba/apu/fifo.cpp Executable file
View File

@ -0,0 +1,28 @@
void APU::FIFO::read() {
if(size == 0) return;
size--;
output = sample[rdoffset++];
}
void APU::FIFO::write(int8 byte) {
if(size == 32) return;
size++;
sample[wroffset++] = byte;
}
void APU::FIFO::reset() {
for(auto &byte : sample) byte = 0;
output = 0;
rdoffset = 0;
wroffset = 0;
size = 0;
}
void APU::FIFO::power() {
reset();
lenable = 0;
renable = 0;
timer = 0;
}

View File

@ -138,8 +138,21 @@ void APU::write(uint32 addr, uint8 byte) {
case 0x04000081: return sequencer.write(1, byte);
//SOUND_CNT_H
case 0x04000082: return;
case 0x04000083: return;
case 0x04000082:
sequencer.volume = byte >> 0;
fifo[0].volume = byte >> 2;
fifo[1].volume = byte >> 3;
return;
case 0x04000083:
fifo[0].renable = byte >> 0;
fifo[0].lenable = byte >> 1;
fifo[0].timer = byte >> 2;
if(byte & 1 << 3) fifo[0].reset();
fifo[1].renable = byte >> 4;
fifo[1].lenable = byte >> 5;
fifo[1].timer = byte >> 6;
if(byte & 1 << 7) fifo[0].reset();
return;
//NR52
case 0x04000084: return sequencer.write(2, byte);
@ -181,5 +194,16 @@ void APU::write(uint32 addr, uint8 byte) {
case 0x0400009e: return wave.writeram(14, byte);
case 0x0400009f: return wave.writeram(15, byte);
//FIFO_A_L
//FIFO_A_H
case 0x040000a0: case 0x040000a1:
case 0x040000a2: case 0x040000a3:
return fifo[0].write(byte);
//FIFO_B_L
//FIFO_B_H
case 0x040000a4: case 0x040000a5:
case 0x040000a6: case 0x040000a7:
return fifo[1].write(byte);
}
}

View File

@ -120,6 +120,7 @@ struct Noise {
} noise;
struct Sequencer {
uint2 volume;
uint3 lvolume;
uint3 rvolume;
uint1 lenable[4];
@ -139,24 +140,19 @@ struct Sequencer {
struct FIFO {
int8 sample[32];
int8 output;
uint5 rdoffset;
uint5 wroffset;
uint6 size;
inline int8 pull() {
size--;
return sample[rdoffset++];
}
uint1 volume; //0 = 50%, 1 = 100%
uint1 lenable;
uint1 renable;
uint1 timer;
inline void push(int8 data) {
size++;
sample[wroffset++] = data;
}
inline void reset() {
rdoffset = 0;
wroffset = 0;
size = 0;
for(auto &byte : sample) byte = 0;
}
};
void read();
void write(int8 byte);
void reset();
void power();
} fifo[2];

View File

@ -22,31 +22,8 @@ void APU::runsequencer() {
if(r.enable[0]) square1.run();
if(r.enable[1]) square2.run();
if(r.enable[2]) wave.run();
if(r.enable[3]) noise.run();
signed lsample = 0;
if(r.lenable[0]) lsample += square1.output;
if(r.lenable[1]) lsample += square2.output;
if(r.lenable[2]) lsample += wave.output;
if(r.lenable[3]) lsample += noise.output;
lsample = (lsample * 512) - 15360;
lsample = (lsample * (r.lvolume + 1)) / 8;
r.lsample = lsample;
signed rsample = 0;
if(r.renable[0]) rsample += square1.output;
if(r.renable[1]) rsample += square2.output;
if(r.renable[2]) rsample += wave.output;
if(r.renable[3]) rsample += noise.output;
rsample = (rsample * 512) - 15360;
rsample = (rsample * (r.rvolume + 1)) / 8;
r.rsample = rsample;
if(r.masterenable == false) {
r.lsample = 0;
r.rsample = 0;
}
if(r.enable[2]) wave.run();
if(r.enable[3]) noise.run();
}
uint8 APU::Sequencer::read(unsigned addr) const {

View File

@ -64,6 +64,7 @@ void APU::Square1::write(unsigned addr, uint8 byte) {
if(initialize) {
enable = envelope.dacenable();
period = 4 * (2048 - frequency);
envelope.period = envelope.frequency;
volume = envelope.volume;
shadowfrequency = frequency;
@ -75,8 +76,6 @@ void APU::Square1::write(unsigned addr, uint8 byte) {
break;
}
period = 4 * (2048 - frequency);
}
void APU::Square1::power() {

View File

@ -32,14 +32,13 @@ void APU::Square2::write(unsigned addr, uint8 byte) {
if(initialize) {
enable = envelope.dacenable();
period = 4 * (2048 - frequency);
envelope.period = envelope.frequency;
volume = envelope.volume;
}
break;
}
period = 4 * (2048 - frequency);
}
void APU::Square2::power() {

View File

@ -55,14 +55,13 @@ void APU::Wave::write(unsigned addr, uint8 byte) {
if(initialize) {
enable = dacenable;
period = 2 * (2048 - frequency);
patternaddr = 0;
patternbank = mode ? (uint1)0 : bank;
}
break;
}
period = 2 * (2048 - frequency);
}
uint8 APU::Wave::readram(unsigned addr) const {

View File

@ -2,19 +2,20 @@
namespace GBA {
#include "eeprom.cpp"
Cartridge cartridge;
bool Cartridge::load(const string &markup, const uint8_t *data, unsigned size) {
if(cartridge.rom.data) delete[] cartridge.rom.data;
cartridge.rom.data = new uint8[cartridge.rom.size = 32 * 1024 * 1024];
if(rom.data) delete[] rom.data;
rom.data = new uint8[rom.size = 32 * 1024 * 1024];
for(unsigned addr = 0; addr < 32 * 1024 * 1024; addr++) {
cartridge.rom.data[addr] = data[Bus::mirror(addr, size)];
rom.data[addr] = data[Bus::mirror(addr, size)];
}
if(cartridge.ram.data) delete[] cartridge.ram.data;
cartridge.ram.data = new uint8[cartridge.ram.size = 64 * 1024]();
if(ram.data) delete[] ram.data;
ram.data = new uint8[ram.size = 64 * 1024]();
sha256 = nall::sha256(cartridge.rom.data, cartridge.rom.size);
sha256 = nall::sha256(rom.data, rom.size);
return loaded = true;
}
@ -23,9 +24,30 @@ void Cartridge::unload() {
if(loaded) return;
loaded = false;
delete[] cartridge.rom.data;
cartridge.rom.data = nullptr;
cartridge.rom.size = 0u;
delete[] rom.data;
rom.data = nullptr;
rom.size = 0u;
}
void Cartridge::power() {
eeprom.power();
has_eeprom = false;
}
uint32 Cartridge::read(uint32 addr, uint32 size) {
if(has_eeprom && (addr & 0x0f000000) == 0x0d000000) return eeprom.read();
if((addr & 0x0e000000) == 0x08000000) return rom.read(addr & 0x01ffffff, size);
if((addr & 0x0e000000) == 0x0a000000) return rom.read(addr & 0x01ffffff, size);
if((addr & 0x0e000000) == 0x0c000000) return rom.read(addr & 0x01ffffff, size);
if((addr & 0x0e000000) == 0x0e000000) return ram.read(addr & 0x0000ffff, size);
}
void Cartridge::write(uint32 addr, uint32 size, uint32 word) {
if((addr & 0x0f000000) == 0x0d000000) { has_eeprom = true; return eeprom.write(word & 1); }
if((addr & 0x0e000000) == 0x0e000000) return ram.write(addr & 0x0000ffff, size, word);
}
Cartridge::Cartridge() {

View File

@ -1,12 +1,18 @@
struct Cartridge : property<Cartridge> {
StaticMemory rom;
StaticMemory ram;
#include "memory.hpp"
readonly<bool> loaded;
readonly<string> sha256;
readonly<bool> has_eeprom;
bool load(const string &markup, const uint8_t *data, unsigned size);
void unload();
void power();
uint32 read(uint32 addr, uint32 size);
void write(uint32 addr, uint32 size, uint32 word);
Cartridge();
};

65
bsnes/gba/cartridge/eeprom.cpp Executable file
View File

@ -0,0 +1,65 @@
bool Cartridge::EEPROM::read() {
bool bit = 1;
if(mode == Mode::ReadData) {
if(offset >= 4) bit = data[address * 64 + (offset - 4)];
if(++offset == 68) mode = Mode::Wait;
}
return bit;
}
void Cartridge::EEPROM::write(bool bit) {
if(mode == Mode::Wait) {
if(bit == 1) mode = Mode::Command;
}
else if(mode == Mode::Command) {
if(bit == 0) mode = Mode::WriteAddress;
if(bit == 1) mode = Mode::ReadAddress;
offset = 0;
address = 0;
}
else if(mode == Mode::ReadAddress) {
address = (address << 1) | bit;
if(++offset == size) {
mode = Mode::ReadValidate;
offset = 0;
}
}
else if(mode == Mode::ReadValidate) {
if(bit == 1); //invalid
mode = Mode::ReadData;
}
else if(mode == Mode::WriteAddress) {
address = (address << 1) | bit;
if(++offset == size) {
mode = Mode::WriteData;
offset = 0;
}
}
else if(mode == Mode::WriteData) {
data[address * 64 + offset] = bit;
if(++offset == 64) {
mode = Mode::WriteValidate;
}
}
else if(mode == Mode::WriteValidate) {
if(bit == 1); //invalid
mode = Mode::Wait;
}
}
void Cartridge::EEPROM::power() {
for(auto &bit : data) bit = 0;
size = 6;
mode = Mode::Wait;
offset = 0;
address = 0;
}

12
bsnes/gba/cartridge/memory.hpp Executable file
View File

@ -0,0 +1,12 @@
struct EEPROM {
bool data[64 * 1024];
unsigned size;
enum class Mode : unsigned { Wait, Command, ReadAddress, ReadValidate, ReadData, WriteAddress, WriteData, WriteValidate } mode;
unsigned offset;
unsigned address;
bool read();
void write(bool bit);
void power();
} eeprom;

View File

@ -21,6 +21,7 @@ void CPU::enter() {
}
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
dma_run();
if(regs.mode == Registers::Mode::Halt) {
if((regs.irq.enable & regs.irq.flag) == 0) {
@ -30,7 +31,6 @@ void CPU::enter() {
regs.mode = Registers::Mode::Normal;
}
dma_run();
exec();
}
}

View File

@ -7,6 +7,7 @@ struct CPU : Processor::ARM, Thread, MMIO {
static void Enter();
void enter();
void step(unsigned clocks);
uint32 bus_read(uint32 addr, uint32 size);
void bus_write(uint32 addr, uint32 size, uint32 word);

View File

@ -19,7 +19,19 @@ void CPU::dma_run() {
case 0: break;
case 1: if(pending.dma.vblank == false) continue; break;
case 2: if(pending.dma.hblank == false) continue; break;
case 3: if(pending.dma.hdma == false || n != 3) continue; break;
case 3:
if(n == 0) {
continue;
}
if(n == 1 || n == 2) {
if(apu.fifo[n - 1].size > 16) continue;
dma.control.targetmode = 2;
dma.control.size = 1;
dma.run.length = 4;
}
if(n == 3) {
if(pending.dma.hdma == false) continue;
}
}
dma_transfer(dma);

View File

@ -4,52 +4,40 @@ uint8 CPU::read(uint32 addr) {
switch(addr) {
//DMA0CNT_H
case 0x040000ba: return regs.dma[0].control >> 0;
case 0x040000bb: return regs.dma[0].control >> 8;
//DMA1CNT_H
case 0x040000c6: return regs.dma[1].control >> 0;
case 0x040000c7: return regs.dma[1].control >> 8;
//DMA2CNT_H
case 0x040000d2: return regs.dma[2].control >> 0;
case 0x040000d3: return regs.dma[2].control >> 8;
//DMA3CNT_H
case 0x040000de: return regs.dma[3].control >> 0;
case 0x040000df: return regs.dma[3].control >> 8;
case 0x040000ba: case 0x040000bb:
case 0x040000c6: case 0x040000c7:
case 0x040000d2: case 0x040000d3:
case 0x040000de: case 0x040000df: {
auto &dma = regs.dma[(addr - 0x040000ba) / 12];
unsigned shift = (addr & 1) * 8;
return dma.control >> shift;
}
//TM0CNT_L
case 0x04000100: return regs.timer[0].counter >> 0;
case 0x04000101: return regs.timer[0].counter >> 8;
//TM1CNT_L
//TM2CNT_L
//TM3CNT_L
case 0x04000100: case 0x04000101:
case 0x04000104: case 0x04000105:
case 0x04000108: case 0x04000109:
case 0x0400010c: case 0x0400010d: {
auto &timer = regs.timer[(addr >> 2) & 3];
unsigned shift = (addr & 1) * 8;
return timer.counter >> shift;
}
//TIM0CNT_H
case 0x04000102: return regs.timer[0].control >> 0;
case 0x04000103: return regs.timer[0].control >> 8;
//TM1CNT_L
case 0x04000104: return regs.timer[1].reload >> 0;
case 0x04000105: return regs.timer[1].reload >> 8;
//TM1CNT_H
case 0x04000106: return regs.timer[1].control >> 0;
case 0x04000107: return regs.timer[1].control >> 8;
//TM2CNT_L
case 0x04000108: return regs.timer[2].reload >> 0;
case 0x04000109: return regs.timer[2].reload >> 8;
//TM2CNT_H
case 0x0400010a: return regs.timer[2].control >> 0;
case 0x0400010b: return regs.timer[2].control >> 8;
//TM3CNT_L
case 0x0400010c: return regs.timer[3].reload >> 0;
case 0x0400010d: return regs.timer[3].reload >> 8;
//TM3CNT_H
case 0x0400010e: return regs.timer[3].control >> 0;
case 0x0400010f: return regs.timer[3].control >> 8;
case 0x04000102: case 0x04000103:
case 0x04000106: case 0x04000107:
case 0x0400010a: case 0x0400010b:
case 0x0400010e: case 0x0400010f: {
auto &timer = regs.timer[(addr >> 2) & 3];
unsigned shift = (addr & 1) * 8;
return timer.control >> shift;
}
//KEYINPUT
case 0x04000130:
@ -208,10 +196,10 @@ void CPU::write(uint32 addr, uint8 byte) {
case 0x0400010e: {
auto &timer = regs.timer[(addr >> 2) & 3];
bool enable = timer.control.enable;
if(timer.control.enable == 0 && enable == 1) {
timer.control = byte;
if(enable == 0 && timer.control.enable == 1) {
timer.counter = timer.reload;
}
timer.control = byte;
return;
}

View File

@ -25,7 +25,7 @@ struct Registers {
struct Run {
uint32 target;
uint32 source;
uint32 length;
uint16 length;
} run;
uint32 basetarget;
} dma[4];

View File

@ -17,7 +17,11 @@ void CPU::timer_increment(unsigned n) {
if(++regs.timer[n].counter == 0) {
if(regs.timer[n].control.irq) regs.irq.flag.timer[n] = 1;
if(apu.fifo[0].timer == n) apu.fifo[0].read();
if(apu.fifo[1].timer == n) apu.fifo[1].read();
regs.timer[n].counter = regs.timer[n].reload;
if(n < 3 && regs.timer[n + 1].control.cascade) {
timer_increment(n + 1);
}

View File

@ -74,7 +74,9 @@ uint32 Bus::mirror(uint32 addr, uint32 size) {
}
uint32 Bus::read(uint32 addr, uint32 size) {
switch(addr & 0x0f000000) {
if(addr & 0x08000000) return cartridge.read(addr, size);
switch(addr & 0x07000000) {
case 0x00000000: return system.bios.read(addr & 0x3fff, size);
case 0x01000000: return system.bios.read(addr & 0x3fff, size);
case 0x02000000: return cpu.ewram.read(addr & 0x3ffff, size);
@ -85,20 +87,14 @@ uint32 Bus::read(uint32 addr, uint32 size) {
return 0u;
case 0x05000000: return ppu.pram.read(addr & 0x3ff, size);
case 0x06000000: return ppu.vram.read(addr & 0x10000 ? (0x10000 + (addr & 0x7fff)) : (addr & 0xffff), size);
case 0x07000000: return ppu.oam.read(addr & 0x3ff, size);
case 0x08000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x09000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0a000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0b000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0c000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0d000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0e000000: return cartridge.ram.read(addr & 0xffff, size);
case 0x0f000000: return cartridge.ram.read(addr & 0xffff, size);
case 0x07000000: return ppu.oam_read(addr & 0x3ff, size);
}
}
void Bus::write(uint32 addr, uint32 size, uint32 word) {
switch(addr & 0x0f000000) {
if(addr & 0x08000000) return cartridge.write(addr, size, word);
switch(addr & 0x07000000) {
case 0x00000000: return;
case 0x01000000: return;
case 0x02000000: return cpu.ewram.write(addr & 0x3ffff, size, word);
@ -109,15 +105,7 @@ void Bus::write(uint32 addr, uint32 size, uint32 word) {
return;
case 0x05000000: return ppu.pram.write(addr & 0x3ff, size, word);
case 0x06000000: return ppu.vram.write(addr & 0x10000 ? (0x10000 + (addr & 0x7fff)) : (addr & 0xffff), size, word);
case 0x07000000: return ppu.oam.write(addr & 0x3ff, size, word);
case 0x08000000: return;
case 0x09000000: return;
case 0x0a000000: return;
case 0x0b000000: return;
case 0x0c000000: return;
case 0x0d000000: return;
case 0x0e000000: return cartridge.ram.write(addr & 0xffff, size, word);
case 0x0f000000: return cartridge.ram.write(addr & 0xffff, size, word);
case 0x07000000: return ppu.oam_write(addr & 0x3ff, size, word);
}
}

View File

@ -66,7 +66,7 @@ void PPU::write(uint32 addr, uint8 byte) {
regs.status.irqvcoincidence = byte & (1 << 5);
return;
case 0x04000005:
regs.status.vcoincidence = byte;
regs.status.vcompare = byte;
return;
//BG0CNT

View File

@ -3,47 +3,8 @@ void PPU::render_objects() {
for(signed n = 127; n >= 0; n--) {
auto &obj = object[n];
uint16 attr0 = oam.read(n * 8 + 0, Half);
uint16 attr1 = oam.read(n * 8 + 2, Half);
uint16 attr2 = oam.read(n * 8 + 4, Half);
obj.y = attr0 >> 0;
obj.affine = attr0 >> 8;
obj.affinesize = attr0 >> 9;
obj.mode = attr0 >> 10;
obj.mosaic = attr0 >> 12;
obj.colors = attr0 >> 13;
obj.shape = attr0 >> 14;
obj.x = attr1 >> 0;
obj.affineparam = attr1 >> 9;
obj.hflip = attr1 >> 12;
obj.vflip = attr1 >> 13;
obj.size = attr1 >> 14;
obj.character = attr2 >> 0;
obj.priority = attr2 >> 10;
obj.palette = attr2 >> 12;
static unsigned widths[] = {
8, 16, 32, 64,
16, 32, 32, 64,
8, 8, 16, 32,
0, 0, 0, 0, //8?
};
static unsigned heights[] = {
8, 16, 32, 64,
8, 8, 16, 32,
16, 32, 32, 64,
0, 0, 0, 0, //8?
};
obj.width = widths [obj.shape * 4 + obj.size];
obj.height = heights[obj.shape * 4 + obj.size];
uint8 py = regs.vcounter - obj.y;
if(py >= obj.height << obj.affinesize) continue;
if(py >= obj.height << obj.affinesize) continue; //offscreen
if(obj.affine == 0 && obj.affinesize == 1) continue; //hidden
if(obj.affine == 0) render_object_linear(obj);
@ -85,10 +46,10 @@ void PPU::render_object_affine(Object &obj) {
unsigned baseaddr = 0x10000 + obj.character * 32;
uint9 sx = obj.x;
int16 pa = oam.read(obj.affineparam * 32 + 0x06, Half);
int16 pb = oam.read(obj.affineparam * 32 + 0x0e, Half);
int16 pc = oam.read(obj.affineparam * 32 + 0x16, Half);
int16 pd = oam.read(obj.affineparam * 32 + 0x1e, Half);
int16 pa = objectparam[obj.affineparam].pa;
int16 pb = objectparam[obj.affineparam].pb;
int16 pc = objectparam[obj.affineparam].pc;
int16 pd = objectparam[obj.affineparam].pd;
//center-of-sprite coordinates
int16 centerx = obj.width / 2;
@ -123,3 +84,142 @@ void PPU::render_object_affine(Object &obj) {
fy += pc;
}
}
uint32 PPU::oam_read(uint32 addr, uint32 size) {
uint32 word = 0;
switch(size) {
case Word:
addr &= ~3;
word |= oam_read(addr + 0) << 0;
word |= oam_read(addr + 1) << 8;
word |= oam_read(addr + 2) << 16;
word |= oam_read(addr + 3) << 24;
break;
case Half:
word |= oam_read(addr + 0) << 0;
word |= oam_read(addr + 1) << 8;
break;
case Byte:
word |= oam_read(addr + 0) << 0;
break;
}
return word;
}
//16-bit bus (8-bit writes are ignored)
void PPU::oam_write(uint32 addr, uint32 size, uint32 word) {
switch(size) {
case Word:
addr &= ~3;
oam_write(addr + 0, word >> 0);
oam_write(addr + 1, word >> 8);
oam_write(addr + 2, word >> 16);
oam_write(addr + 3, word >> 24);
break;
case Half:
addr &= ~1;
oam_write(addr + 0, word >> 0);
oam_write(addr + 1, word >> 8);
break;
}
}
uint8 PPU::oam_read(uint32 addr) {
auto &obj = object[(addr >> 3) & 127];
auto &par = objectparam[(addr >> 5) & 31];
switch(addr & 7) {
case 0: return (obj.y);
case 1: return (obj.affine << 0) + (obj.affinesize << 1) + (obj.mode << 2) + (obj.mosaic << 4) + (obj.colors << 5) + (obj.shape << 6);
case 2: return (obj.x >> 0);
case 3: return (obj.x >> 8) + (obj.affineparam << 1) + (obj.hflip << 4) + (obj.vflip << 5) + (obj.size << 6);
case 4: return (obj.character >> 0);
case 5: return (obj.character >> 8) + (obj.priority << 2) + (obj.palette << 4);
case 6:
switch((addr >> 3) & 3) {
case 0: return par.pa >> 0;
case 1: return par.pb >> 0;
case 2: return par.pc >> 0;
case 3: return par.pd >> 0;
}
case 7:
switch((addr >> 3) & 3) {
case 0: return par.pa >> 8;
case 1: return par.pb >> 8;
case 2: return par.pc >> 8;
case 3: return par.pd >> 8;
}
}
}
void PPU::oam_write(uint32 addr, uint8 byte) {
auto &obj = object[(addr >> 3) & 127];
auto &par = objectparam[(addr >> 5) & 31];
switch(addr & 7) {
case 0:
obj.y = byte;
break;
case 1:
obj.affine = byte >> 0;
obj.affinesize = byte >> 1;
obj.mode = byte >> 2;
obj.mosaic = byte >> 4;
obj.colors = byte >> 5;
obj.shape = byte >> 6;
break;
case 2:
obj.x = (obj.x & 0xff00) | (byte << 0);
break;
case 3:
obj.x = (obj.x & 0x00ff) | (byte << 8);
obj.affineparam = byte >> 1;
obj.hflip = byte >> 4;
obj.vflip = byte >> 5;
obj.size = byte >> 6;
break;
case 4:
obj.character = (obj.character & 0xff00) | (byte << 0);
break;
case 5:
obj.character = (obj.character & 0x00ff) | (byte << 8);
obj.priority = byte >> 2;
obj.palette = byte >> 4;
break;
case 6:
switch((addr >> 3) & 3) {
case 0: par.pa = (par.pa & 0xff00) | (byte << 0); break;
case 1: par.pb = (par.pb & 0xff00) | (byte << 0); break;
case 2: par.pc = (par.pc & 0xff00) | (byte << 0); break;
case 3: par.pd = (par.pd & 0xff00) | (byte << 0); break;
}
break;
case 7:
switch((addr >> 3) & 3) {
case 0: par.pa = (par.pa & 0x00ff) | (byte << 8); break;
case 1: par.pb = (par.pb & 0x00ff) | (byte << 8); break;
case 2: par.pc = (par.pc & 0x00ff) | (byte << 8); break;
case 3: par.pd = (par.pd & 0x00ff) | (byte << 8); break;
}
break;
}
static unsigned widths[] = {
8, 16, 32, 64,
16, 32, 32, 64,
8, 8, 16, 32,
0, 0, 0, 0, //8?
};
static unsigned heights[] = {
8, 16, 32, 64,
8, 8, 16, 32,
16, 32, 32, 64,
0, 0, 0, 0, //8?
};
obj.width = widths [obj.shape * 4 + obj.size];
obj.height = heights[obj.shape * 4 + obj.size];
}

View File

@ -36,10 +36,11 @@ void PPU::power() {
create(PPU::Enter, 16777216);
for(unsigned n = 0; n < vram.size; n++) vram.data[n] = 0;
for(unsigned n = 0; n < oam.size; n++) oam.data[n] = 0;
for(unsigned n = 0; n < pram.size; n++) pram.data[n] = 0;
for(unsigned n = 0; n < 240 * 160; n++) output[n] = 0;
for(unsigned n = 0; n < 1024; n++) oam_write(n, 0);
regs.control = 0;
regs.greenswap = 0;
regs.status = 0;
@ -109,15 +110,19 @@ void PPU::scanline() {
layer[3][x].exists = false;
}
render_backgrounds();
render_objects();
render_screen();
if(regs.control.forceblank) {
render_forceblank();
} else {
render_backgrounds();
render_objects();
render_screen();
}
}
step(960);
regs.status.hblank = 1;
if(regs.status.irqhblank) cpu.regs.irq.flag.hblank = 1;
cpu.pending.dma.hblank = true;
if(regs.vcounter < 160) cpu.pending.dma.hblank = true;
step(240);
regs.status.hblank = 0;
@ -134,7 +139,6 @@ void PPU::frame() {
PPU::PPU() {
vram.data = new uint8[vram.size = 96 * 1024];
oam.data = new uint8[oam.size = 1024];
pram.data = new uint8[pram.size = 1024];
output = new uint16[240 * 160];
}

View File

@ -1,6 +1,5 @@
struct PPU : Thread, MMIO {
StaticMemory vram;
StaticMemory oam;
StaticMemory pram;
#include "registers.hpp"
#include "state.hpp"
@ -25,8 +24,13 @@ struct PPU : Thread, MMIO {
void render_objects();
void render_object_linear(Object&);
void render_object_affine(Object&);
uint32 oam_read(uint32 addr, uint32 size);
void oam_write(uint32 addr, uint32 size, uint32 word);
uint8 oam_read(uint32 addr);
void oam_write(uint32 addr, uint8 byte);
uint15 palette(uint9 index);
void render_forceblank();
void render_screen();
PPU();

View File

@ -5,9 +5,13 @@ uint15 PPU::palette(uint9 index) {
return result;
}
void PPU::render_forceblank() {
uint16 *line = output + regs.vcounter * 240;
for(unsigned x = 0; x < 240; x++) line[x] = 0x7fff;
}
void PPU::render_screen() {
uint16 *line = output + regs.vcounter * 240;
for(unsigned x = 0; x < 240; x++) {
uint15 color = palette(0) & 0x7fff;
if(layer[3][x].exists) color = layer[3][x].color;

View File

@ -27,6 +27,13 @@ struct Object {
unsigned height;
} object[128];
struct ObjectParam {
int16 pa;
int16 pb;
int16 pc;
int16 pd;
} objectparam[32];
struct Tile {
uint10 character;
uint1 hflip;

View File

@ -28,6 +28,7 @@ void System::power() {
cpu.power();
ppu.power();
apu.power();
cartridge.power();
scheduler.power();
}

View File

@ -21,7 +21,6 @@ void ARM::arm_step() {
}
instructions++;
if(pipeline.execute.address == 0x08000000) print("Entry Point\n");
if(trace) {
print(disassemble_registers(), "\n");
print(disassemble_arm_instruction(pipeline.execute.address), "\n");

View File

@ -32,7 +32,7 @@ Config::Config() {
append(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
append(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
append(audio.frequencyGB = 4194304, "Audio::Frequency::GB");
append(audio.frequencyGBA = 4194304, "Audio::Frequency::GBA");
append(audio.frequencyGBA = 32768, "Audio::Frequency::GBA");
append(input.driver = "", "Input::Driver");
append(input.focusPolicy = 1, "Input::FocusPolicy");

View File

@ -70,8 +70,8 @@ AudioSettings::AudioSettings() {
gba.name.setText("GBA:");
gba.slider.setLength(2001);
gba.base = 4194304;
gba.step = 131;
gba.base = 32768;
gba.step = 1;
append(title, { ~0, 0 }, 5);
append(outputLabel, { ~0, 0 }, 0);