From b8d0ec29b28a18def95948f98040bd19da3333a4 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Wed, 4 Apr 2012 09:50:40 +1000 Subject: [PATCH] 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.) --- bsnes/base/base.hpp | 2 +- bsnes/gba/cpu/cpu.cpp | 6 ++ bsnes/gba/cpu/cpu.hpp | 4 ++ bsnes/gba/cpu/dma.cpp | 45 ++++++++++++++ bsnes/gba/cpu/registers.cpp | 36 +++++------ bsnes/gba/cpu/registers.hpp | 15 ++--- bsnes/gba/cpu/state.hpp | 7 +++ bsnes/gba/gba.hpp | 2 +- bsnes/gba/ppu/background.cpp | 72 ++++++++++++++++++++++ bsnes/gba/ppu/object.cpp | 7 ++- bsnes/gba/ppu/ppu.cpp | 10 ++- bsnes/gba/ppu/ppu.hpp | 3 + bsnes/gba/ppu/screen.cpp | 11 +++- bsnes/gba/ppu/state.hpp | 9 ++- bsnes/processor/arm/arm.cpp | 1 - bsnes/processor/arm/instructions-arm.cpp | 4 +- bsnes/processor/arm/instructions-thumb.cpp | 4 +- bsnes/processor/arm/registers.cpp | 8 +-- 18 files changed, 200 insertions(+), 46 deletions(-) create mode 100755 bsnes/gba/cpu/dma.cpp create mode 100755 bsnes/gba/cpu/state.hpp create mode 100755 bsnes/gba/ppu/background.cpp diff --git a/bsnes/base/base.hpp b/bsnes/base/base.hpp index aa168cdc4..cb1edb921 100755 --- a/bsnes/base/base.hpp +++ b/bsnes/base/base.hpp @@ -1,7 +1,7 @@ #ifndef BASE_HPP #define BASE_HPP -static const char Version[] = "087.15"; +static const char Version[] = "087.16"; #include #include diff --git a/bsnes/gba/cpu/cpu.cpp b/bsnes/gba/cpu/cpu.cpp index ecd212f74..909bdeace 100755 --- a/bsnes/gba/cpu/cpu.cpp +++ b/bsnes/gba/cpu/cpu.cpp @@ -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 diff --git a/bsnes/gba/cpu/cpu.hpp b/bsnes/gba/cpu/cpu.hpp index 3fb53966a..83fb0968d 100755 --- a/bsnes/gba/cpu/cpu.hpp +++ b/bsnes/gba/cpu/cpu.hpp @@ -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(); }; diff --git a/bsnes/gba/cpu/dma.cpp b/bsnes/gba/cpu/dma.cpp new file mode 100755 index 000000000..d4b5b1f35 --- /dev/null +++ b/bsnes/gba/cpu/dma.cpp @@ -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; +} diff --git a/bsnes/gba/cpu/registers.cpp b/bsnes/gba/cpu/registers.cpp index e37aa2693..40150c01b 100755 --- a/bsnes/gba/cpu/registers.cpp +++ b/bsnes/gba/cpu/registers.cpp @@ -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(); diff --git a/bsnes/gba/cpu/registers.hpp b/bsnes/gba/cpu/registers.hpp index eb5db46d6..21f3a7baa 100755 --- a/bsnes/gba/cpu/registers.hpp +++ b/bsnes/gba/cpu/registers.hpp @@ -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; diff --git a/bsnes/gba/cpu/state.hpp b/bsnes/gba/cpu/state.hpp new file mode 100755 index 000000000..4926a3d85 --- /dev/null +++ b/bsnes/gba/cpu/state.hpp @@ -0,0 +1,7 @@ +struct Pending { + struct DMA { + bool vblank; + bool hblank; + bool hdma; + } dma; +} pending; diff --git a/bsnes/gba/gba.hpp b/bsnes/gba/gba.hpp index 36788a54e..77f5db26c 100755 --- a/bsnes/gba/gba.hpp +++ b/bsnes/gba/gba.hpp @@ -13,7 +13,7 @@ namespace GBA { /* bgba - Game Boy Advance emulator - author: byuu + authors: byuu, Cydrak license: GPLv3 project started: 2012-03-19 */ diff --git a/bsnes/gba/ppu/background.cpp b/bsnes/gba/ppu/background.cpp new file mode 100755 index 000000000..3a8af01ff --- /dev/null +++ b/bsnes/gba/ppu/background.cpp @@ -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 }; + } +} diff --git a/bsnes/gba/ppu/object.cpp b/bsnes/gba/ppu/object.cpp index 71493a486..3164f005b 100755 --- a/bsnes/gba/ppu/object.cpp +++ b/bsnes/gba/ppu/object.cpp @@ -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 }; } } diff --git a/bsnes/gba/ppu/ppu.cpp b/bsnes/gba/ppu/ppu.cpp index 1ad26b580..585445ef8 100755 --- a/bsnes/gba/ppu/ppu.cpp +++ b/bsnes/gba/ppu/ppu.cpp @@ -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; } diff --git a/bsnes/gba/ppu/ppu.hpp b/bsnes/gba/ppu/ppu.hpp index 36f1c18e1..6dd6bc26c 100755 --- a/bsnes/gba/ppu/ppu.hpp +++ b/bsnes/gba/ppu/ppu.hpp @@ -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&); diff --git a/bsnes/gba/ppu/screen.cpp b/bsnes/gba/ppu/screen.cpp index 1a51b64c1..80543e3d4 100755 --- a/bsnes/gba/ppu/screen.cpp +++ b/bsnes/gba/ppu/screen.cpp @@ -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; } } diff --git a/bsnes/gba/ppu/state.hpp b/bsnes/gba/ppu/state.hpp index 1234accb2..372573200 100755 --- a/bsnes/gba/ppu/state.hpp +++ b/bsnes/gba/ppu/state.hpp @@ -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; +}; diff --git a/bsnes/processor/arm/arm.cpp b/bsnes/processor/arm/arm.cpp index 49844bcd7..1a9a35f15 100755 --- a/bsnes/processor/arm/arm.cpp +++ b/bsnes/processor/arm/arm.cpp @@ -16,7 +16,6 @@ void ARM::power() { crash = false; r(15).modify = [&] { pipeline.reload = true; - r(15).data &= cpsr().t ? ~1 : ~3; }; trace = false; diff --git a/bsnes/processor/arm/instructions-arm.cpp b/bsnes/processor/arm/instructions-arm.cpp index 0d31db6a7..9807dda2f 100755 --- a/bsnes/processor/arm/instructions-arm.cpp +++ b/bsnes/processor/arm/instructions-arm.cpp @@ -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); diff --git a/bsnes/processor/arm/instructions-thumb.cpp b/bsnes/processor/arm/instructions-thumb.cpp index c7320a4a1..db9e4f898 100755 --- a/bsnes/processor/arm/instructions-thumb.cpp +++ b/bsnes/processor/arm/instructions-thumb.cpp @@ -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); diff --git a/bsnes/processor/arm/registers.cpp b/bsnes/processor/arm/registers.cpp index 183a77ef8..9292be3fb 100755 --- a/bsnes/processor/arm/registers.cpp +++ b/bsnes/processor/arm/registers.cpp @@ -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); } }