mirror of https://github.com/bsnes-emu/bsnes.git
Update to v093r10 release.
byuu says: Changelog: - Game Boy (Color): STAT OAM+HBlank IRQs only trigger during LY=0-143 with display enabled - fixes backgrounds and text in Wacky Races - Game Boy (Color): fixed underflow in window clamping - fixes Wacky Races, Prehistorik Man, Alleyway, etc - Game Boy (Color): LCD OAM DMA was running too slow - fixes Shin Megami Tensei - Devichil - Kuro no Sho - Game Boy Advance: removed built-in frame blending; display emulation shaders will handle this going forward - Game Boy Advance: added Game Boy Player emulation - currently the screen is tinted red during rumble, no actual gamepad rumble support yet - this is going to be slow, as we have to hash the frame to detect the GBP logo, it'll be optional later on - Emulator::Interface::Palette can now output a raw palette (for Display Emulation shaders only) - color channels are not yet split up, it's just the raw packed value
This commit is contained in:
parent
926a39d701
commit
84fab07756
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
static const char Name[] = "higan";
|
||||
static const char Version[] = "093.09";
|
||||
static const char Version[] = "093.10";
|
||||
static const char Author[] = "byuu";
|
||||
static const char License[] = "GPLv3";
|
||||
static const char Website[] = "http://byuu.org/";
|
||||
|
|
|
@ -51,7 +51,7 @@ struct Interface {
|
|||
virtual void loadRequest(unsigned, string) {}
|
||||
virtual void saveRequest(unsigned, string) {}
|
||||
virtual uint32_t videoColor(unsigned, uint16_t, uint16_t, uint16_t) { return 0u; }
|
||||
virtual void videoRefresh(const uint32_t*, unsigned, unsigned, unsigned) {}
|
||||
virtual void videoRefresh(const uint32_t*, const uint32_t*, unsigned, unsigned, unsigned) {}
|
||||
virtual void audioSample(int16_t, int16_t) {}
|
||||
virtual int16_t inputPoll(unsigned, unsigned, unsigned) { return 0; }
|
||||
virtual unsigned dipSettings(const Markup::Node&) { return 0; }
|
||||
|
@ -66,7 +66,7 @@ struct Interface {
|
|||
void loadRequest(unsigned id, string path) { return bind->loadRequest(id, path); }
|
||||
void saveRequest(unsigned id, string path) { return bind->saveRequest(id, path); }
|
||||
uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue) { return bind->videoColor(source, red, green, blue); }
|
||||
void videoRefresh(const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(data, pitch, width, height); }
|
||||
void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) { return bind->videoRefresh(palette, data, pitch, width, height); }
|
||||
void audioSample(int16_t lsample, int16_t rsample) { return bind->audioSample(lsample, rsample); }
|
||||
int16_t inputPoll(unsigned port, unsigned device, unsigned input) { return bind->inputPoll(port, device, input); }
|
||||
unsigned dipSettings(const Markup::Node& node) { return bind->dipSettings(node); }
|
||||
|
@ -107,7 +107,8 @@ struct Interface {
|
|||
virtual void cheatSet(const lstring& = lstring{}) {}
|
||||
|
||||
//utility functions
|
||||
virtual void paletteUpdate(bool colorEmulation) {}
|
||||
enum class PaletteMode : unsigned { None, Standard, Emulation };
|
||||
virtual void paletteUpdate(PaletteMode mode) {}
|
||||
|
||||
//debugger functions
|
||||
virtual bool tracerEnable(bool) { return false; }
|
||||
|
|
|
@ -115,8 +115,8 @@ void Interface::cheatSet(const lstring& list) {
|
|||
cheat.synchronize();
|
||||
}
|
||||
|
||||
void Interface::paletteUpdate(bool colorEmulation) {
|
||||
video.generate_palette(colorEmulation);
|
||||
void Interface::paletteUpdate(PaletteMode mode) {
|
||||
video.generate_palette(mode);
|
||||
}
|
||||
|
||||
Interface::Interface() {
|
||||
|
|
|
@ -45,7 +45,7 @@ struct Interface : Emulator::Interface {
|
|||
|
||||
void cheatSet(const lstring&);
|
||||
|
||||
void paletteUpdate(bool colorEmulation);
|
||||
void paletteUpdate(PaletteMode mode);
|
||||
|
||||
Interface();
|
||||
|
||||
|
|
|
@ -324,7 +324,7 @@ void PPU::raster_pixel() {
|
|||
}
|
||||
|
||||
if(raster_enable() == false) palette = 0;
|
||||
output[status.lx] = video.palette[(status.emphasis << 6) | cgram_read(palette)];
|
||||
output[status.lx] = (status.emphasis << 6) | cgram_read(palette);
|
||||
}
|
||||
|
||||
void PPU::raster_sprite() {
|
||||
|
|
|
@ -8,7 +8,7 @@ System system;
|
|||
void System::run() {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
interface->videoRefresh(ppu.buffer, 4 * 256, 256, 240);
|
||||
interface->videoRefresh(video.palette, ppu.buffer, 4 * 256, 256, 240);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ void System::runthreadtosave() {
|
|||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
interface->videoRefresh(ppu.buffer, 4 * 256, 256, 240);
|
||||
interface->videoRefresh(video.palette, ppu.buffer, 4 * 256, 256, 240);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,18 @@ namespace Famicom {
|
|||
|
||||
Video video;
|
||||
|
||||
void Video::generate_palette(bool color_emulation) {
|
||||
for(unsigned n = 0; n < (1 << 9); n++) palette[n] = generate_color(n, 2.0, 0.0, 1.0, 1.0, color_emulation ? 1.8 : 2.2);
|
||||
void Video::generate_palette(Emulator::Interface::PaletteMode mode) {
|
||||
for(unsigned n = 0; n < (1 << 9); n++) {
|
||||
switch(mode) {
|
||||
case Emulator::Interface::PaletteMode::None: palette[n] = n; break;
|
||||
case Emulator::Interface::PaletteMode::Standard: palette[n] = generate_color(n, 2.0, 0.0, 1.0, 1.0, 2.2); break;
|
||||
case Emulator::Interface::PaletteMode::Emulation: palette[n] = generate_color(n, 2.0, 0.0, 1.0, 1.0, 1.8); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Video::Video() {
|
||||
palette = new unsigned[1 << 9]();
|
||||
palette = new uint32_t[1 << 9]();
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
struct Video {
|
||||
unsigned* palette = nullptr;
|
||||
void generate_palette(bool color_emulation);
|
||||
uint32_t* palette = nullptr;
|
||||
void generate_palette(Emulator::Interface::PaletteMode mode);
|
||||
|
||||
Video();
|
||||
~Video();
|
||||
|
|
|
@ -8,15 +8,15 @@ void CPU::op_io() {
|
|||
uint8 CPU::op_read(uint16 addr) {
|
||||
cycle_edge();
|
||||
add_clocks(4);
|
||||
if(oamdma.active) return hram[addr & 0x7f];
|
||||
if(oamdma.active && (addr < 0xff80 || addr == 0xffff)) return 0x00;
|
||||
return bus.read(addr);
|
||||
}
|
||||
|
||||
void CPU::op_write(uint16 addr, uint8 data) {
|
||||
cycle_edge();
|
||||
add_clocks(4);
|
||||
if(oamdma.active) hram[addr & 0x7f] = data;
|
||||
else bus.write(addr, data);
|
||||
if(oamdma.active && (addr < 0xff80 || addr == 0xffff)) return;
|
||||
bus.write(addr, data);
|
||||
}
|
||||
|
||||
void CPU::cycle_edge() {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
void CPU::add_clocks(unsigned clocks) {
|
||||
if(oamdma.active) {
|
||||
for(unsigned n = 0; n < clocks; n++) {
|
||||
for(unsigned n = 0; n < 4 * clocks; n++) {
|
||||
bus.write(0xfe00 + oamdma.offset, bus.read((oamdma.bank << 8) + oamdma.offset));
|
||||
if(++oamdma.offset == 160) {
|
||||
oamdma.active = false;
|
||||
|
|
|
@ -131,8 +131,8 @@ void Interface::cheatSet(const lstring& list) {
|
|||
cheat.synchronize();
|
||||
}
|
||||
|
||||
void Interface::paletteUpdate(bool colorEmulation) {
|
||||
video.generate_palette(colorEmulation);
|
||||
void Interface::paletteUpdate(PaletteMode mode) {
|
||||
video.generate_palette(mode);
|
||||
}
|
||||
|
||||
Interface::Interface() {
|
||||
|
|
|
@ -58,7 +58,7 @@ struct Interface : Emulator::Interface {
|
|||
|
||||
void cheatSet(const lstring&);
|
||||
|
||||
void paletteUpdate(bool colorEmulation);
|
||||
void paletteUpdate(PaletteMode mode);
|
||||
|
||||
Interface();
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ void PPU::cgb_run() {
|
|||
}
|
||||
|
||||
uint32* output = screen + status.ly * 160 + px++;
|
||||
*output = video.palette[color];
|
||||
*output = color;
|
||||
}
|
||||
|
||||
void PPU::cgb_run_bg() {
|
||||
|
@ -115,10 +115,10 @@ void PPU::cgb_run_bg() {
|
|||
}
|
||||
|
||||
void PPU::cgb_run_window() {
|
||||
if(status.ly - status.wy >= 144u) return;
|
||||
if(status.wx >= 167u) return;
|
||||
unsigned scrolly = (status.ly - status.wy) & 255;
|
||||
unsigned scrollx = (px + 7 - status.wx) & 255;
|
||||
unsigned scrolly = status.ly - status.wy;
|
||||
unsigned scrollx = px + 7 - status.wx;
|
||||
if(scrolly >= 144u) return; //also matches underflow (scrolly < 0)
|
||||
if(scrollx >= 160u) return; //also matches underflow (scrollx < 0)
|
||||
unsigned tx = scrollx & 7;
|
||||
if(tx == 0 || px == 0) cgb_read_tile(status.window_tilemap_select, scrollx, scrolly, window.attr, window.data);
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ void PPU::dmg_run() {
|
|||
}
|
||||
|
||||
uint32* output = screen + status.ly * 160 + px++;
|
||||
*output = video.palette[color];
|
||||
*output = color;
|
||||
}
|
||||
|
||||
void PPU::dmg_run_bg() {
|
||||
|
@ -96,10 +96,10 @@ void PPU::dmg_run_bg() {
|
|||
}
|
||||
|
||||
void PPU::dmg_run_window() {
|
||||
if(status.ly - status.wy >= 144u) return;
|
||||
if(status.wx >= 167u) return;
|
||||
unsigned scrolly = (status.ly - status.wy) & 255;
|
||||
unsigned scrollx = (px + 7 - status.wx) & 255;
|
||||
unsigned scrolly = status.ly - status.wy;
|
||||
unsigned scrollx = px + 7 - status.wx;
|
||||
if(scrolly >= 144u) return; //also matches underflow (scrolly < 0)
|
||||
if(scrollx >= 160u) return; //also matches underflow (scrollx < 0)
|
||||
unsigned tx = scrollx & 7;
|
||||
if(tx == 0 || px == 0) dmg_read_tile(status.window_tilemap_select, scrollx, scrolly, window.data);
|
||||
|
||||
|
|
|
@ -25,30 +25,20 @@ void PPU::main() {
|
|||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
//X = 0
|
||||
if(status.display_enable) {
|
||||
if(status.display_enable && status.ly < 144) {
|
||||
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
add_clocks(92);
|
||||
|
||||
if(status.ly < 144) {
|
||||
add_clocks(92);
|
||||
for(unsigned n = 0; n < 160; n++) {
|
||||
system.cgb() ? cgb_run() : dmg_run();
|
||||
add_clocks(1);
|
||||
}
|
||||
} else {
|
||||
//Vblank
|
||||
add_clocks(160);
|
||||
}
|
||||
|
||||
//X = 252
|
||||
if(status.display_enable) {
|
||||
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
cpu.hblank();
|
||||
add_clocks(204);
|
||||
} else {
|
||||
add_clocks(456);
|
||||
}
|
||||
|
||||
add_clocks(204);
|
||||
scanline();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ void System::run() {
|
|||
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
interface->videoRefresh(ppu.screen, 4 * 160, 160, 144);
|
||||
interface->videoRefresh(video.palette, ppu.screen, 4 * 160, 160, 144);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ void System::runthreadtosave() {
|
|||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
interface->videoRefresh(ppu.screen, 4 * 160, 160, 144);
|
||||
interface->videoRefresh(video.palette, ppu.screen, 4 * 160, 160, 144);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,15 @@ namespace GameBoy {
|
|||
|
||||
Video video;
|
||||
|
||||
void Video::generate_palette(bool color_emulation) {
|
||||
this->color_emulation = color_emulation;
|
||||
void Video::generate_palette(Emulator::Interface::PaletteMode mode) {
|
||||
this->mode = mode;
|
||||
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);
|
||||
}
|
||||
|
||||
Video::Video() {
|
||||
palette = new unsigned[1 << 15]();
|
||||
palette = new uint32_t[1 << 15]();
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
|
@ -21,7 +21,9 @@ Video::~Video() {
|
|||
}
|
||||
|
||||
unsigned Video::palette_dmg(unsigned color) const {
|
||||
if(color_emulation == false) {
|
||||
if(mode == Emulator::Interface::PaletteMode::None) return color;
|
||||
|
||||
if(mode == Emulator::Interface::PaletteMode::Standard) {
|
||||
unsigned L = (3 - color) * 21845;
|
||||
return interface->videoColor(color, L, L, L);
|
||||
}
|
||||
|
@ -34,19 +36,17 @@ unsigned Video::palette_dmg(unsigned color) const {
|
|||
}
|
||||
|
||||
unsigned Video::palette_sgb(unsigned color) const {
|
||||
unsigned R = (3 - color) * 21845;
|
||||
unsigned G = (3 - color) * 21845;
|
||||
unsigned B = (3 - color) * 21845;
|
||||
|
||||
return interface->videoColor(color, R, G, B);
|
||||
return color;
|
||||
}
|
||||
|
||||
unsigned Video::palette_cgb(unsigned color) const {
|
||||
if(mode == Emulator::Interface::PaletteMode::None) return color;
|
||||
|
||||
unsigned r = (color >> 0) & 31;
|
||||
unsigned g = (color >> 5) & 31;
|
||||
unsigned b = (color >> 10) & 31;
|
||||
|
||||
if(color_emulation == false) {
|
||||
if(mode == Emulator::Interface::PaletteMode::Standard) {
|
||||
unsigned R = (r << 11) | (r << 6) | (r << 1) | (r >> 4);
|
||||
unsigned G = (g << 11) | (g << 6) | (g << 1) | (g >> 4);
|
||||
unsigned B = (b << 11) | (b << 6) | (b << 1) | (b >> 4);
|
||||
|
@ -74,10 +74,10 @@ unsigned Video::palette_cgb(unsigned color) const {
|
|||
|
||||
const uint16 Video::monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0x9a9a, 0xbbbb, 0x0505},
|
||||
{0x7878, 0x8484, 0x0505},
|
||||
{0x1d1d, 0x5555, 0x1d1d},
|
||||
{0x0505, 0x2525, 0x0505},
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
struct Video {
|
||||
uint32_t* palette;
|
||||
void generate_palette(bool color_emulation);
|
||||
uint32_t* palette = nullptr;
|
||||
void generate_palette(Emulator::Interface::PaletteMode mode);
|
||||
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
private:
|
||||
bool color_emulation;
|
||||
Emulator::Interface::PaletteMode mode;
|
||||
static const uint16 monochrome[4][3];
|
||||
uint32_t palette_dmg(unsigned color) const;
|
||||
uint32_t palette_sgb(unsigned color) const;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
gba_objects := gba-memory gba-interface gba-scheduler gba-system
|
||||
gba_objects += gba-video gba-cartridge
|
||||
gba_objects += gba-video gba-cartridge gba-player
|
||||
gba_objects += gba-cpu gba-ppu gba-apu
|
||||
objects += $(gba_objects)
|
||||
|
||||
|
@ -9,6 +9,7 @@ obj/gba-scheduler.o: $(gba)/scheduler/scheduler.cpp $(call rwildcard,$(gba)/sche
|
|||
obj/gba-system.o: $(gba)/system/system.cpp $(call rwildcard,$(gba)/system)
|
||||
obj/gba-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
|
||||
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
|
||||
obj/gba-player.o: $(gba)/player/player.cpp $(call rwildcard,$(gba)/player)
|
||||
obj/gba-cpu.o: $(gba)/cpu/cpu.cpp $(call rwildcard,$(gba)/cpu)
|
||||
obj/gba-ppu.o: $(gba)/ppu/ppu.cpp $(call rwildcard,$(gba)/ppu)
|
||||
obj/gba-apu.o: $(gba)/apu/apu.cpp $(call rwildcard,$(gba)/apu)
|
||||
|
|
|
@ -47,8 +47,9 @@ uint8 CPU::read(uint32 addr) {
|
|||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
if(auto data = player.read()) return data() >> ((addr & 3) << 3);
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
return data >> shift;
|
||||
}
|
||||
|
||||
|
@ -62,11 +63,13 @@ uint8 CPU::read(uint32 addr) {
|
|||
|
||||
//KEYINPUT
|
||||
case 0x04000130:
|
||||
if(auto result = player.keyinput()) return result() >> 0;
|
||||
for(unsigned n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
|
||||
if((result & 0xc0) == 0xc0) result &= ~0xc0; //up+down cannot be pressed simultaneously
|
||||
if((result & 0x30) == 0x30) result &= ~0x30; //left+right cannot be pressed simultaneously
|
||||
return result ^ 0xff;
|
||||
case 0x04000131:
|
||||
if(auto result = player.keyinput()) return result() >> 8;
|
||||
result |= interface->inputPoll(0, 0, 8) << 0;
|
||||
result |= interface->inputPoll(0, 0, 9) << 1;
|
||||
return result ^ 0x03;
|
||||
|
@ -241,6 +244,7 @@ void CPU::write(uint32 addr, uint8 byte) {
|
|||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
player.write(byte, addr & 3);
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
data = (data & ~(255 << shift)) | (byte << shift);
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace GameBoyAdvance {
|
|||
#include <gba/scheduler/scheduler.hpp>
|
||||
#include <gba/system/system.hpp>
|
||||
#include <gba/cartridge/cartridge.hpp>
|
||||
#include <gba/player/player.hpp>
|
||||
#include <gba/cpu/cpu.hpp>
|
||||
#include <gba/ppu/ppu.hpp>
|
||||
#include <gba/apu/apu.hpp>
|
||||
|
|
|
@ -109,8 +109,8 @@ bool Interface::unserialize(serializer& s) {
|
|||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::paletteUpdate(bool colorEmulation) {
|
||||
video.generate_palette(colorEmulation);
|
||||
void Interface::paletteUpdate(PaletteMode mode) {
|
||||
video.generate_palette(mode);
|
||||
}
|
||||
|
||||
Interface::Interface() {
|
||||
|
|
|
@ -43,7 +43,7 @@ struct Interface : Emulator::Interface {
|
|||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void paletteUpdate(bool colorEmulation);
|
||||
void paletteUpdate(PaletteMode mode);
|
||||
|
||||
Interface();
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
#include <gba/gba.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
//Game Boy Player emulation
|
||||
|
||||
#include "serialization.cpp"
|
||||
Player player;
|
||||
|
||||
void Player::power() {
|
||||
status.enable = false;
|
||||
status.rumble = false;
|
||||
|
||||
status.logoDetected = false;
|
||||
status.logoCounter = 0;
|
||||
|
||||
status.packet = 0;
|
||||
status.send = 0;
|
||||
status.recv = 0;
|
||||
}
|
||||
|
||||
void Player::frame() {
|
||||
uint32 hash = crc32_calculate((const uint8*)ppu.output, 240 * 160 * sizeof(uint32));
|
||||
status.logoDetected = (hash == 0x7776eb55);
|
||||
|
||||
if(status.logoDetected) {
|
||||
status.enable = true;
|
||||
status.logoCounter = (status.logoCounter + 1) % 3;
|
||||
status.packet = 0;
|
||||
}
|
||||
|
||||
if(status.enable == false) return;
|
||||
|
||||
if(cpu.regs.joybus.settings == 0x0000 && cpu.regs.serial.control == 0x5088) {
|
||||
status.packet = (status.packet + 1) % 17;
|
||||
switch(status.packet) {
|
||||
case 0: status.send = 0x0000494e; break;
|
||||
case 1: status.send = 0xb6b1494e; break;
|
||||
case 2: status.send = 0xb6b1494e; break;
|
||||
case 3: status.send = 0xb6b1544e; break;
|
||||
case 4: status.send = 0xabb1544e; break;
|
||||
case 5: status.send = 0xabb14e45; break;
|
||||
case 6: status.send = 0xb1ba4e45; break;
|
||||
case 7: status.send = 0xb1ba4f44; break;
|
||||
case 8: status.send = 0xb0bb4f44; break;
|
||||
case 9: status.send = 0xb0bb8002; break;
|
||||
case 10: status.send = 0x10000010; break;
|
||||
case 11: status.send = 0x20000013; break;
|
||||
case 12: status.send = 0x30000003; break;
|
||||
case 13: status.send = 0x30000003; break;
|
||||
case 14: status.send = 0x30000003; break;
|
||||
case 15: status.send = 0x30000003; break;
|
||||
case 16: status.send = 0x30000003; break;
|
||||
}
|
||||
cpu.regs.irq.flag.serial = true;
|
||||
}
|
||||
|
||||
if(status.rumble) {
|
||||
//todo: support actual gamepad rumble; for now, color screen red during rumble
|
||||
for(unsigned n = 0; n < 240 * 160; n++) ppu.output[n] &= 0x001f;
|
||||
}
|
||||
}
|
||||
|
||||
optional<uint16> Player::keyinput() {
|
||||
if(status.logoDetected == false) return false;
|
||||
|
||||
switch(status.logoCounter) {
|
||||
case 0: return {true, 0x03ff};
|
||||
case 1: return {true, 0x03ff};
|
||||
case 2: return {true, 0x030f};
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
optional<uint32> Player::read() {
|
||||
if(status.enable == false) return false;
|
||||
|
||||
return {true, status.send};
|
||||
}
|
||||
|
||||
void Player::write(uint8 byte, uint2 addr) {
|
||||
if(status.enable == false) return;
|
||||
|
||||
unsigned shift = addr << 3;
|
||||
status.recv &= ~(255 << shift);
|
||||
status.recv |= byte << shift;
|
||||
|
||||
if(addr == 3 && status.packet == 15) {
|
||||
status.rumble = (status.recv & 0xff) == 0x26; //on = 0x26, off = 0x04
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
struct Player {
|
||||
struct Status {
|
||||
bool enable;
|
||||
bool rumble;
|
||||
|
||||
bool logoDetected;
|
||||
unsigned logoCounter;
|
||||
|
||||
unsigned packet;
|
||||
uint32 send;
|
||||
uint32 recv;
|
||||
} status;
|
||||
|
||||
void power();
|
||||
void frame();
|
||||
|
||||
optional<uint16> keyinput();
|
||||
optional<uint32> read();
|
||||
void write(uint8 byte, uint2 addr);
|
||||
|
||||
void serialize(serializer& s);
|
||||
};
|
||||
|
||||
extern Player player;
|
|
@ -0,0 +1,11 @@
|
|||
void Player::serialize(serializer& s) {
|
||||
s.integer(status.enable);
|
||||
s.integer(status.rumble);
|
||||
|
||||
s.integer(status.logoDetected);
|
||||
s.integer(status.logoCounter);
|
||||
|
||||
s.integer(status.packet);
|
||||
s.integer(status.send);
|
||||
s.integer(status.recv);
|
||||
}
|
|
@ -44,7 +44,7 @@ void PPU::step(unsigned clocks) {
|
|||
void PPU::power() {
|
||||
create(PPU::Enter, 16777216);
|
||||
|
||||
for(unsigned n = 0; n < 240 * 160; n++) output[n] = 0, blur[n] = 0;
|
||||
for(unsigned n = 0; n < 240 * 160; n++) output[n] = 0;
|
||||
|
||||
for(unsigned n = 0; n < 1024; n += 2) pram_write(n, Half, 0x0000);
|
||||
for(unsigned n = 0; n < 1024; n += 2) oam_write(n, Half, 0x0000);
|
||||
|
@ -149,12 +149,12 @@ void PPU::scanline() {
|
|||
}
|
||||
|
||||
void PPU::frame() {
|
||||
player.frame();
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
}
|
||||
|
||||
PPU::PPU() {
|
||||
output = new uint32[240 * 160];
|
||||
blur = new uint16[240 * 160];
|
||||
|
||||
regs.bg[0].id = BG0;
|
||||
regs.bg[1].id = BG1;
|
||||
|
@ -164,7 +164,6 @@ PPU::PPU() {
|
|||
|
||||
PPU::~PPU() {
|
||||
delete[] output;
|
||||
delete[] blur;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ struct PPU : Thread, MMIO {
|
|||
#include "registers.hpp"
|
||||
#include "state.hpp"
|
||||
uint32* output;
|
||||
uint16* blur;
|
||||
|
||||
static void Enter();
|
||||
void main();
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
void PPU::render_forceblank() {
|
||||
uint32* line = output + regs.vcounter * 240;
|
||||
uint16* last = blur + regs.vcounter * 240;
|
||||
for(unsigned x = 0; x < 240; x++) {
|
||||
line[x] = video.palette[(0x7fff + last[x] - ((0x7fff ^ last[x]) & 0x0421)) >> 1];
|
||||
last[x] = 0x7fff;
|
||||
line[x] = 0x7fff;
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::render_screen() {
|
||||
uint32* line = output + regs.vcounter * 240;
|
||||
uint16* last = blur + regs.vcounter * 240;
|
||||
|
||||
if(regs.bg[0].control.mosaic) render_mosaic_background(BG0);
|
||||
if(regs.bg[1].control.mosaic) render_mosaic_background(BG1);
|
||||
|
@ -58,9 +55,8 @@ void PPU::render_screen() {
|
|||
color = blend(above[x].color, 16 - regs.blend.evy, 0x0000, regs.blend.evy);
|
||||
}
|
||||
|
||||
//output pixel; blend with previous pixel to simulate GBA LCD blur
|
||||
line[x] = video.palette[(color + last[x] - ((color ^ last[x]) & 0x0421)) >> 1];
|
||||
last[x] = color;
|
||||
//output pixel
|
||||
line[x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ void System::serialize_all(serializer& s) {
|
|||
ppu.serialize(s);
|
||||
apu.serialize(s);
|
||||
bus.serialize(s);
|
||||
player.serialize(s);
|
||||
}
|
||||
|
||||
void System::serialize_init() {
|
||||
|
|
|
@ -15,6 +15,7 @@ void System::term() {
|
|||
|
||||
void System::power() {
|
||||
bus.power();
|
||||
player.power();
|
||||
cpu.power();
|
||||
ppu.power();
|
||||
apu.power();
|
||||
|
@ -39,7 +40,7 @@ void System::run() {
|
|||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) break;
|
||||
}
|
||||
interface->videoRefresh(ppu.output, 4 * 240, 240, 160);
|
||||
interface->videoRefresh(video.palette, ppu.output, 4 * 240, 240, 160);
|
||||
}
|
||||
|
||||
void System::runtosave() {
|
||||
|
@ -62,7 +63,7 @@ void System::runthreadtosave() {
|
|||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
interface->videoRefresh(ppu.output, 4 * 240, 240, 160);
|
||||
interface->videoRefresh(video.palette, ppu.output, 4 * 240, 240, 160);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,22 @@ namespace GameBoyAdvance {
|
|||
|
||||
Video video;
|
||||
|
||||
void Video::generate_palette(bool color_emulation) {
|
||||
//todo: implement LCD color emulation
|
||||
void Video::generate_palette(Emulator::Interface::PaletteMode mode) {
|
||||
for(unsigned color = 0; color < (1 << 15); color++) {
|
||||
if(mode == Emulator::Interface::PaletteMode::None) {
|
||||
palette[color] = color;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned B = (color >> 10) & 31;
|
||||
unsigned G = (color >> 5) & 31;
|
||||
unsigned R = (color >> 0) & 31;
|
||||
|
||||
if(color_emulation) {
|
||||
if(mode == Emulator::Interface::PaletteMode::Standard) {
|
||||
R = R << 11 | R << 6 | R << 1 | R >> 4;
|
||||
G = G << 11 | G << 6 | G << 1 | G >> 4;
|
||||
B = B << 11 | B << 6 | B << 1 | B >> 4;
|
||||
} else {
|
||||
R = curve[R];
|
||||
G = curve[G];
|
||||
B = curve[B];
|
||||
|
@ -47,10 +55,6 @@ void Video::generate_palette(bool color_emulation) {
|
|||
R = R << 8 | R;
|
||||
G = G << 8 | G;
|
||||
B = B << 8 | B;
|
||||
} else {
|
||||
R = R << 11 | R << 6 | R << 1 | R >> 4;
|
||||
G = G << 11 | G << 6 | G << 1 | G >> 4;
|
||||
B = B << 11 | B << 6 | B << 1 | B >> 4;
|
||||
}
|
||||
|
||||
palette[color] = interface->videoColor(color, R, G, B);
|
||||
|
@ -58,7 +62,7 @@ void Video::generate_palette(bool color_emulation) {
|
|||
}
|
||||
|
||||
Video::Video() {
|
||||
palette = new uint32[1 << 15]();
|
||||
palette = new uint32_t[1 << 15]();
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
struct Video {
|
||||
unsigned* palette;
|
||||
void generate_palette(bool color_emulation);
|
||||
uint32_t* palette = nullptr;
|
||||
void generate_palette(Emulator::Interface::PaletteMode mode);
|
||||
|
||||
Video();
|
||||
~Video();
|
||||
|
|
|
@ -93,7 +93,7 @@ inline void PPU::render_line_output() {
|
|||
if(!regs.pseudo_hires && regs.bg_mode != 5 && regs.bg_mode != 6) {
|
||||
for(unsigned x = 0; x < 256; x++) {
|
||||
curr = (regs.display_brightness << 15) | get_pixel_normal(x);
|
||||
*ptr++ = video.palette[curr];
|
||||
*ptr++ = curr;
|
||||
}
|
||||
} else {
|
||||
for(unsigned x = 0, prev = 0; x < 256; x++) {
|
||||
|
@ -101,11 +101,11 @@ inline void PPU::render_line_output() {
|
|||
//blending code is left for reference purposes
|
||||
|
||||
curr = (regs.display_brightness << 15) | get_pixel_swap(x);
|
||||
*ptr++ = video.palette[curr]; //(prev + curr - ((prev ^ curr) & 0x0421)) >> 1;
|
||||
*ptr++ = curr; //(prev + curr - ((prev ^ curr) & 0x0421)) >> 1;
|
||||
//prev = curr;
|
||||
|
||||
curr = (regs.display_brightness << 15) | get_pixel_normal(x);
|
||||
*ptr++ = video.palette[curr]; //(prev + curr - ((prev ^ curr) & 0x0421)) >> 1;
|
||||
*ptr++ = curr; //(prev + curr - ((prev ^ curr) & 0x0421)) >> 1;
|
||||
//prev = curr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,12 +120,12 @@ void PPU::Screen::render() {
|
|||
|
||||
if(!self.regs.pseudo_hires && self.regs.bgmode != 5 && self.regs.bgmode != 6) {
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
data[i] = video.palette[self.regs.display_brightness << 15 | get_pixel_main(i)];
|
||||
data[i] = self.regs.display_brightness << 15 | get_pixel_main(i);
|
||||
}
|
||||
} else {
|
||||
for(unsigned i = 0; i < 256; i++) {
|
||||
*data++ = video.palette[self.regs.display_brightness << 15 | get_pixel_sub(i)];
|
||||
*data++ = video.palette[self.regs.display_brightness << 15 | get_pixel_main(i)];
|
||||
*data++ = self.regs.display_brightness << 15 | get_pixel_sub(i);
|
||||
*data++ = self.regs.display_brightness << 15 | get_pixel_main(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ void ICD2::reset() {
|
|||
joyp14lock = 0;
|
||||
pulselock = true;
|
||||
|
||||
GameBoy::video.generate_palette(false);
|
||||
GameBoy::video.generate_palette(Emulator::Interface::PaletteMode::None);
|
||||
GameBoy::system.init();
|
||||
GameBoy::system.power();
|
||||
}
|
||||
|
|
|
@ -338,8 +338,8 @@ void Interface::cheatSet(const lstring& list) {
|
|||
cheat.synchronize();
|
||||
}
|
||||
|
||||
void Interface::paletteUpdate(bool colorEmulation) {
|
||||
video.generate_palette(colorEmulation);
|
||||
void Interface::paletteUpdate(PaletteMode mode) {
|
||||
video.generate_palette(mode);
|
||||
}
|
||||
|
||||
bool Interface::tracerEnable(bool trace) {
|
||||
|
|
|
@ -115,7 +115,7 @@ struct Interface : Emulator::Interface {
|
|||
|
||||
void cheatSet(const lstring&);
|
||||
|
||||
void paletteUpdate(bool colorEmulation);
|
||||
void paletteUpdate(PaletteMode mode);
|
||||
|
||||
//debugger functions
|
||||
bool tracerEnable(bool);
|
||||
|
|
|
@ -20,13 +20,12 @@ void PPU::Screen::scanline() {
|
|||
void PPU::Screen::run() {
|
||||
if(ppu.vcounter() == 0) return;
|
||||
|
||||
auto palette = &video.palette[self.regs.display_brightness << 15];
|
||||
bool hires = self.regs.pseudo_hires || self.regs.bgmode == 5 || self.regs.bgmode == 6;
|
||||
auto sscolor = get_pixel_sub(hires);
|
||||
auto mscolor = get_pixel_main();
|
||||
|
||||
*output++ = palette[hires ? sscolor : mscolor];
|
||||
*output++ = palette[mscolor];
|
||||
*output++ = (self.regs.display_brightness << 15) | (hires ? sscolor : mscolor);
|
||||
*output++ = (self.regs.display_brightness << 15) | (mscolor);
|
||||
}
|
||||
|
||||
uint16 PPU::Screen::get_pixel_sub(bool hires) {
|
||||
|
|
|
@ -2,14 +2,19 @@
|
|||
|
||||
Video video;
|
||||
|
||||
void Video::generate_palette(bool color_emulation) {
|
||||
void Video::generate_palette(Emulator::Interface::PaletteMode mode) {
|
||||
for(unsigned color = 0; color < (1 << 19); color++) {
|
||||
if(mode == Emulator::Interface::PaletteMode::None) {
|
||||
palette[color] = color;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned l = (color >> 15) & 15;
|
||||
unsigned b = (color >> 10) & 31;
|
||||
unsigned g = (color >> 5) & 31;
|
||||
unsigned r = (color >> 0) & 31;
|
||||
|
||||
if(color_emulation == true) {
|
||||
if(mode == Emulator::Interface::PaletteMode::Emulation) {
|
||||
r = gamma_ramp[r];
|
||||
g = gamma_ramp[g];
|
||||
b = gamma_ramp[b];
|
||||
|
@ -30,7 +35,7 @@ void Video::generate_palette(bool color_emulation) {
|
|||
}
|
||||
|
||||
Video::Video() {
|
||||
palette = new unsigned[1 << 19]();
|
||||
palette = new uint32_t[1 << 19]();
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
|
@ -126,6 +131,7 @@ void Video::update() {
|
|||
//overscan: when disabled, shift image down (by scrolling video buffer up) to center image onscreen
|
||||
//(memory before ppu.output is filled with black scanlines)
|
||||
interface->videoRefresh(
|
||||
video.palette,
|
||||
ppu.output - (ppu.overscan() ? 0 : 7 * 1024),
|
||||
4 * (1024 >> ppu.interlace()),
|
||||
256 << hires,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
struct Video {
|
||||
unsigned* palette;
|
||||
void generate_palette(bool color_emulation);
|
||||
uint32_t* palette;
|
||||
void generate_palette(Emulator::Interface::PaletteMode mode);
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ uint32_t Interface::videoColor(unsigned source, uint16_t r, uint16_t g, uint16_t
|
|||
return 0u;
|
||||
}
|
||||
|
||||
void Interface::videoRefresh(const uint32_t* data, unsigned pitch, unsigned width, unsigned height) {
|
||||
void Interface::videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height) {
|
||||
uint32_t* output;
|
||||
unsigned outputPitch;
|
||||
|
||||
|
@ -59,7 +59,11 @@ void Interface::videoRefresh(const uint32_t* data, unsigned pitch, unsigned widt
|
|||
pitch >>= 2, outputPitch >>= 2;
|
||||
|
||||
for(unsigned y = 0; y < height; y++) {
|
||||
memcpy(output + y * outputPitch, data + y * pitch, 4 * width);
|
||||
const uint32_t* sp = data + y * pitch;
|
||||
uint32_t* dp = output + y * outputPitch;
|
||||
for(unsigned x = 0; x < width; x++) {
|
||||
*dp++ = palette[*sp++];
|
||||
}
|
||||
}
|
||||
|
||||
if(system().information.overscan && config->video.maskOverscan.enable) {
|
||||
|
|
|
@ -3,7 +3,7 @@ struct Interface : Emulator::Interface::Bind {
|
|||
void loadRequest(unsigned id, string path);
|
||||
void saveRequest(unsigned id, string path);
|
||||
uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue);
|
||||
void videoRefresh(const uint32_t* data, unsigned pitch, unsigned width, unsigned height);
|
||||
void videoRefresh(const uint32_t* palette, const uint32_t* data, unsigned pitch, unsigned width, unsigned height);
|
||||
void audioSample(int16_t lsample, int16_t rsample);
|
||||
int16_t inputPoll(unsigned port, unsigned device, unsigned input);
|
||||
unsigned dipSettings(const Markup::Node& node);
|
||||
|
|
|
@ -60,5 +60,5 @@ void VideoSettings::synchronize() {
|
|||
overscanHorizontal.value.setText({config->video.maskOverscan.horizontal, "px"});
|
||||
overscanVertical.value.setText({config->video.maskOverscan.vertical, "px"});
|
||||
|
||||
if(program->active) system().paletteUpdate(config->video.colorEmulation);
|
||||
utility->updatePalette();
|
||||
}
|
||||
|
|
|
@ -91,7 +91,6 @@ void Utility::load() {
|
|||
cheatEditor->load({pathname[0], "cheats.bml"});
|
||||
stateManager->load({pathname[0], "bsnes/states.bsa"}, 1);
|
||||
|
||||
system().paletteUpdate(config->video.colorEmulation);
|
||||
synchronizeDSP();
|
||||
|
||||
resize();
|
||||
|
@ -180,6 +179,18 @@ void Utility::synchronizeRuby() {
|
|||
synchronizeDSP();
|
||||
}
|
||||
|
||||
void Utility::updatePalette() {
|
||||
if(program->active == nullptr) return;
|
||||
|
||||
if(config->video.shader == "Display Emulation") {
|
||||
system().paletteUpdate(Emulator::Interface::PaletteMode::None);
|
||||
} else if(config->video.colorEmulation) {
|
||||
system().paletteUpdate(Emulator::Interface::PaletteMode::Emulation);
|
||||
} else {
|
||||
system().paletteUpdate(Emulator::Interface::PaletteMode::Standard);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::updateShader() {
|
||||
if(config->video.shader == "None") {
|
||||
video.set(Video::Shader, (const char*)"");
|
||||
|
@ -187,7 +198,6 @@ void Utility::updateShader() {
|
|||
} else if(config->video.shader == "Blur") {
|
||||
video.set(Video::Shader, (const char*)"");
|
||||
video.set(Video::Filter, Video::FilterLinear);
|
||||
return;
|
||||
} else if(config->video.shader == "Display Emulation") {
|
||||
if(program->active) {
|
||||
string pathname = program->path("Video Shaders/");
|
||||
|
@ -206,6 +216,7 @@ void Utility::updateShader() {
|
|||
} else {
|
||||
video.set(Video::Shader, (const char*)config->video.shader);
|
||||
}
|
||||
updatePalette();
|
||||
}
|
||||
|
||||
void Utility::resize(bool resizeWindow) {
|
||||
|
|
|
@ -21,6 +21,7 @@ struct Utility {
|
|||
|
||||
void synchronizeDSP();
|
||||
void synchronizeRuby();
|
||||
void updatePalette();
|
||||
void updateShader();
|
||||
void resize(bool resizeWindow = false);
|
||||
void toggleFullScreen();
|
||||
|
|
Loading…
Reference in New Issue