Update to v083r06 release.

byuu says:

All cores: Video classes have internal->{RGB30,24,16,15} palette
generation support
All cores: video output is now RGB24, all filters except HQ2x were
updated to reflect this (HQ2x will be very hard)
NES: MMC5 CHR mapping fixes (Bandit Kings, RTK2, Uchuu Keibitai SDF)
[Cydrak]
NES: MMC5 vertical split screen support (Uchuu Keibitai SDF) [Cydrak]
Game Boy + Game Boy Color: fixed a potential freezing bug when loading
save states (re-create cothreads on state load; was implied when using
SGB mode.)
Game Boy Color: fixed freezing bug with Zelda: LA opening (SVBK is
readable.)
Game Boy Color: more accurate colors (better than GiiBii, probably worse
than KiGB)
SNES: luminance of zero is no longer pure black, as on real hardware.
This is possible thanks to using RGB888 output now.

The current major problems I'd like to solve:
- Zelda: Link's Awakening music when Link first wakes up in the house is
  atrociously bad
- Shin Megami Tensei: Devil Children - White Book (Shiro no Sho) plays
  music at 50% speed; yet Black Book (Kuro no Sho) does not ... one of
  my favorite games, so it'd be great to fix it
This commit is contained in:
Tim Allen 2011-10-28 00:30:19 +11:00
parent 118a393c4c
commit aaffd000a4
29 changed files with 397 additions and 210 deletions

View File

@ -1,7 +1,7 @@
gameboy_objects := gameboy-interface gameboy-system gameboy-scheduler
gameboy_objects += gameboy-memory gameboy-cartridge
gameboy_objects += gameboy-cpu gameboy-apu gameboy-lcd
gameboy_objects += gameboy-cheat
gameboy_objects += gameboy-cheat gameboy-video
objects += $(gameboy_objects)
obj/gameboy-interface.o: $(gameboy)/interface/interface.cpp $(call rwildcard,$(gameboy)/interface/)
@ -13,3 +13,4 @@ obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/)
obj/gameboy-apu.o: $(gameboy)/apu/apu.cpp $(call rwildcard,$(gameboy)/apu/)
obj/gameboy-lcd.o: $(gameboy)/lcd/lcd.cpp $(call rwildcard,$(gameboy)/lcd/)
obj/gameboy-cheat.o: $(gameboy)/cheat/cheat.cpp $(call rwildcard,$(gameboy)/cheat/)
obj/gameboy-video.o: $(gameboy)/video/video.cpp $(call rwildcard,$(gameboy)/video/)

View File

