mirror of https://github.com/bsnes-emu/bsnes.git
Update to v087r23 release.
byuu says: Changelog: - fixed cascading timers and readouts (speed hit from 320fps to 240fps; would be 155fps with r20 timers) (fixes Spyro) - OBJ mode 3 acts like OBJ mode 2 now (may not be correct, but nobody has info on it) - added background + object vertical+horizontal mosaic in all modes (linear+affine+bitmap) - object mosaic uses sprite (0,0) for start coordinates, not screen (0,0) (again, nobody seems to have info on it) - BIOS cannot be read by r(15)>=0x02000000; returns last BIOS read instead (I can't believe games rely on this to work ... fixes SMA Mario Bros.) Mosaic is what concerns me the most, I've no idea if I'm doing it correctly. But anything is probably better than nothing, so there's that. I don't really notice the effect in Metroid Fusion. So either it's broken, or it's really subtle.
This commit is contained in:
parent
d423ae0a29
commit
28885db586
|
@ -1,7 +1,7 @@
|
|||
#ifndef BASE_HPP
|
||||
#define BASE_HPP
|
||||
|
||||
static const char Version[] = "087.22";
|
||||
static const char Version[] = "087.23";
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/algorithm.hpp>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
void CPU::timer_step(unsigned clocks) {
|
||||
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) {
|
||||
static unsigned mask[] = { 0, 63, 255, 1023 };
|
||||
if((regs.clock & mask[timer.control.frequency]) == 0) {
|
||||
timer_increment(n);
|
||||
timer.counter = timer.period();
|
||||
}
|
||||
}
|
||||
|
||||
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(timer.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();
|
||||
|
@ -21,3 +28,4 @@ void CPU::timer_increment(unsigned n) {
|
|||
timer_increment(n + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,43 +1,28 @@
|
|||
void PPU::render_objects() {
|
||||
if(regs.control.enable[OBJ] == false) return;
|
||||
for(unsigned n = 0; n < 128; n++) render_object(object[n]);
|
||||
}
|
||||
|
||||
for(unsigned n = 0; n < 128; n++) {
|
||||
auto &obj = object[n];
|
||||
//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(py >= obj.height << obj.affinesize) continue; //offscreen
|
||||
if(obj.affine == 0 && obj.affinesize == 1) continue; //hidden
|
||||
if(obj.affine == 0 && obj.affinesize == 1) return; //hidden
|
||||
if(py >= obj.height << obj.affinesize) return; //offscreen
|
||||
|
||||
if(obj.affine == 0) render_object_linear(obj);
|
||||
if(obj.affine == 1) render_object_affine(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_object_linear(Object &obj) {
|
||||
auto &output = layer[OBJ];
|
||||
uint8 py = regs.vcounter - obj.y;
|
||||
if(obj.vflip) py ^= obj.height - 1;
|
||||
|
||||
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] };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ struct Registers {
|
|||
|
||||
//internal
|
||||
int28 lx, ly;
|
||||
unsigned vmosaic;
|
||||
unsigned hmosaic;
|
||||
unsigned id;
|
||||
} bg[4];
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ struct Pixel {
|
|||
} layer[6][240];
|
||||
|
||||
bool windowmask[3][240];
|
||||
unsigned vmosaic[5];
|
||||
unsigned hmosaic[5];
|
||||
|
||||
struct Object {
|
||||
uint8 y;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue