diff --git a/bsnes/base/base.hpp b/bsnes/base/base.hpp index dadc3f5a..06f92f31 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.22"; +static const char Version[] = "087.23"; #include #include diff --git a/bsnes/gba/cpu/cpu.cpp b/bsnes/gba/cpu/cpu.cpp index a0dfa87f..47294b90 100755 --- a/bsnes/gba/cpu/cpu.cpp +++ b/bsnes/gba/cpu/cpu.cpp @@ -69,9 +69,9 @@ void CPU::power() { dma.control = 0; } for(auto &timer : regs.timer) { + timer.period = 0; timer.reload = 0; timer.control = 0; - timer.counter = 0; } regs.keypad.control = 0; regs.ime = 0; diff --git a/bsnes/gba/cpu/mmio.cpp b/bsnes/gba/cpu/mmio.cpp index 665f31a6..d4bcbc1b 100755 --- a/bsnes/gba/cpu/mmio.cpp +++ b/bsnes/gba/cpu/mmio.cpp @@ -26,7 +26,7 @@ uint8 CPU::read(uint32 addr) { case 0x0400010c: case 0x0400010d: { auto &timer = regs.timer[(addr >> 2) & 3]; unsigned shift = (addr & 1) * 8; - return timer.counter >> shift; + return timer.period >> shift; } //TIM0CNT_H @@ -178,9 +178,7 @@ void CPU::write(uint32 addr, uint8 byte) { bool enable = timer.control.enable; timer.control = byte; if(enable == 0 && timer.control.enable == 1) { - timer.counter = timer.period(); - } else if(timer.control.enable == 0) { - timer.counter = 0; + timer.period = timer.reload; } return; } diff --git a/bsnes/gba/cpu/registers.cpp b/bsnes/gba/cpu/registers.cpp index e9515da6..600588a5 100755 --- a/bsnes/gba/cpu/registers.cpp +++ b/bsnes/gba/cpu/registers.cpp @@ -23,11 +23,6 @@ uint16 CPU::Registers::DMAControl::operator=(uint16 source) { return operator uint16(); } -unsigned CPU::Registers::TimerControl::multiplier() const { - static unsigned multiplier[] = { 1, 64, 256, 1024 }; - return multiplier[frequency]; -} - CPU::Registers::TimerControl::operator uint8() const { return ( (frequency << 0) @@ -45,11 +40,6 @@ uint8 CPU::Registers::TimerControl::operator=(uint8 source) { return operator uint8(); } -//return number of clocks before counter overflow -signed CPU::Registers::Timer::period() const { - return (65536 - reload) * control.multiplier() + counter; -} - CPU::Registers::KeypadControl::operator uint16() const { return ( (a << 0) diff --git a/bsnes/gba/cpu/registers.hpp b/bsnes/gba/cpu/registers.hpp index e2f0ca15..bf439a82 100755 --- a/bsnes/gba/cpu/registers.hpp +++ b/bsnes/gba/cpu/registers.hpp @@ -34,19 +34,15 @@ struct Registers { uint1 irq; uint1 enable; - unsigned multiplier() const; operator uint8() const; uint8 operator=(uint8 source); TimerControl& operator=(const TimerControl&) = delete; }; struct Timer { + uint16 period; uint16 reload; TimerControl control; - - //internal - signed period() const; - signed counter; } timer[4]; struct KeypadControl { diff --git a/bsnes/gba/cpu/timer.cpp b/bsnes/gba/cpu/timer.cpp index e74543a7..dbb78ed6 100755 --- a/bsnes/gba/cpu/timer.cpp +++ b/bsnes/gba/cpu/timer.cpp @@ -1,23 +1,31 @@ void CPU::timer_step(unsigned clocks) { - for(unsigned n = 0; n < 4; n++) { - auto &timer = regs.timer[n]; - if(timer.control.enable == false || timer.control.cascade == true) continue; + for(unsigned c = 0; c < clocks; c++) { + for(unsigned n = 0; n < 4; n++) { + auto &timer = regs.timer[n]; + if(timer.control.enable == false || timer.control.cascade == true) continue; - timer.counter -= clocks; - while(timer.counter <= 0) { - timer_increment(n); - timer.counter = timer.period(); + static unsigned mask[] = { 0, 63, 255, 1023 }; + if((regs.clock & mask[timer.control.frequency]) == 0) { + timer_increment(n); + } } + + regs.clock++; } } void CPU::timer_increment(unsigned n) { - if(regs.timer[n].control.irq) regs.irq.flag.timer[n] = 1; + auto &timer = regs.timer[n]; + if(++timer.period == 0) { + timer.period = timer.reload; - if(apu.fifo[0].timer == n) apu.fifo[0].read(); - if(apu.fifo[1].timer == n) apu.fifo[1].read(); + if(timer.control.irq) regs.irq.flag.timer[n] = 1; - if(n < 3 && regs.timer[n + 1].control.enable && regs.timer[n + 1].control.cascade) { - timer_increment(n + 1); + if(apu.fifo[0].timer == n) apu.fifo[0].read(); + if(apu.fifo[1].timer == n) apu.fifo[1].read(); + + if(n < 3 && regs.timer[n + 1].control.enable && regs.timer[n + 1].control.cascade) { + timer_increment(n + 1); + } } } diff --git a/bsnes/gba/memory/memory.cpp b/bsnes/gba/memory/memory.cpp index 3ecec664..9e4a3c8a 100755 --- a/bsnes/gba/memory/memory.cpp +++ b/bsnes/gba/memory/memory.cpp @@ -96,8 +96,8 @@ uint32 Bus::read(uint32 addr, uint32 size) { 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 0x00000000: return bios.read(addr, size); + case 0x01000000: return bios.read(addr, size); case 0x02000000: return cpu.ewram.read(addr & 0x3ffff, size); case 0x03000000: return cpu.iwram.read(addr & 0x7fff, size); case 0x04000000: diff --git a/bsnes/gba/ppu/background.cpp b/bsnes/gba/ppu/background.cpp index 878cf0f5..44912342 100755 --- a/bsnes/gba/ppu/background.cpp +++ b/bsnes/gba/ppu/background.cpp @@ -25,7 +25,11 @@ void PPU::render_background_linear(Registers::Background &bg) { if(regs.control.enable[bg.id] == false) return; auto &output = layer[bg.id]; - uint9 voffset = regs.vcounter + bg.voffset; + if(bg.control.mosaic == false || (regs.vcounter % (1 + regs.mosaic.bgvsize)) == 0) { + bg.vmosaic = regs.vcounter; + } + + uint9 voffset = bg.vmosaic + bg.voffset; uint9 hoffset = bg.hoffset; unsigned basemap = bg.control.screenbaseblock << 11; @@ -83,8 +87,13 @@ void PPU::render_background_affine(Registers::Background &bg) { unsigned screensize = 16 << bg.control.screensize; unsigned screenwrap = (1 << (bg.control.affinewrap ? 7 + bg.control.screensize : 20)) - 1; - int28 fx = bg.lx; - int28 fy = bg.ly; + if(bg.control.mosaic == false || (regs.vcounter % (1 + regs.mosaic.bgvsize)) == 0) { + bg.hmosaic = bg.lx; + bg.vmosaic = bg.ly; + } + + int28 fx = bg.hmosaic; + int28 fy = bg.vmosaic; for(unsigned x = 0; x < 240; x++) { unsigned cx = (fx >> 8) & screenwrap, tx = cx / 8, px = cx & 7; @@ -115,8 +124,13 @@ void PPU::render_background_bitmap(Registers::Background &bg) { unsigned height = regs.control.bgmode == 5 ? 128 : 160; unsigned size = depth ? Half : Byte; - int28 fx = bg.lx; - int28 fy = bg.ly; + if(bg.control.mosaic == false || (regs.vcounter % (1 + regs.mosaic.bgvsize)) == 0) { + bg.hmosaic = bg.lx; + bg.vmosaic = bg.ly; + } + + int28 fx = bg.hmosaic; + int28 fy = bg.vmosaic; for(unsigned x = 0; x < 240; x++) { unsigned px = fx >> 8; diff --git a/bsnes/gba/ppu/object.cpp b/bsnes/gba/ppu/object.cpp index e029ffc7..82e5b376 100755 --- a/bsnes/gba/ppu/object.cpp +++ b/bsnes/gba/ppu/object.cpp @@ -1,43 +1,28 @@ void PPU::render_objects() { if(regs.control.enable[OBJ] == false) return; - - for(unsigned n = 0; n < 128; n++) { - auto &obj = object[n]; - uint8 py = regs.vcounter - obj.y; - 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); - if(obj.affine == 1) render_object_affine(obj); - } + for(unsigned n = 0; n < 128; n++) render_object(object[n]); } -void PPU::render_object_linear(Object &obj) { - auto &output = layer[OBJ]; +//px,py = pixel coordinates within sprite [0,0 - width,height) +//fx,fy = affine pixel coordinates +//pa,pb,pc,pd = affine pixel adjustments +//x,y = adjusted coordinates within sprite (linear = vflip/hflip, affine = rotation/zoom) +void PPU::render_object(Object &obj) { uint8 py = regs.vcounter - obj.y; - if(obj.vflip) py ^= obj.height - 1; + if(obj.affine == 0 && obj.affinesize == 1) return; //hidden + if(py >= obj.height << obj.affinesize) return; //offscreen + auto &output = layer[OBJ]; unsigned rowsize = regs.control.objmapping == 0 ? 32 >> obj.colors : obj.width / 8; unsigned baseaddr = 0x10000 + obj.character * 32; - uint9 sx = obj.x; - for(unsigned x = 0; x < obj.width; x++, sx++) { - unsigned px = x; - if(obj.hflip) px ^= obj.width - 1; - - if(sx < 240) { - render_object_pixel(obj, sx, px, py, rowsize, baseaddr); - } + if(obj.vflip && obj.affine == 0) { + py ^= obj.height - 1; } -} -void PPU::render_object_affine(Object &obj) { - auto &output = layer[OBJ]; - uint8 py = regs.vcounter - obj.y; - - unsigned rowsize = regs.control.objmapping == 0 ? 32 >> obj.colors : obj.width / 8; - unsigned baseaddr = 0x10000 + obj.character * 32; - uint9 sx = obj.x; + if(obj.mosaic && regs.mosaic.objvsize) { + py = (py / (1 + regs.mosaic.objvsize)) * (1 + regs.mosaic.objvsize); + } int16 pa = objectparam[obj.affineparam].pa; int16 pb = objectparam[obj.affineparam].pb; @@ -52,41 +37,43 @@ void PPU::render_object_affine(Object &obj) { int28 originx = -(centerx << obj.affinesize); int28 originy = -(centery << obj.affinesize) + py; + //fractional pixel coordinates int28 fx = originx * pa + originy * pb; int28 fy = originx * pc + originy * pd; - for(unsigned x = 0; x < (obj.width << obj.affinesize); x++, sx++) { - unsigned px = (fx >> 8) + centerx; - unsigned py = (fy >> 8) + centery; + for(unsigned px = 0; px < (obj.width << obj.affinesize); px++) { + unsigned x, y; + if(obj.affine == 0) { + x = px; + y = py; + if(obj.hflip) x ^= obj.width - 1; + } else { + x = (fx >> 8) + centerx; + y = (fy >> 8) + centery; + } - if(sx < 240 && px < obj.width && py < obj.height) { - render_object_pixel(obj, sx, px, py, rowsize, baseaddr); + if(obj.mosaic && regs.mosaic.objhsize) { + x = (x / (1 + regs.mosaic.objhsize)) * (1 + regs.mosaic.objhsize); + } + + unsigned ox = obj.x + px; + if(ox < 240 && x < obj.width && y < obj.height) { + unsigned offset = (y / 8) * rowsize + (x / 8); + offset = offset * 64 + (y & 7) * 8 + (x & 7); + + uint8 color = vram[baseaddr + (offset >> !obj.colors)]; + if(obj.colors == 0) color = (x & 1) ? color >> 4 : color & 15; + if(color) { + if(obj.mode & 2) { + windowmask[Obj][ox] = true; + } else if(output[ox].enable == false || obj.priority < output[ox].priority) { + if(obj.colors == 0) color = obj.palette * 16 + color; + output[ox] = { true, obj.mode == 1, obj.priority, pram[256 + color] }; + } + } } fx += pa; fy += pc; } } - -void PPU::render_object_pixel(Object &obj, unsigned x, unsigned px, unsigned py, unsigned rowsize, unsigned baseaddr) { - auto &output = layer[OBJ]; - - unsigned offset = (py / 8) * rowsize + (px / 8); - if(obj.colors == 0) offset = baseaddr + offset * 32 + (py & 7) * 4 + (px & 7) / 2; - if(obj.colors == 1) offset = baseaddr + offset * 64 + (py & 7) * 8 + (px & 7); - - uint8 color = vram[offset]; - if(obj.colors == 0) color = (px & 1) ? color >> 4 : color & 15; - - if(color == 0) return; //transparent - - if(obj.mode == 2) { - windowmask[Obj][x] = true; - return; - } - - if(output[x].enable == false || obj.priority < output[x].priority) { - if(obj.colors == 0) output[x] = { true, obj.mode == 1, obj.priority, pram[256 + obj.palette * 16 + color] }; - if(obj.colors == 1) output[x] = { true, obj.mode == 1, obj.priority, pram[256 + color] }; - } -} diff --git a/bsnes/gba/ppu/ppu.hpp b/bsnes/gba/ppu/ppu.hpp index 650d988a..f9597cef 100755 --- a/bsnes/gba/ppu/ppu.hpp +++ b/bsnes/gba/ppu/ppu.hpp @@ -32,12 +32,11 @@ struct PPU : Thread, MMIO { void render_background_bitmap(Registers::Background&); void render_objects(); - void render_object_linear(Object&); - void render_object_affine(Object&); - void render_object_pixel(Object&, unsigned x, unsigned px, unsigned py, unsigned rowsize, unsigned baseaddr); + void render_object(Object&); void render_forceblank(); void render_screen(); + void render_mosaic(unsigned id, unsigned width); void render_window(unsigned window); unsigned blend(unsigned above, unsigned eva, unsigned below, unsigned evb); diff --git a/bsnes/gba/ppu/registers.hpp b/bsnes/gba/ppu/registers.hpp index 7ce05b5e..c09c999e 100755 --- a/bsnes/gba/ppu/registers.hpp +++ b/bsnes/gba/ppu/registers.hpp @@ -60,6 +60,8 @@ struct Registers { //internal int28 lx, ly; + unsigned vmosaic; + unsigned hmosaic; unsigned id; } bg[4]; diff --git a/bsnes/gba/ppu/screen.cpp b/bsnes/gba/ppu/screen.cpp index 7867066f..af250b04 100755 --- a/bsnes/gba/ppu/screen.cpp +++ b/bsnes/gba/ppu/screen.cpp @@ -11,6 +11,11 @@ void PPU::render_screen() { uint16 *line = output + regs.vcounter * 240; uint16 *last = blur + regs.vcounter * 240; + if(regs.bg[0].control.mosaic) render_mosaic(BG0, regs.mosaic.bghsize); + if(regs.bg[1].control.mosaic) render_mosaic(BG1, regs.mosaic.bghsize); + if(regs.bg[2].control.mosaic) render_mosaic(BG2, regs.mosaic.bghsize); + if(regs.bg[3].control.mosaic) render_mosaic(BG3, regs.mosaic.bghsize); + for(unsigned x = 0; x < 240; x++) { Registers::WindowFlags flags; flags = ~0; //enable all layers if no windows are enabled @@ -58,6 +63,19 @@ void PPU::render_screen() { } } +void PPU::render_mosaic(unsigned id, unsigned width) { + if(++width == 1) return; + auto &buffer = layer[id]; + + for(unsigned x = 0; x < 240;) { + for(unsigned m = 1; m < width; m++) { + if(x + m >= 240) break; + buffer[x + m] = buffer[x]; + } + x += width; + } +} + void PPU::render_window(unsigned w) { unsigned y = regs.vcounter; diff --git a/bsnes/gba/ppu/state.hpp b/bsnes/gba/ppu/state.hpp index cbbb6350..9cfc9c0a 100755 --- a/bsnes/gba/ppu/state.hpp +++ b/bsnes/gba/ppu/state.hpp @@ -6,6 +6,8 @@ struct Pixel { } layer[6][240]; bool windowmask[3][240]; +unsigned vmosaic[5]; +unsigned hmosaic[5]; struct Object { uint8 y; diff --git a/bsnes/gba/system/bios.cpp b/bsnes/gba/system/bios.cpp new file mode 100755 index 00000000..b6b56781 --- /dev/null +++ b/bsnes/gba/system/bios.cpp @@ -0,0 +1,29 @@ +void BIOS::load(const uint8 *biosdata, unsigned biossize) { + memcpy(data, biosdata, min(size, biossize)); + + string sha256 = nall::sha256(data, size); + if(sha256 != "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570") { + interface->message("Warning: Game Boy Advance BIOS SHA256 sum is incorrect."); + } +} + +uint32 BIOS::read(uint32 addr, uint32 size) { + //GBA BIOS is read-protected; only the BIOS itself can read its own memory + //when accessed elsewhere; this returns the last value read by the BIOS program + if(cpu.r(15) >= 0x02000000) return mdr; + + if(size == Word) return mdr = read(addr &~ 2, Half) << 0 | read(addr | 2, Half) << 16; + if(size == Half) return mdr = read(addr &~ 1, Byte) << 0 | read(addr | 1, Byte) << 8; + return mdr = data[addr & 0x3fff]; +} + +void BIOS::write(uint32 addr, uint32 size, uint32 word) { +} + +BIOS::BIOS() { + data = new uint8[size = 16384](); +} + +BIOS::~BIOS() { + delete[] data; +} diff --git a/bsnes/gba/system/system.cpp b/bsnes/gba/system/system.cpp index 09cfdceb..05bcda01 100755 --- a/bsnes/gba/system/system.cpp +++ b/bsnes/gba/system/system.cpp @@ -2,21 +2,10 @@ namespace GBA { +#include "bios.cpp" +BIOS bios; System system; -void System::BIOS::load(const uint8_t *biosdata, unsigned biossize) { - memcpy(data, biosdata, min(size, biossize)); - - string sha256 = nall::sha256(data, size); - if(sha256 != "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570") { - interface->message("Warning: Game Boy Advance BIOS SHA256 sum is incorrect."); - } -} - -System::BIOS::BIOS() { - data = new uint8[size = 16384](); -} - void System::init() { } diff --git a/bsnes/gba/system/system.hpp b/bsnes/gba/system/system.hpp index 4315ccf7..6f89488c 100755 --- a/bsnes/gba/system/system.hpp +++ b/bsnes/gba/system/system.hpp @@ -2,16 +2,25 @@ enum class Input : unsigned { A, B, Select, Start, Right, Left, Up, Down, R, L, }; -struct System { - struct BIOS : StaticMemory { - void load(const uint8_t *data, unsigned size); - BIOS(); - } bios; +struct BIOS : Memory { + uint8 *data; + unsigned size; + uint32 mdr; + void load(const uint8 *data, unsigned size); + uint32 read(uint32 addr, uint32 size); + void write(uint32 addr, uint32 size, uint32 word); + + BIOS(); + ~BIOS(); +}; + +struct System { void init(); void term(); void power(); void run(); }; +extern BIOS bios; extern System system; diff --git a/bsnes/target-ui/interface/gba/gba.cpp b/bsnes/target-ui/interface/gba/gba.cpp index f8633f74..11ae4c98 100755 --- a/bsnes/target-ui/interface/gba/gba.cpp +++ b/bsnes/target-ui/interface/gba/gba.cpp @@ -41,7 +41,7 @@ bool InterfaceGBA::loadCartridge(const string &filename) { string markup; markup.readfile(interface->base.filename("manifest.xml", ".xml")); - GBA::system.bios.load(biosdata, biossize); + GBA::bios.load(biosdata, biossize); GBA::cartridge.load(markup, cartdata, cartsize); GBA::system.power(); delete[] biosdata;