@ -47,14 +47,12 @@ void APU::main() {
master.run();
interface->audioSample(master.center, master.left, master.right);
clock += 2;
if(clock >= 0) co_switch(scheduler.active_thread = cpu.thread);
if(++clock >= 0) co_switch(scheduler.active_thread = cpu.thread);
}
}
void APU::power() {
create(Main, 8 * 1024 * 1024);
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
for(auto &n : mmio_data) n = 0x00;

View File

@ -94,7 +94,7 @@ void CPU::interrupt_exec(uint16 pc) {
}
void CPU::power() {
create(Main, 8 * 1024 * 1024);
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
for(unsigned n = 0xe000; n <= 0xfdff; n++) bus.mmio[n] = this; //WRAM (mirror)

View File

@ -3,7 +3,8 @@
unsigned CPU::wram_addr(uint16 addr) const {
addr &= 0x1fff;
if(addr < 0x1000) return addr;
return (status.wram_bank * 0x1000) + (addr & 0x0fff);
auto bank = status.wram_bank + (status.wram_bank == 0);
return (bank * 0x1000) + (addr & 0x0fff);
}
void CPU::mmio_joyp_poll() {
@ -82,13 +83,37 @@ uint8 CPU::mmio_read(uint16 addr) {
return 0x02;
}
if(addr == 0xff6c) return 0xfe | status.ff6c;
if(addr == 0xff72) return status.ff72;
if(addr == 0xff73) return status.ff73;
if(addr == 0xff74) return status.ff74;
if(addr == 0xff75) return 0x8f | status.ff75;
if(addr == 0xff76) return 0x00;
if(addr == 0xff77) return 0x00;
if(addr == 0xff6c) { //???
return 0xfe | status.ff6c;
}
if(addr == 0xff70) { //SVBK
return status.wram_bank;
}
if(addr == 0xff72) { //???
return status.ff72;
}
if(addr == 0xff73) { //???
return status.ff73;
}
if(addr == 0xff74) { //???
return status.ff74;
}
if(addr == 0xff75) { //???
return 0x8f | status.ff75;
}
if(addr == 0xff76) { //???
return 0x00;
}
if(addr == 0xff77) { //???
return 0x00;
}
if(addr == 0xffff) { //IE
return (status.interrupt_enable_joypad << 4)
@ -158,7 +183,7 @@ void CPU::mmio_write(uint16 addr, uint8 data) {
if(addr == 0xff46) { //DMA
for(unsigned n = 0x00; n <= 0x9f; n++) {
bus.write(0xfe00 + n, bus.read((data << 8) + n));
add_clocks(8);
add_clocks(4);
}
return;
}
@ -194,7 +219,7 @@ void CPU::mmio_write(uint16 addr, uint8 data) {
if(status.dma_mode == 0) do {
bus.write(status.dma_target++, bus.read(status.dma_source++));
add_clocks(8);
add_clocks(4);
} while(--status.dma_length);
return;
}
@ -230,7 +255,6 @@ void CPU::mmio_write(uint16 addr, uint8 data) {
if(addr == 0xff70) { //SVBK
status.wram_bank = data & 0x07;
if(status.wram_bank == 0) status.wram_bank = 1;
return;
}

View File

@ -2,20 +2,20 @@
void CPU::op_io() {
cycle_edge();
add_clocks(8 >> status.speed_double);
add_clocks(4 >> status.speed_double);
}
uint8 CPU::op_read(uint16 addr) {
cycle_edge();
uint8 r = bus.read(addr);
add_clocks(8 >> status.speed_double);
add_clocks(4 >> status.speed_double);
return r;
}
void CPU::op_write(uint16 addr, uint8 data) {
cycle_edge();
bus.write(addr, data);
add_clocks(8 >> status.speed_double);
add_clocks(4 >> status.speed_double);
}
void CPU::cycle_edge() {

View File

@ -11,17 +11,17 @@ void CPU::add_clocks(unsigned clocks) {
scheduler.exit(Scheduler::ExitReason::StepEvent);
status.clock += clocks;
if(status.clock >= 8 * 1024 * 1024) {
status.clock -= 8 * 1024 * 1024;
if(status.clock >= 4 * 1024 * 1024) {
status.clock -= 4 * 1024 * 1024;
cartridge.mbc3.second();
}
//8MHz / N(hz) - 1 = mask
if((status.clock & 31) == 0) timer_262144hz();
if((status.clock & 127) == 0) timer_65536hz();
if((status.clock & 511) == 0) timer_16384hz();
if((status.clock & 1023) == 0) timer_8192hz();
if((status.clock & 2047) == 0) timer_4096hz();
//4MHz / N(hz) - 1 = mask
if((status.clock & 15) == 0) timer_262144hz();
if((status.clock & 63) == 0) timer_65536hz();
if((status.clock & 255) == 0) timer_16384hz();
if((status.clock & 511) == 0) timer_8192hz();
if((status.clock & 1023) == 0) timer_4096hz();
lcd.clock -= clocks;
if(lcd.clock <= 0) co_switch(scheduler.active_thread = lcd.thread);
@ -81,7 +81,7 @@ void CPU::hblank() {
if(status.dma_mode == 1 && status.dma_length) {
for(unsigned n = 0; n < 16; n++) {
bus.write(status.dma_target++, bus.read(status.dma_source++));
add_clocks(8);
add_clocks(4);
}
status.dma_length -= 16;
}

View File

@ -108,6 +108,7 @@ namespace GameBoy {
#include <gameboy/apu/apu.hpp>
#include <gameboy/lcd/lcd.hpp>
#include <gameboy/cheat/cheat.hpp>
#include <gameboy/video/video.hpp>
};
#endif

View File

@ -25,7 +25,7 @@ void LCD::main() {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
add_clocks(8);
add_clocks(4);
status.lx += 4;
if(status.lx >= 456) scanline();
@ -82,7 +82,7 @@ unsigned LCD::hflip(unsigned data) const {
}
void LCD::power() {
create(Main, 8 * 1024 * 1024);
create(Main, 4 * 1024 * 1024);
for(unsigned n = 0x8000; n <= 0x9fff; n++) bus.mmio[n] = this; //VRAM
for(unsigned n = 0xfe00; n <= 0xfe9f; n++) bus.mmio[n] = this; //OAM

View File

@ -29,6 +29,7 @@ bool System::unserialize(serializer &s) {
if(version != Info::SerializerVersion) return false;
//if(crc32 != 0) return false;
power();
serialize_all(s);
return true;
}

82
bsnes/gameboy/video/video.cpp Executable file
View File

@ -0,0 +1,82 @@
#include <gameboy/gameboy.hpp>
#define VIDEO_CPP
namespace GameBoy {
Video video;
unsigned Video::palette_dmg(unsigned color) const {
unsigned R = monochrome[color][0] * 1023.0;
unsigned G = monochrome[color][1] * 1023.0;
unsigned B = monochrome[color][2] * 1023.0;
return (R << 20) + (G << 10) + (B << 0);
}
unsigned Video::palette_sgb(unsigned color) const {
unsigned R = (3 - color) * 341;
unsigned G = (3 - color) * 341;
unsigned B = (3 - color) * 341;
return (R << 20) + (G << 10) + (B << 0);
}
unsigned Video::palette_cgb(unsigned color) const {
unsigned r = (color >> 0) & 31;
unsigned g = (color >> 5) & 31;
unsigned b = (color >> 10) & 31;
unsigned R = (r * 26 + g * 4 + b * 2);
unsigned G = ( g * 24 + b * 8);
unsigned B = (r * 6 + g * 4 + b * 22);
R = min(960, R);
G = min(960, G);
B = min(960, B);
return (R << 20) + (G << 10) + (B << 0);
}
void Video::generate(Format format) {
if(system.dmg()) for(unsigned n = 0; n < 4; n++) palette[n] = palette_dmg(n);
if(system.sgb()) for(unsigned n = 0; n < 4; n++) palette[n] = palette_sgb(n);
if(system.cgb()) for(unsigned n = 0; n < (1 << 15); n++) palette[n] = palette_cgb(n);
if(format == Format::RGB24) {
for(unsigned n = 0; n < (1 << 15); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 6) & 0xff0000) + ((color >> 4) & 0x00ff00) + ((color >> 2) & 0x0000ff);
}
}
if(format == Format::RGB16) {
for(unsigned n = 0; n < (1 << 15); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 14) & 0xf800) + ((color >> 9) & 0x07e0) + ((color >> 5) & 0x001f);
}
}
if(format == Format::RGB15) {
for(unsigned n = 0; n < (1 << 15); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 15) & 0x7c00) + ((color >> 10) & 0x03e0) + ((color >> 5) & 0x001f);
}
}
}
Video::Video() {
palette = new unsigned[1 << 15];
}
Video::~Video() {
delete[] palette;
}
const double Video::monochrome[4][3] = {
{ 0.605, 0.734, 0.059 },
{ 0.543, 0.672, 0.059 },
{ 0.188, 0.383, 0.188 },
{ 0.059, 0.219, 0.059 },
};
}

17
bsnes/gameboy/video/video.hpp Executable file
View File

@ -0,0 +1,17 @@
struct Video {
enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 };
unsigned *palette;
unsigned palette_dmg(unsigned color) const;
unsigned palette_sgb(unsigned color) const;
unsigned palette_cgb(unsigned color) const;
void generate(Format format);
Video();
~Video();
private:
static const double monochrome[4][3];
};
extern Video video;

View File

@ -1,6 +1,6 @@
nes_objects := nes-interface nes-system nes-scheduler nes-input
nes_objects += nes-memory nes-cartridge nes-cpu nes-apu nes-ppu
nes_objects += nes-cheat
nes_objects += nes-cheat nes-video
objects += $(nes_objects)
obj/nes-interface.o: $(nes)/interface/interface.cpp $(call rwildcard,$(nes)/interface/)
@ -13,3 +13,4 @@ obj/nes-cpu.o: $(nes)/cpu/cpu.cpp $(call rwildcard,$(nes)/cpu/)
obj/nes-apu.o: $(nes)/apu/apu.cpp $(call rwildcard,$(nes)/apu/)
obj/nes-ppu.o: $(nes)/ppu/ppu.cpp $(call rwildcard,$(nes)/ppu/)
obj/nes-cheat.o: $(nes)/cheat/cheat.cpp $(call rwildcard,$(nes)/cheat/)
obj/nes-video.o: $(nes)/video/video.cpp $(call rwildcard,$(nes)/video/)

View File

@ -17,7 +17,7 @@ uint2 prgram_write_protect[2]; //$5102,$5103
uint2 exram_mode; //$5104
uint2 nametable_mode[4]; //$5105
uint8 fillmode_tile; //$5106
uint2 fillmode_color; //$5107
uint8 fillmode_color; //$5107
bool ram_select; //$5113
uint2 ram_bank; //$5113
@ -54,6 +54,10 @@ bool sprite_8x16;
uint8 exbank;
uint8 exattr;
bool vs_fetch;
uint8 vs_vpos;
uint8 vs_hpos;
void main() {
while(true) {
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
@ -188,6 +192,8 @@ void prg_write(unsigned addr, uint8 data) {
case 0x5107:
fillmode_color = data & 3;
fillmode_color |= fillmode_color << 2;
fillmode_color |= fillmode_color << 4;
break;
case 0x5113:
@ -296,6 +302,10 @@ unsigned chr_bg_addr(unsigned addr) {
}
}
unsigned chr_vs_addr(unsigned addr) {
return (vs_bank * 0x1000) + (addr & 0x0ff8) + (vs_vpos & 7);
}
void blank() {
in_frame = false;
}
@ -316,11 +326,14 @@ void scanline() {
}
uint8 ciram_read(unsigned addr) {
if(vs_fetch && (hcounter & 2) == 0) return exram[vs_vpos / 8 * 32 + vs_hpos / 8];
if(vs_fetch && (hcounter & 2) != 0) return exram[vs_vpos / 32 * 8 + vs_hpos / 32 + 0x03c0];
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
case 3: return (hcounter & 2) == 0 ? fillmode_tile : (uint8)fillmode_color;
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
}
}
@ -337,14 +350,21 @@ uint8 chr_read(unsigned addr) {
&& (chr_access[3] & 0x2000)) scanline();
if(in_frame == false) {
vs_fetch = false;
if(addr & 0x2000) return ciram_read(addr);
return 0x00;
return board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
}
unsigned mode = nametable_mode[(addr >> 10) & 3];
bool bg_fetch = (hcounter < 256 || hcounter >= 320);
uint8 result = 0x00;
if((hcounter & 7) == 0) {
vs_hpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
vs_vpos = vcounter + vs_scroll;
vs_fetch = vs_enable && bg_fetch && exram_mode < 2
&& (vs_side ? vs_hpos / 8 >= vs_tile : vs_hpos / 8 < vs_tile);
if(vs_vpos >= 240) vs_vpos -= 240;
result = ciram_read(addr);
exbank = (chr_bank_hi << 6) | (exram[addr & 0x03ff] & 0x3f);
@ -353,21 +373,12 @@ uint8 chr_read(unsigned addr) {
exattr |= exattr << 4;
} else if((hcounter & 7) == 2) {
result = ciram_read(addr);
if((hcounter < 256 || hcounter >= 320) && exram_mode == 1) {
result = exattr;
}
if(bg_fetch && exram_mode == 1) result = exattr;
} else {
if(sprite_8x16 == false) {
result = board.chrrom.read(chr_active ? chr_bg_addr(addr) : chr_sprite_addr(addr));
}
else if(hcounter < 256) result = board.chrrom.read(chr_bg_addr(addr));
else if(hcounter < 320) result = board.chrrom.read(chr_sprite_addr(addr));
else /* hcounter < 340*/result = board.chrrom.read(chr_bg_addr(addr));
if((hcounter < 256 || hcounter >= 320) && exram_mode == 1) {
result = board.chrrom.read(exbank * 0x1000 + addr);
}
if(vs_fetch) result = board.chrrom.read(chr_vs_addr(addr));
else if(sprite_8x16 ? bg_fetch : chr_active) result = board.chrrom.read(chr_bg_addr(addr));
else result = board.chrrom.read(chr_sprite_addr(addr));
if(bg_fetch && exram_mode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
}
hcounter += 2;
@ -376,9 +387,11 @@ uint8 chr_read(unsigned addr) {
void chr_write(unsigned addr, uint8 data) {
if(addr & 0x2000) {
unsigned mode = nametable_mode[(addr >> 10) & 3];
if(mode == 0) ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
if(mode == 1) ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
switch(nametable_mode[(addr >> 10) & 3]) {
case 0: return ppu.ciram_write(0x0000 | (addr & 0x03ff), data);
case 1: return ppu.ciram_write(0x0400 | (addr & 0x03ff), data);
case 2: exram[addr & 0x03ff] = data; break;
}
}
}
@ -426,6 +439,10 @@ void reset() {
exbank = 0;
exattr = 0;
vs_fetch = 0;
vs_vpos = 0;
vs_hpos = 0;
}
void serialize(serializer &s) {
@ -467,6 +484,10 @@ void serialize(serializer &s) {
s.integer(exbank);
s.integer(exattr);
s.integer(vs_fetch);
s.integer(vs_vpos);
s.integer(vs_hpos);
}
MMC5(Board &board) : Chip(board) {

View File

@ -111,6 +111,7 @@ namespace NES {
#include <nes/apu/apu.hpp>
#include <nes/ppu/ppu.hpp>
#include <nes/cheat/cheat.hpp>
#include <nes/video/video.hpp>
#include <nes/interface/interface.hpp>
}

View File

@ -27,7 +27,7 @@ bool System::unserialize(serializer &s) {
if(version != Info::SerializerVersion) return false;
//if(crc32 != 0) return false;
reset();
power();
serialize_all(s);
return true;
}

87
bsnes/nes/video/video.cpp Executable file
View File

@ -0,0 +1,87 @@
#include <nes/nes.hpp>
#define VIDEO_CPP
namespace NES {
Video video;
unsigned Video::palette30(
unsigned n, double saturation, double hue,
double contrast, double brightness, double gamma
) {
signed color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](signed p, signed color) { return (color + p + 8) % 12 < 6; };
for(signed p = 0; p < 12; p++) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
return (uclamp<10>(1023.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q)) << 20)
+ (uclamp<10>(1023.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q)) << 10)
+ (uclamp<10>(1023.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q)) << 0);
}
void Video::generate(Format format) {
for(unsigned n = 0; n < (1 << 9); n++) palette[n] = palette30(n, 2.0, 0.0, 1.0, 1.0, 1.8);
if(format == Format::RGB24) {
for(unsigned n = 0; n < (1 << 9); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 6) & 0xff0000) + ((color >> 4) & 0x00ff00) + ((color >> 2) & 0x0000ff);
}
}
if(format == Format::RGB16) {
for(unsigned n = 0; n < (1 << 9); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 14) & 0xf800) + ((color >> 9) & 0x07e0) + ((color >> 5) & 0x001f);
}
}
if(format == Format::RGB15) {
for(unsigned n = 0; n < (1 << 9); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 15) & 0x7c00) + ((color >> 10) & 0x03e0) + ((color >> 5) & 0x001f);
}
}
}
Video::Video() {
palette = new unsigned[1 << 9];
}
Video::~Video() {
delete[] palette;
}
}

11
bsnes/nes/video/video.hpp Executable file
View File

@ -0,0 +1,11 @@
struct Video {
enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 };
unsigned *palette;
unsigned palette30(unsigned, double, double, double, double, double);
void generate(Format format);
Video();
~Video();
};
extern Video video;

