mirror of https://github.com/bsnes-emu/bsnes.git
Update to v087r16 release.
byuu says: Fixed the r15 mask per Cydrak. Added DMA support (immediate + Vblank + Hblank + HDMA) with IRQ support. Basically only missing FIFO reload mode for the APU on channel 2. Added background linear renderer (tilemap mode.) Added really inefficient pixel priority selector, so that all BGs+OBJ could be visible onscreen at the same time. As a result of the above: * Mr. Driller is our first fully playable game * Bakunetsu Dodge Ball Fighters is also fully playable * Pinobee no Daibouken is also fully playable Most games (15 of 16 tested) are now showing *something*, many things look really really good in fact. Absolutely essential missing components: - APU - CPU timers and their interrupts - DMA FIFO mode - OBJ affine mode - BG affine mode - BG bitmap mode - PPU windows (BG and OBJ) - PPU mosaic - PPU blending modes - SRAM / EEPROM (going to rely on a database, not heuristics. Homebrew will require a manifest file.)
This commit is contained in:
parent
79a47d133a
commit
b8d0ec29b2
|
@ -1,7 +1,7 @@
|
|||
#ifndef BASE_HPP
|
||||
#define BASE_HPP
|
||||
|
||||
static const char Version[] = "087.15";
|
||||
static const char Version[] = "087.16";
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/algorithm.hpp>
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace GBA {
|
|||
|
||||
#include "registers.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "dma.cpp"
|
||||
CPU cpu;
|
||||
|
||||
void CPU::Enter() { cpu.enter(); }
|
||||
|
@ -28,6 +29,7 @@ void CPU::enter() {
|
|||
regs.mode = Registers::Mode::Normal;
|
||||
}
|
||||
|
||||
dma_run();
|
||||
exec();
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +74,10 @@ void CPU::power() {
|
|||
regs.mode = Registers::Mode::Normal;
|
||||
regs.memory.control = 0x0d000020;
|
||||
|
||||
pending.dma.vblank = 0;
|
||||
pending.dma.hblank = 0;
|
||||
pending.dma.hdma = 0;
|
||||
|
||||
for(unsigned n = 0x0b0; n <= 0x0df; n++) bus.mmio[n] = this; //DMA
|
||||
for(unsigned n = 0x100; n <= 0x10f; n++) bus.mmio[n] = this; //Timers
|
||||
for(unsigned n = 0x130; n <= 0x133; n++) bus.mmio[n] = this; //Keypad
|
||||
|
|
|
@ -2,6 +2,7 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
|||
StaticMemory iwram;
|
||||
StaticMemory ewram;
|
||||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
|
||||
static void Enter();
|
||||
void enter();
|
||||
|
@ -14,6 +15,9 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
|||
uint8 read(uint32 addr);
|
||||
void write(uint32 addr, uint8 byte);
|
||||
|
||||
void dma_run();
|
||||
void dma_transfer(uint2 channel);
|
||||
|
||||
CPU();
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
void CPU::dma_run() {
|
||||
for(unsigned n = 0; n < 4; n++) {
|
||||
if(regs.dma[n].control.enable == false) continue;
|
||||
switch(regs.dma[n].control.timingmode) {
|
||||
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;
|
||||
}
|
||||
dma_transfer(n);
|
||||
}
|
||||
|
||||
pending.dma.vblank = false;
|
||||
pending.dma.hblank = false;
|
||||
pending.dma.hdma = false;
|
||||
}
|
||||
|
||||
void CPU::dma_transfer(uint2 n) {
|
||||
auto &channel = regs.dma[n];
|
||||
|
||||
unsigned size = channel.control.size ? Word : Half;
|
||||
unsigned seek = channel.control.size ? 4 : 2;
|
||||
uint16 length = channel.length;
|
||||
|
||||
channel.basetarget = channel.target;
|
||||
do {
|
||||
uint32 word = bus.read(channel.source, size);
|
||||
bus.write(channel.target, size, word);
|
||||
|
||||
switch(channel.control.sourcemode) {
|
||||
case 0: channel.source += seek; break;
|
||||
case 1: channel.source -= seek; break;
|
||||
}
|
||||
|
||||
switch(channel.control.targetmode) {
|
||||
case 0: channel.target += seek; break;
|
||||
case 1: channel.target -= seek; break;
|
||||
case 3: channel.target += seek; break;
|
||||
}
|
||||
} while(--length);
|
||||
if(channel.control.targetmode == 3) channel.target = channel.basetarget;
|
||||
|
||||
channel.control.enable = false;
|
||||
if(channel.control.irq) regs.irq.flag.dma[n] = 1;
|
||||
}
|
|
@ -5,7 +5,7 @@ CPU::Registers::DMA::Control::operator uint16() const {
|
|||
| (repeat << 9)
|
||||
| (size << 10)
|
||||
| (drq << 11)
|
||||
| (timing << 12)
|
||||
| (timingmode << 12)
|
||||
| (irq << 14)
|
||||
| (enable << 15)
|
||||
);
|
||||
|
@ -17,7 +17,7 @@ uint16 CPU::Registers::DMA::Control::operator=(uint16 source) {
|
|||
repeat = source >> 9;
|
||||
size = source >> 10;
|
||||
drq = source >> 11;
|
||||
timing = source >> 12;
|
||||
timingmode = source >> 12;
|
||||
irq = source >> 14;
|
||||
enable = source >> 15;
|
||||
return operator uint16();
|
||||
|
@ -78,15 +78,15 @@ CPU::Registers::Interrupt::operator uint16() const {
|
|||
(vblank << 0)
|
||||
| (hblank << 1)
|
||||
| (vcoincidence << 2)
|
||||
| (timer0 << 3)
|
||||
| (timer1 << 4)
|
||||
| (timer2 << 5)
|
||||
| (timer3 << 6)
|
||||
| (timer[0] << 3)
|
||||
| (timer[1] << 4)
|
||||
| (timer[2] << 5)
|
||||
| (timer[3] << 6)
|
||||
| (serial << 7)
|
||||
| (dma0 << 8)
|
||||
| (dma1 << 9)
|
||||
| (dma2 << 10)
|
||||
| (dma3 << 11)
|
||||
| (dma[0] << 8)
|
||||
| (dma[1] << 9)
|
||||
| (dma[2] << 10)
|
||||
| (dma[3] << 11)
|
||||
| (keypad << 12)
|
||||
| (cartridge << 13)
|
||||
);
|
||||
|
@ -96,15 +96,15 @@ uint16 CPU::Registers::Interrupt::operator=(uint16 source) {
|
|||
vblank = source & (1 << 0);
|
||||
hblank = source & (1 << 1);
|
||||
vcoincidence = source & (1 << 2);
|
||||
timer0 = source & (1 << 3);
|
||||
timer1 = source & (1 << 4);
|
||||
timer2 = source & (1 << 5);
|
||||
timer3 = source & (1 << 6);
|
||||
timer[0] = source & (1 << 3);
|
||||
timer[1] = source & (1 << 4);
|
||||
timer[2] = source & (1 << 5);
|
||||
timer[3] = source & (1 << 6);
|
||||
serial = source & (1 << 7);
|
||||
dma0 = source & (1 << 8);
|
||||
dma1 = source & (1 << 9);
|
||||
dma2 = source & (1 << 10);
|
||||
dma3 = source & (1 << 11);
|
||||
dma[0] = source & (1 << 8);
|
||||
dma[1] = source & (1 << 9);
|
||||
dma[2] = source & (1 << 10);
|
||||
dma[3] = source & (1 << 11);
|
||||
keypad = source & (1 << 12);
|
||||
cartridge = source & (1 << 13);
|
||||
return operator uint16();
|
||||
|
|
|
@ -9,7 +9,7 @@ struct Registers {
|
|||
uint1 repeat;
|
||||
uint1 size;
|
||||
uint1 drq;
|
||||
uint2 timing;
|
||||
uint2 timingmode;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
|
@ -17,6 +17,9 @@ struct Registers {
|
|||
uint16 operator=(uint16 source);
|
||||
DMA& operator=(const DMA&) = delete;
|
||||
} control;
|
||||
|
||||
//internal
|
||||
uint32 basetarget;
|
||||
} dma[4];
|
||||
|
||||
struct TimerControl {
|
||||
|
@ -64,15 +67,9 @@ struct Registers {
|
|||
bool vblank;
|
||||
bool hblank;
|
||||
bool vcoincidence;
|
||||
bool timer0;
|
||||
bool timer1;
|
||||
bool timer2;
|
||||
bool timer3;
|
||||
bool timer[4];
|
||||
bool serial;
|
||||
bool dma0;
|
||||
bool dma1;
|
||||
bool dma2;
|
||||
bool dma3;
|
||||
bool dma[4];
|
||||
bool keypad;
|
||||
bool cartridge;
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
struct Pending {
|
||||
struct DMA {
|
||||
bool vblank;
|
||||
bool hblank;
|
||||
bool hdma;
|
||||
} dma;
|
||||
} pending;
|
|
@ -13,7 +13,7 @@ namespace GBA {
|
|||
|
||||
/*
|
||||
bgba - Game Boy Advance emulator
|
||||
author: byuu
|
||||
authors: byuu, Cydrak
|
||||
license: GPLv3
|
||||
project started: 2012-03-19
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
void PPU::render_backgrounds() {
|
||||
if(regs.control.bgmode == 0) {
|
||||
render_background_linear(0);
|
||||
render_background_linear(1);
|
||||
render_background_linear(2);
|
||||
render_background_linear(3);
|
||||
}
|
||||
|
||||
if(regs.control.bgmode == 1) {
|
||||
render_background_linear(0);
|
||||
render_background_linear(1);
|
||||
//render_background_affine(2);
|
||||
}
|
||||
|
||||
if(regs.control.bgmode == 2) {
|
||||
//render_background_affine(2);
|
||||
//render_background_affine(3);
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_background_linear(unsigned bgnumber) {
|
||||
for(unsigned n = 0; n < 240; n++) pixel[bgnumber][n].exists = false;
|
||||
if(regs.control.enablebg[bgnumber] == false) return;
|
||||
|
||||
auto &bg = regs.bg[bgnumber];
|
||||
uint9 voffset = regs.vcounter + bg.voffset;
|
||||
uint9 hoffset = bg.hoffset;
|
||||
|
||||
unsigned basemap = bg.control.screenbaseblock << 11;
|
||||
unsigned basechr = bg.control.characterbaseblock << 14;
|
||||
unsigned px = hoffset & 7, py = voffset & 7;
|
||||
|
||||
Tile tile;
|
||||
uint8 data[8];
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
if(x == 0 || px & 8) {
|
||||
px &= 7;
|
||||
|
||||
unsigned tx = hoffset / 8, ty = voffset / 8;
|
||||
unsigned offset = (ty & 31) * 32 + (tx & 31);
|
||||
if(bg.control.screensize & 1) if(tx & 32) offset += 32 * 32;
|
||||
if(bg.control.screensize & 2) if(ty & 32) offset += 32 * 32 * (1 + (bg.control.screensize & 1));
|
||||
offset = basemap + offset * 2;
|
||||
uint16 mapdata = vram.read(offset, Half);
|
||||
|
||||
tile.character = mapdata >> 0;
|
||||
tile.hflip = mapdata >> 10;
|
||||
tile.vflip = mapdata >> 11;
|
||||
tile.palette = mapdata >> 12;
|
||||
|
||||
if(bg.control.colormode == 0) {
|
||||
offset = basechr + tile.character * 32 + (py ^ (tile.vflip ? 7 : 0)) * 4;
|
||||
uint32 word = vram.read(offset, Word);
|
||||
for(unsigned n = 0; n < 8; n++) data[n] = (word >> (n * 4)) & 15;
|
||||
} else {
|
||||
offset = basechr + tile.character * 64 + (py ^ (tile.vflip ? 7 : 0)) * 8;
|
||||
uint32 wordlo = vram.read(offset + 0, Word);
|
||||
uint32 wordhi = vram.read(offset + 4, Word);
|
||||
for(unsigned n = 0; n < 4; n++) data[0 + n] = (wordlo >> (n * 8)) & 255;
|
||||
for(unsigned n = 0; n < 4; n++) data[4 + n] = (wordhi >> (n * 8)) & 255;
|
||||
}
|
||||
}
|
||||
|
||||
hoffset++;
|
||||
uint8 color = data[px++ ^ (tile.hflip ? 7 : 0)];
|
||||
if(color == 0) continue; //transparent
|
||||
|
||||
if(bg.control.colormode == 0) pixel[bgnumber][x] = { true, palette(tile.palette * 16 + color), bg.control.priority };
|
||||
if(bg.control.colormode == 1) pixel[bgnumber][x] = { true, palette(color), bg.control.priority };
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
void PPU::render_objects() {
|
||||
for(unsigned n = 0; n < 240; n++) pixel[n].exists = false;
|
||||
for(unsigned n = 0; n < 240; n++) pixel[4][n].exists = false;
|
||||
if(regs.control.enableobj == false) return;
|
||||
|
||||
for(unsigned n = 0; n < 128; n++) {
|
||||
auto &obj = object[n];
|
||||
|
@ -72,8 +73,8 @@ void PPU::render_object_linear(Object &obj) {
|
|||
if(obj.colors == 0) color = (px & 1) ? color >> 4 : color & 15;
|
||||
if(color == 0) continue; //transparent
|
||||
|
||||
if(obj.colors == 0) pixel[sx] = { true, palette(256 + obj.palette * 16 + color), obj.priority };
|
||||
if(obj.colors == 1) pixel[sx] = { true, palette(256 + color), obj.priority };
|
||||
if(obj.colors == 0) pixel[4][sx] = { true, palette(256 + obj.palette * 16 + color), obj.priority };
|
||||
if(obj.colors == 1) pixel[4][sx] = { true, palette(256 + color), obj.priority };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
namespace GBA {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "background.cpp"
|
||||
#include "object.cpp"
|
||||
#include "screen.cpp"
|
||||
#include "mmio.cpp"
|
||||
|
@ -85,6 +86,7 @@ void PPU::scanline() {
|
|||
|
||||
if(regs.vcounter == 160) {
|
||||
if(regs.status.irqvblank) cpu.regs.irq.flag.vblank = 1;
|
||||
cpu.pending.dma.vblank = true;
|
||||
}
|
||||
|
||||
if(regs.status.irqvcoincidence) {
|
||||
|
@ -92,17 +94,21 @@ void PPU::scanline() {
|
|||
}
|
||||
|
||||
if(regs.vcounter < 160) {
|
||||
render_backgrounds();
|
||||
render_objects();
|
||||
render_screen();
|
||||
}
|
||||
|
||||
step(256 * 4);
|
||||
step(1024);
|
||||
regs.status.hblank = 1;
|
||||
if(regs.status.irqhblank) cpu.regs.irq.flag.hblank = 1;
|
||||
cpu.pending.dma.hblank = true;
|
||||
|
||||
step( 52 * 4);
|
||||
step(200);
|
||||
regs.status.hblank = 0;
|
||||
cpu.pending.dma.hdma = true;
|
||||
|
||||
step(8);
|
||||
if(++regs.vcounter == 228) regs.vcounter = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,9 @@ struct PPU : Thread, MMIO {
|
|||
uint8 read(uint32 addr);
|
||||
void write(uint32 addr, uint8 byte);
|
||||
|
||||
void render_backgrounds();
|
||||
void render_background_linear(unsigned bgnumber);
|
||||
|
||||
void render_objects();
|
||||
void render_object_linear(Object&);
|
||||
void render_object_affine(Object&);
|
||||
|
|
|
@ -9,7 +9,14 @@ void PPU::render_screen() {
|
|||
uint16 *line = output + regs.vcounter * 240;
|
||||
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
if(pixel[x].exists) line[x] = pixel[x].color;
|
||||
else line[x] = palette(0) & 0x7fff;
|
||||
uint15 color = palette(0) & 0x7fff;
|
||||
for(signed p = 3; p >= 0; p--) {
|
||||
if(pixel[3][x].exists && pixel[3][x].priority == p) color = pixel[3][x].color;
|
||||
if(pixel[2][x].exists && pixel[2][x].priority == p) color = pixel[2][x].color;
|
||||
if(pixel[1][x].exists && pixel[1][x].priority == p) color = pixel[1][x].color;
|
||||
if(pixel[0][x].exists && pixel[0][x].priority == p) color = pixel[0][x].color;
|
||||
if(pixel[4][x].exists && pixel[4][x].priority == p) color = pixel[4][x].color;
|
||||
}
|
||||
line[x] = color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ struct Pixel {
|
|||
bool exists;
|
||||
uint15 color;
|
||||
uint2 priority;
|
||||
} pixel[256];
|
||||
} pixel[5][256];
|
||||
|
||||
struct Object {
|
||||
uint8 y;
|
||||
|
@ -27,3 +27,10 @@ struct Object {
|
|||
unsigned width;
|
||||
unsigned height;
|
||||
} object[128];
|
||||
|
||||
struct Tile {
|
||||
uint10 character;
|
||||
uint1 hflip;
|
||||
uint1 vflip;
|
||||
uint4 palette;
|
||||
};
|
||||
|
|
|
@ -16,7 +16,6 @@ void ARM::power() {
|
|||
crash = false;
|
||||
r(15).modify = [&] {
|
||||
pipeline.reload = true;
|
||||
r(15).data &= cpsr().t ? ~1 : ~3;
|
||||
};
|
||||
|
||||
trace = false;
|
||||
|
|
|
@ -5,8 +5,8 @@ void ARM::arm_step() {
|
|||
pipeline.reload = false;
|
||||
r(15).data &= ~3;
|
||||
|
||||
pipeline.fetch.address = r(15);
|
||||
pipeline.fetch.instruction = read(r(15), Word);
|
||||
pipeline.fetch.address = r(15) & ~3;
|
||||
pipeline.fetch.instruction = read(pipeline.fetch.address, Word);
|
||||
|
||||
pipeline_step();
|
||||
step(2);
|
||||
|
|
|
@ -5,8 +5,8 @@ void ARM::thumb_step() {
|
|||
pipeline.reload = false;
|
||||
r(15).data &= ~1;
|
||||
|
||||
pipeline.fetch.address = r(15);
|
||||
pipeline.fetch.instruction = read(r(15), Half);
|
||||
pipeline.fetch.address = r(15) & ~1;
|
||||
pipeline.fetch.instruction = read(pipeline.fetch.address, Half);
|
||||
|
||||
pipeline_step();
|
||||
step(1);
|
||||
|
|
|
@ -66,12 +66,12 @@ void ARM::pipeline_step() {
|
|||
|
||||
if(cpsr().t == 0) {
|
||||
r(15).data += 4;
|
||||
pipeline.fetch.address = r(15);
|
||||
pipeline.fetch.instruction = read(r(15), Word);
|
||||
pipeline.fetch.address = r(15) & ~3;
|
||||
pipeline.fetch.instruction = read(pipeline.fetch.address, Word);
|
||||
} else {
|
||||
r(15).data += 2;
|
||||
pipeline.fetch.address = r(15);
|
||||
pipeline.fetch.instruction = read(r(15), Half);
|
||||
pipeline.fetch.address = r(15) & ~1;
|
||||
pipeline.fetch.instruction = read(pipeline.fetch.address, Half);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue