2011-01-03 04:28:36 +00:00
|
|
|
#include <gameboy/gameboy.hpp>
|
2010-12-29 11:03:42 +00:00
|
|
|
|
|
|
|
#define LCD_CPP
|
|
|
|
namespace GameBoy {
|
|
|
|
|
|
|
|
#include "mmio/mmio.cpp"
|
2011-01-07 11:11:56 +00:00
|
|
|
#include "serialization.cpp"
|
2010-12-29 11:03:42 +00:00
|
|
|
LCD lcd;
|
|
|
|
|
2010-12-30 07:18:47 +00:00
|
|
|
void LCD::Main() {
|
|
|
|
lcd.main();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::main() {
|
|
|
|
while(true) {
|
2011-01-07 11:11:56 +00:00
|
|
|
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
|
|
|
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
|
|
|
}
|
|
|
|
|
2010-12-30 07:18:47 +00:00
|
|
|
add_clocks(4);
|
2011-08-18 13:58:27 +00:00
|
|
|
status.lx += 4;
|
|
|
|
if(status.lx >= 456) scanline();
|
2011-01-02 04:46:54 +00:00
|
|
|
|
2011-08-19 11:36:26 +00:00
|
|
|
if(status.display_enable && status.lx == 0) {
|
Update to v074r11 release.
byuu says:
Changelog:
- debugger compiles on all three profiles
- libsnes compiles on all three platforms (no API changes to libsnes)
- memory.cpp : namespace memory removed (wram -> cpu, apuram -> smp,
vram, oam, cgram -> ppu)
- sa1.cpp : namespace memory removed (SA-1 specific functions merged
inline to SA1::bus_read,write)
- GameBoy: added serial link support with interrupts and proper 8192hz
timing, but obviously it acts as if no other GB is connected to it
- GameBoy: added STAT OAM interrupt, and better STAT d1,d0 mode values
- UI: since Qt is dead, I've renamed the config files back to bsnes.cfg
and bsnes-geometry.cfg
- SA1: IRAM was not syncing to CPU on SA-1 side
- PPU/Accuracy and PPU/Performance needed Sprite oam renamed to Sprite
sprite; so that I could add uint8 oam[544]
- makes more sense anyway, OAM = object attribute memory, obj or
sprite are better names for Sprite rendering class
- more cleanup
2011-01-24 09:03:17 +00:00
|
|
|
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
|
|
|
}
|
|
|
|
|
2011-08-19 11:36:26 +00:00
|
|
|
if(status.display_enable && status.lx == 252) {
|
2011-01-02 04:46:54 +00:00
|
|
|
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
|
|
|
}
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::add_clocks(unsigned clocks) {
|
2011-02-02 10:37:31 +00:00
|
|
|
clock += clocks;
|
|
|
|
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
2011-01-07 11:11:56 +00:00
|
|
|
co_switch(scheduler.active_thread = cpu.thread);
|
|
|
|
}
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::scanline() {
|
|
|
|
status.lx -= 456;
|
2011-01-06 10:16:07 +00:00
|
|
|
if(++status.ly == 154) frame();
|
2010-12-30 07:18:47 +00:00
|
|
|
|
2011-08-19 11:36:26 +00:00
|
|
|
if(status.display_enable && status.interrupt_lyc == true) {
|
2011-01-02 04:46:54 +00:00
|
|
|
if(status.ly == status.lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
|
|
|
}
|
|
|
|
|
2011-01-06 10:16:07 +00:00
|
|
|
if(status.ly < 144) render();
|
|
|
|
|
2011-08-19 11:36:26 +00:00
|
|
|
if(status.display_enable && status.ly == 144) {
|
2011-01-02 04:46:54 +00:00
|
|
|
cpu.interrupt_raise(CPU::Interrupt::Vblank);
|
|
|
|
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
|
|
|
}
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::frame() {
|
|
|
|
system.interface->video_refresh(screen);
|
|
|
|
system.interface->input_poll();
|
2010-12-31 05:43:47 +00:00
|
|
|
cpu.mmio_joyp_poll();
|
2010-12-30 07:18:47 +00:00
|
|
|
|
|
|
|
status.ly = 0;
|
2011-01-07 11:11:56 +00:00
|
|
|
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::render() {
|
2011-08-18 13:58:27 +00:00
|
|
|
for(unsigned n = 0; n < 160; n++) {
|
|
|
|
line[n] = 0x00;
|
|
|
|
origin[n] = Origin::None;
|
|
|
|
}
|
2011-01-02 04:46:54 +00:00
|
|
|
|
|
|
|
if(status.display_enable == true) {
|
|
|
|
if(status.bg_enable == true) render_bg();
|
|
|
|
if(status.window_display_enable == true) render_window();
|
2011-01-03 04:28:36 +00:00
|
|
|
if(status.obj_enable == true) render_obj();
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
|
|
|
|
2010-12-30 07:18:47 +00:00
|
|
|
uint8_t *output = screen + status.ly * 160;
|
2011-01-03 04:28:36 +00:00
|
|
|
for(unsigned n = 0; n < 160; n++) output[n] = (3 - line[n]) * 0x55;
|
2011-05-08 13:46:37 +00:00
|
|
|
system.interface->lcd_scanline();
|
2011-01-03 04:28:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint16 LCD::read_tile(bool select, unsigned x, unsigned y) {
|
|
|
|
unsigned tmaddr = 0x1800 + (select << 10), tdaddr;
|
|
|
|
tmaddr += (((y >> 3) << 5) + (x >> 3)) & 0x03ff;
|
|
|
|
if(status.bg_tiledata_select == 0) {
|
|
|
|
tdaddr = 0x1000 + ((int8)vram[tmaddr] << 4);
|
|
|
|
} else {
|
|
|
|
tdaddr = 0x0000 + (vram[tmaddr] << 4);
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
2011-01-03 04:28:36 +00:00
|
|
|
tdaddr += (y & 7) << 1;
|
|
|
|
return (vram[tdaddr + 0] << 0) | (vram[tdaddr + 1] << 8);
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::render_bg() {
|
|
|
|
unsigned iy = (status.ly + status.scy) & 255;
|
2011-01-03 04:28:36 +00:00
|
|
|
unsigned ix = status.scx, tx = ix & 7;
|
2011-01-04 10:42:27 +00:00
|
|
|
unsigned data = read_tile(status.bg_tilemap_select, ix, iy);
|
2011-01-02 04:46:54 +00:00
|
|
|
|
2011-01-03 04:28:36 +00:00
|
|
|
for(unsigned ox = 0; ox < 160; ox++) {
|
2011-01-04 10:42:27 +00:00
|
|
|
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
|
|
|
|
| ((data & (0x8000 >> tx)) ? 2 : 0);
|
2011-08-18 13:58:27 +00:00
|
|
|
|
2011-01-03 04:28:36 +00:00
|
|
|
line[ox] = status.bgp[palette];
|
2011-08-18 13:58:27 +00:00
|
|
|
origin[ox] = Origin::BG;
|
2011-01-02 04:46:54 +00:00
|
|
|
|
|
|
|
ix = (ix + 1) & 255;
|
|
|
|
tx = (tx + 1) & 7;
|
2011-01-03 04:28:36 +00:00
|
|
|
|
|
|
|
if(tx == 0) data = read_tile(status.bg_tilemap_select, ix, iy);
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::render_window() {
|
2011-01-03 04:28:36 +00:00
|
|
|
if(status.ly - status.wy >= 144U) return;
|
|
|
|
unsigned iy = status.ly - status.wy;
|
2011-08-18 13:58:27 +00:00
|
|
|
unsigned ix = (7 - status.wx) & 255, tx = ix & 7;
|
2011-01-04 10:42:27 +00:00
|
|
|
unsigned data = read_tile(status.window_tilemap_select, ix, iy);
|
2011-01-03 04:28:36 +00:00
|
|
|
|
|
|
|
for(unsigned ox = 0; ox < 160; ox++) {
|
2011-01-04 10:42:27 +00:00
|
|
|
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
|
|
|
|
| ((data & (0x8000 >> tx)) ? 2 : 0);
|
2011-08-18 13:58:27 +00:00
|
|
|
if(ox - (status.wx - 7) < 160U) {
|
|
|
|
line[ox] = status.bgp[palette];
|
|
|
|
origin[ox] = Origin::Window;
|
|
|
|
}
|
2011-01-02 04:46:54 +00:00
|
|
|
|
|
|
|
ix = (ix + 1) & 255;
|
|
|
|
tx = (tx + 1) & 7;
|
2011-01-03 04:28:36 +00:00
|
|
|
|
|
|
|
if(tx == 0) data = read_tile(status.window_tilemap_select, ix, iy);
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LCD::render_obj() {
|
2011-08-18 13:58:27 +00:00
|
|
|
enum : unsigned { Priority = 0x80, YFlip = 0x40, XFlip = 0x20, Palette = 0x10 };
|
|
|
|
|
2011-01-02 04:46:54 +00:00
|
|
|
unsigned obj_size = (status.obj_size == 0 ? 8 : 16);
|
2010-12-31 05:43:47 +00:00
|
|
|
|
2011-01-04 10:42:27 +00:00
|
|
|
unsigned sprite[10], sprites = 0;
|
|
|
|
|
|
|
|
//find first ten sprites on this scanline
|
2010-12-31 05:43:47 +00:00
|
|
|
for(unsigned s = 0; s < 40; s++) {
|
2011-01-02 04:46:54 +00:00
|
|
|
unsigned sy = oam[(s << 2) + 0] - 16;
|
|
|
|
unsigned sx = oam[(s << 2) + 1] - 8;
|
2010-12-31 05:43:47 +00:00
|
|
|
|
2011-01-02 04:46:54 +00:00
|
|
|
sy = status.ly - sy;
|
|
|
|
if(sy >= obj_size) continue;
|
2010-12-31 05:43:47 +00:00
|
|
|
|
2011-01-04 10:42:27 +00:00
|
|
|
sprite[sprites++] = s;
|
|
|
|
if(sprites == 10) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
//sort by X-coordinate, when equal, lower address comes first
|
|
|
|
for(unsigned x = 0; x < sprites; x++) {
|
|
|
|
for(unsigned y = x + 1; y < sprites; y++) {
|
|
|
|
signed sx = oam[(sprite[x] << 2) + 1] - 8;
|
|
|
|
signed sy = oam[(sprite[y] << 2) + 1] - 8;
|
|
|
|
if(sy < sx) {
|
|
|
|
sprite[x] ^= sprite[y];
|
|
|
|
sprite[y] ^= sprite[x];
|
|
|
|
sprite[x] ^= sprite[y];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//render backwards, so that first sprite has highest priority
|
|
|
|
for(signed s = sprites - 1; s >= 0; s--) {
|
|
|
|
unsigned n = sprite[s] << 2;
|
|
|
|
unsigned sy = oam[n + 0] - 16;
|
|
|
|
unsigned sx = oam[n + 1] - 8;
|
|
|
|
unsigned tile = oam[n + 2];
|
|
|
|
unsigned attribute = oam[n + 3];
|
|
|
|
|
|
|
|
sy = status.ly - sy;
|
|
|
|
if(sy >= obj_size) continue;
|
2011-08-18 13:58:27 +00:00
|
|
|
if(attribute & YFlip) sy ^= (obj_size - 1);
|
2010-12-31 05:43:47 +00:00
|
|
|
|
2011-01-04 10:42:27 +00:00
|
|
|
unsigned tdaddr = (tile << 4) + (sy << 1);
|
2011-01-02 04:46:54 +00:00
|
|
|
uint8 d0 = vram[tdaddr + 0];
|
|
|
|
uint8 d1 = vram[tdaddr + 1];
|
2011-08-18 13:58:27 +00:00
|
|
|
unsigned xflip = attribute & XFlip ? 7 : 0;
|
2010-12-31 05:43:47 +00:00
|
|
|
|
2011-01-02 04:46:54 +00:00
|
|
|
for(unsigned tx = 0; tx < 8; tx++) {
|
2011-01-04 10:42:27 +00:00
|
|
|
uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0)
|
|
|
|
| ((d1 & (0x80 >> tx)) ? 2 : 0);
|
2010-12-31 05:43:47 +00:00
|
|
|
if(palette == 0) continue;
|
2011-01-02 04:46:54 +00:00
|
|
|
|
2011-08-18 13:58:27 +00:00
|
|
|
palette = status.obp[(bool)(attribute & Palette)][palette];
|
2011-01-02 04:46:54 +00:00
|
|
|
unsigned ox = sx + (tx ^ xflip);
|
|
|
|
|
|
|
|
if(ox <= 159) {
|
2011-08-18 13:58:27 +00:00
|
|
|
if(attribute & Priority) {
|
|
|
|
if(origin[ox] == Origin::BG || origin[ox] == Origin::Window) {
|
|
|
|
if(line[ox] > 0) continue;
|
|
|
|
}
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
2011-01-03 04:28:36 +00:00
|
|
|
line[ox] = palette;
|
2011-08-18 13:58:27 +00:00
|
|
|
origin[ox] = Origin::OBJ;
|
2011-01-02 04:46:54 +00:00
|
|
|
}
|
2010-12-31 05:43:47 +00:00
|
|
|
}
|
|
|
|
}
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
|
|
|
|
2010-12-29 11:03:42 +00:00
|
|
|
void LCD::power() {
|
Update to v075r06 release.
byuu says:
Removed the floating-point volume adjustments from the wave channel and
the left/right speaker mixers. Also, against my better judgment I'm
backing out of left/right computation when they are both turned off.
This basically makes non-stereo games run faster, but will make stereo
games appear to run slower. I don't like it when end-users experience
mystery slowdowns.
Anyway, it appears that the audio calculation is really fucking
demanding. Knocks FPS from 800 down to 300. I thought it might be libco,
so I took it out and it only went up to 305fps o.O
There is also some sort of problem with bsnes/Super Game Boy audio. The
latency is really great when you first start, but it seems to drift
apart over time until it is well over 500ms, and then it either pops or
fades back to very low, sub-50ms latency again. The way I handle mixing
is that the coprocessor audio samples go into a resampler to the native
SNES rate, and fed to an output buffer. SNES audio samples go there
untouched. When there is a sample in each, I add them together and
average the result (I still don't understand why we divide by two since
these are signed integers), and output it immediately. It's just-in-time
sampling, so as long as DSP v Coprocessor do not drift very far, it
should have very low latency. And I make the CPU sync DSP and
Coprocessor once per scanline, which is something like 15 samples or so.
2011-02-03 11:17:35 +00:00
|
|
|
create(Main, 4194304);
|
2011-01-03 04:28:36 +00:00
|
|
|
|
2010-12-30 07:18:47 +00:00
|
|
|
for(unsigned n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM
|
|
|
|
for(unsigned n = 0xff40; n <= 0xff4b; n++) bus.mmio[n] = this; //MMIO
|
2010-12-29 11:03:42 +00:00
|
|
|
for(unsigned n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM
|
|
|
|
|
2010-12-30 07:18:47 +00:00
|
|
|
for(unsigned n = 0; n < 8192; n++) vram[n] = 0x00;
|
|
|
|
for(unsigned n = 0; n < 160; n++) oam [n] = 0x00;
|
|
|
|
|
|
|
|
for(unsigned n = 0; n < 160 * 144; n++) screen[n] = 0x00;
|
|
|
|
|
|
|
|
status.lx = 0;
|
|
|
|
|
|
|
|
status.display_enable = 0;
|
|
|
|
status.window_tilemap_select = 0;
|
|
|
|
status.window_display_enable = 0;
|
|
|
|
status.bg_tiledata_select = 0;
|
|
|
|
status.bg_tilemap_select = 0;
|
|
|
|
status.obj_size = 0;
|
|
|
|
status.obj_enable = 0;
|
2011-01-02 04:46:54 +00:00
|
|
|
status.bg_enable = 0;
|
2010-12-30 07:18:47 +00:00
|
|
|
|
|
|
|
status.interrupt_lyc = 0;
|
|
|
|
status.interrupt_oam = 0;
|
|
|
|
status.interrupt_vblank = 0;
|
|
|
|
status.interrupt_hblank = 0;
|
|
|
|
|
|
|
|
status.scy = 0;
|
|
|
|
status.scx = 0;
|
2010-12-29 11:03:42 +00:00
|
|
|
status.ly = 0;
|
2010-12-30 07:18:47 +00:00
|
|
|
status.lyc = 0;
|
|
|
|
|
|
|
|
for(unsigned n = 0; n < 4; n++) {
|
|
|
|
status.bgp[n] = n;
|
2010-12-31 05:43:47 +00:00
|
|
|
status.obp[0][n] = n;
|
|
|
|
status.obp[1][n] = n;
|
2010-12-30 07:18:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
status.wy = 0;
|
|
|
|
status.wx = 0;
|
2010-12-29 11:03:42 +00:00
|
|
|
}
|
|
|
|
|
2011-01-07 11:11:56 +00:00
|
|
|
LCD::LCD() {
|
|
|
|
}
|
|
|
|
|
2010-12-29 11:03:42 +00:00
|
|
|
}
|