View File

@ -34,7 +34,7 @@ bool System::unserialize(serializer &s) {
//if(crc32 != cartridge.crc32()) return false;
if(strcmp(profile, Info::Profile)) return false;
reset();
power();
serialize_all(s);
return true;
}

View File

@ -2,6 +2,55 @@
Video video;
unsigned Video::palette30(unsigned color) {
unsigned l = (color >> 15) & 15;
unsigned b = (color >> 10) & 31;
unsigned g = (color >> 5) & 31;
unsigned r = (color >> 0) & 31;
double L = (1.0 + l) / 16.0;
unsigned R = L * ((r << 5) + (r << 0));
unsigned G = L * ((g << 5) + (g << 0));
unsigned B = L * ((b << 5) + (b << 0));
return (R << 20) + (G << 10) + (B << 0);
}
void Video::generate(Format format) {
for(unsigned n = 0; n < (1 << 19); n++) palette[n] = palette30(n);
if(format == Format::RGB24) {
for(unsigned n = 0; n < (1 << 19); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 6) & 0xff0000) + ((color >> 4) & 0x00ff00) + ((color >> 2) & 0x0000ff);
}
}
if(format == Format::RGB16) {
for(unsigned n = 0; n < (1 << 19); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 14) & 0xf800) + ((color >> 9) & 0x07e0) + ((color >> 5) & 0x001f);
}
}
if(format == Format::RGB15) {
for(unsigned n = 0; n < (1 << 19); n++) {
unsigned color = palette[n];
palette[n] = ((color >> 15) & 0x7c00) + ((color >> 10) & 0x03e0) + ((color >> 5) & 0x001f);
}
}
}
Video::Video() {
palette = new unsigned[1 << 19];
}
Video::~Video() {
delete[] palette;
}
//internal
const uint8_t Video::cursor[15 * 15] = {
0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,
0,0,0,0,1,1,2,2,2,1,1,0,0,0,0,

View File

@ -1,4 +1,12 @@
struct Video {
enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 };
unsigned *palette;
unsigned palette30(unsigned color);
void generate(Format format);
Video();
~Video();
private:
bool hires;
unsigned line_width[240];

View File

@ -20,6 +20,7 @@ bool InterfaceGameBoy::loadCartridge(GameBoy::System::Revision revision, const s
}
GameBoy::interface = this;
GameBoy::video.generate(GameBoy::Video::Format::RGB24);
interface->loadCartridge(::Interface::Mode::GameBoy);
return true;
}
@ -53,35 +54,18 @@ bool InterfaceGameBoy::loadState(const string &filename) {
//
void InterfaceGameBoy::videoRefresh(const uint16_t *data) {
static uint16_t output[160 * 144];
static uint32_t output[160 * 144];
if(GameBoy::system.cgb() == false) { //L2
static uint32_t palette[] = {
0x9bbc0f, 0x8bac0f, 0x306230, 0x0f380f
};
for(unsigned y = 0; y < 144; y++) {
const uint16_t *sp = data + y * 160;
uint16_t *dp = output + y * 160;
for(unsigned x = 0; x < 160; x++) {
uint32_t color = palette[*sp++];
*dp++ = ((color & 0xf80000) >> 9) | ((color & 0x00f800) >> 6) | ((color & 0x0000f8) >> 3);
}
for(unsigned y = 0; y < 144; y++) {
const uint16_t *sp = data + y * 160;
uint32_t *dp = output + y * 160;
for(unsigned x = 0; x < 160; x++) {
uint16_t color = *sp++;
*dp++ = GameBoy::video.palette[color];
}
}
if(GameBoy::system.cgb() == true) { //BGR555
for(unsigned y = 0; y < 144; y++) {
const uint16_t *sp = data + y * 160;
uint16_t *dp = output + y * 160;
for(unsigned x = 0; x < 160; x++) {
uint16_t color = *sp++;
*dp++ = ((color >> 10) & 0x001f) | (color & 0x03e0) | ((color << 10) & 0x7c00);
}
}
}
interface->videoRefresh(output, 160 * 2, 160, 144);
interface->videoRefresh(output, 160 * 4, 160, 144);
}
void InterfaceGameBoy::audioSample(int16_t csample, int16_t lsample, int16_t rsample) {

View File

@ -7,15 +7,15 @@ Interface *interface = 0;
Filter filter;
void Filter::render(const uint16_t *input, unsigned inputPitch, unsigned inputWidth, unsigned inputHeight) {
void Filter::render(const uint32_t *input, unsigned inputPitch, unsigned inputWidth, unsigned inputHeight) {
width = inputWidth, height = inputHeight;
dl_size(width, height);
dl_render(data, pitch, input, inputPitch, inputWidth, inputHeight);
}
Filter::Filter() {
data = new uint16_t[2048 * 2048];
pitch = 2048 * sizeof(uint16_t);
data = new uint32_t[2048 * 2048];
pitch = 2048 * sizeof(uint32_t);
}
Filter::~Filter() {
@ -215,8 +215,7 @@ bool Interface::loadFile(const string &filename, uint8_t *&data, unsigned &size)
return true;
}
//RGB555 input
void Interface::videoRefresh(const uint16_t *input, unsigned inputPitch, unsigned width, unsigned height) {
void Interface::videoRefresh(const uint32_t *input, unsigned inputPitch, unsigned width, unsigned height) {
uint32_t *output;
unsigned outputPitch;
@ -229,13 +228,13 @@ void Interface::videoRefresh(const uint16_t *input, unsigned inputPitch, unsigne
}
if(video.lock(output, outputPitch, width, height)) {
inputPitch >>= 1, outputPitch >>= 2;
inputPitch >>= 2, outputPitch >>= 2;
for(unsigned y = 0; y < height; y++) {
const uint16_t *sp = input + y * inputPitch;
const uint32_t *sp = input + y * inputPitch;
uint32_t *dp = output + y * outputPitch;
for(unsigned x = 0; x < width; x++) {
*dp++ = palette[*sp++];
*dp++ = *sp++; //palette[*sp++];
}
}

View File

@ -6,12 +6,12 @@
struct Filter : public library {
function<void (unsigned&, unsigned&)> dl_size;
function<void (uint16_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned)> dl_render;
void render(const uint16_t*, unsigned, unsigned, unsigned);
function<void (uint32_t*, unsigned, const uint32_t*, unsigned, unsigned, unsigned)> dl_render;
void render(const uint32_t*, unsigned, unsigned, unsigned);
Filter();
~Filter();
uint16_t *data;
uint32_t *data;
unsigned pitch;
unsigned width;
unsigned height;
@ -47,7 +47,7 @@ struct Interface : property<Interface> {
Interface();
bool loadFile(const string &filename, uint8_t *&data, unsigned &size);
void videoRefresh(const uint16_t *input, unsigned inputPitch, unsigned width, unsigned height);
void videoRefresh(const uint32_t *input, unsigned inputPitch, unsigned width, unsigned height);
string baseName; // = "/path/to/cartridge" (no extension)
lstring slotName;

View File

@ -37,6 +37,7 @@ bool InterfaceNES::loadCartridge(const string &filename) {
}
interface->loadCartridge(::Interface::Mode::NES);
NES::video.generate(NES::Video::Format::RGB24);
return true;
}
@ -71,14 +72,14 @@ bool InterfaceNES::loadState(const string &filename) {
//
void InterfaceNES::videoRefresh(const uint16_t *data) {
static uint16_t output[256 * 240];
static uint32_t output[256 * 240];
for(unsigned y = 0; y < 240; y++) {
const uint16_t *sp = data + y * 256;
uint16_t *dp = output + y * 256;
uint32_t *dp = output + y * 256;
for(unsigned x = 0; x < 256; x++) {
uint32_t color = palette[*sp++];
*dp++ = ((color & 0xf80000) >> 9) | ((color & 0x00f800) >> 6) | ((color & 0x0000f8) >> 3);;
uint32_t color = *sp++;
*dp++ = NES::video.palette[color];
}
}
@ -87,7 +88,7 @@ void InterfaceNES::videoRefresh(const uint16_t *data) {
unsigned osh = config->video.maskOverscanVertical;
for(unsigned y = 0; y < 240; y++) {
uint16_t *dp = output + y * 256;
uint32_t *dp = output + y * 256;
if(y < osh || y >= 240 - osh) {
memset(dp, 0, 256 * 2);
} else {
@ -97,7 +98,7 @@ void InterfaceNES::videoRefresh(const uint16_t *data) {
}
}
interface->videoRefresh(output, 256 * 2, 256, 240);
interface->videoRefresh(output, 256 * 4, 256, 240);
}
void InterfaceNES::audioSample(int16_t sample) {
@ -113,72 +114,3 @@ int16_t InterfaceNES::inputPoll(bool port, unsigned device, unsigned id) {
if(port == 0 && device == 0) return inputManager->nes.port1.gamepad.poll(id);
return 0;
}
//
//n = BGRCCCCCC (BGR=emphasis bits; C=color)
unsigned InterfaceNES::paletteColor(
unsigned n, double saturation, double hue,
double contrast, double brightness, double gamma
) {
signed color = (n & 0x0f), level = color < 0xe ? (n >> 4) & 3 : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](signed p, signed color) { return (color + p + 8) % 12 < 6; };
for(signed p = 0; p < 12; p++) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * std::cos((3.141592653 / 6.0) * (p + hue));
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
return (uclamp<8>(255.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q)) << 16)
+ (uclamp<8>(255.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q)) << 8)
+ (uclamp<8>(255.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q)) << 0);
}
InterfaceNES::InterfaceNES() {
for(unsigned n = 0; n < 512; n++) {
palette[n] = paletteColor(n, 2.0);
}
for(unsigned e = 1; e < 8; e++) {
static const double rfactor[8] = { 1.000, 1.239, 0.794, 1.019, 0.905, 1.023, 0.741, 0.750 };
static const double gfactor[8] = { 1.000, 0.915, 1.086, 0.980, 1.026, 0.908, 0.987, 0.750 };
static const double bfactor[8] = { 1.000, 0.743, 0.882, 0.653, 1.277, 0.979, 0.101, 0.750 };
for(unsigned n = 0; n < 64; n++) {
unsigned c = palette[n];
uint8_t r = c >> 16, g = c >> 8, b = c >> 0;
r = uclamp<8>((unsigned)(r * rfactor[e]));
g = uclamp<8>((unsigned)(g * gfactor[e]));
b = uclamp<8>((unsigned)(b * bfactor[e]));
palette[e * 64 + n] = (r << 16) | (g << 8) | (b << 0);
}
}
}

View File

@ -10,13 +10,4 @@ struct InterfaceNES : NES::Interface {
void videoRefresh(const uint16_t *data);
void audioSample(int16_t sample);
int16_t inputPoll(bool port, unsigned device, unsigned id);
InterfaceNES();
private:
unsigned palette[512];
unsigned paletteColor(
unsigned color, double saturation = 1.0, double hue = 0.0,
double contrast = 1.0, double brightness = 1.0, double gamma = 1.8
);
};

View File

@ -39,6 +39,7 @@ bool InterfaceSNES::loadCartridge(const string &basename) {
loadMemory();
interface->loadCartridge(::Interface::Mode::SNES);
SNES::video.generate(SNES::Video::Format::RGB24);
return true;
}
@ -63,6 +64,7 @@ bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, cons
loadMemory();
interface->loadCartridge(::Interface::Mode::SNES);
SNES::video.generate(SNES::Video::Format::RGB24);
return true;
}
@ -87,6 +89,7 @@ bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const strin
loadMemory();
interface->loadCartridge(::Interface::Mode::SNES);
SNES::video.generate(SNES::Video::Format::RGB24);
return true;
}
@ -115,6 +118,7 @@ bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const strin
loadMemory();
interface->loadCartridge(::Interface::Mode::SNES);
SNES::video.generate(SNES::Video::Format::RGB24);
return true;
}
@ -142,6 +146,7 @@ bool InterfaceSNES::loadSuperGameBoyCartridge(const string &basename, const stri
loadMemory();
interface->loadCartridge(::Interface::Mode::SNES);
SNES::video.generate(SNES::Video::Format::RGB24);
return true;
}
@ -194,7 +199,7 @@ bool InterfaceSNES::loadState(const string &filename) {
//
void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlace, bool overscan) {
static uint16_t output[512 * 480];
static uint32_t output[512 * 480];
unsigned width = 256 << hires;
unsigned height = 240 << interlace;
@ -206,9 +211,9 @@ void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlac
for(unsigned y = 0; y < height; y++) {
const uint32_t *sp = data + y * pitch;
uint16_t *dp = output + y * 512;
uint32_t *dp = output + y * 512;
for(unsigned x = 0; x < width; x++) {
*dp++ = palette[*sp++];
*dp++ = SNES::video.palette[*sp++];
}
}
@ -217,7 +222,7 @@ void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlac
unsigned osh = config->video.maskOverscanVertical << interlace;
for(unsigned y = 0; y < height; y++) {
uint16_t *dp = output + y * 512;
uint32_t *dp = output + y * 512;
if(y < osh || y >= height - osh) {
memset(dp, 0, width * 2);
} else {
@ -227,7 +232,7 @@ void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlac
}
}
interface->videoRefresh(output, 512 * 2, width, height);
interface->videoRefresh(output, 512 * 4, width, height);
}
void InterfaceSNES::audioSample(int16_t lsample, int16_t rsample) {
@ -279,25 +284,3 @@ string InterfaceSNES::path(SNES::Cartridge::Slot slot, const string &hint) {
void InterfaceSNES::message(const string &text) {
MessageWindow::information(*mainWindow, text);
}
InterfaceSNES::InterfaceSNES() {
//{llll bbbbb ggggg rrrrr} -> { rrrrr ggggg bbbbb }
palette = new uint32_t[16 * 32 * 32 * 32];
for(unsigned l = 0; l < 16; l++) {
for(unsigned r = 0; r < 32; r++) {
for(unsigned g = 0; g < 32; g++) {
for(unsigned b = 0; b < 32; b++) {
double luma = (double)l / 15.0;
unsigned ar = (luma * r + 0.5);
unsigned ag = (luma * g + 0.5);
unsigned ab = (luma * b + 0.5);
palette[(l << 15) + (r << 10) + (g << 5) + (b << 0)] = (ab << 10) + (ag << 5) + (ar << 0);
}
}
}
}
}
InterfaceSNES::~InterfaceSNES() {
delete[] palette;
}

View File

@ -20,10 +20,4 @@ struct InterfaceSNES : SNES::Interface {
string path(SNES::Cartridge::Slot slot, const string &hint);
void message(const string &text);
InterfaceSNES();
~InterfaceSNES();
private:
unsigned *palette;
};

View File

@ -27,7 +27,7 @@ void Application::run() {
}
Application::Application(int argc, char **argv) {
title = "bsnes v083.05";
title = "bsnes v083.06";
application = this;
quit = false;

View File

@ -31,11 +31,13 @@ VideoSettings::VideoSettings() {
RadioBox::group(fullScreen[0], fullScreen[1], fullScreen[2]);
append(title, ~0, 0, 5);
#if 0
append(colorAdjustment, ~0, 0);
append(brightness, ~0, 0);
append(contrast, ~0, 0);
append(gamma, ~0, 0);
append(gammaRamp, ~0, 0, 5);
#endif
append(overscanAdjustment, ~0, 0);
append(overscanHorizontal, ~0, 0);
append(overscanVertical, ~0, 0, 5);